Merge pull request #266 from Atmosphere-NX/fatal

Implement custom fatal sysmodule.
This commit is contained in:
SciresM 2018-11-29 12:04:40 -08:00 committed by GitHub
commit ab33329129
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 4454 additions and 16 deletions

View file

@ -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 ../;

View file

@ -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 \

View file

@ -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 \

View file

@ -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 \

View file

@ -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 ?

View file

@ -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

View file

@ -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

View file

@ -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];

View file

@ -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. */

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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
View 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
#---------------------------------------------------------------------------------------

View 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
}]
}

File diff suppressed because it is too large Load diff

View 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();
}

View 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();

View 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;
}
}
}
}

View 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!");

View 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]);
}
}

View 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();

View 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;
}

View 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);
};

View 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;
}

View 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());
}

View 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>(),
};
};

View 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);
}
}

View 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();

View 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));
}

View 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);

View 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();
}

View 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";
}
};

View 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(&timestamp)) {
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;
}

View 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";
}
};

View 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;
}

View 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";
}
};

View 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;
}

View 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";
}
};

View 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;
}

View 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";
}
};

View 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;
}

View 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);

View 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",
};

View 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));
}
}

View 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>(),
};
};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 \