mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-12-22 18:32:05 +00:00
Merge pull request #266 from Atmosphere-NX/fatal
Implement custom fatal sysmodule.
This commit is contained in:
commit
ab33329129
52 changed files with 4454 additions and 16 deletions
2
Makefile
2
Makefile
|
@ -40,11 +40,13 @@ dist: all
|
|||
mkdir atmosphere-$(AMSVER)
|
||||
mkdir atmosphere-$(AMSVER)/atmosphere
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000036
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000034
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032
|
||||
cp fusee/fusee-secondary/fusee-secondary.bin atmosphere-$(AMSVER)/fusee-secondary.bin
|
||||
cp common/defaults/BCT.ini atmosphere-$(AMSVER)/BCT.ini
|
||||
cp common/defaults/loader.ini atmosphere-$(AMSVER)/atmosphere/loader.ini
|
||||
cp stratosphere/creport/creport.nsp atmosphere-$(AMSVER)/atmosphere/titles/0100000000000036/exefs.nsp
|
||||
cp stratosphere/fatal/fatal.nsp atmosphere-$(AMSVER)/atmosphere/titles/0100000000000034/exefs.nsp
|
||||
cp stratosphere/set_mitm/set_mitm.nsp atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/exefs.nsp
|
||||
touch atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/boot2.flag
|
||||
cd atmosphere-$(AMSVER); zip -r ../atmosphere-$(AMSVER).zip ./*; cd ../;
|
||||
|
|
|
@ -9,6 +9,13 @@ endif
|
|||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/devkitA64/base_rules
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
|
@ -26,7 +33,7 @@ INCLUDES := include ../common/include
|
|||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a -mtune=cortex-a57 -mgeneral-regs-only #<- important
|
||||
DEFINES := -D__CCPLEX__
|
||||
DEFINES := -D__CCPLEX__ -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
CFLAGS := \
|
||||
-g \
|
||||
-O2 \
|
||||
|
|
|
@ -9,6 +9,13 @@ endif
|
|||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITARM)/base_rules
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
|
@ -26,7 +33,7 @@ INCLUDES := include ../../common/include
|
|||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv4t -mtune=arm7tdmi -mthumb -mthumb-interwork
|
||||
DEFINES := -D__BPMP__ -DFUSEE_STAGE1_SRC
|
||||
DEFINES := -D__BPMP__ -DFUSEE_STAGE1_SRC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
|
||||
CFLAGS := \
|
||||
-g \
|
||||
|
|
|
@ -12,6 +12,13 @@ AMS := $(TOPDIR)/../../
|
|||
|
||||
include $(DEVKITARM)/base_rules
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
|
@ -29,7 +36,7 @@ INCLUDES := include ../../common/include
|
|||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv4t -mtune=arm7tdmi -marm
|
||||
DEFINES := -D__BPMP__ -DFUSEE_STAGE2_SRC
|
||||
DEFINES := -D__BPMP__ -DFUSEE_STAGE2_SRC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
|
||||
CFLAGS := \
|
||||
-g \
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
KIPS := loader pm sm boot fs_mitm set_mitm creport
|
||||
KIPS := loader pm sm boot fs_mitm set_mitm creport fatal
|
||||
|
||||
#TODO: boot2 ?
|
||||
|
||||
|
|
|
@ -9,6 +9,14 @@ endif
|
|||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
endif
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
|
@ -24,7 +32,7 @@ DATA := data
|
|||
INCLUDES := include ../../common/include
|
||||
EXEFS_SRC := exefs_src
|
||||
|
||||
DEFINES := -DDISABLE_IPC
|
||||
DEFINES := -DDISABLE_IPC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
|
|
|
@ -9,6 +9,13 @@ endif
|
|||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
|
@ -24,7 +31,7 @@ DATA := data
|
|||
INCLUDES := include ../../common/include
|
||||
EXEFS_SRC := exefs_src
|
||||
|
||||
DEFINES := -DDISABLE_IPC
|
||||
DEFINES := -DDISABLE_IPC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
|
|
|
@ -29,7 +29,7 @@ struct CodeInfo {
|
|||
};
|
||||
|
||||
class CodeList {
|
||||
private:
|
||||
public:
|
||||
static const size_t max_code_count = 0x60;
|
||||
u32 code_count = 0;
|
||||
CodeInfo code_infos[max_code_count];
|
||||
|
|
|
@ -47,6 +47,35 @@ void CrashReport::BuildReport(u64 pid, bool has_extra_info) {
|
|||
}
|
||||
}
|
||||
|
||||
FatalContext *CrashReport::GetFatalContext() {
|
||||
FatalContext *ctx = new FatalContext;
|
||||
*ctx = (FatalContext){0};
|
||||
|
||||
ctx->is_aarch32 = false;
|
||||
ctx->type = static_cast<u32>(this->exception_info.type);
|
||||
|
||||
for (size_t i = 0; i < 29; i++) {
|
||||
ctx->aarch64_ctx.x[i] = this->crashed_thread_info.context.cpu_gprs[i].x;
|
||||
}
|
||||
ctx->aarch64_ctx.fp = this->crashed_thread_info.context.fp;
|
||||
ctx->aarch64_ctx.lr = this->crashed_thread_info.context.lr;
|
||||
ctx->aarch64_ctx.pc = this->crashed_thread_info.context.pc.x;
|
||||
|
||||
ctx->aarch64_ctx.stack_trace_size = this->crashed_thread_info.stack_trace_size;
|
||||
for (size_t i = 0; i < ctx->aarch64_ctx.stack_trace_size; i++) {
|
||||
ctx->aarch64_ctx.stack_trace[i] = this->crashed_thread_info.stack_trace[i];
|
||||
}
|
||||
|
||||
if (this->code_list.code_count) {
|
||||
ctx->aarch64_ctx.start_address = this->code_list.code_infos[0].start_address;
|
||||
}
|
||||
|
||||
/* For ams fatal... */
|
||||
ctx->aarch64_ctx.afsr0 = this->process_info.title_id;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void CrashReport::ProcessExceptions() {
|
||||
if (!IsOpen()) {
|
||||
return;
|
||||
|
@ -233,7 +262,7 @@ void CrashReport::EnsureReportDirectories() {
|
|||
}
|
||||
|
||||
void CrashReport::SaveReport() {
|
||||
/* TODO: Save the report to the SD card. */
|
||||
/* Save the report to the SD card. */
|
||||
char report_path[FS_MAX_PATH];
|
||||
|
||||
/* Ensure path exists. */
|
||||
|
|
|
@ -61,6 +61,7 @@ class CrashReport {
|
|||
|
||||
public:
|
||||
void BuildReport(u64 pid, bool has_extra_info);
|
||||
FatalContext *GetFatalContext();
|
||||
void SaveReport();
|
||||
|
||||
bool IsAddressReadable(u64 address, u64 size, MemoryInfo *mi = NULL);
|
||||
|
|
|
@ -132,7 +132,9 @@ int main(int argc, char **argv) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
fatalWithType(g_Creport.GetResult(), FatalType_ErrorScreen);
|
||||
FatalContext *ctx = g_Creport.GetFatalContext();
|
||||
|
||||
fatalWithContext(g_Creport.GetResult(), FatalType_ErrorScreen, ctx);
|
||||
}
|
||||
|
||||
}
|
|
@ -23,7 +23,7 @@
|
|||
class CodeList;
|
||||
|
||||
class ThreadInfo {
|
||||
private:
|
||||
public:
|
||||
ThreadContext context{};
|
||||
u64 thread_id = 0;
|
||||
u64 stack_top = 0;
|
||||
|
|
166
stratosphere/fatal/Makefile
Normal file
166
stratosphere/fatal/Makefile
Normal file
|
@ -0,0 +1,166 @@
|
|||
#---------------------------------------------------------------------------------
|
||||
.SUFFIXES:
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(strip $(DEVKITPRO)),)
|
||||
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
|
||||
endif
|
||||
|
||||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
# SOURCES is a list of directories containing source code
|
||||
# DATA is a list of directories containing data files
|
||||
# INCLUDES is a list of directories containing header files
|
||||
# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
BUILD := build
|
||||
SOURCES := source
|
||||
DATA := data
|
||||
INCLUDES := include ../../common/include
|
||||
EXEFS_SRC := exefs_src
|
||||
|
||||
DEFINES := -DDISABLE_IPC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||
$(ARCH) $(DEFINES)
|
||||
|
||||
CFLAGS += $(INCLUDE) -D__SWITCH__ `freetype-config --cflags`
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
||||
LIBS := `freetype-config --libs` -lstratosphere -lnx
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
# include and lib
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/../libstratosphere
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# 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),$(notdir $(wildcard $(dir)/*.c)))
|
||||
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.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
|
||||
#---------------------------------------------------------------------------------------
|
95
stratosphere/fatal/fatal.json
Normal file
95
stratosphere/fatal/fatal.json
Normal file
|
@ -0,0 +1,95 @@
|
|||
{
|
||||
"name": "fatal",
|
||||
"title_id": "0x0100000000000034",
|
||||
"title_id_range_min": "0x0100000000000034",
|
||||
"title_id_range_max": "0x0100000000000034",
|
||||
"main_thread_stack_size": "0x00010000",
|
||||
"main_thread_priority": 37,
|
||||
"default_cpu_id": 3,
|
||||
"process_category": 0,
|
||||
"is_retail": true,
|
||||
"pool_partition": 2,
|
||||
"is_64_bit": true,
|
||||
"address_space_type": 3,
|
||||
"filesystem_access": {
|
||||
"permissions": "0xFFFFFFFFFFFFFFFF"
|
||||
},
|
||||
"service_access": ["bpc", "bpc:c", "erpt:c", "fsp-srv", "gpio", "i2c", "lbl", "lm", "nvdrv:s", "pcv", "pl:u", "pm:info", "psm", "set", "set:sys", "spsm", "vi:m", "vi:s"],
|
||||
"service_host": ["fatal:p", "fatal:u", "time:s"],
|
||||
"kernel_capabilities": [{
|
||||
"type": "kernel_flags",
|
||||
"value": {
|
||||
"highest_thread_priority": 63,
|
||||
"lowest_thread_priority": 12,
|
||||
"lowest_cpu_id": 0,
|
||||
"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",
|
||||
"svcReadWriteRegister": "0x4E",
|
||||
"svcDebugActiveProcess": "0x60",
|
||||
"svcGetDebugEvent": "0x63",
|
||||
"svcGetThreadList": "0x66",
|
||||
"svcGetDebugThreadContext": "0x67",
|
||||
"svcQueryDebugProcessMemory": "0x69",
|
||||
"svcReadDebugProcessMemory": "0x6a",
|
||||
"svcGetDebugThreadParam": "0x6d"
|
||||
}
|
||||
}, {
|
||||
"type": "min_kernel_version",
|
||||
"value": "0x0060"
|
||||
}, {
|
||||
"type": "handle_table_size",
|
||||
"value": 128
|
||||
}]
|
||||
}
|
1304
stratosphere/fatal/source/ams_logo.hpp
Normal file
1304
stratosphere/fatal/source/ams_logo.hpp
Normal file
File diff suppressed because it is too large
Load diff
88
stratosphere/fatal/source/fatal_config.cpp
Normal file
88
stratosphere/fatal/source/fatal_config.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "fatal_types.hpp"
|
||||
#include "fatal_config.hpp"
|
||||
|
||||
static FatalConfig g_fatal_config;
|
||||
|
||||
static IEvent *g_fatal_settings_event = nullptr;
|
||||
|
||||
FatalConfig *GetFatalConfig() {
|
||||
return &g_fatal_config;
|
||||
}
|
||||
|
||||
static void UpdateLanguageCode() {
|
||||
setGetLanguageCode(&GetFatalConfig()->language_code);
|
||||
}
|
||||
|
||||
IEvent *GetFatalSettingsEvent() {
|
||||
if (g_fatal_settings_event == nullptr) {
|
||||
Event evt;
|
||||
if (R_FAILED(setsysBindFatalDirtyFlagEvent(&evt))) {
|
||||
std::abort();
|
||||
}
|
||||
g_fatal_settings_event = LoadReadOnlySystemEvent(evt.revent, [](u64 timeout) {
|
||||
u64 flags_0, flags_1;
|
||||
if (R_SUCCEEDED(setsysGetFatalDirtyFlags(&flags_0, &flags_1)) && (flags_0 & 1)) {
|
||||
UpdateLanguageCode();
|
||||
}
|
||||
return 0;
|
||||
}, true);
|
||||
}
|
||||
|
||||
return g_fatal_settings_event;
|
||||
}
|
||||
|
||||
static void SetupConfigLanguages() {
|
||||
FatalConfig *config = GetFatalConfig();
|
||||
|
||||
/* Defaults. */
|
||||
config->error_msg = u8"Error Code: 2%03d-%04d (0x%x)\n";
|
||||
|
||||
if (config->quest_flag) {
|
||||
config->error_desc = u8"Please call 1-800-875-1852 for service.\n";
|
||||
} else {
|
||||
config->error_desc = u8"An error has occured.\n\n"
|
||||
u8"Please press the POWER Button to restart the console. If you are\n"
|
||||
u8"unable to restart the console, hold the POWER Button for 12 seconds\n"
|
||||
u8"to turn the console off.\n\n"
|
||||
u8"If the problem persists, refer to the Nintendo Support Website.\n"
|
||||
u8"support.nintendo.com/switch/error\n";
|
||||
}
|
||||
|
||||
/* TODO: Try to load dynamically. */
|
||||
/* FsStorage message_storage; */
|
||||
/* TODO: if (R_SUCCEEDED(fsOpenDataStorageByDataId(0x010000000000081D, "fatal_msg"))) { ... } */
|
||||
}
|
||||
|
||||
void InitializeFatalConfig() {
|
||||
FatalConfig *config = GetFatalConfig();
|
||||
|
||||
memset(config, 0, sizeof(*config));
|
||||
setsysGetSerialNumber(config->serial_number);
|
||||
setsysGetFirmwareVersion(&config->firmware_version);
|
||||
UpdateLanguageCode();
|
||||
|
||||
setsysGetSettingsItemValue("fatal", "transition_to_fatal", &config->transition_to_fatal, sizeof(config->transition_to_fatal));
|
||||
setsysGetSettingsItemValue("fatal", "show_extra_info", &config->show_extra_info, sizeof(config->show_extra_info));
|
||||
setsysGetSettingsItemValue("fatal", "quest_reboot_interval_second", &config->quest_reboot_interval_second, sizeof(config->quest_reboot_interval_second));
|
||||
|
||||
setsysGetFlag(SetSysFlag_Quest, &config->quest_flag);
|
||||
|
||||
SetupConfigLanguages();
|
||||
}
|
37
stratosphere/fatal/source/fatal_config.hpp
Normal file
37
stratosphere/fatal/source/fatal_config.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
struct FatalConfig {
|
||||
char serial_number[0x18];
|
||||
SetSysFirmwareVersion firmware_version;
|
||||
u64 language_code;
|
||||
u64 quest_reboot_interval_second;
|
||||
bool transition_to_fatal;
|
||||
bool show_extra_info;
|
||||
bool quest_flag;
|
||||
const char *error_msg;
|
||||
const char *error_desc;
|
||||
const char *quest_desc;
|
||||
};
|
||||
|
||||
IEvent *GetFatalSettingsEvent();
|
||||
FatalConfig *GetFatalConfig();
|
||||
|
||||
void InitializeFatalConfig();
|
267
stratosphere/fatal/source/fatal_debug.cpp
Normal file
267
stratosphere/fatal/source/fatal_debug.cpp
Normal file
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <map>
|
||||
#include <switch.h>
|
||||
#include "fatal_debug.hpp"
|
||||
#include "fatal_config.hpp"
|
||||
|
||||
static bool IsAddressReadable(Handle debug_handle, u64 address, u64 size, MemoryInfo *o_mi) {
|
||||
MemoryInfo mi;
|
||||
u32 pi;
|
||||
|
||||
if (o_mi == NULL) {
|
||||
o_mi = &mi;
|
||||
}
|
||||
|
||||
if (R_FAILED(svcQueryDebugProcessMemory(o_mi, &pi, debug_handle, address))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Must be readable */
|
||||
if ((o_mi->perm & Perm_R) != Perm_R) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Must have space for both userdata address and userdata size. */
|
||||
if (address < o_mi->addr || o_mi->addr + o_mi->size < address + size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool CheckThreadIsFatalCaller(FatalThrowContext *ctx, u64 debug_handle, u64 thread_id, u64 thread_tls_addr, ThreadContext *thread_ctx) {
|
||||
/* Verify that the thread is running or waiting. */
|
||||
{
|
||||
u64 _;
|
||||
u32 thread_state;
|
||||
if (R_FAILED(svcGetDebugThreadParam(&_, &thread_state, debug_handle, thread_id, DebugThreadParam_State))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (thread_state > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the thread context. */
|
||||
if (R_FAILED(svcGetDebugThreadContext(thread_ctx, debug_handle, thread_id, 0xF))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check if PC is readable. */
|
||||
if (!IsAddressReadable(debug_handle, thread_ctx->pc.x, sizeof(u32), NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Try to read the current instruction. */
|
||||
u32 insn;
|
||||
if (R_FAILED(svcReadDebugProcessMemory(&insn, debug_handle, thread_ctx->pc.x, sizeof(insn)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the instruction isn't svcSendSyncRequest, it's not the fatal caller. */
|
||||
if (insn != 0xD4000421) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The fatal caller will have readable tls. */
|
||||
if (!IsAddressReadable(debug_handle, thread_tls_addr, 0x100, NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Read in the fatal caller's tls. */
|
||||
u8 thread_tls[0x100];
|
||||
if (R_FAILED(svcReadDebugProcessMemory(thread_tls, debug_handle, thread_tls_addr, sizeof(thread_tls)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Replace our tls with the fatal caller's. */
|
||||
std::memcpy(armGetTls(), thread_tls, sizeof(thread_tls));
|
||||
|
||||
/* Parse the command that the thread sent. */
|
||||
{
|
||||
IpcParsedCommand r;
|
||||
if (R_FAILED(ipcParse(&r))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Fatal command takes in a PID, only one buffer max. */
|
||||
if (!r.HasPid || r.NumStatics || r.NumStaticsOut || r.NumHandles) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u64 cmd_id;
|
||||
u32 err_code;
|
||||
} *raw = (decltype(raw))(r.Raw);
|
||||
|
||||
if (raw->magic != SFCI_MAGIC) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (raw->cmd_id > 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (raw->cmd_id != 2 && r.NumBuffers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (raw->err_code != ctx->error_code) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* We found our caller. */
|
||||
return true;
|
||||
}
|
||||
|
||||
void TryCollectDebugInformation(FatalThrowContext *ctx, u64 pid) {
|
||||
Handle debug_handle;
|
||||
if (R_SUCCEEDED(svcDebugActiveProcess(&debug_handle, pid))) {
|
||||
/* Ensure we close the debugged process. */
|
||||
ON_SCOPE_EXIT { svcCloseHandle(debug_handle); };
|
||||
|
||||
/* First things first, check if process is 64 bits, and get list of thread infos. */
|
||||
std::unordered_map<u64, u64> thread_id_to_tls;
|
||||
{
|
||||
bool got_attach_process = false;
|
||||
DebugEventInfo d;
|
||||
while (R_SUCCEEDED(svcGetDebugEvent((u8 *)&d, debug_handle))) {
|
||||
if (d.type == DebugEventType::AttachProcess) {
|
||||
ctx->cpu_ctx.is_aarch32 = (d.info.attach_process.flags & 1) == 0;
|
||||
memcpy(ctx->proc_name, d.info.attach_process.name, sizeof(d.info.attach_process.name));
|
||||
got_attach_process = true;
|
||||
} else if (d.type == DebugEventType::AttachThread) {
|
||||
thread_id_to_tls[d.info.attach_thread.thread_id] = d.info.attach_thread.tls_address;
|
||||
}
|
||||
}
|
||||
|
||||
if (!got_attach_process) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Try to collect information on 32-bit fatals. This shouldn't really matter for any real use case. */
|
||||
if (ctx->cpu_ctx.is_aarch32) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Welcome to hell. */
|
||||
bool found_fatal_caller = false;
|
||||
u64 thread_id = 0;
|
||||
ThreadContext thread_ctx;
|
||||
{
|
||||
/* We start by trying to get a list of threads. */
|
||||
u32 thread_count;
|
||||
u64 thread_ids[0x60];
|
||||
if (R_FAILED(svcGetThreadList(&thread_count, thread_ids, 0x60, debug_handle))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* We need to locate the thread that's called fatal. */
|
||||
for (u32 i = 0; i < thread_count; i++) {
|
||||
const u64 cur_thread_id = thread_ids[i];
|
||||
if (thread_id_to_tls.find(cur_thread_id) == thread_id_to_tls.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CheckThreadIsFatalCaller(ctx, debug_handle, cur_thread_id, thread_id_to_tls[cur_thread_id], &thread_ctx)) {
|
||||
thread_id = cur_thread_id;
|
||||
found_fatal_caller = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found_fatal_caller) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (R_FAILED(svcGetDebugThreadContext(&thread_ctx, debug_handle, thread_id, 0xF))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* So we found our caller. */
|
||||
for (u32 i = 0; i < 29; i++) {
|
||||
/* GetDebugThreadContext won't give us any of these registers, because thread is in SVC :( */
|
||||
ctx->has_gprs[i] = false;
|
||||
}
|
||||
for (u32 i = 29; i < NumAarch64Gprs; i++) {
|
||||
ctx->has_gprs[i] = true;
|
||||
}
|
||||
ctx->cpu_ctx.aarch64_ctx.fp = thread_ctx.fp;
|
||||
ctx->cpu_ctx.aarch64_ctx.lr = thread_ctx.lr;
|
||||
ctx->cpu_ctx.aarch64_ctx.sp = thread_ctx.sp;
|
||||
ctx->cpu_ctx.aarch64_ctx.pc = thread_ctx.pc.x;
|
||||
|
||||
|
||||
/* Parse a stack trace. */
|
||||
u64 cur_fp = thread_ctx.fp;
|
||||
for (unsigned int i = 0; i < sizeof(ctx->cpu_ctx.aarch64_ctx.stack_trace)/sizeof(u64); i++) {
|
||||
/* Validate the current frame. */
|
||||
if (cur_fp == 0 || (cur_fp & 0xF)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Read a new frame. */
|
||||
StackFrame cur_frame;
|
||||
if (R_FAILED(svcReadDebugProcessMemory(&cur_frame, debug_handle, cur_fp, sizeof(StackFrame)))) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Advance to the next frame. */
|
||||
ctx->cpu_ctx.aarch64_ctx.stack_trace[ctx->cpu_ctx.aarch64_ctx.stack_trace_size++] = cur_frame.lr;
|
||||
cur_fp = cur_frame.fp;
|
||||
}
|
||||
|
||||
/* Try to read up to 0x100 of stack. */
|
||||
for (size_t sz = 0x100; sz > 0; sz -= 0x10) {
|
||||
if (IsAddressReadable(debug_handle, ctx->cpu_ctx.aarch64_ctx.sp, sz, nullptr)) {
|
||||
if (R_SUCCEEDED(svcReadDebugProcessMemory(ctx->stack_dump, debug_handle, ctx->cpu_ctx.aarch64_ctx.sp, sz))) {
|
||||
ctx->stack_dump_size = sz;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse the starting address. */
|
||||
{
|
||||
u64 guess = thread_ctx.pc.x;
|
||||
MemoryInfo mi;
|
||||
u32 pi;
|
||||
if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess)) || mi.perm != Perm_Rx) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Iterate backwards until we find the memory before the code region. */
|
||||
while (mi.addr > 0) {
|
||||
if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mi.type == MemType_Unmapped) {
|
||||
/* Code region will be at the end of the unmapped region preceding it. */
|
||||
ctx->cpu_ctx.aarch64_ctx.start_address = mi.addr + mi.size;
|
||||
break;
|
||||
}
|
||||
|
||||
guess -= 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
149
stratosphere/fatal/source/fatal_debug.hpp
Normal file
149
stratosphere/fatal/source/fatal_debug.hpp
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fatal_types.hpp"
|
||||
|
||||
void TryCollectDebugInformation(FatalThrowContext *ctx, u64 pid);
|
||||
|
||||
struct StackFrame {
|
||||
u64 fp;
|
||||
u64 lr;
|
||||
};
|
||||
|
||||
struct AttachProcessInfo {
|
||||
u64 title_id;
|
||||
u64 process_id;
|
||||
char name[0xC];
|
||||
u32 flags;
|
||||
u64 user_exception_context_address; /* 5.0.0+ */
|
||||
};
|
||||
|
||||
struct AttachThreadInfo {
|
||||
u64 thread_id;
|
||||
u64 tls_address;
|
||||
u64 entrypoint;
|
||||
};
|
||||
|
||||
/* TODO: ExitProcessInfo */
|
||||
/* TODO: ExitThreadInfo */
|
||||
|
||||
enum class DebugExceptionType : u32 {
|
||||
UndefinedInstruction = 0,
|
||||
InstructionAbort = 1,
|
||||
DataAbort = 2,
|
||||
AlignmentFault = 3,
|
||||
DebuggerAttached = 4,
|
||||
BreakPoint = 5,
|
||||
UserBreak = 6,
|
||||
DebuggerBreak = 7,
|
||||
BadSvc = 8,
|
||||
UnknownNine = 9,
|
||||
};
|
||||
|
||||
static inline const char *GetDebugExceptionTypeStr(DebugExceptionType type) {
|
||||
switch (type) {
|
||||
case DebugExceptionType::UndefinedInstruction:
|
||||
return "Undefined Instruction";
|
||||
case DebugExceptionType::InstructionAbort:
|
||||
return "Instruction Abort";
|
||||
case DebugExceptionType::DataAbort:
|
||||
return "Data Abort";
|
||||
case DebugExceptionType::AlignmentFault:
|
||||
return "Alignment Fault";
|
||||
case DebugExceptionType::DebuggerAttached:
|
||||
return "Debugger Attached";
|
||||
case DebugExceptionType::BreakPoint:
|
||||
return "Break Point";
|
||||
case DebugExceptionType::UserBreak:
|
||||
return "User Break";
|
||||
case DebugExceptionType::DebuggerBreak:
|
||||
return "Debugger Break";
|
||||
case DebugExceptionType::BadSvc:
|
||||
return "Bad Svc";
|
||||
case DebugExceptionType::UnknownNine:
|
||||
return "Unknown Nine";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
struct UndefinedInstructionInfo {
|
||||
u32 insn;
|
||||
};
|
||||
|
||||
struct DataAbortInfo {
|
||||
u64 address;
|
||||
};
|
||||
|
||||
struct AlignmentFaultInfo {
|
||||
u64 address;
|
||||
};
|
||||
|
||||
struct UserBreakInfo {
|
||||
u64 break_reason;
|
||||
u64 address;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
struct BadSvcInfo {
|
||||
u32 id;
|
||||
};
|
||||
|
||||
union SpecificExceptionInfo {
|
||||
UndefinedInstructionInfo undefined_instruction;
|
||||
DataAbortInfo data_abort;
|
||||
AlignmentFaultInfo alignment_fault;
|
||||
UserBreakInfo user_break;
|
||||
BadSvcInfo bad_svc;
|
||||
u64 raw;
|
||||
};
|
||||
|
||||
struct ExceptionInfo {
|
||||
DebugExceptionType type;
|
||||
u64 address;
|
||||
SpecificExceptionInfo specific;
|
||||
};
|
||||
|
||||
|
||||
enum class DebugEventType : u32 {
|
||||
AttachProcess = 0,
|
||||
AttachThread = 1,
|
||||
ExitProcess = 2,
|
||||
ExitThread = 3,
|
||||
Exception = 4
|
||||
};
|
||||
|
||||
union DebugInfo {
|
||||
AttachProcessInfo attach_process;
|
||||
AttachThreadInfo attach_thread;
|
||||
ExceptionInfo exception;
|
||||
};
|
||||
|
||||
struct DebugEventInfo {
|
||||
DebugEventType type;
|
||||
u32 flags;
|
||||
u64 thread_id;
|
||||
union {
|
||||
DebugInfo info;
|
||||
u64 _[0x40/sizeof(u64)];
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(DebugEventInfo) >= 0x50, "Incorrect DebugEventInfo definition!");
|
52
stratosphere/fatal/source/fatal_event_manager.cpp
Normal file
52
stratosphere/fatal/source/fatal_event_manager.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "fatal_types.hpp"
|
||||
#include "fatal_event_manager.hpp"
|
||||
|
||||
static FatalEventManager g_event_manager;
|
||||
|
||||
FatalEventManager *GetEventManager() {
|
||||
return &g_event_manager;
|
||||
}
|
||||
|
||||
FatalEventManager::FatalEventManager() {
|
||||
/* Just create all the events. */
|
||||
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
|
||||
if (R_FAILED(eventCreate(&this->events[i], true))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result FatalEventManager::GetEvent(Handle *out) {
|
||||
std::scoped_lock<HosMutex> lk{this->lock};
|
||||
|
||||
/* Only allow GetEvent to succeed NumFatalEvents times. */
|
||||
if (this->events_gotten >= FatalEventManager::NumFatalEvents) {
|
||||
return FatalResult_TooManyEvents;
|
||||
}
|
||||
|
||||
*out = this->events[this->events_gotten++].revent;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FatalEventManager::SignalEvents() {
|
||||
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
|
||||
eventFire(&this->events[i]);
|
||||
}
|
||||
}
|
34
stratosphere/fatal/source/fatal_event_manager.hpp
Normal file
34
stratosphere/fatal/source/fatal_event_manager.hpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
class FatalEventManager {
|
||||
private:
|
||||
static constexpr size_t NumFatalEvents = 3;
|
||||
|
||||
HosMutex lock;
|
||||
size_t events_gotten = 0;
|
||||
Event events[NumFatalEvents];
|
||||
public:
|
||||
FatalEventManager();
|
||||
Result GetEvent(Handle *out);
|
||||
void SignalEvents();
|
||||
};
|
||||
|
||||
FatalEventManager *GetEventManager();
|
237
stratosphere/fatal/source/fatal_font.cpp
Normal file
237
stratosphere/fatal/source/fatal_font.cpp
Normal file
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <switch.h>
|
||||
#include "fatal_types.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
#include "fatal_config.hpp"
|
||||
#include "fatal_font.hpp"
|
||||
|
||||
static u16 *g_fb = nullptr;
|
||||
static u32 (*g_unswizzle_func)(u32, u32) = nullptr;
|
||||
static u16 g_font_color = 0xFFFF;
|
||||
static float g_font_sz = 16.0f;
|
||||
static u32 g_line_x = 0, g_cur_x = 0, g_cur_y = 0;
|
||||
|
||||
static u32 g_mono_adv = 0;
|
||||
|
||||
static PlFontData g_font;
|
||||
static PlFontData g_fonts[PlSharedFontType_Total];
|
||||
static FT_Library g_library;
|
||||
static FT_Face g_face;
|
||||
static FT_Error g_ft_err = 0;
|
||||
|
||||
static u16 Blend(u16 color, u16 bg, u8 alpha) {
|
||||
const u32 c_r = RGB565_GET_R8(color);
|
||||
const u32 c_g = RGB565_GET_G8(color);
|
||||
const u32 c_b = RGB565_GET_B8(color);
|
||||
const u32 b_r = RGB565_GET_R8(bg);
|
||||
const u32 b_g = RGB565_GET_G8(bg);
|
||||
const u32 b_b = RGB565_GET_B8(bg);
|
||||
|
||||
const u32 r = ((alpha * c_r) + ((0xFF - alpha) * b_r)) / 0xFF;
|
||||
const u32 g = ((alpha * c_g) + ((0xFF - alpha) * b_g)) / 0xFF;
|
||||
const u32 b = ((alpha * c_b) + ((0xFF - alpha) * b_b)) / 0xFF;
|
||||
|
||||
return RGB888_TO_RGB565(r, g, b);
|
||||
}
|
||||
|
||||
static void DrawGlyph(FT_Bitmap *bitmap, u32 x, u32 y) {
|
||||
u8* imageptr = bitmap->buffer;
|
||||
|
||||
if (bitmap->pixel_mode!=FT_PIXEL_MODE_GRAY) return;
|
||||
|
||||
for (u32 tmpy = 0; tmpy < bitmap->rows; tmpy++) {
|
||||
for (u32 tmpx = 0; tmpx < bitmap->width; tmpx++) {
|
||||
/* Implement very simple blending, as the bitmap value is an alpha value. */
|
||||
u16 *ptr = &g_fb[g_unswizzle_func(x + tmpx, y + tmpy)];
|
||||
*ptr = Blend(g_font_color, *ptr, imageptr[tmpx]);
|
||||
}
|
||||
imageptr += bitmap->pitch;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawString(const char *str, bool add_line, bool mono = false) {
|
||||
FT_UInt glyph_index;
|
||||
FT_GlyphSlot slot = g_face->glyph;
|
||||
|
||||
const size_t len = strlen(str);
|
||||
|
||||
u32 cur_x = g_cur_x, cur_y = g_cur_y;
|
||||
ON_SCOPE_EXIT {
|
||||
if (add_line) {
|
||||
/* Advance to next line. */
|
||||
g_cur_x = g_line_x;
|
||||
g_cur_y = cur_y + (g_face->size->metrics.height >> 6);
|
||||
} else {
|
||||
g_cur_x = cur_x;
|
||||
g_cur_y = cur_y;
|
||||
}
|
||||
};
|
||||
|
||||
for (u32 i = 0; i < len; ) {
|
||||
u32 cur_char;
|
||||
ssize_t unit_count = decode_utf8(&cur_char, reinterpret_cast<const u8 *>(&str[i]));
|
||||
if (unit_count <= 0) break;
|
||||
i += unit_count;
|
||||
|
||||
if (cur_char == '\n') {
|
||||
cur_x = g_line_x;
|
||||
cur_y += g_face->size->metrics.height >> 6;
|
||||
continue;
|
||||
}
|
||||
|
||||
glyph_index = FT_Get_Char_Index(g_face, cur_char);
|
||||
|
||||
g_ft_err = FT_Load_Glyph(g_face, glyph_index, FT_LOAD_DEFAULT);
|
||||
|
||||
if (g_ft_err == 0) {
|
||||
g_ft_err = FT_Render_Glyph(g_face->glyph, FT_RENDER_MODE_NORMAL);
|
||||
}
|
||||
|
||||
if (g_ft_err) {
|
||||
return;
|
||||
}
|
||||
|
||||
DrawGlyph(&slot->bitmap, cur_x + slot->bitmap_left + ((mono && g_mono_adv > slot->advance.x) ? ((g_mono_adv - slot->advance.x) >> 7) : 0), cur_y - slot->bitmap_top);
|
||||
|
||||
cur_x += (mono ? g_mono_adv : slot->advance.x) >> 6;
|
||||
cur_y += slot->advance.y >> 6;
|
||||
}
|
||||
}
|
||||
|
||||
void FontManager::PrintLine(const char *str) {
|
||||
return DrawString(str, true);
|
||||
}
|
||||
|
||||
void FontManager::PrintFormatLine(const char *format, ...) {
|
||||
va_list va_arg;
|
||||
va_start(va_arg, format);
|
||||
|
||||
char char_buf[0x400];
|
||||
vsnprintf(char_buf, sizeof(char_buf), format, va_arg);
|
||||
|
||||
PrintLine(char_buf);
|
||||
}
|
||||
|
||||
void FontManager::Print(const char *str) {
|
||||
return DrawString(str, false);
|
||||
}
|
||||
|
||||
void FontManager::PrintFormat(const char *format, ...) {
|
||||
va_list va_arg;
|
||||
va_start(va_arg, format);
|
||||
|
||||
char char_buf[0x400];
|
||||
vsnprintf(char_buf, sizeof(char_buf), format, va_arg);
|
||||
|
||||
Print(char_buf);
|
||||
}
|
||||
|
||||
void FontManager::PrintMonospaceU64(u64 x) {
|
||||
char char_buf[0x400];
|
||||
snprintf(char_buf, sizeof(char_buf), "%016lX", x);
|
||||
|
||||
DrawString(char_buf, false, true);
|
||||
}
|
||||
|
||||
void FontManager::PrintMonospaceU32(u32 x) {
|
||||
char char_buf[0x400];
|
||||
snprintf(char_buf, sizeof(char_buf), "%08X", x);
|
||||
|
||||
DrawString(char_buf, false, true);
|
||||
}
|
||||
|
||||
void FontManager::PrintMonospaceBlank(u32 width) {
|
||||
char char_buf[0x400] = {0};
|
||||
for (size_t i = 0; i < width && i < sizeof(char_buf); i++) {
|
||||
char_buf[i] = ' ';
|
||||
}
|
||||
|
||||
DrawString(char_buf, false, true);
|
||||
}
|
||||
|
||||
void FontManager::SetFontColor(u16 color) {
|
||||
g_font_color = color;
|
||||
}
|
||||
|
||||
void FontManager::SetPosition(u32 x, u32 y) {
|
||||
g_line_x = x;
|
||||
g_cur_x = x;
|
||||
g_cur_y = y;
|
||||
}
|
||||
|
||||
u32 FontManager::GetX() {
|
||||
return g_cur_x;
|
||||
}
|
||||
|
||||
u32 FontManager::GetY() {
|
||||
return g_cur_y;
|
||||
}
|
||||
|
||||
void FontManager::SetFontSize(float fsz) {
|
||||
g_font_sz = fsz;
|
||||
g_ft_err = FT_Set_Char_Size(g_face, 0, static_cast<u32>(g_font_sz * 64.0f), 96, 96);
|
||||
|
||||
g_ft_err = FT_Load_Glyph(g_face, FT_Get_Char_Index(g_face, 'A'), FT_LOAD_DEFAULT);
|
||||
|
||||
if (g_ft_err == 0) {
|
||||
g_ft_err = FT_Render_Glyph(g_face->glyph, FT_RENDER_MODE_NORMAL);
|
||||
}
|
||||
if (g_ft_err == 0) {
|
||||
g_mono_adv = g_face->glyph->advance.x;
|
||||
}
|
||||
}
|
||||
|
||||
void FontManager::AddSpacingLines(float num_lines) {
|
||||
g_cur_x = g_line_x;
|
||||
g_cur_y += static_cast<u32>((static_cast<float>(g_face->size->metrics.height) * num_lines) / 64.0f);
|
||||
}
|
||||
|
||||
void FontManager::ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32)) {
|
||||
g_fb = fb;
|
||||
g_unswizzle_func = unswizzle_func;
|
||||
}
|
||||
|
||||
Result FontManager::InitializeSharedFont() {
|
||||
Result rc;
|
||||
size_t total_fonts = 0;
|
||||
|
||||
if (R_FAILED((rc = plGetSharedFont(GetFatalConfig()->language_code, g_fonts, PlSharedFontType_Total, &total_fonts)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (R_FAILED((rc = plGetSharedFontByType(&g_font, PlSharedFontType_Standard)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
g_ft_err = FT_Init_FreeType(&g_library);
|
||||
if (g_ft_err) return g_ft_err;
|
||||
|
||||
g_ft_err = FT_New_Memory_Face(g_library, reinterpret_cast<const FT_Byte *>(g_font.address), g_font.size, 0, &g_face);
|
||||
if (g_ft_err) return g_ft_err;
|
||||
|
||||
SetFontSize(g_font_sz);
|
||||
return g_ft_err;
|
||||
}
|
45
stratosphere/fatal/source/fatal_font.hpp
Normal file
45
stratosphere/fatal/source/fatal_font.hpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdarg>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#define RGB888_TO_RGB565(r, g, b) ((((r >> 3) << 11) & 0xF800) | (((g >> 2) << 5) & 0x7E0) | ((b >> 3) & 0x1F))
|
||||
#define RGB565_GET_R8(c) ((((c >> 11) & 0x1F) << 3) | ((c >> 13) & 7))
|
||||
#define RGB565_GET_G8(c) ((((c >> 5) & 0x3F) << 2) | ((c >> 9) & 3))
|
||||
#define RGB565_GET_B8(c) ((((c >> 0) & 0x1F) << 3) | ((c >> 2) & 7))
|
||||
|
||||
class FontManager {
|
||||
public:
|
||||
static Result InitializeSharedFont();
|
||||
static void ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32));
|
||||
|
||||
static void SetFontColor(u16 color);
|
||||
static void SetPosition(u32 x, u32 y);
|
||||
static u32 GetX();
|
||||
static u32 GetY();
|
||||
static void SetFontSize(float fsz);
|
||||
static void AddSpacingLines(float num_lines);
|
||||
static void PrintLine(const char *str);
|
||||
static void PrintFormatLine(const char *format, ...);
|
||||
static void Print(const char *str);
|
||||
static void PrintFormat(const char *format, ...);
|
||||
static void PrintMonospaceU64(u64 x);
|
||||
static void PrintMonospaceU32(u32 x);
|
||||
static void PrintMonospaceBlank(u32 width);
|
||||
};
|
184
stratosphere/fatal/source/fatal_main.cpp
Normal file
184
stratosphere/fatal/source/fatal_main.cpp
Normal file
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <malloc.h>
|
||||
|
||||
#include <switch.h>
|
||||
#include <atmosphere.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fatal_types.hpp"
|
||||
#include "fatal_private.hpp"
|
||||
#include "fatal_user.hpp"
|
||||
#include "fatal_config.hpp"
|
||||
#include "fatal_repair.hpp"
|
||||
#include "fatal_font.hpp"
|
||||
|
||||
extern "C" {
|
||||
extern u32 __start__;
|
||||
|
||||
u32 __nx_applet_type = AppletType_None;
|
||||
|
||||
#define INNER_HEAP_SIZE 0x2A0000
|
||||
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
|
||||
char nx_inner_heap[INNER_HEAP_SIZE];
|
||||
|
||||
u32 __nx_nv_transfermem_size = 0x40000;
|
||||
ViLayerFlags __nx_vi_stray_layer_flags = (ViLayerFlags)0;
|
||||
|
||||
void __libnx_initheap(void);
|
||||
void __appInit(void);
|
||||
void __appExit(void);
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
Result rc;
|
||||
|
||||
rc = smInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = setInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = setsysInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = pminfoInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = i2cInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = bpcInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = pcvInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = lblInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = psmInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = spsmInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = plInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = fsInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = fsInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
rc = fsdevMountSdmc();
|
||||
if (R_FAILED(rc)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* fatal cannot throw fatal, so don't do: CheckAtmosphereVersion(CURRENT_ATMOSPHERE_VERSION); */
|
||||
}
|
||||
|
||||
void __appExit(void) {
|
||||
/* Cleanup services. */
|
||||
fsdevUnmountAll();
|
||||
fsExit();
|
||||
plExit();
|
||||
spsmExit();
|
||||
psmExit();
|
||||
lblExit();
|
||||
pcvExit();
|
||||
bpcExit();
|
||||
i2cExit();
|
||||
pminfoExit();
|
||||
setsysExit();
|
||||
setExit();
|
||||
smExit();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
/* Load settings from set:sys. */
|
||||
InitializeFatalConfig();
|
||||
|
||||
/* Load shared font. */
|
||||
if (R_FAILED(FontManager::InitializeSharedFont())) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Check whether we should throw fatal due to repair process. */
|
||||
CheckRepairStatus();
|
||||
|
||||
/* TODO: What's a good timeout value to use here? */
|
||||
auto server_manager = new WaitableManager(1);
|
||||
|
||||
/* Create services. */
|
||||
server_manager->AddWaitable(new ServiceServer<PrivateService>("fatal:p", 4));
|
||||
server_manager->AddWaitable(new ServiceServer<UserService>("fatal:u", 4));
|
||||
server_manager->AddWaitable(GetFatalSettingsEvent());
|
||||
|
||||
/* Loop forever, servicing our services. */
|
||||
server_manager->Process();
|
||||
|
||||
delete server_manager;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
23
stratosphere/fatal/source/fatal_private.cpp
Normal file
23
stratosphere/fatal/source/fatal_private.cpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "fatal_private.hpp"
|
||||
#include "fatal_event_manager.hpp"
|
||||
|
||||
Result PrivateService::GetFatalEvent(Out<CopiedHandle> out_h) {
|
||||
return GetEventManager()->GetEvent(out_h.GetHandlePointer());
|
||||
}
|
33
stratosphere/fatal/source/fatal_private.hpp
Normal file
33
stratosphere/fatal/source/fatal_private.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
enum PrivateCmd {
|
||||
Private_Cmd_GetFatalEvent = 0,
|
||||
};
|
||||
|
||||
class PrivateService final : public IServiceObject {
|
||||
private:
|
||||
/* Actual commands. */
|
||||
Result GetFatalEvent(Out<CopiedHandle> out_h);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MakeServiceCommandMeta<Private_Cmd_GetFatalEvent, &PrivateService::GetFatalEvent>(),
|
||||
};
|
||||
};
|
106
stratosphere/fatal/source/fatal_repair.cpp
Normal file
106
stratosphere/fatal/source/fatal_repair.cpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_types.hpp"
|
||||
#include "fatal_repair.hpp"
|
||||
#include "fatal_throw.hpp"
|
||||
|
||||
static bool InRepairWithoutVolHeld() {
|
||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_300) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool in_repair;
|
||||
if (R_FAILED(setsysGetFlag(SetSysFlag_InRepairProcessEnable, &in_repair)) || !in_repair) {
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
GpioPadSession vol_btn;
|
||||
if (R_FAILED(gpioOpenSession(&vol_btn, GpioPadName_ButtonVolUp))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Ensure we close even on early return. */
|
||||
ON_SCOPE_EXIT { gpioPadClose(&vol_btn); };
|
||||
|
||||
/* Set direction input. */
|
||||
gpioPadSetDirection(&vol_btn, GpioDirection_Input);
|
||||
|
||||
/* Ensure that we're holding the volume button for a full second. */
|
||||
TimeoutHelper timeout_helper(1000000000UL);
|
||||
while (!timeout_helper.TimedOut()) {
|
||||
GpioValue val;
|
||||
if (R_FAILED(gpioPadGetValue(&vol_btn, &val)) || val != GpioValue_Low) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Sleep for 100 ms. */
|
||||
svcSleepThread(100000000UL);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool InRepairWithoutTimeReviserCartridge() {
|
||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_500) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool requires_time_reviser;
|
||||
if (R_FAILED(setsysGetFlag(SetSysFlag_RequiresRunRepairTimeReviser, &requires_time_reviser)) || !requires_time_reviser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FsGameCardHandle gc_hnd;
|
||||
u8 gc_attr;
|
||||
{
|
||||
FsDeviceOperator devop;
|
||||
if (R_FAILED(fsOpenDeviceOperator(&devop))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Ensure we close even on early return. */
|
||||
ON_SCOPE_EXIT { fsDeviceOperatorClose(&devop); };
|
||||
|
||||
/* Check that a gamecard is inserted. */
|
||||
bool inserted;
|
||||
if (R_FAILED(fsDeviceOperatorIsGameCardInserted(&devop, &inserted)) || !inserted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check that we can retrieve the gamecard's attributes. */
|
||||
if (R_FAILED(fsDeviceOperatorGetGameCardHandle(&devop, &gc_hnd)) || R_FAILED(fsDeviceOperatorGetGameCardAttribute(&devop, &gc_hnd, &gc_attr))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check that the gamecard is a repair tool. */
|
||||
return (gc_attr & FsGameCardAttribute_Repair) == FsGameCardAttribute_Repair;
|
||||
}
|
||||
|
||||
void CheckRepairStatus() {
|
||||
if (InRepairWithoutVolHeld()) {
|
||||
ThrowFatalForSelf(FatalResult_InRepairWithoutVolHeld);
|
||||
}
|
||||
|
||||
if (InRepairWithoutTimeReviserCartridge()) {
|
||||
ThrowFatalForSelf(FatalResult_InRepairWithoutTimeReviserCartridge);
|
||||
}
|
||||
}
|
21
stratosphere/fatal/source/fatal_repair.hpp
Normal file
21
stratosphere/fatal/source/fatal_repair.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
void CheckRepairStatus();
|
65
stratosphere/fatal/source/fatal_task.cpp
Normal file
65
stratosphere/fatal/source/fatal_task.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "fatal_types.hpp"
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
#include "fatal_task_error_report.hpp"
|
||||
#include "fatal_task_screen.hpp"
|
||||
#include "fatal_task_sound.hpp"
|
||||
#include "fatal_task_clock.hpp"
|
||||
#include "fatal_task_power.hpp"
|
||||
|
||||
|
||||
static constexpr size_t MaxTasks = 8;
|
||||
static HosThread g_task_threads[MaxTasks];
|
||||
static size_t g_num_threads = 0;
|
||||
|
||||
|
||||
static void RunTaskThreadFunc(void *arg) {
|
||||
IFatalTask *task = reinterpret_cast<IFatalTask *>(arg);
|
||||
|
||||
Result rc = task->Run();
|
||||
if (R_FAILED(rc)) {
|
||||
/* TODO: Log task failure, somehow? */
|
||||
}
|
||||
|
||||
/* Finish. */
|
||||
svcExitThread();
|
||||
}
|
||||
|
||||
static void RunTask(IFatalTask *task, u32 stack_size = 0x4000) {
|
||||
if (g_num_threads >= MaxTasks) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
HosThread *cur_thread = &g_task_threads[g_num_threads++];
|
||||
|
||||
cur_thread->Initialize(&RunTaskThreadFunc, task, stack_size, 15);
|
||||
cur_thread->Start();
|
||||
}
|
||||
|
||||
void RunFatalTasks(FatalThrowContext *ctx, u64 title_id, bool error_report, Event *erpt_event, Event *battery_event) {
|
||||
RunTask(new ErrorReportTask(ctx, title_id, error_report, erpt_event));
|
||||
RunTask(new PowerControlTask(ctx, title_id, erpt_event, battery_event));
|
||||
RunTask(new ShowFatalTask(ctx, title_id, battery_event), 0x10000);
|
||||
RunTask(new StopSoundTask(ctx, title_id));
|
||||
RunTask(new BacklightControlTask(ctx, title_id));
|
||||
RunTask(new AdjustClockTask(ctx, title_id));
|
||||
RunTask(new PowerButtonObserveTask(ctx, title_id, erpt_event));
|
||||
RunTask(new StateTransitionStopTask(ctx, title_id));
|
||||
}
|
32
stratosphere/fatal/source/fatal_task.hpp
Normal file
32
stratosphere/fatal/source/fatal_task.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_types.hpp"
|
||||
|
||||
class IFatalTask {
|
||||
protected:
|
||||
FatalThrowContext *ctx;
|
||||
u64 title_id;
|
||||
public:
|
||||
IFatalTask(FatalThrowContext *ctx, u64 tid) : ctx(ctx), title_id(tid) { }
|
||||
virtual Result Run() = 0;
|
||||
virtual const char *GetName() const = 0;
|
||||
};
|
||||
|
||||
void RunFatalTasks(FatalThrowContext *ctx, u64 title_id, bool error_report, Event *erpt_event, Event *battery_event);
|
45
stratosphere/fatal/source/fatal_task_clock.cpp
Normal file
45
stratosphere/fatal/source/fatal_task_clock.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "fatal_task_clock.hpp"
|
||||
|
||||
|
||||
Result AdjustClockTask::AdjustClock() {
|
||||
/* Fatal sets the CPU to 1020MHz, the GPU to 307 MHz, and the EMC to 1331MHz. */
|
||||
constexpr u32 CPU_CLOCK_1020MHZ = 0x3CCBF700L;
|
||||
constexpr u32 GPU_CLOCK_307MHZ = 0x124F8000L;
|
||||
constexpr u32 EMC_CLOCK_1331MHZ = 0x4F588000L;
|
||||
Result rc = 0;
|
||||
|
||||
if (R_FAILED((rc = pcvSetClockRate(PcvModule_Cpu, CPU_CLOCK_1020MHZ)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (R_FAILED((rc = pcvSetClockRate(PcvModule_Gpu, GPU_CLOCK_307MHZ)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (R_FAILED((rc = pcvSetClockRate(PcvModule_Emc, EMC_CLOCK_1331MHZ)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result AdjustClockTask::Run() {
|
||||
return AdjustClock();
|
||||
}
|
31
stratosphere/fatal/source/fatal_task_clock.hpp
Normal file
31
stratosphere/fatal/source/fatal_task_clock.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
class AdjustClockTask : public IFatalTask {
|
||||
private:
|
||||
Result AdjustClock();
|
||||
public:
|
||||
AdjustClockTask(FatalThrowContext *ctx, u64 title_id) : IFatalTask(ctx, title_id) { }
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "AdjustClockTask";
|
||||
}
|
||||
};
|
140
stratosphere/fatal/source/fatal_task_error_report.cpp
Normal file
140
stratosphere/fatal/source/fatal_task_error_report.cpp
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <switch.h>
|
||||
#include <atmosphere/version.h>
|
||||
|
||||
#include "fatal_task_error_report.hpp"
|
||||
#include "fatal_config.hpp"
|
||||
|
||||
void ErrorReportTask::EnsureReportDirectories() {
|
||||
char path[FS_MAX_PATH];
|
||||
strcpy(path, "sdmc:/atmosphere");
|
||||
mkdir(path, S_IRWXU);
|
||||
strcat(path, "/fatal_reports");
|
||||
mkdir(path, S_IRWXU);
|
||||
strcat(path, "/dumps");
|
||||
mkdir(path, S_IRWXU);
|
||||
}
|
||||
|
||||
bool ErrorReportTask::GetCurrentTime(u64 *out) {
|
||||
*out = 0;
|
||||
|
||||
/* Verify that pcv isn't dead. */
|
||||
{
|
||||
Handle dummy;
|
||||
if (R_SUCCEEDED(smRegisterService(&dummy, "time:s", false, 0x20))) {
|
||||
svcCloseHandle(dummy);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to get the current time. */
|
||||
bool success = false;
|
||||
if (R_SUCCEEDED(timeInitialize())) {
|
||||
if (R_SUCCEEDED(timeGetCurrentTime(TimeType_LocalSystemClock, out))) {
|
||||
success = true;
|
||||
}
|
||||
timeExit();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void ErrorReportTask::SaveReportToSdCard() {
|
||||
char file_path[FS_MAX_PATH];
|
||||
|
||||
/* Ensure path exists. */
|
||||
EnsureReportDirectories();
|
||||
|
||||
/* Get a timestamp. */
|
||||
u64 timestamp;
|
||||
if (!GetCurrentTime(×tamp)) {
|
||||
timestamp = svcGetSystemTick();
|
||||
}
|
||||
|
||||
/* Open report file. */
|
||||
snprintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/%011lu_%016lx.log", timestamp, this->title_id);
|
||||
FILE *f_report = fopen(file_path, "w");
|
||||
if (f_report != NULL) {
|
||||
ON_SCOPE_EXIT { fclose(f_report); };
|
||||
|
||||
fprintf(f_report, "Atmosphère Fatal Report (v1.0):\n");
|
||||
fprintf(f_report, "Result: 0x%X (2%03d-%04d)\n\n", this->ctx->error_code, R_MODULE(this->ctx->error_code), R_DESCRIPTION(this->ctx->error_code));
|
||||
fprintf(f_report, "Title ID: %016lx\n", this->title_id);
|
||||
if (strlen(this->ctx->proc_name)) {
|
||||
fprintf(f_report, "Process Name: %s\n", this->ctx->proc_name);
|
||||
}
|
||||
fprintf(f_report, u8"Firmware: %s (Atmosphère %u.%u.%u-%s)\n", GetFatalConfig()->firmware_version.display_version, CURRENT_ATMOSPHERE_VERSION, GetAtmosphereGitRevision());
|
||||
|
||||
if (this->ctx->cpu_ctx.is_aarch32) {
|
||||
fprintf(f_report, "General Purpose Registers:\n");
|
||||
for (size_t i = 0; i < NumAarch32Gprs; i++) {
|
||||
if (this->ctx->has_gprs[i]) {
|
||||
fprintf(f_report, " %3s: %08x\n", Aarch32GprNames[i], this->ctx->cpu_ctx.aarch32_ctx.r[i]);
|
||||
}
|
||||
}
|
||||
fprintf(f_report, " PC: %08x\n", this->ctx->cpu_ctx.aarch32_ctx.pc);
|
||||
fprintf(f_report, "Start Address: %08x\n", this->ctx->cpu_ctx.aarch32_ctx.start_address);
|
||||
fprintf(f_report, "Stack Trace:\n");
|
||||
for (unsigned int i = 0; i < this->ctx->cpu_ctx.aarch32_ctx.stack_trace_size; i++) {
|
||||
fprintf(f_report, " ReturnAddress[%02u]: %08x\n", i, this->ctx->cpu_ctx.aarch32_ctx.stack_trace[i]);
|
||||
}
|
||||
} else {
|
||||
fprintf(f_report, "General Purpose Registers:\n");
|
||||
for (size_t i = 0; i < NumAarch64Gprs; i++) {
|
||||
if (this->ctx->has_gprs[i]) {
|
||||
fprintf(f_report, " %3s: %016lx\n", Aarch64GprNames[i], this->ctx->cpu_ctx.aarch64_ctx.x[i]);
|
||||
}
|
||||
}
|
||||
fprintf(f_report, " PC: %016lx\n", this->ctx->cpu_ctx.aarch64_ctx.pc);
|
||||
fprintf(f_report, "Start Address: %016lx\n", this->ctx->cpu_ctx.aarch64_ctx.start_address);
|
||||
fprintf(f_report, "Stack Trace:\n");
|
||||
for (unsigned int i = 0; i < this->ctx->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
|
||||
fprintf(f_report, " ReturnAddress[%02u]: %016lx\n", i, this->ctx->cpu_ctx.aarch64_ctx.stack_trace[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (this->ctx->stack_dump_size) {
|
||||
snprintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/dumps/%011lu_%016lx.bin", timestamp, this->title_id);
|
||||
FILE *f_stackdump = fopen(file_path, "wb");
|
||||
if (f_stackdump == NULL) { return; }
|
||||
ON_SCOPE_EXIT { fclose(f_stackdump); };
|
||||
|
||||
fwrite(this->ctx->stack_dump, this->ctx->stack_dump_size, 1, f_stackdump);
|
||||
}
|
||||
}
|
||||
|
||||
Result ErrorReportTask::Run() {
|
||||
if (this->create_report) {
|
||||
/* Here, Nintendo creates an error report with erpt. AMS will not do that. */
|
||||
}
|
||||
|
||||
/* Save report to SD card. */
|
||||
if (!this->ctx->is_creport) {
|
||||
SaveReportToSdCard();
|
||||
}
|
||||
|
||||
/* Signal we're done with our job. */
|
||||
eventFire(this->erpt_event);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
36
stratosphere/fatal/source/fatal_task_error_report.hpp
Normal file
36
stratosphere/fatal/source/fatal_task_error_report.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
class ErrorReportTask : public IFatalTask {
|
||||
private:
|
||||
bool create_report;
|
||||
Event *erpt_event;
|
||||
private:
|
||||
void EnsureReportDirectories();
|
||||
bool GetCurrentTime(u64 *out);
|
||||
void SaveReportToSdCard();
|
||||
public:
|
||||
ErrorReportTask(FatalThrowContext *ctx, u64 title_id, bool error_report, Event *evt) : IFatalTask(ctx, title_id), create_report(error_report), erpt_event(evt) { }
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "WriteErrorReport";
|
||||
}
|
||||
};
|
128
stratosphere/fatal/source/fatal_task_power.cpp
Normal file
128
stratosphere/fatal/source/fatal_task_power.cpp
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "fatal_task_power.hpp"
|
||||
#include "fatal_config.hpp"
|
||||
|
||||
bool PowerControlTask::TryShutdown() {
|
||||
/* Set a timeout of 30 seconds. */
|
||||
TimeoutHelper timeout_helper(30000000000UL);
|
||||
bool cancel_shutdown = false;
|
||||
PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal;
|
||||
|
||||
while (true) {
|
||||
if (timeout_helper.TimedOut()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (R_FAILED(psmGetBatteryVoltageState(&bv_state)) || bv_state == PsmBatteryVoltageState_NeedsShutdown) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (bv_state == PsmBatteryVoltageState_Normal) {
|
||||
cancel_shutdown = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Query voltage state every 5 seconds, for 30 seconds. */
|
||||
svcSleepThread(5000000000UL);
|
||||
}
|
||||
|
||||
if (!cancel_shutdown) {
|
||||
bpcShutdownSystem();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void PowerControlTask::MonitorBatteryState() {
|
||||
PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal;
|
||||
|
||||
/* Check the battery state, and shutdown on low voltage. */
|
||||
if (R_FAILED(psmGetBatteryVoltageState(&bv_state)) || bv_state == PsmBatteryVoltageState_NeedsShutdown) {
|
||||
/* Wait a second for the error report task to finish. */
|
||||
eventWait(this->erpt_event, TimeoutHelper::NsToTick(1000000000UL));
|
||||
this->TryShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Signal we've checked the battery at least once. */
|
||||
eventFire(this->battery_event);
|
||||
|
||||
while (true) {
|
||||
if (R_FAILED(psmGetBatteryVoltageState(&bv_state))) {
|
||||
bv_state = PsmBatteryVoltageState_NeedsShutdown;
|
||||
}
|
||||
|
||||
switch (bv_state) {
|
||||
case PsmBatteryVoltageState_NeedsShutdown:
|
||||
case PsmBatteryVoltageState_NeedsSleep:
|
||||
{
|
||||
bool shutdown = this->TryShutdown();
|
||||
if (shutdown) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Query voltage state every 5 seconds. */
|
||||
svcSleepThread(5000000000UL);
|
||||
}
|
||||
}
|
||||
|
||||
void PowerButtonObserveTask::WaitForPowerButton() {
|
||||
/* Wait up to a second for error report generation to finish. */
|
||||
eventWait(this->erpt_event, TimeoutHelper::NsToTick(1000000000UL));
|
||||
|
||||
/* Force a reboot after some time if kiosk unit. */
|
||||
const FatalConfig *config = GetFatalConfig();
|
||||
TimeoutHelper reboot_helper(config->quest_reboot_interval_second * 1000000000UL);
|
||||
|
||||
BpcSleepButtonState state;
|
||||
while (true) {
|
||||
|
||||
|
||||
Result rc = bpcGetSleepButtonState(&state);
|
||||
if ((R_SUCCEEDED(rc) && state == BpcSleepButtonState_Held) || (config->quest_flag && reboot_helper.TimedOut())) {
|
||||
bpcRebootSystem();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Wait 100 ms between button checks. */
|
||||
svcSleepThread(100000000UL);
|
||||
}
|
||||
}
|
||||
|
||||
Result PowerControlTask::Run() {
|
||||
MonitorBatteryState();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result PowerButtonObserveTask::Run() {
|
||||
WaitForPowerButton();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result StateTransitionStopTask::Run() {
|
||||
/* Nintendo ignores the output of this call... */
|
||||
spsmPutErrorState();
|
||||
return 0;
|
||||
}
|
57
stratosphere/fatal/source/fatal_task_power.hpp
Normal file
57
stratosphere/fatal/source/fatal_task_power.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
class PowerControlTask : public IFatalTask {
|
||||
private:
|
||||
Event *erpt_event;
|
||||
Event *battery_event;
|
||||
private:
|
||||
bool TryShutdown();
|
||||
void MonitorBatteryState();
|
||||
public:
|
||||
PowerControlTask(FatalThrowContext *ctx, u64 title_id, Event *er_evt, Event *bt_evt) : IFatalTask(ctx, title_id), erpt_event(er_evt), battery_event(bt_evt) { }
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "PowerControlTask";
|
||||
}
|
||||
};
|
||||
|
||||
class PowerButtonObserveTask : public IFatalTask {
|
||||
private:
|
||||
Event *erpt_event;
|
||||
private:
|
||||
void WaitForPowerButton();
|
||||
public:
|
||||
PowerButtonObserveTask(FatalThrowContext *ctx, u64 title_id, Event *er_evt) : IFatalTask(ctx, title_id), erpt_event(er_evt) { }
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "PowerButtonObserveTask";
|
||||
}
|
||||
};
|
||||
|
||||
class StateTransitionStopTask : public IFatalTask {
|
||||
public:
|
||||
StateTransitionStopTask(FatalThrowContext *ctx, u64 title_id) : IFatalTask(ctx, title_id) { }
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "StateTransitionStopTask";
|
||||
}
|
||||
};
|
410
stratosphere/fatal/source/fatal_task_screen.cpp
Normal file
410
stratosphere/fatal/source/fatal_task_screen.cpp
Normal file
|
@ -0,0 +1,410 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
#include <atmosphere/version.h>
|
||||
|
||||
#include "fatal_task_screen.hpp"
|
||||
#include "fatal_config.hpp"
|
||||
#include "fatal_font.hpp"
|
||||
#include "ams_logo.hpp"
|
||||
|
||||
static constexpr u32 FatalScreenWidth = 1280;
|
||||
static constexpr u32 FatalScreenHeight = 720;
|
||||
static constexpr u32 FatalScreenBpp = 2;
|
||||
|
||||
static constexpr u32 FatalScreenWidthAlignedBytes = (FatalScreenWidth * FatalScreenBpp + 63) & ~63;
|
||||
static constexpr u32 FatalScreenWidthAligned = FatalScreenWidthAlignedBytes / FatalScreenBpp;
|
||||
|
||||
u32 GetPixelOffset(uint32_t x, uint32_t y)
|
||||
{
|
||||
u32 tmp_pos;
|
||||
|
||||
tmp_pos = ((y & 127) / 16) + (x/32*8) + ((y/16/8)*(((FatalScreenWidthAligned/2)/16*8)));
|
||||
tmp_pos *= 16*16 * 4;
|
||||
|
||||
tmp_pos += ((y%16)/8)*512 + ((x%32)/16)*256 + ((y%8)/2)*64 + ((x%16)/8)*32 + (y%2)*16 + (x%8)*2;//This line is a modified version of code from the Tegra X1 datasheet.
|
||||
|
||||
return tmp_pos / 2;
|
||||
}
|
||||
|
||||
Result ShowFatalTask::SetupDisplayInternal() {
|
||||
Result rc;
|
||||
ViDisplay display;
|
||||
/* Try to open the display. */
|
||||
if (R_FAILED((rc = viOpenDisplay("Internal", &display)))) {
|
||||
if (rc == 0xE72) {
|
||||
return 0;
|
||||
} else {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
/* Guarantee we close the display. */
|
||||
ON_SCOPE_EXIT { viCloseDisplay(&display); };
|
||||
|
||||
/* Turn on the screen. */
|
||||
if (R_FAILED((rc = viSetDisplayPowerState(&display, ViPowerState_On)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Set alpha to 1.0f. */
|
||||
if (R_FAILED((rc = viSetDisplayAlpha(&display, 1.0f)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result ShowFatalTask::SetupDisplayExternal() {
|
||||
Result rc;
|
||||
ViDisplay display;
|
||||
/* Try to open the display. */
|
||||
if (R_FAILED((rc = viOpenDisplay("External", &display)))) {
|
||||
if (rc == 0xE72) {
|
||||
return 0;
|
||||
} else {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
/* Guarantee we close the display. */
|
||||
ON_SCOPE_EXIT { viCloseDisplay(&display); };
|
||||
|
||||
/* Set alpha to 1.0f. */
|
||||
if (R_FAILED((rc = viSetDisplayAlpha(&display, 1.0f)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result ShowFatalTask::PrepareScreenForDrawing() {
|
||||
Result rc = 0;
|
||||
|
||||
/* Connect to vi. */
|
||||
if (R_FAILED((rc = viInitialize(ViServiceType_Manager)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Close other content. */
|
||||
viSetContentVisibility(false);
|
||||
|
||||
/* Setup the two displays. */
|
||||
if (R_FAILED((rc = SetupDisplayInternal())) || R_FAILED((rc = SetupDisplayExternal()))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Open the default display. */
|
||||
if (R_FAILED((rc = viOpenDefaultDisplay(&this->display)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Reset the display magnification to its default value. */
|
||||
u32 display_width, display_height;
|
||||
if (R_FAILED((rc = viGetDisplayLogicalResolution(&this->display, &display_width, &display_height)))) {
|
||||
return rc;
|
||||
}
|
||||
if (R_FAILED((rc = viSetDisplayMagnification(&this->display, 0, 0, display_width, display_height)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Create layer to draw to. */
|
||||
if (R_FAILED((rc = viCreateLayer(&this->display, &this->layer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Setup the layer. */
|
||||
{
|
||||
/* Display a layer of 1280 x 720 at 1.5x magnification */
|
||||
/* NOTE: N uses 2 (770x400) RGBA4444 buffers (tiled buffer + linear). */
|
||||
/* We use a single 1280x720 tiled RGB565 buffer. */
|
||||
constexpr u32 raw_width = FatalScreenWidth;
|
||||
constexpr u32 raw_height = FatalScreenHeight;
|
||||
constexpr u32 layer_width = ((raw_width) * 3) / 2;
|
||||
constexpr u32 layer_height = ((raw_height) * 3) / 2;
|
||||
|
||||
const float layer_x = static_cast<float>((display_width - layer_width) / 2);
|
||||
const float layer_y = static_cast<float>((display_height - layer_height) / 2);
|
||||
u64 layer_z;
|
||||
|
||||
if (R_FAILED((rc = viSetLayerSize(&this->layer, layer_width, layer_height)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Set the layer's Z at display maximum, to be above everything else .*/
|
||||
/* NOTE: Fatal hardcodes 100 here. */
|
||||
if (R_SUCCEEDED((rc = viGetDisplayMaximumZ(&this->display, &layer_z)))) {
|
||||
if (R_FAILED((rc = viSetLayerZ(&this->layer, layer_z)))) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/* Center the layer in the screen. */
|
||||
if (R_FAILED((rc = viSetLayerPosition(&this->layer, layer_x, layer_y)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Create framebuffer. */
|
||||
if (R_FAILED(rc = nwindowCreateFromLayer(&this->win, &this->layer))) {
|
||||
return rc;
|
||||
}
|
||||
if (R_FAILED(rc = framebufferCreate(&this->fb, &this->win, raw_width, raw_height, PIXEL_FORMAT_RGB_565, 1))) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result ShowFatalTask::ShowFatal() {
|
||||
Result rc = 0;
|
||||
const FatalConfig *config = GetFatalConfig();
|
||||
|
||||
if (R_FAILED((rc = PrepareScreenForDrawing()))) {
|
||||
*(volatile u32 *)(0xCAFEBABE) = rc;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Dequeue a buffer. */
|
||||
u16 *tiled_buf = reinterpret_cast<u16 *>(framebufferBegin(&this->fb, NULL));
|
||||
if (tiled_buf == nullptr) {
|
||||
return FatalResult_NullGfxBuffer;
|
||||
}
|
||||
|
||||
/* Let the font manager know about our framebuffer. */
|
||||
FontManager::ConfigureFontFramebuffer(tiled_buf, GetPixelOffset);
|
||||
FontManager::SetFontColor(0xFFFF);
|
||||
|
||||
/* Draw a background. */
|
||||
for (size_t i = 0; i < this->fb.fb_size / sizeof(*tiled_buf); i++) {
|
||||
tiled_buf[i] = 0x39C9;
|
||||
}
|
||||
|
||||
/* Draw the atmosphere logo in the bottom right corner. */
|
||||
for (size_t y = 0; y < AMS_LOGO_HEIGHT; y++) {
|
||||
for (size_t x = 0; x < AMS_LOGO_WIDTH; x++) {
|
||||
tiled_buf[GetPixelOffset(FatalScreenWidth - AMS_LOGO_WIDTH - 32 + x, 32 + y)] = AMS_LOGO_BIN[y * AMS_LOGO_WIDTH + x];
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Actually draw meaningful shit here. */
|
||||
FontManager::SetPosition(32, 64);
|
||||
FontManager::SetFontSize(16.0f);
|
||||
FontManager::PrintFormat(config->error_msg, R_MODULE(this->ctx->error_code), R_DESCRIPTION(this->ctx->error_code), this->ctx->error_code);
|
||||
FontManager::AddSpacingLines(0.5f);
|
||||
FontManager::PrintFormatLine("Title: %016lX", this->title_id);
|
||||
FontManager::AddSpacingLines(0.5f);
|
||||
FontManager::PrintFormatLine(u8"Firmware: %s (Atmosphère %u.%u.%u-%s)", GetFatalConfig()->firmware_version.display_version, CURRENT_ATMOSPHERE_VERSION, GetAtmosphereGitRevision());
|
||||
FontManager::AddSpacingLines(1.5f);
|
||||
FontManager::Print(config->error_desc);
|
||||
|
||||
/* Add a line. */
|
||||
for (size_t x = 32; x < FatalScreenWidth - 32; x++) {
|
||||
tiled_buf[GetPixelOffset(x, FontManager::GetY())] = 0xFFFF;
|
||||
}
|
||||
|
||||
|
||||
FontManager::AddSpacingLines(1.5f);
|
||||
|
||||
u32 backtrace_y = FontManager::GetY();
|
||||
u32 backtrace_x = 0;
|
||||
|
||||
/* Print GPRs. */
|
||||
FontManager::SetFontSize(14.0f);
|
||||
FontManager::Print("General Purpose Registers ");
|
||||
{
|
||||
FontManager::SetPosition(FontManager::GetX() + 2, FontManager::GetY());
|
||||
u32 x = FontManager::GetX();
|
||||
FontManager::Print("PC: ");
|
||||
FontManager::SetPosition(x + 47, FontManager::GetY());
|
||||
}
|
||||
if (this->ctx->cpu_ctx.is_aarch32) {
|
||||
FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.pc);
|
||||
} else {
|
||||
FontManager::PrintMonospaceU64(this->ctx->cpu_ctx.aarch64_ctx.pc);
|
||||
}
|
||||
FontManager::PrintLine("");
|
||||
FontManager::SetPosition(32, FontManager::GetY());
|
||||
FontManager::AddSpacingLines(0.5f);
|
||||
if (this->ctx->cpu_ctx.is_aarch32) {
|
||||
for (size_t i = 0; i < (NumAarch32Gprs / 2); i++) {
|
||||
u32 x = FontManager::GetX();
|
||||
FontManager::PrintFormat("%s:", Aarch32GprNames[i]);
|
||||
FontManager::SetPosition(x + 47, FontManager::GetY());
|
||||
if (this->ctx->has_gprs[i]) {
|
||||
FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.r[i]);
|
||||
} else {
|
||||
FontManager::PrintMonospaceBlank(8);
|
||||
}
|
||||
FontManager::Print(" ");
|
||||
x = FontManager::GetX();
|
||||
FontManager::PrintFormat("%s:", Aarch32GprNames[i + (NumAarch32Gprs / 2)]);
|
||||
FontManager::SetPosition(x + 47, FontManager::GetY());
|
||||
if (this->ctx->has_gprs[i + (NumAarch32Gprs / 2)]) {
|
||||
FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.r[i + (NumAarch32Gprs / 2)]);
|
||||
} else {
|
||||
FontManager::PrintMonospaceBlank(8);
|
||||
}
|
||||
|
||||
if (i == (NumAarch32Gprs / 2) - 1) {
|
||||
FontManager::Print(" ");
|
||||
backtrace_x = FontManager::GetX();
|
||||
}
|
||||
|
||||
FontManager::PrintLine("");
|
||||
FontManager::SetPosition(32, FontManager::GetY());
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < NumAarch64Gprs / 2; i++) {
|
||||
u32 x = FontManager::GetX();
|
||||
FontManager::PrintFormat("%s:", Aarch64GprNames[i]);
|
||||
FontManager::SetPosition(x + 47, FontManager::GetY());
|
||||
if (this->ctx->has_gprs[i]) {
|
||||
FontManager::PrintMonospaceU64(this->ctx->cpu_ctx.aarch64_ctx.x[i]);
|
||||
} else {
|
||||
FontManager::PrintMonospaceBlank(16);
|
||||
}
|
||||
FontManager::Print(" ");
|
||||
x = FontManager::GetX();
|
||||
FontManager::PrintFormat("%s:", Aarch64GprNames[i + (NumAarch64Gprs / 2)]);
|
||||
FontManager::SetPosition(x + 47, FontManager::GetY());
|
||||
if (this->ctx->has_gprs[i + (NumAarch64Gprs / 2)]) {
|
||||
FontManager::PrintMonospaceU64(this->ctx->cpu_ctx.aarch64_ctx.x[i + (NumAarch64Gprs / 2)]);
|
||||
} else {
|
||||
FontManager::PrintMonospaceBlank(16);
|
||||
}
|
||||
|
||||
if (i == (NumAarch64Gprs / 2) - 1) {
|
||||
FontManager::Print(" ");
|
||||
backtrace_x = FontManager::GetX();
|
||||
}
|
||||
|
||||
FontManager::PrintLine("");
|
||||
FontManager::SetPosition(32, FontManager::GetY());
|
||||
}
|
||||
}
|
||||
|
||||
/* Print Backtrace. */
|
||||
u32 bt_size;
|
||||
if (this->ctx->cpu_ctx.is_aarch32) {
|
||||
bt_size = this->ctx->cpu_ctx.aarch32_ctx.stack_trace_size;
|
||||
} else {
|
||||
bt_size = this->ctx->cpu_ctx.aarch64_ctx.stack_trace_size;
|
||||
}
|
||||
|
||||
|
||||
FontManager::SetPosition(backtrace_x, backtrace_y);
|
||||
if (bt_size == 0) {
|
||||
if (this->ctx->cpu_ctx.is_aarch32) {
|
||||
FontManager::Print("Start Address: ");
|
||||
FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.start_address);
|
||||
FontManager::PrintLine("");
|
||||
} else {
|
||||
FontManager::Print("Start Address: ");
|
||||
FontManager::PrintMonospaceU64(this->ctx->cpu_ctx.aarch64_ctx.start_address);
|
||||
FontManager::PrintLine("");
|
||||
}
|
||||
} else {
|
||||
if (this->ctx->cpu_ctx.is_aarch32) {
|
||||
FontManager::Print("Backtrace - Start Address: ");
|
||||
FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.start_address);
|
||||
FontManager::PrintLine("");
|
||||
FontManager::AddSpacingLines(0.5f);
|
||||
for (u32 i = 0; i < Aarch32CpuContext::MaxStackTraceDepth / 2; i++) {
|
||||
u32 bt_cur = 0, bt_next = 0;
|
||||
if (i < this->ctx->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
||||
bt_cur = this->ctx->cpu_ctx.aarch32_ctx.stack_trace[i];
|
||||
}
|
||||
if (i + Aarch32CpuContext::MaxStackTraceDepth / 2 < this->ctx->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
||||
bt_next = this->ctx->cpu_ctx.aarch32_ctx.stack_trace[i + Aarch32CpuContext::MaxStackTraceDepth / 2];
|
||||
}
|
||||
|
||||
if (i < this->ctx->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
||||
u32 x = FontManager::GetX();
|
||||
FontManager::PrintFormat("BT[%02d]: ", i);
|
||||
FontManager::SetPosition(x + 72, FontManager::GetY());
|
||||
FontManager::PrintMonospaceU32(bt_cur);
|
||||
FontManager::Print(" ");
|
||||
}
|
||||
|
||||
if (i + Aarch32CpuContext::MaxStackTraceDepth / 2 < this->ctx->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
||||
u32 x = FontManager::GetX();
|
||||
FontManager::PrintFormat("BT[%02d]: ", i + Aarch32CpuContext::MaxStackTraceDepth / 2);
|
||||
FontManager::SetPosition(x + 72, FontManager::GetY());
|
||||
FontManager::PrintMonospaceU32(bt_next);
|
||||
}
|
||||
|
||||
FontManager::PrintLine("");
|
||||
FontManager::SetPosition(backtrace_x, FontManager::GetY());
|
||||
}
|
||||
} else {
|
||||
FontManager::Print("Backtrace - Start Address: ");
|
||||
FontManager::PrintMonospaceU64(this->ctx->cpu_ctx.aarch64_ctx.start_address);
|
||||
FontManager::PrintLine("");
|
||||
FontManager::AddSpacingLines(0.5f);
|
||||
for (u32 i = 0; i < Aarch64CpuContext::MaxStackTraceDepth / 2; i++) {
|
||||
u64 bt_cur = 0, bt_next = 0;
|
||||
if (i < this->ctx->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
||||
bt_cur = this->ctx->cpu_ctx.aarch64_ctx.stack_trace[i];
|
||||
}
|
||||
if (i + Aarch64CpuContext::MaxStackTraceDepth / 2 < this->ctx->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
||||
bt_next = this->ctx->cpu_ctx.aarch64_ctx.stack_trace[i + Aarch64CpuContext::MaxStackTraceDepth / 2];
|
||||
}
|
||||
|
||||
if (i < this->ctx->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
||||
u32 x = FontManager::GetX();
|
||||
FontManager::PrintFormat("BT[%02d]: ", i);
|
||||
FontManager::SetPosition(x + 72, FontManager::GetY());
|
||||
FontManager::PrintMonospaceU64(bt_cur);
|
||||
FontManager::Print(" ");
|
||||
}
|
||||
|
||||
if (i + Aarch64CpuContext::MaxStackTraceDepth / 2 < this->ctx->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
||||
u32 x = FontManager::GetX();
|
||||
FontManager::PrintFormat("BT[%02d]: ", i + Aarch64CpuContext::MaxStackTraceDepth / 2);
|
||||
FontManager::SetPosition(x + 72, FontManager::GetY());
|
||||
FontManager::PrintMonospaceU64(bt_next);
|
||||
}
|
||||
|
||||
FontManager::PrintLine("");
|
||||
FontManager::SetPosition(backtrace_x, FontManager::GetY());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Enqueue the buffer. */
|
||||
framebufferEnd(&fb);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result ShowFatalTask::Run() {
|
||||
/* Don't show the fatal error screen until we've verified the battery is okay. */
|
||||
eventWait(this->battery_event, U64_MAX);
|
||||
|
||||
return ShowFatal();
|
||||
}
|
||||
|
||||
void BacklightControlTask::TurnOnBacklight() {
|
||||
lblSwitchBacklightOn(0);
|
||||
}
|
||||
|
||||
Result BacklightControlTask::Run() {
|
||||
TurnOnBacklight();
|
||||
return 0;
|
||||
}
|
51
stratosphere/fatal/source/fatal_task_screen.hpp
Normal file
51
stratosphere/fatal/source/fatal_task_screen.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
class ShowFatalTask : public IFatalTask {
|
||||
private:
|
||||
Event *battery_event;
|
||||
ViDisplay display;
|
||||
ViLayer layer;
|
||||
NWindow win;
|
||||
Framebuffer fb;
|
||||
private:
|
||||
Result SetupDisplayInternal();
|
||||
Result SetupDisplayExternal();
|
||||
Result PrepareScreenForDrawing();
|
||||
Result ShowFatal();
|
||||
public:
|
||||
ShowFatalTask(FatalThrowContext *ctx, u64 title_id, Event *evt) : IFatalTask(ctx, title_id), battery_event(evt) { }
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "ShowFatal";
|
||||
}
|
||||
};
|
||||
|
||||
class BacklightControlTask : public IFatalTask {
|
||||
private:
|
||||
void TurnOnBacklight();
|
||||
public:
|
||||
BacklightControlTask(FatalThrowContext *ctx, u64 title_id) : IFatalTask(ctx, title_id) { }
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "BacklightControlTask";
|
||||
}
|
||||
};
|
73
stratosphere/fatal/source/fatal_task_sound.cpp
Normal file
73
stratosphere/fatal/source/fatal_task_sound.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "fatal_task_sound.hpp"
|
||||
|
||||
|
||||
void StopSoundTask::StopSound() {
|
||||
/* Talk to the ALC5639 over I2C, and disable audio output. */
|
||||
{
|
||||
I2cSession audio;
|
||||
if (R_SUCCEEDED(i2cOpenSession(&audio, I2cDevice_AudioCodec))) {
|
||||
struct {
|
||||
u16 dev;
|
||||
u8 val;
|
||||
} __attribute__((packed)) cmd;
|
||||
static_assert(sizeof(cmd) == 3, "I2C command definition!");
|
||||
|
||||
cmd.dev = 0xC801;
|
||||
cmd.val = 200;
|
||||
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
|
||||
|
||||
cmd.dev = 0xC802;
|
||||
cmd.val = 200;
|
||||
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
|
||||
|
||||
cmd.dev = 0xC802;
|
||||
cmd.val = 200;
|
||||
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
|
||||
|
||||
for (u16 dev = 97; dev <= 102; dev++) {
|
||||
cmd.dev = dev;
|
||||
cmd.val = 0;
|
||||
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
|
||||
}
|
||||
|
||||
i2csessionClose(&audio);
|
||||
}
|
||||
}
|
||||
|
||||
/* Talk to the ALC5639 over GPIO, and disable audio output */
|
||||
{
|
||||
GpioPadSession audio;
|
||||
if (R_SUCCEEDED(gpioOpenSession(&audio, GpioPadName_AudioCodec))) {
|
||||
/* Set direction output, sleep 200 ms so it can take effect. */
|
||||
gpioPadSetDirection(&audio, GpioDirection_Output);
|
||||
svcSleepThread(200000000UL);
|
||||
|
||||
/* Pull audio codec low. */
|
||||
gpioPadSetValue(&audio, GpioValue_Low);
|
||||
|
||||
gpioPadClose(&audio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result StopSoundTask::Run() {
|
||||
StopSound();
|
||||
return 0;
|
||||
}
|
31
stratosphere/fatal/source/fatal_task_sound.hpp
Normal file
31
stratosphere/fatal/source/fatal_task_sound.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
class StopSoundTask : public IFatalTask {
|
||||
private:
|
||||
void StopSound();
|
||||
public:
|
||||
StopSoundTask(FatalThrowContext *ctx, u64 title_id) : IFatalTask(ctx, title_id) { }
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "SoundTask";
|
||||
}
|
||||
};
|
116
stratosphere/fatal/source/fatal_throw.cpp
Normal file
116
stratosphere/fatal/source/fatal_throw.cpp
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "fatal_throw.hpp"
|
||||
#include "fatal_event_manager.hpp"
|
||||
#include "fatal_task.hpp"
|
||||
#include "fatal_config.hpp"
|
||||
#include "fatal_debug.hpp"
|
||||
|
||||
static bool g_thrown = false;
|
||||
|
||||
static Result SetThrown() {
|
||||
/* This should be fine, since fatal only has a single IPC thread. */
|
||||
if (g_thrown) {
|
||||
return FatalResult_AlreadyThrown;
|
||||
}
|
||||
|
||||
g_thrown = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result ThrowFatalForSelf(u32 error) {
|
||||
u64 pid = 0;
|
||||
|
||||
svcGetProcessId(&pid, CUR_PROCESS_HANDLE);
|
||||
return ThrowFatalImpl(error, pid, FatalType_ErrorScreen, nullptr);
|
||||
}
|
||||
|
||||
Result ThrowFatalImpl(u32 error, u64 pid, FatalType policy, FatalCpuContext *cpu_ctx) {
|
||||
Result rc = 0;
|
||||
FatalThrowContext ctx = {0};
|
||||
ctx.error_code = error;
|
||||
if (cpu_ctx != nullptr) {
|
||||
ctx.cpu_ctx = *cpu_ctx;
|
||||
/* Assume if we're provided a context that it's complete. */
|
||||
for (u32 i = 0; i < NumAarch64Gprs; i++) {
|
||||
ctx.has_gprs[i] = true;
|
||||
}
|
||||
} else {
|
||||
std::memset(&ctx.cpu_ctx, 0, sizeof(ctx.cpu_ctx));
|
||||
cpu_ctx = &ctx.cpu_ctx;
|
||||
}
|
||||
|
||||
/* Get config. */
|
||||
const FatalConfig *config = GetFatalConfig();
|
||||
|
||||
/* Get title id. On failure, it'll be zero. */
|
||||
u64 title_id = 0;
|
||||
pminfoGetTitleId(&title_id, pid);
|
||||
ctx.is_creport = title_id == 0x0100000000000036;
|
||||
|
||||
/* Support for ams creport. TODO: Make this its own command? */
|
||||
if (ctx.is_creport && !cpu_ctx->is_aarch32 && cpu_ctx->aarch64_ctx.afsr0 != 0) {
|
||||
title_id = cpu_ctx->aarch64_ctx.afsr0;
|
||||
}
|
||||
|
||||
/* Atmosphere extension: automatic debug info collection. */
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200 && !ctx.is_creport) {
|
||||
if ((cpu_ctx->is_aarch32 && cpu_ctx->aarch32_ctx.stack_trace_size == 0) || (!cpu_ctx->is_aarch32 && cpu_ctx->aarch32_ctx.stack_trace_size == 0)) {
|
||||
TryCollectDebugInformation(&ctx, pid);
|
||||
}
|
||||
}
|
||||
|
||||
switch (policy) {
|
||||
case FatalType_ErrorReport:
|
||||
/* TODO: Don't write an error report. */
|
||||
break;
|
||||
case FatalType_ErrorReportAndErrorScreen:
|
||||
case FatalType_ErrorScreen:
|
||||
{
|
||||
/* Ensure we only throw once. */
|
||||
if (R_FAILED((rc = SetThrown()))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Signal that fatal is about to happen. */
|
||||
GetEventManager()->SignalEvents();
|
||||
|
||||
/* Create events. */
|
||||
Event erpt_event;
|
||||
Event battery_event;
|
||||
if (R_FAILED(eventCreate(&erpt_event, false)) || R_FAILED(eventCreate(&battery_event, false))) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Run tasks. */
|
||||
if (config->transition_to_fatal) {
|
||||
RunFatalTasks(&ctx, title_id, policy == FatalType_ErrorReportAndErrorScreen, &erpt_event, &battery_event);
|
||||
} else {
|
||||
/* If flag is not set, don't show the fatal screen. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* N aborts here. Should we just return an error code? */
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
24
stratosphere/fatal/source/fatal_throw.hpp
Normal file
24
stratosphere/fatal/source/fatal_throw.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fatal_types.hpp"
|
||||
|
||||
Result ThrowFatalForSelf(u32 error);
|
||||
Result ThrowFatalImpl(u32 error, u64 pid, FatalType policy, FatalCpuContext *cpu_ctx);
|
167
stratosphere/fatal/source/fatal_types.hpp
Normal file
167
stratosphere/fatal/source/fatal_types.hpp
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
enum FatalResult : Result {
|
||||
FatalResult_NullGfxBuffer = 0x4A3,
|
||||
FatalResult_AlreadyThrown = 0x6A3,
|
||||
FatalResult_TooManyEvents = 0x8A3,
|
||||
FatalResult_InRepairWithoutVolHeld = 0xAA3,
|
||||
FatalResult_InRepairWithoutTimeReviserCartridge = 0xCA3,
|
||||
};
|
||||
|
||||
static constexpr size_t NumAarch64Gprs = 32;
|
||||
static constexpr size_t NumAarch32Gprs = 16;
|
||||
|
||||
struct Aarch64CpuContext {
|
||||
using RegisterType = u64;
|
||||
static constexpr size_t MaxStackTraceDepth = 0x20;
|
||||
|
||||
/* Registers, exception context. N left names for these fields in fatal .rodata. */
|
||||
union {
|
||||
RegisterType x[NumAarch64Gprs];
|
||||
struct {
|
||||
RegisterType _x[29];
|
||||
RegisterType fp;
|
||||
RegisterType lr;
|
||||
RegisterType sp;
|
||||
RegisterType pc;
|
||||
};
|
||||
};
|
||||
RegisterType pstate;
|
||||
RegisterType afsr0;
|
||||
RegisterType afsr1;
|
||||
RegisterType esr;
|
||||
RegisterType far;
|
||||
|
||||
/* Misc. */
|
||||
RegisterType stack_trace[MaxStackTraceDepth];
|
||||
RegisterType start_address;
|
||||
RegisterType register_set_flags;
|
||||
u32 stack_trace_size;
|
||||
};
|
||||
|
||||
struct Aarch32CpuContext {
|
||||
using RegisterType = u32;
|
||||
static constexpr size_t MaxStackTraceDepth = 0x20;
|
||||
|
||||
/* Registers, exception context. N left names for these fields in fatal .rodata. */
|
||||
union {
|
||||
RegisterType r[NumAarch32Gprs];
|
||||
struct {
|
||||
RegisterType _r[11];
|
||||
RegisterType fp;
|
||||
RegisterType ip;
|
||||
RegisterType sp;
|
||||
RegisterType lr;
|
||||
RegisterType pc;
|
||||
};
|
||||
};
|
||||
RegisterType pstate;
|
||||
RegisterType afsr0;
|
||||
RegisterType afsr1;
|
||||
RegisterType esr;
|
||||
RegisterType far;
|
||||
|
||||
/* Misc. Yes, stack_trace_size is really laid out differently than aarch64... */
|
||||
RegisterType stack_trace[MaxStackTraceDepth];
|
||||
u32 stack_trace_size;
|
||||
RegisterType start_address;
|
||||
RegisterType register_set_flags;
|
||||
};
|
||||
|
||||
struct FatalCpuContext {
|
||||
union {
|
||||
Aarch64CpuContext aarch64_ctx;
|
||||
Aarch32CpuContext aarch32_ctx;
|
||||
};
|
||||
|
||||
bool is_aarch32;
|
||||
u32 type;
|
||||
};
|
||||
|
||||
struct FatalThrowContext {
|
||||
u32 error_code;
|
||||
bool is_creport;
|
||||
bool has_gprs[NumAarch64Gprs];
|
||||
size_t stack_dump_size;
|
||||
u8 stack_dump[0x100];
|
||||
char proc_name[0xD];
|
||||
FatalCpuContext cpu_ctx;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Aarch64CpuContext) == 0x248, "Aarch64CpuContext definition!");
|
||||
static_assert(sizeof(Aarch32CpuContext) == 0xE0, "Aarch32CpuContext definition!");
|
||||
static_assert(sizeof(FatalCpuContext) == 0x250, "FatalCpuContext definition!");
|
||||
static_assert(std::is_pod_v<FatalCpuContext>, "FatalCpuContext definition!");
|
||||
|
||||
static constexpr const char *Aarch64GprNames[NumAarch64Gprs] = {
|
||||
u8"X0",
|
||||
u8"X1",
|
||||
u8"X2",
|
||||
u8"X3",
|
||||
u8"X4",
|
||||
u8"X5",
|
||||
u8"X6",
|
||||
u8"X7",
|
||||
u8"X8",
|
||||
u8"X9",
|
||||
u8"X10",
|
||||
u8"X11",
|
||||
u8"X12",
|
||||
u8"X13",
|
||||
u8"X14",
|
||||
u8"X15",
|
||||
u8"X16",
|
||||
u8"X17",
|
||||
u8"X18",
|
||||
u8"X19",
|
||||
u8"X20",
|
||||
u8"X21",
|
||||
u8"X22",
|
||||
u8"X23",
|
||||
u8"X24",
|
||||
u8"X25",
|
||||
u8"X26",
|
||||
u8"X27",
|
||||
u8"X28",
|
||||
u8"FP",
|
||||
u8"LR",
|
||||
u8"SP",
|
||||
};
|
||||
|
||||
static constexpr const char *Aarch32GprNames[NumAarch32Gprs] = {
|
||||
u8"R0",
|
||||
u8"R1",
|
||||
u8"R2",
|
||||
u8"R3",
|
||||
u8"R4",
|
||||
u8"R5",
|
||||
u8"R6",
|
||||
u8"R7",
|
||||
u8"R8",
|
||||
u8"R9",
|
||||
u8"R10",
|
||||
u8"FP",
|
||||
u8"IP",
|
||||
u8"LR",
|
||||
u8"SP",
|
||||
u8"PC",
|
||||
};
|
||||
|
37
stratosphere/fatal/source/fatal_user.cpp
Normal file
37
stratosphere/fatal/source/fatal_user.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "fatal_user.hpp"
|
||||
#include "fatal_throw.hpp"
|
||||
#include "fatal_event_manager.hpp"
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
Result UserService::ThrowFatal(u32 error, PidDescriptor pid_desc) {
|
||||
return ThrowFatalImpl(error, pid_desc.pid, FatalType_ErrorReportAndErrorScreen, nullptr);
|
||||
}
|
||||
|
||||
Result UserService::ThrowFatalWithPolicy(u32 error, PidDescriptor pid_desc, FatalType policy) {
|
||||
return ThrowFatalImpl(error, pid_desc.pid, policy, nullptr);
|
||||
}
|
||||
|
||||
Result UserService::ThrowFatalWithCpuContext(u32 error, PidDescriptor pid_desc, FatalType policy, InBuffer<u8> _ctx) {
|
||||
if (_ctx.num_elements < sizeof(FatalCpuContext)) {
|
||||
return ThrowFatalImpl(error, pid_desc.pid, policy, nullptr);
|
||||
} else {
|
||||
return ThrowFatalImpl(error, pid_desc.pid, policy, reinterpret_cast<FatalCpuContext *>(_ctx.buffer));
|
||||
}
|
||||
}
|
41
stratosphere/fatal/source/fatal_user.hpp
Normal file
41
stratosphere/fatal/source/fatal_user.hpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fatal_types.hpp"
|
||||
|
||||
enum UserCmd {
|
||||
User_Cmd_ThrowFatal = 0,
|
||||
User_Cmd_ThrowFatalWithPolicy = 1,
|
||||
User_Cmd_ThrowFatalWithCpuContext = 2,
|
||||
};
|
||||
|
||||
class UserService final : public IServiceObject {
|
||||
private:
|
||||
/* Actual commands. */
|
||||
Result ThrowFatal(u32 error, PidDescriptor pid_desc);
|
||||
Result ThrowFatalWithPolicy(u32 error, PidDescriptor pid_desc, FatalType policy);
|
||||
Result ThrowFatalWithCpuContext(u32 error, PidDescriptor pid_desc, FatalType policy, InBuffer<u8> _ctx);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MakeServiceCommandMeta<User_Cmd_ThrowFatal, &UserService::ThrowFatal>(),
|
||||
MakeServiceCommandMeta<User_Cmd_ThrowFatalWithPolicy, &UserService::ThrowFatalWithPolicy>(),
|
||||
MakeServiceCommandMeta<User_Cmd_ThrowFatalWithCpuContext, &UserService::ThrowFatalWithCpuContext>(),
|
||||
};
|
||||
};
|
|
@ -9,6 +9,13 @@ endif
|
|||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
|
@ -24,7 +31,7 @@ DATA := data
|
|||
INCLUDES := include ../../common/include
|
||||
EXEFS_SRC := exefs_src
|
||||
|
||||
DEFINES := -DDISABLE_IPC
|
||||
DEFINES := -DDISABLE_IPC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit bcd80ab445258c20d968aad1c083fd8cb0937bee
|
||||
Subproject commit 0fb33e9c094bffde737c7a73cd5ccce4d7cbae33
|
|
@ -9,6 +9,13 @@ endif
|
|||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
|
@ -24,7 +31,7 @@ DATA := data
|
|||
INCLUDES := include ../../common/include
|
||||
EXEFS_SRC := exefs_src
|
||||
|
||||
DEFINES := -DDISABLE_IPC
|
||||
DEFINES := -DDISABLE_IPC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
|
|
|
@ -9,6 +9,13 @@ endif
|
|||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
|
@ -24,7 +31,7 @@ DATA := data
|
|||
INCLUDES := include ../../common/include
|
||||
EXEFS_SRC := exefs_src
|
||||
|
||||
DEFINES := -DDISABLE_IPC
|
||||
DEFINES := -DDISABLE_IPC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
|
|
|
@ -9,6 +9,13 @@ endif
|
|||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
|
@ -24,7 +31,7 @@ DATA := data
|
|||
INCLUDES := include ../../common/include
|
||||
EXEFS_SRC := exefs_src
|
||||
|
||||
DEFINES := -DDISABLE_IPC
|
||||
DEFINES := -DDISABLE_IPC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
|
|
|
@ -9,6 +9,13 @@ endif
|
|||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
|
@ -24,7 +31,7 @@ DATA := data
|
|||
INCLUDES := include ../../common/include
|
||||
EXEFS_SRC := exefs_src
|
||||
|
||||
DEFINES := -DDISABLE_IPC
|
||||
DEFINES := -DDISABLE_IPC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
|
|
|
@ -9,6 +9,13 @@ endif
|
|||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/devkitA64/base_rules
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
|
@ -26,6 +33,7 @@ INCLUDES := include ../common/include
|
|||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a -mtune=cortex-a57
|
||||
DEFINES := -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
|
||||
CFLAGS := \
|
||||
-g \
|
||||
|
|
Loading…
Reference in a new issue