mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-04-22 20:44:49 +00:00
Add LogManager
This commit is contained in:
parent
52c89a76b2
commit
ae54769e5b
13 changed files with 903 additions and 1 deletions
3
Makefile
3
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
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
103
stratosphere/LogManager/LogManager.json
Normal file
103
stratosphere/LogManager/LogManager.json
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
128
stratosphere/LogManager/Makefile
Normal file
128
stratosphere/LogManager/Makefile
Normal 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
|
||||
#---------------------------------------------------------------------------------------
|
133
stratosphere/LogManager/source/impl/lm_log_packet.cpp
Normal file
133
stratosphere/LogManager/source/impl/lm_log_packet.cpp
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
184
stratosphere/LogManager/source/impl/lm_log_packet.hpp
Normal file
184
stratosphere/LogManager/source/impl/lm_log_packet.hpp
Normal 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);
|
||||
|
||||
}
|
49
stratosphere/LogManager/source/impl/lm_scoped_log_file.cpp
Normal file
49
stratosphere/LogManager/source/impl/lm_scoped_log_file.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
43
stratosphere/LogManager/source/impl/lm_scoped_log_file.hpp
Normal file
43
stratosphere/LogManager/source/impl/lm_scoped_log_file.hpp
Normal 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);
|
||||
};
|
||||
|
||||
}
|
144
stratosphere/LogManager/source/lm_main.cpp
Normal file
144
stratosphere/LogManager/source/lm_main.cpp
Normal 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;
|
||||
}
|
39
stratosphere/LogManager/source/lm_service.cpp
Normal file
39
stratosphere/LogManager/source/lm_service.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
59
stratosphere/LogManager/source/lm_service.hpp
Normal file
59
stratosphere/LogManager/source/lm_service.hpp
Normal 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),
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
14
stratosphere/LogManager/source/lm_types.hpp
Normal file
14
stratosphere/LogManager/source/lm_types.hpp
Normal 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,
|
||||
};
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue