mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-07-19 23:12:15 +02:00
Add LogManager
This commit is contained in:
parent
52c89a76b2
commit
ae54769e5b
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/0100000000000036
|
||||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000037
|
mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000037
|
||||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/010000000000003C
|
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/fatal_errors
|
||||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/config_templates
|
mkdir -p atmosphere-$(AMSVER)/atmosphere/config_templates
|
||||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/config
|
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/creport/creport.nsp atmosphere-$(AMSVER)/atmosphere/contents/0100000000000036/exefs.nsp
|
||||||
cp stratosphere/ro/ro.nsp atmosphere-$(AMSVER)/atmosphere/contents/0100000000000037/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/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
|
mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000032/flags
|
||||||
touch atmosphere-$(AMSVER)/atmosphere/contents/0100000000000032/flags/boot2.flag
|
touch atmosphere-$(AMSVER)/atmosphere/contents/0100000000000032/flags/boot2.flag
|
||||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000037/flags
|
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/spl/spl.elf atmosphere-$(AMSVER)-debug/spl.elf
|
||||||
cp stratosphere/erpt/erpt.elf atmosphere-$(AMSVER)-debug/erpt.elf
|
cp stratosphere/erpt/erpt.elf atmosphere-$(AMSVER)-debug/erpt.elf
|
||||||
cp stratosphere/jpegdec/jpegdec.elf atmosphere-$(AMSVER)-debug/jpegdec.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 ../;
|
cd atmosphere-$(AMSVER)-debug; zip -r ../atmosphere-$(AMSVER)-debug.zip ./*; cd ../;
|
||||||
rm -r atmosphere-$(AMSVER)-debug
|
rm -r atmosphere-$(AMSVER)-debug
|
||||||
mv atmosphere-$(AMSVER)-debug.zip out/atmosphere-$(AMSVER)-debug.zip
|
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, Main);
|
||||||
AMS_DEFINE_SYSTEM_THREAD(21, pgl, ProcessControlTask);
|
AMS_DEFINE_SYSTEM_THREAD(21, pgl, ProcessControlTask);
|
||||||
|
|
||||||
|
/* lm. */
|
||||||
|
AMS_DEFINE_SYSTEM_THREAD(-18, lm, IpcServer);
|
||||||
|
|
||||||
#undef AMS_DEFINE_SYSTEM_THREAD
|
#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)
|
SUBFOLDERS := $(MODULES)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user