Add LogManager

This commit is contained in:
XorTroll 2020-04-27 22:32:48 +02:00
parent 52c89a76b2
commit ae54769e5b
13 changed files with 903 additions and 1 deletions

View file

@ -64,6 +64,7 @@ dist-no-debug: all
mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000036
mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000037
mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/010000000000003C
mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000015
mkdir -p atmosphere-$(AMSVER)/atmosphere/fatal_errors
mkdir -p atmosphere-$(AMSVER)/atmosphere/config_templates
mkdir -p atmosphere-$(AMSVER)/atmosphere/config
@ -90,6 +91,7 @@ dist-no-debug: all
cp stratosphere/creport/creport.nsp atmosphere-$(AMSVER)/atmosphere/contents/0100000000000036/exefs.nsp
cp stratosphere/ro/ro.nsp atmosphere-$(AMSVER)/atmosphere/contents/0100000000000037/exefs.nsp
cp stratosphere/jpegdec/jpegdec.nsp atmosphere-$(AMSVER)/atmosphere/contents/010000000000003C/exefs.nsp
cp stratosphere/LogManager/LogManager.nsp atmosphere-$(AMSVER)/atmosphere/contents/0100000000000015/exefs.nsp
mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000032/flags
touch atmosphere-$(AMSVER)/atmosphere/contents/0100000000000032/flags/boot2.flag
mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000037/flags
@ -139,6 +141,7 @@ dist: dist-no-debug
cp stratosphere/spl/spl.elf atmosphere-$(AMSVER)-debug/spl.elf
cp stratosphere/erpt/erpt.elf atmosphere-$(AMSVER)-debug/erpt.elf
cp stratosphere/jpegdec/jpegdec.elf atmosphere-$(AMSVER)-debug/jpegdec.elf
cp stratosphere/LogManager/LogManager.elf atmosphere-$(AMSVER)-debug/LogManager.elf
cd atmosphere-$(AMSVER)-debug; zip -r ../atmosphere-$(AMSVER)-debug.zip ./*; cd ../;
rm -r atmosphere-$(AMSVER)-debug
mv atmosphere-$(AMSVER)-debug.zip out/atmosphere-$(AMSVER)-debug.zip

View file

@ -109,6 +109,9 @@ namespace ams::impl {
AMS_DEFINE_SYSTEM_THREAD(21, pgl, Main);
AMS_DEFINE_SYSTEM_THREAD(21, pgl, ProcessControlTask);
/* lm. */
AMS_DEFINE_SYSTEM_THREAD(-18, lm, IpcServer);
#undef AMS_DEFINE_SYSTEM_THREAD
}

View file

@ -0,0 +1,103 @@
{
"name": "LogManager",
"title_id": "0x0100000000000015",
"title_id_range_min": "0x0100000000000015",
"title_id_range_max": "0x0100000000000015",
"main_thread_stack_size": "0x00004000",
"main_thread_priority": 10,
"default_cpu_id": 3,
"process_category": 0,
"is_retail": true,
"pool_partition": 2,
"is_64_bit": true,
"address_space_type": 1,
"filesystem_access": {
"permissions": "0xFFFFFFFFFFFFFFFF"
},
"service_access": [ "fsp-srv", "pm:info", "psc:m" ],
"service_host": [ "lm" ],
"kernel_capabilities": [
{
"type": "kernel_flags",
"value": {
"highest_thread_priority": 63,
"lowest_thread_priority": 8,
"lowest_cpu_id": 3,
"highest_cpu_id": 3
}
},
{
"type": "syscalls",
"value": {
"svcSetHeapSize": "0x01",
"svcSetMemoryPermission": "0x02",
"svcSetMemoryAttribute": "0x03",
"svcMapMemory": "0x04",
"svcUnmapMemory": "0x05",
"svcQueryMemory": "0x06",
"svcExitProcess": "0x07",
"svcCreateThread": "0x08",
"svcStartThread": "0x09",
"svcExitThread": "0x0a",
"svcSleepThread": "0x0b",
"svcGetThreadPriority": "0x0c",
"svcSetThreadPriority": "0x0d",
"svcGetThreadCoreMask": "0x0e",
"svcSetThreadCoreMask": "0x0f",
"svcGetCurrentProcessorNumber": "0x10",
"svcSignalEvent": "0x11",
"svcClearEvent": "0x12",
"svcMapSharedMemory": "0x13",
"svcUnmapSharedMemory": "0x14",
"svcCreateTransferMemory": "0x15",
"svcCloseHandle": "0x16",
"svcResetSignal": "0x17",
"svcWaitSynchronization": "0x18",
"svcCancelSynchronization": "0x19",
"svcArbitrateLock": "0x1a",
"svcArbitrateUnlock": "0x1b",
"svcWaitProcessWideKeyAtomic": "0x1c",
"svcSignalProcessWideKey": "0x1d",
"svcGetSystemTick": "0x1e",
"svcConnectToNamedPort": "0x1f",
"svcSendSyncRequestLight": "0x20",
"svcSendSyncRequest": "0x21",
"svcSendSyncRequestWithUserBuffer": "0x22",
"svcSendAsyncRequestWithUserBuffer": "0x23",
"svcGetProcessId": "0x24",
"svcGetThreadId": "0x25",
"svcBreak": "0x26",
"svcOutputDebugString": "0x27",
"svcReturnFromException": "0x28",
"svcGetInfo": "0x29",
"svcWaitForAddress": "0x34",
"svcSignalToAddress": "0x35",
"svcCreateSession": "0x40",
"svcAcceptSession": "0x41",
"svcReplyAndReceiveLight": "0x42",
"svcReplyAndReceive": "0x43",
"svcReplyAndReceiveWithUserBuffer": "0x44",
"svcCreateEvent": "0x45",
"svcDebugActiveProcess": "0x60",
"svcGetDebugEvent": "0x63",
"svcGetThreadList": "0x66",
"svcGetDebugThreadContext": "0x67",
"svcQueryDebugProcessMemory": "0x69",
"svcReadDebugProcessMemory": "0x6a",
"svcGetDebugThreadParam": "0x6d",
"svcCallSecureMonitor": "0x7F"
}
},
{
"type": "min_kernel_version",
"value": "0x0030"
},
{
"type": "debug_flags",
"value": {
"allow_debug": false,
"force_debug": true
}
}
]
}

View file

@ -0,0 +1,128 @@
#---------------------------------------------------------------------------------
# pull in common stratosphere sysmodule configuration
#---------------------------------------------------------------------------------
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.c)) $(notdir $(wildcard $(dir)/*.board.*.c)) $(notdir $(wildcard $(dir)/*.os.*.c)), \
$(notdir $(wildcard $(dir)/*.c))))
CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).c)))
CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).c)))
CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).c)))
CPPFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.cpp)) $(notdir $(wildcard $(dir)/*.board.*.cpp)) $(notdir $(wildcard $(dir)/*.os.*.cpp)), \
$(notdir $(wildcard $(dir)/*.cpp))))
CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).cpp)))
CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).cpp)))
CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).cpp)))
SFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.s)) $(notdir $(wildcard $(dir)/*.board.*.s)) $(notdir $(wildcard $(dir)/*.os.*.s)), \
$(notdir $(wildcard $(dir)/*.s))))
SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).s)))
SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).s)))
SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT).nsp
ifeq ($(strip $(APP_JSON)),)
$(OUTPUT).nsp : $(OUTPUT).nso
else
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
endif
$(OUTPUT).nso : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

View file

@ -0,0 +1,133 @@
#include "lm_log_packet.hpp"
#include "lm_scoped_log_file.hpp"
namespace ams::lm::impl {
namespace {
bool can_access_fs = true;
os::Mutex fs_access_lock(false);
}
void SetCanAccessFs(bool can_access) {
std::scoped_lock lk(fs_access_lock);
can_access_fs = can_access;
}
void WriteLogPackets(const char *log_path, std::vector<LogPacket> &packet_list, u64 program_id, LogDestination destination) {
std::scoped_lock lk(fs_access_lock);
if(!can_access_fs) {
/* Only write log if we can access fs. */
return;
}
if(packet_list.empty()) {
/* This shouldn't happen... */
return;
}
/* For everything except the text log, use the first/head packet. */
auto &head_packet = packet_list.front();
const bool is_single_packet = packet_list.size() == 1;
ScopedLogFile log_file(log_path);
log_file.WriteFormat("\n");
log_file.WriteFormat("/----------------------------------------------------------------------------------------------------\\\n");
log_file.WriteFormat("|\n");
log_file.WriteFormat("| Log - %s (0x%016lX)\n", is_single_packet ? "single packet" : "multiple packets", program_id);
/* Log severity. */
log_file.WriteFormat("| Log severity (Trace, Info, Warn, Error, Fatal): ");
switch(static_cast<LogSeverity>(head_packet.header.severity)) {
case LogSeverity::Trace:
log_file.WriteFormat("Trace");
break;
case LogSeverity::Info:
log_file.WriteFormat("Info");
break;
case LogSeverity::Warn:
log_file.WriteFormat("Warn");
break;
case LogSeverity::Error:
log_file.WriteFormat("Error");
break;
case LogSeverity::Fatal:
log_file.WriteFormat("Fatal");
break;
default:
log_file.WriteFormat("Unknown");
break;
}
log_file.WriteFormat("\n");
/* Log verbosity. */
log_file.WriteFormat("| Log verbosity: %d\n", head_packet.header.verbosity);
/* Log destination. */
log_file.WriteFormat("| Log destination: ");
switch(destination) {
case LogDestination::TMA:
log_file.WriteFormat("TMA");
break;
case LogDestination::UART:
log_file.WriteFormat("UART");
break;
case LogDestination::UARTSleeping:
log_file.WriteFormat("UART (when sleeping)");
break;
case LogDestination::All:
log_file.WriteFormat("All (TMA and UART)");
break;
default:
log_file.WriteFormat("Unknown");
break;
}
log_file.WriteFormat("\n");
log_file.WriteFormat("|\n");
/* Process ID and thread ID. */
log_file.WriteFormat("| Process ID: 0x%016lX\n", head_packet.header.process_id);
log_file.WriteFormat("| Thread ID: 0x%016lX\n", head_packet.header.thread_id);
/* File name, line number, function name. */
if(!head_packet.payload.file_name.IsEmpty()) {
/* At <file-name> */
log_file.WriteFormat("| At %s", head_packet.payload.file_name.value);
if(!head_packet.payload.function_name.IsEmpty()) {
/* If function name is present, line number is present too. */
/* At <file-name>:<line-number> (<function-name>) */
log_file.WriteFormat(":%d (%s)", head_packet.payload.line_number.value, head_packet.payload.function_name.value);
}
log_file.WriteFormat("\n");
}
if(!head_packet.payload.process_name.IsEmpty()) {
log_file.WriteFormat("| Process name: %s\n", head_packet.payload.process_name.value);
}
if(!head_packet.payload.module_name.IsEmpty()) {
log_file.WriteFormat("| Module name: %s\n", head_packet.payload.module_name.value);
}
if(!head_packet.payload.thread_name.IsEmpty()) {
log_file.WriteFormat("| Thread name: %s\n", head_packet.payload.thread_name.value);
}
if(!head_packet.payload.log_packet_drop_count.IsEmpty()) {
/* Log packet drop count (what is this used for...?) */
log_file.WriteFormat("| Log packet drop count: %ld\n", head_packet.payload.log_packet_drop_count.value);
}
if(!head_packet.payload.user_system_clock.IsEmpty()) {
/* User system clock - seconds since the title has started. */
log_file.WriteFormat("| Time since title started: %lds\n", head_packet.payload.user_system_clock.value);
}
log_file.WriteFormat("|\n");
log_file.WriteFormat("\\----------------------------------------------------------------------------------------------------/\n");
if(!head_packet.payload.text_log.IsEmpty()) {
/* Concatenate all the packets' messages. */
for(auto &packet: packet_list) {
log_file.WriteFormat("%s", packet.payload.text_log.value);
}
log_file.WriteFormat("\n");
}
}
}

View file

@ -0,0 +1,184 @@
#pragma once
#include <stratosphere.hpp>
#include "../lm_types.hpp"
namespace ams::lm::impl {
enum class LogDataChunkKey : u8 {
SessionBegin = 0,
SessionEnd = 1,
TextLog = 2,
LineNumber = 3,
FileName = 4,
FunctionName = 5,
ModuleName = 6,
ThreadName = 7,
LogPacketDropCount = 8,
UserSystemClock = 9,
ProcessName = 10,
};
enum class LogSeverity : u8 {
Trace = 0,
Info = 1,
Warn = 2,
Error = 3,
Fatal = 4,
};
enum LogPacketFlags : u8 {
LogPacketFlags_Head = BIT(0),
LogPacketFlags_Tail = BIT(1),
};
struct LogPacketHeader {
u64 process_id;
u64 thread_id;
u8 flags;
u8 pad;
u8 severity;
u8 verbosity;
u32 payload_size;
inline constexpr bool IsHead() {
/* Head -> a packet list is being sent, with this packet being the initial one. */
return this->flags & LogPacketFlags_Head;
}
inline constexpr bool IsTail() {
/* Tail -> this is the final packet of the packet list. */
return this->flags & LogPacketFlags_Tail;
}
} PACKED;
static_assert(sizeof(LogPacketHeader) == 0x18, "LogPacketHeader definition");
/* Log data chunk base type. */
struct LogDataChunkTypeHeader {
u8 key;
u8 chunk_length;
} PACKED;
template<typename T>
struct LogDataChunkType {
LogDataChunkTypeHeader header;
T value;
inline constexpr bool IsEmpty() {
/* If it's not present, its length will not be set. */
return this->header.chunk_length == 0;
}
} PACKED;
/* Since the length is stored as a single byte, the string will never be larger than 0xFF */
struct LogDataChunkStringType : public LogDataChunkType<char[UINT8_MAX + 1]> {};
/* Actual chunk base types. */
struct LogDataChunkSessionBeginType : public LogDataChunkType<bool> {};
struct LogDataChunkSessionEndType : public LogDataChunkType<bool> {};
struct LogDataChunkTextLogType : public LogDataChunkStringType {};
struct LogDataChunkLineNumberType : public LogDataChunkType<u32> {};
struct LogDataChunkFileNameType : public LogDataChunkStringType {};
struct LogDataChunkFunctionNameType : public LogDataChunkStringType {};
struct LogDataChunkModuleNameType : public LogDataChunkStringType {};
struct LogDataChunkThreadNameType : public LogDataChunkStringType {};
struct LogDataChunkLogPacketDropCountLogType : public LogDataChunkType<u64> {};
struct LogDataChunkUserSystemClockType : public LogDataChunkType<u64> {};
struct LogDataChunkProcessNameType : public LogDataChunkStringType {};
/* One of each chunk type. */
struct LogPacketPayload {
LogDataChunkSessionBeginType session_begin;
LogDataChunkSessionEndType session_end;
LogDataChunkTextLogType text_log;
LogDataChunkLineNumberType line_number;
LogDataChunkFileNameType file_name;
LogDataChunkFunctionNameType function_name;
LogDataChunkModuleNameType module_name;
LogDataChunkThreadNameType thread_name;
LogDataChunkLogPacketDropCountLogType log_packet_drop_count;
LogDataChunkUserSystemClockType user_system_clock;
LogDataChunkProcessNameType process_name;
};
struct LogPacket {
LogPacketHeader header;
LogPacketPayload payload;
};
template<typename C>
inline C ParseStringChunkType(const u8 *chunk_buf) {
static_assert(std::is_base_of_v<LogDataChunkStringType, C>, "Invalid type");
auto chunk_header = *reinterpret_cast<const LogDataChunkTypeHeader*>(chunk_buf);
auto type = *reinterpret_cast<const C*>(chunk_buf);
auto chunk_str_buf = reinterpret_cast<const char*>(chunk_buf + sizeof(LogDataChunkTypeHeader));
/* Zero the string and copy it from the log buffer. */
/* This chunk type can't be directly read like the rest, since the string size isn't fixed like with the other types. */
__builtin_memset(type.value, 0, sizeof(type.value));
strncpy(type.value, chunk_str_buf, chunk_header.chunk_length);
return type;
}
inline LogPacket ParseLogPacket(const void *buf, size_t size) {
LogPacket packet = {};
auto buf8 = reinterpret_cast<const u8*>(buf);
packet.header = *reinterpret_cast<const LogPacketHeader*>(buf);
auto payload_buf = buf8 + sizeof(LogPacketHeader);
size_t offset = 0;
while(offset < packet.header.payload_size) {
/* Each chunk data consists on: u8 id, u8 length, u8 data[length]; */
auto chunk_buf = payload_buf + offset;
auto chunk_header = *reinterpret_cast<const LogDataChunkTypeHeader*>(chunk_buf);
/* Parse the chunk depending on the type (strings require special parsing) */
switch(static_cast<LogDataChunkKey>(chunk_header.key)) {
case LogDataChunkKey::SessionBegin:
packet.payload.session_begin = *reinterpret_cast<const LogDataChunkSessionBeginType*>(chunk_buf);
break;
case LogDataChunkKey::SessionEnd:
packet.payload.session_end = *reinterpret_cast<const LogDataChunkSessionEndType*>(chunk_buf);
break;
case LogDataChunkKey::TextLog:
packet.payload.text_log = ParseStringChunkType<LogDataChunkTextLogType>(chunk_buf);
break;
case LogDataChunkKey::LineNumber:
packet.payload.line_number = *reinterpret_cast<const LogDataChunkLineNumberType*>(chunk_buf);
break;
case LogDataChunkKey::FileName:
packet.payload.file_name = ParseStringChunkType<LogDataChunkFileNameType>(chunk_buf);
break;
case LogDataChunkKey::FunctionName:
packet.payload.function_name = ParseStringChunkType<LogDataChunkFunctionNameType>(chunk_buf);
break;
case LogDataChunkKey::ModuleName:
packet.payload.module_name = ParseStringChunkType<LogDataChunkModuleNameType>(chunk_buf);
break;
case LogDataChunkKey::ThreadName:
packet.payload.thread_name = ParseStringChunkType<LogDataChunkThreadNameType>(chunk_buf);
break;
case LogDataChunkKey::LogPacketDropCount:
packet.payload.log_packet_drop_count = *reinterpret_cast<const LogDataChunkLogPacketDropCountLogType*>(chunk_buf);
break;
case LogDataChunkKey::UserSystemClock:
packet.payload.user_system_clock = *reinterpret_cast<const LogDataChunkUserSystemClockType*>(chunk_buf);
break;
case LogDataChunkKey::ProcessName:
packet.payload.process_name = ParseStringChunkType<LogDataChunkProcessNameType>(chunk_buf);
break;
}
offset += sizeof(LogDataChunkTypeHeader);
offset += chunk_header.chunk_length;
}
return packet;
}
void SetCanAccessFs(bool can_access);
void WriteLogPackets(const char *log_path, std::vector<LogPacket> &packet_list, u64 program_id, LogDestination destination);
}

View file

@ -0,0 +1,49 @@
#include "lm_scoped_log_file.hpp"
namespace ams::lm::impl {
namespace {
os::Mutex g_log_lock(true);
/* Longest strings will be slightly bigger than 0x100 bytes, so this will be enough. */
char g_log_format_buffer[0x400];
}
void ScopedLogFile::WriteString(const char *str) {
std::scoped_lock lk(g_log_lock);
this->Write(str, std::strlen(str));
}
void ScopedLogFile::WriteFormat(const char *fmt, ...) {
std::scoped_lock lk(g_log_lock);
/* Format to the buffer. */
{
std::va_list vl;
va_start(vl, fmt);
std::vsnprintf(g_log_format_buffer, sizeof(g_log_format_buffer), fmt, vl);
va_end(vl);
}
/* Write data. */
this->WriteString(g_log_format_buffer);
}
void ScopedLogFile::Write(const void *data, size_t size) {
std::scoped_lock lk(g_log_lock);
/* If we're not open, we can't write. */
if (!this->IsOpen()) {
return;
}
/* Advance, if we write successfully. */
if (R_SUCCEEDED(fs::WriteFile(this->file, this->offset, data, size, fs::WriteOption::Flush))) {
this->offset += size;
}
}
}

View file

@ -0,0 +1,43 @@
#pragma once
#include <stratosphere.hpp>
namespace ams::lm::impl {
/* Based on creport's ScopedFile. */
class ScopedLogFile {
NON_COPYABLE(ScopedLogFile);
NON_MOVEABLE(ScopedLogFile);
private:
fs::FileHandle file;
s64 offset;
bool opened;
public:
ScopedLogFile(const char *path) : file(), offset(), opened(false) {
/* Ensure that the file exists. */
fs::CreateFile(path, 0);
this->opened = R_SUCCEEDED(fs::OpenFile(std::addressof(this->file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend));
if(this->opened) {
/* Set current file size as offset to properly append the logs after each other. */
fs::GetFileSize(&this->offset, this->file);
}
}
~ScopedLogFile() {
if(this->opened) {
fs::CloseFile(this->file);
}
}
bool IsOpen() const {
return this->opened;
}
void WriteString(const char *str);
void WriteFormat(const char *fmt, ...) __attribute__((format(printf, 2, 3)));
void Write(const void *data, size_t size);
};
}

View file

@ -0,0 +1,144 @@
#include "lm_service.hpp"
extern "C" {
extern u32 __start__;
u32 __nx_applet_type = AppletType_None;
u32 __nx_fs_num_sessions = 1;
#define INNER_HEAP_SIZE 0x10000
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[INNER_HEAP_SIZE];
void __libnx_initheap(void);
void __appInit(void);
void __appExit(void);
}
namespace ams {
ncm::ProgramId CurrentProgramId = ncm::SystemProgramId::LogManager;
namespace result {
/* Fatal is launched way later after we are launched, so disable this. */
bool CallFatalOnResultAssertion = false;
}
}
using namespace ams;
void __libnx_initheap(void) {
void* addr = nx_inner_heap;
size_t size = nx_inner_heap_size;
/* Newlib */
extern char* fake_heap_start;
extern char* fake_heap_end;
fake_heap_start = (char*)addr;
fake_heap_end = (char*)addr + size;
}
void __appInit(void) {
hos::InitializeForStratosphere();
/* Initialize services. */
sm::DoWithSession([&]() {
R_ABORT_UNLESS(pminfoInitialize());
R_ABORT_UNLESS(fsInitialize());
R_ABORT_UNLESS(pscmInitialize());
});
R_ABORT_UNLESS(fs::MountSdCard("sdmc"));
ams::CheckApiVersion();
}
void __appExit(void) {
/* Cleanup services. */
fs::Unmount("sdmc");
pscmExit();
fsExit();
pminfoExit();
}
namespace {
/* TODO: this domain/domain object amounts work fine, but which ones does N's LogManager actually use? */
struct ServerOptions {
static constexpr size_t PointerBufferSize = 0;
static constexpr size_t MaxDomains = 0x40;
static constexpr size_t MaxDomainObjects = 0x200;
};
constexpr sm::ServiceName LmServiceName = sm::ServiceName::Encode("lm");
constexpr size_t LmMaxSessions = 30;
/* lm */
constexpr size_t NumServers = 1;
sf::hipc::ServerManager<NumServers, ServerOptions, LmMaxSessions> g_server_manager;
psc::PmModule g_pm_module;
os::WaitableHolderType g_module_waitable_holder;
}
namespace ams::lm {
void StartAndLoopProcess() {
/* Get our psc:m module to handle requests. */
R_ABORT_UNLESS(g_pm_module.Initialize(psc::PmModuleId_Lm, nullptr, 0, os::EventClearMode_ManualClear));
os::InitializeWaitableHolder(std::addressof(g_module_waitable_holder), g_pm_module.GetEventPointer()->GetBase());
os::SetWaitableHolderUserData(std::addressof(g_module_waitable_holder), static_cast<uintptr_t>(psc::PmModuleId_Lm));
g_server_manager.AddUserWaitableHolder(std::addressof(g_module_waitable_holder));
psc::PmState pm_state;
psc::PmFlagSet pm_flags;
while(true) {
auto *signaled_holder = g_server_manager.WaitSignaled();
if(signaled_holder != std::addressof(g_module_waitable_holder)) {
R_ABORT_UNLESS(g_server_manager.Process(signaled_holder));
}
else {
g_pm_module.GetEventPointer()->Clear();
if(R_SUCCEEDED(g_pm_module.GetRequest(std::addressof(pm_state), std::addressof(pm_flags)))) {
switch(pm_state) {
case psc::PmState_Awake:
case psc::PmState_ReadyAwaken:
/* Awake, enable logging. */
impl::SetCanAccessFs(true);
break;
case psc::PmState_ReadySleep:
case psc::PmState_ReadyShutdown:
/* Sleep, disable logging. */
impl::SetCanAccessFs(false);
break;
default:
break;
}
R_ABORT_UNLESS(g_pm_module.Acknowledge(pm_state, ResultSuccess()));
}
g_server_manager.AddUserWaitableHolder(signaled_holder);
}
}
}
}
int main(int argc, char **argv) {
/* Set thread name. */
os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(lm, IpcServer));
AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(lm, IpcServer));
/* Add service to manager. */
R_ABORT_UNLESS(g_server_manager.RegisterServer<lm::LogService>(LmServiceName, LmMaxSessions));
/* Loop forever, servicing our services. */
lm::StartAndLoopProcess();
return 0;
}

View file

@ -0,0 +1,39 @@
#include "lm_service.hpp"
namespace ams::lm {
void Logger::WriteAndClearQueuedPackets() {
impl::WriteLogPackets(this->log_path, this->queued_packets, this->program_id, this->destination);
this->queued_packets.clear();
}
void Logger::Log(const sf::InAutoSelectBuffer &buf) {
auto packet = impl::ParseLogPacket(buf.GetPointer(), buf.GetSize());
/* Check if there's a queue already started. */
const bool has_queued_packets = !this->queued_packets.empty();
/* Initially always push the packet. */
this->queued_packets.push_back(packet);
if(has_queued_packets && packet.header.IsTail()) {
/* This is the final packet of the queue - write and clear it. */
this->WriteAndClearQueuedPackets();
}
else if(!has_queued_packets && !packet.header.IsHead()) {
/* No head flag and queue not started, so just log the packet and don't start a queue. */
this->WriteAndClearQueuedPackets();
}
/* Otherwise, the packet is a regular packet of a list, so push it and continue. */
}
void Logger::SetDestination(LogDestination destination) {
this->destination = destination;
}
void LogService::OpenLogger(const sf::ClientProcessId &client_pid, sf::Out<std::shared_ptr<Logger>> out_logger) {
u64 program_id = 0;
/* Apparently lm succeeds on many/all commands, so we will succeed on them too. */
pminfoGetProgramId(&program_id, static_cast<u64>(client_pid.GetValue()));
auto logger = std::make_shared<Logger>(program_id);
out_logger.SetValue(std::move(logger));
}
}

View file

@ -0,0 +1,59 @@
#pragma once
#include "impl/lm_log_packet.hpp"
namespace ams::lm {
static constexpr const char LogsDirectory[] = "sdmc:/atmosphere/debug_logs";
class Logger : public sf::IServiceObject {
private:
enum class CommandId {
Log = 0,
SetDestination = 1,
};
private:
u64 program_id;
LogDestination destination;
std::vector<impl::LogPacket> queued_packets;
char log_path[FS_MAX_PATH];
void WriteAndClearQueuedPackets();
public:
Logger(u64 program_id) : program_id(program_id), destination(LogDestination::TMA), queued_packets(), log_path() {
/* Ensure that the logs directory exists. */
fs::CreateDirectory(LogsDirectory);
sprintf(this->log_path, "%s/%016lX.log", LogsDirectory, program_id);
}
private:
void Log(const sf::InAutoSelectBuffer &buf);
void SetDestination(LogDestination destination);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(Log),
MAKE_SERVICE_COMMAND_META(SetDestination, hos::Version_3_0_0),
};
};
class LogService : public sf::IServiceObject {
private:
enum class CommandId {
OpenLogger = 0,
};
private:
void OpenLogger(const sf::ClientProcessId &client_pid, sf::Out<std::shared_ptr<Logger>> out_logger);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(OpenLogger),
};
};
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <stratosphere.hpp>
namespace ams::lm {
enum class LogDestination : u32 {
TMA = 1,
UART = 2,
UARTSleeping = 4,
All = 0xFFFF,
};
}

View file

@ -1,4 +1,4 @@
MODULES := loader ncm pm sm boot ams_mitm spl eclct.stub ro creport fatal dmnt boot2 erpt pgl jpegdec
MODULES := loader ncm pm sm boot ams_mitm spl eclct.stub ro creport fatal dmnt boot2 erpt pgl jpegdec LogManager
SUBFOLDERS := $(MODULES)