From ae54769e5bdee1d561b43da7a2c593357e889847 Mon Sep 17 00:00:00 2001 From: XorTroll Date: Mon, 27 Apr 2020 22:32:48 +0200 Subject: [PATCH] Add LogManager --- Makefile | 3 + .../impl/ams_system_thread_definitions.hpp | 3 + stratosphere/LogManager/LogManager.json | 103 ++++++++++ stratosphere/LogManager/Makefile | 128 ++++++++++++ .../LogManager/source/impl/lm_log_packet.cpp | 133 +++++++++++++ .../LogManager/source/impl/lm_log_packet.hpp | 184 ++++++++++++++++++ .../source/impl/lm_scoped_log_file.cpp | 49 +++++ .../source/impl/lm_scoped_log_file.hpp | 43 ++++ stratosphere/LogManager/source/lm_main.cpp | 144 ++++++++++++++ stratosphere/LogManager/source/lm_service.cpp | 39 ++++ stratosphere/LogManager/source/lm_service.hpp | 59 ++++++ stratosphere/LogManager/source/lm_types.hpp | 14 ++ stratosphere/Makefile | 2 +- 13 files changed, 903 insertions(+), 1 deletion(-) create mode 100644 stratosphere/LogManager/LogManager.json create mode 100644 stratosphere/LogManager/Makefile create mode 100644 stratosphere/LogManager/source/impl/lm_log_packet.cpp create mode 100644 stratosphere/LogManager/source/impl/lm_log_packet.hpp create mode 100644 stratosphere/LogManager/source/impl/lm_scoped_log_file.cpp create mode 100644 stratosphere/LogManager/source/impl/lm_scoped_log_file.hpp create mode 100644 stratosphere/LogManager/source/lm_main.cpp create mode 100644 stratosphere/LogManager/source/lm_service.cpp create mode 100644 stratosphere/LogManager/source/lm_service.hpp create mode 100644 stratosphere/LogManager/source/lm_types.hpp diff --git a/Makefile b/Makefile index b1f77f19f..7c99d9d23 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp index 82da25ee9..f76783f20 100644 --- a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp +++ b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp @@ -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 } diff --git a/stratosphere/LogManager/LogManager.json b/stratosphere/LogManager/LogManager.json new file mode 100644 index 000000000..f77ac741c --- /dev/null +++ b/stratosphere/LogManager/LogManager.json @@ -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 + } + } + ] +} \ No newline at end of file diff --git a/stratosphere/LogManager/Makefile b/stratosphere/LogManager/Makefile new file mode 100644 index 000000000..7320b9f99 --- /dev/null +++ b/stratosphere/LogManager/Makefile @@ -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 +#--------------------------------------------------------------------------------------- diff --git a/stratosphere/LogManager/source/impl/lm_log_packet.cpp b/stratosphere/LogManager/source/impl/lm_log_packet.cpp new file mode 100644 index 000000000..12e8280e0 --- /dev/null +++ b/stratosphere/LogManager/source/impl/lm_log_packet.cpp @@ -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 &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(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 */ + 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 : () */ + 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"); + } + } +} \ No newline at end of file diff --git a/stratosphere/LogManager/source/impl/lm_log_packet.hpp b/stratosphere/LogManager/source/impl/lm_log_packet.hpp new file mode 100644 index 000000000..0c51f546f --- /dev/null +++ b/stratosphere/LogManager/source/impl/lm_log_packet.hpp @@ -0,0 +1,184 @@ + +#pragma once +#include +#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 + 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 {}; + + /* Actual chunk base types. */ + + struct LogDataChunkSessionBeginType : public LogDataChunkType {}; + struct LogDataChunkSessionEndType : public LogDataChunkType {}; + struct LogDataChunkTextLogType : public LogDataChunkStringType {}; + struct LogDataChunkLineNumberType : public LogDataChunkType {}; + struct LogDataChunkFileNameType : public LogDataChunkStringType {}; + struct LogDataChunkFunctionNameType : public LogDataChunkStringType {}; + struct LogDataChunkModuleNameType : public LogDataChunkStringType {}; + struct LogDataChunkThreadNameType : public LogDataChunkStringType {}; + struct LogDataChunkLogPacketDropCountLogType : public LogDataChunkType {}; + struct LogDataChunkUserSystemClockType : public LogDataChunkType {}; + 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 + inline C ParseStringChunkType(const u8 *chunk_buf) { + static_assert(std::is_base_of_v, "Invalid type"); + + auto chunk_header = *reinterpret_cast(chunk_buf); + auto type = *reinterpret_cast(chunk_buf); + auto chunk_str_buf = reinterpret_cast(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(buf); + packet.header = *reinterpret_cast(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(chunk_buf); + /* Parse the chunk depending on the type (strings require special parsing) */ + switch(static_cast(chunk_header.key)) { + case LogDataChunkKey::SessionBegin: + packet.payload.session_begin = *reinterpret_cast(chunk_buf); + break; + case LogDataChunkKey::SessionEnd: + packet.payload.session_end = *reinterpret_cast(chunk_buf); + break; + case LogDataChunkKey::TextLog: + packet.payload.text_log = ParseStringChunkType(chunk_buf); + break; + case LogDataChunkKey::LineNumber: + packet.payload.line_number = *reinterpret_cast(chunk_buf); + break; + case LogDataChunkKey::FileName: + packet.payload.file_name = ParseStringChunkType(chunk_buf); + break; + case LogDataChunkKey::FunctionName: + packet.payload.function_name = ParseStringChunkType(chunk_buf); + break; + case LogDataChunkKey::ModuleName: + packet.payload.module_name = ParseStringChunkType(chunk_buf); + break; + case LogDataChunkKey::ThreadName: + packet.payload.thread_name = ParseStringChunkType(chunk_buf); + break; + case LogDataChunkKey::LogPacketDropCount: + packet.payload.log_packet_drop_count = *reinterpret_cast(chunk_buf); + break; + case LogDataChunkKey::UserSystemClock: + packet.payload.user_system_clock = *reinterpret_cast(chunk_buf); + break; + case LogDataChunkKey::ProcessName: + packet.payload.process_name = ParseStringChunkType(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 &packet_list, u64 program_id, LogDestination destination); + +} \ No newline at end of file diff --git a/stratosphere/LogManager/source/impl/lm_scoped_log_file.cpp b/stratosphere/LogManager/source/impl/lm_scoped_log_file.cpp new file mode 100644 index 000000000..1a6fc85bc --- /dev/null +++ b/stratosphere/LogManager/source/impl/lm_scoped_log_file.cpp @@ -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; + } + } + +} \ No newline at end of file diff --git a/stratosphere/LogManager/source/impl/lm_scoped_log_file.hpp b/stratosphere/LogManager/source/impl/lm_scoped_log_file.hpp new file mode 100644 index 000000000..e2f9e0e97 --- /dev/null +++ b/stratosphere/LogManager/source/impl/lm_scoped_log_file.hpp @@ -0,0 +1,43 @@ + +#pragma once +#include + +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); + }; + +} \ No newline at end of file diff --git a/stratosphere/LogManager/source/lm_main.cpp b/stratosphere/LogManager/source/lm_main.cpp new file mode 100644 index 000000000..95107377f --- /dev/null +++ b/stratosphere/LogManager/source/lm_main.cpp @@ -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 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(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(LmServiceName, LmMaxSessions)); + + /* Loop forever, servicing our services. */ + lm::StartAndLoopProcess(); + + return 0; +} \ No newline at end of file diff --git a/stratosphere/LogManager/source/lm_service.cpp b/stratosphere/LogManager/source/lm_service.cpp new file mode 100644 index 000000000..9c64e8b05 --- /dev/null +++ b/stratosphere/LogManager/source/lm_service.cpp @@ -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> 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(client_pid.GetValue())); + auto logger = std::make_shared(program_id); + out_logger.SetValue(std::move(logger)); + } + +} \ No newline at end of file diff --git a/stratosphere/LogManager/source/lm_service.hpp b/stratosphere/LogManager/source/lm_service.hpp new file mode 100644 index 000000000..6181aa571 --- /dev/null +++ b/stratosphere/LogManager/source/lm_service.hpp @@ -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 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> out_logger); + + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(OpenLogger), + }; + + }; + +} \ No newline at end of file diff --git a/stratosphere/LogManager/source/lm_types.hpp b/stratosphere/LogManager/source/lm_types.hpp new file mode 100644 index 000000000..a26db3d50 --- /dev/null +++ b/stratosphere/LogManager/source/lm_types.hpp @@ -0,0 +1,14 @@ + +#pragma once +#include + +namespace ams::lm { + + enum class LogDestination : u32 { + TMA = 1, + UART = 2, + UARTSleeping = 4, + All = 0xFFFF, + }; + +} \ No newline at end of file diff --git a/stratosphere/Makefile b/stratosphere/Makefile index 9b4cc7470..1d0416dd8 100644 --- a/stratosphere/Makefile +++ b/stratosphere/Makefile @@ -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)