LogManager: implement system module, client api, logging api (#1617)

Some notes:

* Unless `atmosphere!enable_log_manager` is true, Nintendo's log manager will be used instead.
  * This prevents paying memory costs for LM when not enabling logging.
  * To facilitate this, Atmosphere's log manager has a different program id from Nintendo's.
  * `atmosphere!enable_htc` implies `atmosphere!enable_log_manager`.
* LogManager logs to tma, and the SD card (if `lm!enable_sd_card_logging` is true, which it is by default).
* Binary logs are saved to `lm!sd_card_log_output_directory`, which is `atmosphere/binlogs` by default.
This commit is contained in:
SciresM 2021-09-11 19:32:14 -07:00 committed by GitHub
parent a1fb8a91c8
commit e9849c74cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
94 changed files with 5595 additions and 45 deletions

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::lm::impl {
enum LogDataChunkKey {
LogDataChunkKey_LogSessionBegin = 0,
LogDataChunkKey_LogSessionEnd = 1,
LogDataChunkKey_TextLog = 2,
LogDataChunkKey_LineNumber = 3,
LogDataChunkKey_FileName = 4,
LogDataChunkKey_FunctionName = 5,
LogDataChunkKey_ModuleName = 6,
LogDataChunkKey_ThreadName = 7,
LogDataChunkKey_LogPacketDropCount = 8,
LogDataChunkKey_UserSystemClock = 9,
LogDataChunkKey_ProcessName = 10,
};
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::lm::impl {
constexpr inline size_t LogPacketHeaderSize = 0x18;
class LogPacketHeader {
private:
u64 m_process_id;
u64 m_thread_id;
u8 m_flags;
u8 m_padding;
u8 m_severity;
u8 m_verbosity;
u32 m_payload_size;
public:
constexpr u64 GetProcessId() const { return m_process_id; }
constexpr void SetProcessId(u64 v) { m_process_id = v; }
constexpr u64 GetThreadId() const { return m_thread_id; }
constexpr void SetThreadId(u64 v) { m_thread_id = v; }
constexpr bool IsHead() const { return (m_flags & (1 << 0)) != 0; }
constexpr void SetHead(bool v) { m_flags = (m_flags & ~(1 << 0)) | ((v ? 1 : 0) << 0); }
constexpr bool IsTail() const { return (m_flags & (1 << 1)) != 0; }
constexpr void SetTail(bool v) { m_flags = (m_flags & ~(1 << 1)) | ((v ? 1 : 0) << 1); }
constexpr bool IsLittleEndian() const { return (m_flags & (1 << 2)) != 0; }
constexpr void SetLittleEndian(bool v) { m_flags = (m_flags & ~(1 << 2)) | ((v ? 1 : 0) << 2); }
constexpr u8 GetSeverity() const { return m_severity; }
constexpr void SetSeverity(u8 v) { m_severity = v; }
constexpr u8 GetVerbosity() const { return m_verbosity; }
constexpr void SetVerbosity(u8 v) { m_verbosity = v; }
constexpr u32 GetPayloadSize() const { return m_payload_size; }
constexpr void SetPayloadSize(u32 v) { m_payload_size = v; }
};
static_assert(sizeof(LogPacketHeader) == LogPacketHeaderSize);
}

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "lm_log_packet_transmitter_base.hpp"
namespace ams::lm::impl {
class LogPacketTransmitter : public LogPacketTransmitterBase {
public:
LogPacketTransmitter(void *buffer, size_t buffer_size, FlushFunction flush_func, u8 severity, u8 verbosity, u64 process_id, bool head, bool tail)
: LogPacketTransmitterBase(buffer, buffer_size, flush_func, severity, verbosity, process_id, head, tail) { /* ... */ }
void PushLogSessionBegin() {
bool value = true;
this->PushDataChunk(LogDataChunkKey_LogSessionBegin, std::addressof(value), sizeof(value));
}
void PushLogSessionEnd() {
bool value = true;
this->PushDataChunk(LogDataChunkKey_LogSessionEnd, std::addressof(value), sizeof(value));
}
void PushTextLog(const char *str, size_t len) {
this->PushDataChunk(LogDataChunkKey_TextLog, str, len);
}
void PushLineNumber(u32 line) {
this->PushDataChunk(LogDataChunkKey_LineNumber, std::addressof(line), sizeof(line));
}
void PushFileName(const char *str, size_t len) {
this->PushDataChunk(LogDataChunkKey_FileName, str, len);
}
void PushFunctionName(const char *str, size_t len) {
this->PushDataChunk(LogDataChunkKey_FunctionName, str, len);
}
void PushModuleName(const char *str, size_t len) {
this->PushDataChunk(LogDataChunkKey_ModuleName, str, len);
}
void PushThreadName(const char *str, size_t len) {
this->PushDataChunk(LogDataChunkKey_ThreadName, str, len);
}
void PushLogPacketDropCount(u64 count) {
this->PushDataChunk(LogDataChunkKey_LineNumber, std::addressof(count), sizeof(count));
}
void PushUserSystemClock(s64 posix_time) {
this->PushDataChunk(LogDataChunkKey_LineNumber, std::addressof(posix_time), sizeof(posix_time));
}
void PushProcessName(const char *str, size_t len) {
this->PushDataChunk(LogDataChunkKey_ProcessName, str, len);
}
};
}

View file

@ -0,0 +1,166 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "lm_log_packet_transmitter_base.hpp"
namespace ams::lm::impl {
LogPacketTransmitterBase::LogPacketTransmitterBase(void *buffer, size_t buffer_size, FlushFunction flush_func, u8 severity, u8 verbosity, u64 process_id, bool head, bool tail) {
/* Check pre-conditions. */
AMS_ASSERT(buffer != nullptr);
AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(buffer), alignof(LogPacketHeader)));
AMS_ASSERT(buffer_size >= LogPacketHeaderSize);
AMS_ASSERT(flush_func != nullptr);
/* Construct log packet header. */
m_header = std::construct_at(static_cast<LogPacketHeader *>(buffer));
/* Set fields. */
m_start = static_cast<u8 *>(buffer);
m_end = m_start + buffer_size;
m_payload = m_start + LogPacketHeaderSize;
m_current = m_payload;
m_is_tail = tail;
m_flush_function = flush_func;
/* Set header fields. */
m_header->SetProcessId(process_id);
m_header->SetThreadId(os::GetThreadId(os::GetCurrentThread()));
m_header->SetHead(head);
m_header->SetLittleEndian(util::IsLittleEndian());
m_header->SetSeverity(severity);
m_header->SetVerbosity(verbosity);
}
bool LogPacketTransmitterBase::Flush(bool is_tail) {
/* Check if we're already flushed. */
if (m_current == m_payload) {
return true;
}
/* Flush the data. */
m_header->SetTail(is_tail);
m_header->SetPayloadSize(static_cast<u32>(m_current - m_payload));
const auto result = m_flush_function(m_start, static_cast<size_t>(m_current - m_start));
m_header->SetHead(false);
/* Reset. */
m_current = m_payload;
return result;
}
size_t LogPacketTransmitterBase::GetRemainSize() {
return static_cast<size_t>(m_end - m_current);
}
size_t LogPacketTransmitterBase::GetPushableDataSize(size_t uleb_size) {
const size_t remain = this->GetRemainSize();
if (remain < uleb_size + 2) {
return 0;
}
const size_t cmp = remain - uleb_size;
u64 mask = 0x7F;
size_t n;
for (n = 1; mask + n < cmp; ++n) {
mask |= mask << 7;
}
return cmp - n;
}
size_t LogPacketTransmitterBase::GetRequiredSizeToPushUleb128(u64 v) {
/* Determine bytes needed for uleb128 value. */
size_t required = 0;
do {
++required;
v >>= 7;
} while (v > 0);
return required;
}
void LogPacketTransmitterBase::PushUleb128(u64 v) {
const u32 Mask = 0x7F;
const u32 InverseMask = ~Mask;
do {
/* Check we're within bounds. */
AMS_ASSERT(m_current < m_end);
/* Write byte. */
*(m_current++) = static_cast<u8>(v & Mask) | (((v & InverseMask) != 0) ? 0x80 : 0x00);
/* Adjust remaining bit range. */
v >>= 7;
} while (v > 0);
}
void LogPacketTransmitterBase::PushDataChunkImpl(LogDataChunkKey key, const void *data, size_t data_size, bool is_text) {
/* Check pre-conditions. */
AMS_ASSERT(data != nullptr);
/* Push as much data as we can, until the chunk is complete. */
const u8 *cur = static_cast<const u8 *>(data);
const u8 * const end = cur + data_size;
const size_t required_key = this->GetRequiredSizeToPushUleb128(key);
do {
/* Get the pushable size. */
size_t pushable_size = this->GetPushableDataSize(required_key);
size_t required_size = is_text ? 4 : 1;
if (pushable_size < required_size) {
this->Flush(false);
pushable_size = this->GetPushableDataSize(required_key);
}
AMS_ASSERT(pushable_size >= required_size);
/* Determine the current size. */
size_t current_size = std::min<size_t>(pushable_size, end - cur);
if (is_text) {
const auto valid_size = diag::impl::GetValidSizeAsUtf8String(reinterpret_cast<const char *>(cur), current_size);
if (valid_size >= 0) {
current_size = static_cast<size_t>(valid_size);
}
}
/* Push data. */
this->PushUleb128(key);
this->PushUleb128(current_size);
this->PushData(cur, current_size);
/* Advance. */
cur = cur + current_size;
} while (cur < end);
/* Check that we pushed all the data. */
AMS_ASSERT(cur == end);
}
void LogPacketTransmitterBase::PushData(const void *data, size_t size) {
/* Check pre-conditions. */
AMS_ASSERT(data != nullptr);
AMS_ASSERT(size <= this->GetRemainSize());
/* Push the data. */
if (size > 0) {
std::memcpy(m_current, data, size);
m_current += size;
}
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "lm_log_packet_header.hpp"
#include "lm_log_data_chunk.hpp"
namespace ams::lm::impl {
class LogPacketTransmitterBase {
public:
using FlushFunction = bool (*)(const u8 *data, size_t size);
private:
LogPacketHeader *m_header;
u8 *m_start;
u8 *m_end;
u8 *m_payload;
u8 *m_current;
bool m_is_tail;
FlushFunction m_flush_function;
protected:
LogPacketTransmitterBase(void *buffer, size_t buffer_size, FlushFunction flush_func, u8 severity, u8 verbosity, u64 process_id, bool head, bool tail);
~LogPacketTransmitterBase() {
this->Flush(m_is_tail);
}
void PushDataChunk(LogDataChunkKey key, const void *data, size_t size) {
this->PushDataChunkImpl(key, data, size, false);
}
void PushDataChunk(LogDataChunkKey key, const char *str, size_t size) {
this->PushDataChunkImpl(key, str, size, true);
}
public:
bool Flush(bool is_tail);
private:
size_t GetRemainSize();
size_t GetPushableDataSize(size_t uleb_size);
size_t GetRequiredSizeToPushUleb128(u64 v);
void PushUleb128(u64 v);
void PushDataChunkImpl(LogDataChunkKey key, const void *data, size_t size, bool is_text);
void PushData(const void *data, size_t size);
};
}