fatal: refactor into sts namespace

This commit is contained in:
Michael Scire 2019-07-18 19:09:35 -07:00 committed by SciresM
parent 442ebff829
commit 39d041466d
38 changed files with 2176 additions and 1926 deletions

View file

@ -115,19 +115,22 @@ namespace sts::creport {
}
}
void CrashReport::GetFatalContext(FatalContext *out) const {
void CrashReport::GetFatalContext(FatalContext *_out) const {
static_assert(sizeof(*_out) == sizeof(sts::fatal::CpuContext));
sts::fatal::CpuContext *out = reinterpret_cast<sts::fatal::CpuContext *>(_out);
std::memset(out, 0, sizeof(*out));
/* TODO: Support generating 32-bit fatal contexts? */
out->is_aarch32 = false;
out->architecture = fatal::CpuContext::Architecture_Aarch64;
out->type = static_cast<u32>(this->exception_info.type);
for (size_t i = 0; i < 29; i++) {
out->aarch64_ctx.x[i] = this->crashed_thread.GetGeneralPurposeRegister(i);
for (size_t i = 0; i < fatal::aarch64::RegisterName_FP; i++) {
out->aarch64_ctx.SetRegisterValue(static_cast<fatal::aarch64::RegisterName>(i), this->crashed_thread.GetGeneralPurposeRegister(i));
}
out->aarch64_ctx.fp = this->crashed_thread.GetFP();
out->aarch64_ctx.lr = this->crashed_thread.GetLR();
out->aarch64_ctx.pc = this->crashed_thread.GetPC();
out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_FP, this->crashed_thread.GetFP());
out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_LR, this->crashed_thread.GetLR());
out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_SP, this->crashed_thread.GetSP());
out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_PC, this->crashed_thread.GetPC());
out->aarch64_ctx.stack_trace_size = this->crashed_thread.GetStackTraceSize();
for (size_t i = 0; i < out->aarch64_ctx.stack_trace_size; i++) {
@ -135,11 +138,11 @@ namespace sts::creport {
}
if (this->module_list.GetModuleCount()) {
out->aarch64_ctx.start_address = this->module_list.GetModuleStartAddress(0);
out->aarch64_ctx.SetBaseAddress(this->module_list.GetModuleStartAddress(0));
}
/* For ams fatal, which doesn't use afsr0, pass title_id instead. */
out->aarch64_ctx.afsr0 = this->process_info.title_id;
out->aarch64_ctx.SetTitleIdForAtmosphere(ncm::TitleId{this->process_info.title_id});
}
void CrashReport::ProcessExceptions() {

View file

@ -92,5 +92,12 @@
}, {
"type": "handle_table_size",
"value": 128
}]
},
{
"type": "debug_flags",
"value": {
"allow_debug": false,
"force_debug": true
}
}]
}

View file

@ -14,11 +14,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#define AMS_LOGO_WIDTH 0xA0
#define AMS_LOGO_HEIGHT 0x80
constexpr size_t AtmosphereLogoWidth = 0xA0;
constexpr size_t AtmosphereLogoHeight = 0x80;
static constexpr u16 AMS_LOGO_BIN[] = {
static constexpr u16 AtmosphereLogoData[] = {
0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9,
0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9,
0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9,
@ -1301,4 +1300,4 @@ static constexpr u16 AMS_LOGO_BIN[] = {
0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9
};
static_assert(sizeof(AMS_LOGO_BIN) == AMS_LOGO_WIDTH * AMS_LOGO_HEIGHT * sizeof(*AMS_LOGO_BIN), "Logo definition!");
static_assert(util::size(AtmosphereLogoData) == AtmosphereLogoWidth * AtmosphereLogoHeight, "Logo definition!");

View file

@ -14,78 +14,82 @@
* 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 = {};
namespace sts::fatal::srv {
static IEvent *g_fatal_settings_event = nullptr;
namespace {
FatalConfig *GetFatalConfig() {
return &g_fatal_config;
}
/* Global config. */
FatalConfig g_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();
/* Event creator. */
IEvent *CreateFatalDirtyEvent() {
Event evt;
R_ASSERT(setsysBindFatalDirtyFlagEvent(&evt));
return LoadReadOnlySystemEvent(evt.revent, [](u64 timeout) {
u64 flags_0, flags_1;
if (R_SUCCEEDED(setsysGetFatalDirtyFlags(&flags_0, &flags_1)) && (flags_0 & 1)) {
g_config.UpdateLanguageCode();
}
return ResultSuccess;
}, true);
}
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 ResultSuccess;
}, true);
/* Global event. */
IEvent *g_fatal_dirty_event = CreateFatalDirtyEvent();
}
return g_fatal_settings_event;
}
FatalConfig::FatalConfig() {
/* Clear this. */
std::memset(this, 0, sizeof(*this));
static void SetupConfigLanguages() {
FatalConfig *config = GetFatalConfig();
/* Get information from set. */
setsysGetSerialNumber(this->serial_number);
setsysGetFirmwareVersion(&this->firmware_version);
setsysGetFlag(SetSysFlag_Quest, &this->quest_flag);
this->UpdateLanguageCode();
/* Defaults. */
config->error_msg = u8"Error Code: 2%03d-%04d (0x%x)\n";
/* Read information from settings. */
setsysGetSettingsItemValue("fatal", "transition_to_fatal", &this->transition_to_fatal, sizeof(this->transition_to_fatal));
setsysGetSettingsItemValue("fatal", "show_extra_info", &this->show_extra_info, sizeof(this->show_extra_info));
setsysGetSettingsItemValue("fatal", "quest_reboot_interval_second", &this->quest_reboot_interval_second, sizeof(this->quest_reboot_interval_second));
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 normally, or a VOL button\n"
u8"to reboot to a payload (or RCM, if none is present). If you are unable to\n"
u8"restart the console, hold the POWER Button for 12 seconds 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";
/* Atmosphere extension for automatic reboot. */
if (R_SUCCEEDED(setsysGetSettingsItemValue("atmosphere", "fatal_auto_reboot_interval", &this->fatal_auto_reboot_interval, sizeof(this->fatal_auto_reboot_interval)))) {
this->fatal_auto_reboot_enabled = this->fatal_auto_reboot_interval != 0;
}
/* Setup messages. */
{
this->error_msg = u8"Error Code: 2%03d-%04d (0x%x)\n";
this->error_desc = u8"An error has occured.\n\n"
u8"Please press the POWER Button to restart the console normally, or a VOL button\n"
u8"to reboot to a payload (or RCM, if none is present). If you are unable to\n"
u8"restart the console, hold the POWER Button for 12 seconds 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";
/* If you're running Atmosphere on a quest unit for some reason, talk to me on discord. */
this->quest_desc = u8"Please call 1-800-875-1852 for service.\n\n"
u8"Also, please be aware that running Atmosphere on a Quest device is not fully\n"
u8"supported. Perhaps try booting your device without Atmosphere before calling\n"
u8"an official Nintendo service hotline. If you encounter further issues, please\n"
u8"contact SciresM#0524 on Discord, or via some other means.\n";
/* TODO: Try to load dynamically? */
/* FsStorage message_storage; */
/* TODO: if (R_SUCCEEDED(fsOpenDataStorageByDataId(0x010000000000081D, "fatal_msg"))) { ... } */
}
}
IEvent *GetFatalDirtyEvent() {
return g_fatal_dirty_event;
}
const FatalConfig &GetFatalConfig() {
return g_config;
}
/* 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);
config->is_auto_reboot_enabled = R_SUCCEEDED(setsysGetSettingsItemValue("atmosphere", "fatal_auto_reboot_interval", &config->fatal_auto_reboot_interval, sizeof(config->fatal_auto_reboot_interval)));
config->is_auto_reboot_enabled &= (config->fatal_auto_reboot_interval != 0);
SetupConfigLanguages();
}

View file

@ -18,22 +18,79 @@
#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;
u64 fatal_auto_reboot_interval;
bool is_auto_reboot_enabled;
};
namespace sts::fatal::srv {
IEvent *GetFatalSettingsEvent();
FatalConfig *GetFatalConfig();
class FatalConfig {
private:
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;
u64 fatal_auto_reboot_interval;
bool fatal_auto_reboot_enabled;
public:
FatalConfig();
void InitializeFatalConfig();
const char *GetSerialNumber() const {
return this->serial_number;
}
const SetSysFirmwareVersion &GetFirmwareVersion() const {
return this->firmware_version;
}
void UpdateLanguageCode() {
setGetLanguageCode(&this->language_code);
}
u64 GetLanguageCode() const {
return this->language_code;
}
bool ShouldTransitionToFatal() const {
return this->transition_to_fatal;
}
bool ShouldShowExtraInfo() const {
return this->show_extra_info;
}
bool IsQuest() const {
return this->quest_flag;
}
bool IsFatalRebootEnabled() const {
return this->fatal_auto_reboot_enabled;
}
u64 GetQuestRebootTimeoutInterval() const {
return this->quest_reboot_interval_second * 1'000'000'000ul;
}
u64 GetFatalRebootTimeoutInterval() const {
return this->fatal_auto_reboot_interval * 1'000'000ul;
}
const char *GetErrorMessage() const {
return this->error_msg;
}
const char *GetErrorDescription() const {
if (this->IsQuest()) {
return this->quest_desc;
} else {
return this->error_desc;
}
}
};
IEvent *GetFatalDirtyEvent();
const FatalConfig &GetFatalConfig();
}

View file

@ -13,234 +13,101 @@
* 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 <unordered_map>
#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;
namespace sts::fatal::srv {
if (o_mi == NULL) {
o_mi = &mi;
}
namespace {
if (R_FAILED(svcQueryDebugProcessMemory(o_mi, &pi, debug_handle, address))) {
return false;
}
constexpr u32 SvcSendSyncRequestInstruction = 0xD4000421;
/* Must be readable */
if ((o_mi->perm & Perm_R) != Perm_R) {
return false;
}
struct StackFrame {
u64 fp;
u64 lr;
};
/* 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;
}
bool IsThreadFatalCaller(u32 error_code, u32 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;
}
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;
const svc::ThreadState thread_state = static_cast<svc::ThreadState>(_thread_state);
if (thread_state != svc::ThreadState::Waiting && thread_state != svc::ThreadState::Running) {
return false;
}
}
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;
/* Get the thread context. */
if (R_FAILED(svcGetDebugThreadContext(thread_ctx, debug_handle, thread_id, svc::ThreadContextFlag_All))) {
return false;
}
/* 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;
/* 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 != SvcSendSyncRequestInstruction) {
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;
}
/* HACK: We want to parse the command the fatal caller sent. */
/* The easiest way to do this is to copy their TLS over ours, and parse ours. */
std::memcpy(armGetTls(), thread_tls, sizeof(thread_tls));
{
IpcParsedCommand r;
if (R_FAILED(ipcParse(&r))) {
return false;
}
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;
/* 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 != error_code) {
return false;
}
}
if (!found_fatal_caller) {
return;
}
}
if (R_FAILED(svcGetDebugThreadContext(&thread_ctx, debug_handle, thread_id, 0xF))) {
return;
/* We found our caller. */
return true;
}
/* 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;
}
}
/* Helper to guess start address. */
auto TryGuessStartAddress = [&](u64 guess) {
bool TryGuessBaseAddress(u64 *out_base_address, u32 debug_handle, u64 guess) {
MemoryInfo mi;
u32 pi;
if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess)) || mi.perm != Perm_Rx) {
@ -255,29 +122,146 @@ void TryCollectDebugInformation(FatalThrowContext *ctx, u64 pid) {
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;
*out_base_address = mi.addr + mi.size;
return true;
}
guess -= 4;
guess = mi.addr - 4;
}
return false;
};
}
/* Parse the starting address. */
{
if (TryGuessStartAddress(thread_ctx.pc.x)) {
return;
u64 GetBaseAddress(const ThrowContext *throw_ctx, const ThreadContext *thread_ctx, u32 debug_handle) {
u64 base_address = 0;
if (TryGuessBaseAddress(&base_address, debug_handle, thread_ctx->pc.x)) {
return base_address;
}
if (TryGuessStartAddress(thread_ctx.lr)) {
return;
if (TryGuessBaseAddress(&base_address, debug_handle, thread_ctx->lr)) {
return base_address;
}
for (size_t i = 0; i < ctx->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
if (TryGuessStartAddress(ctx->cpu_ctx.aarch64_ctx.stack_trace[i])) {
return;
for (size_t i = 0; i < throw_ctx->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
if (TryGuessBaseAddress(&base_address, debug_handle, throw_ctx->cpu_ctx.aarch64_ctx.stack_trace[i])) {
return base_address;
}
}
return base_address;
}
}
}
void TryCollectDebugInformation(ThrowContext *ctx, u64 process_id) {
/* Try to debug the process. This may fail, if we called into ourself. */
AutoHandle debug_handle;
if (R_FAILED(svcDebugActiveProcess(debug_handle.GetPointer(), process_id))) {
return;
}
/* 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;
svc::DebugEventInfo d;
while (R_SUCCEEDED(svcGetDebugEvent(reinterpret_cast<u8 *>(&d), debug_handle.Get()))) {
switch (d.type) {
case svc::DebugEventType::AttachProcess:
ctx->cpu_ctx.architecture = (d.info.attach_process.flags & 1) ? CpuContext::Architecture_Aarch64 : CpuContext::Architecture_Aarch32;
std::memcpy(ctx->proc_name, d.info.attach_process.name, sizeof(d.info.attach_process.name));
got_attach_process = true;
break;
case svc::DebugEventType::AttachThread:
thread_id_to_tls[d.info.attach_thread.thread_id] = d.info.attach_thread.tls_address;
break;
case svc::DebugEventType::Exception:
case svc::DebugEventType::ExitProcess:
case svc::DebugEventType::ExitThread:
break;
}
}
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.architecture == CpuContext::Architecture_Aarch32) {
return;
}
/* Welcome to hell. Here, we try to identify which thread called into fatal. */
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.Get()))) {
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 (IsThreadFatalCaller(ctx->error_code, debug_handle.Get(), 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.Get(), thread_id, svc::ThreadContextFlag_All))) {
return;
}
/* Set register states. */
ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_FP, thread_ctx.fp);
ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_LR, thread_ctx.lr);
ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_SP, thread_ctx.sp);
ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_PC, thread_ctx.pc.x);
/* Parse a stack trace. */
u64 cur_fp = thread_ctx.fp;
ctx->cpu_ctx.aarch64_ctx.stack_trace_size = 0;
for (unsigned int i = 0; i < aarch64::CpuContext::MaxStackTraceDepth; 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.Get(), 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 (R_SUCCEEDED(svcReadDebugProcessMemory(ctx->stack_dump, debug_handle.Get(), thread_ctx.sp, sz))) {
ctx->stack_dump_size = sz;
break;
}
}
/* Parse the base address. */
ctx->cpu_ctx.aarch64_ctx.SetBaseAddress(GetBaseAddress(ctx, &thread_ctx, debug_handle.Get()));
}
}

View file

@ -18,132 +18,8 @@
#include <switch.h>
#include <stratosphere.hpp>
#include "fatal_types.hpp"
namespace sts::fatal::srv {
void TryCollectDebugInformation(FatalThrowContext *ctx, u64 pid);
void TryCollectDebugInformation(ThrowContext *ctx, u64 process_id);
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

@ -14,39 +14,33 @@
* 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;
namespace sts::fatal::srv {
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();
FatalEventManager::FatalEventManager() {
/* Just create all the events. */
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
R_ASSERT(eventCreate(&this->events[i], true));
}
}
}
Result FatalEventManager::GetEvent(Handle *out) {
std::scoped_lock<HosMutex> lk{this->lock};
Result FatalEventManager::GetEvent(Handle *out) {
std::scoped_lock lk{this->lock};
/* Only allow GetEvent to succeed NumFatalEvents times. */
if (this->events_gotten >= FatalEventManager::NumFatalEvents) {
return ResultFatalTooManyEvents;
/* Only allow GetEvent to succeed NumFatalEvents times. */
if (this->num_events_gotten >= FatalEventManager::NumFatalEvents) {
return ResultFatalTooManyEvents;
}
*out = this->events[this->num_events_gotten++].revent;
return ResultSuccess;
}
*out = this->events[this->events_gotten++].revent;
return ResultSuccess;
}
void FatalEventManager::SignalEvents() {
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
eventFire(&this->events[i]);
void FatalEventManager::SignalEvents() {
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
eventFire(&this->events[i]);
}
}
}

View file

@ -18,17 +18,19 @@
#include <switch.h>
#include <stratosphere.hpp>
class FatalEventManager {
private:
static constexpr size_t NumFatalEvents = 3;
namespace sts::fatal::srv {
HosMutex lock;
size_t events_gotten = 0;
Event events[NumFatalEvents];
public:
FatalEventManager();
Result GetEvent(Handle *out);
void SignalEvents();
};
class FatalEventManager {
private:
static constexpr size_t NumFatalEvents = 3;
FatalEventManager *GetEventManager();
HosMutex lock;
size_t num_events_gotten = 0;
Event events[NumFatalEvents];
public:
FatalEventManager();
Result GetEvent(Handle *out);
void SignalEvents();
};
}

View file

@ -14,220 +14,228 @@
* 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;
/* Define color conversion macros. */
#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))
static u32 g_mono_adv = 0;
namespace sts::fatal::srv::font {
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;
namespace {
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);
/* Font state globals. */
u16 *g_frame_buffer = nullptr;
u32 (*g_unswizzle_func)(u32, u32) = nullptr;
u16 g_font_color = 0xFFFF; /* White. */
float g_font_size = 16.0f;
u32 g_line_x = 0, g_cur_x = 0, g_cur_y = 0;
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;
u32 g_mono_adv = 0;
return RGB888_TO_RGB565(r, g, b);
}
PlFontData g_font;
PlFontData g_fonts[PlSharedFontType_Total];
static void DrawGlyph(FT_Bitmap *bitmap, u32 x, u32 y) {
u8* imageptr = bitmap->buffer;
FT_Library g_library;
FT_Face g_face;
FT_Error g_ft_err = 0;
if (bitmap->pixel_mode!=FT_PIXEL_MODE_GRAY) return;
/* Helpers. */
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);
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]);
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);
}
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_frame_buffer[g_unswizzle_func(x + tmpx, y + tmpy)];
*ptr = Blend(g_font_color, *ptr, imageptr[tmpx]);
}
imageptr += bitmap->pitch;
}
}
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;
}
}
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;
void PrintLine(const char *str) {
return DrawString(str, true);
}
const size_t len = strlen(str);
void PrintFormatLine(const char *format, ...) {
char char_buf[0x400];
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;
}
};
va_list va_arg;
va_start(va_arg, format);
vsnprintf(char_buf, sizeof(char_buf), format, va_arg);
va_end(va_arg);
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;
PrintLine(char_buf);
}
if (cur_char == '\n') {
cur_x = g_line_x;
cur_y += g_face->size->metrics.height >> 6;
continue;
void Print(const char *str) {
return DrawString(str, false);
}
void PrintFormat(const char *format, ...) {
char char_buf[0x400];
va_list va_arg;
va_start(va_arg, format);
vsnprintf(char_buf, sizeof(char_buf), format, va_arg);
va_end(va_arg);
Print(char_buf);
}
void PrintMonospaceU64(u64 x) {
char char_buf[0x400];
snprintf(char_buf, sizeof(char_buf), "%016lX", x);
DrawString(char_buf, false, true);
}
void PrintMonospaceU32(u32 x) {
char char_buf[0x400];
snprintf(char_buf, sizeof(char_buf), "%08X", x);
DrawString(char_buf, false, true);
}
void PrintMonospaceBlank(u32 width) {
char char_buf[0x400] = {0};
for (size_t i = 0; i < width && i < sizeof(char_buf); i++) {
char_buf[i] = ' ';
}
glyph_index = FT_Get_Char_Index(g_face, cur_char);
DrawString(char_buf, false, true);
}
g_ft_err = FT_Load_Glyph(g_face, glyph_index, FT_LOAD_DEFAULT);
void SetFontColor(u16 color) {
g_font_color = color;
}
void SetPosition(u32 x, u32 y) {
g_line_x = x;
g_cur_x = x;
g_cur_y = y;
}
u32 GetX() {
return g_cur_x;
}
u32 GetY() {
return g_cur_y;
}
void SetFontSize(float fsz) {
g_font_size = fsz;
g_ft_err = FT_Set_Char_Size(g_face, 0, static_cast<u32>(g_font_size * 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) {
return;
if (g_ft_err == 0) {
g_mono_adv = g_face->glyph->advance.x;
}
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, ...) {
char char_buf[0x400];
va_list va_arg;
va_start(va_arg, format);
vsnprintf(char_buf, sizeof(char_buf), format, va_arg);
va_end(va_arg);
PrintLine(char_buf);
}
void FontManager::Print(const char *str) {
return DrawString(str, false);
}
void FontManager::PrintFormat(const char *format, ...) {
char char_buf[0x400];
va_list va_arg;
va_start(va_arg, format);
vsnprintf(char_buf, sizeof(char_buf), format, va_arg);
va_end(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);
void 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);
}
if (g_ft_err == 0) {
g_mono_adv = g_face->glyph->advance.x;
void ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32)) {
g_frame_buffer = fb;
g_unswizzle_func = unswizzle_func;
}
Result InitializeSharedFont() {
size_t total_fonts = 0;
R_TRY(plGetSharedFont(GetFatalConfig().GetLanguageCode(), g_fonts, PlSharedFontType_Total, &total_fonts));
R_TRY(plGetSharedFontByType(&g_font, PlSharedFontType_Standard));
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_size);
return g_ft_err;
}
}
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() {
size_t total_fonts = 0;
R_TRY(plGetSharedFont(GetFatalConfig()->language_code, g_fonts, PlSharedFontType_Total, &total_fonts));
R_TRY(plGetSharedFontByType(&g_font, PlSharedFontType_Standard));
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

@ -19,27 +19,23 @@
#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))
namespace sts::fatal::srv::font {
class FontManager {
public:
static Result InitializeSharedFont();
static void ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32));
Result InitializeSharedFont();
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);
};
void SetFontColor(u16 color);
void SetPosition(u32 x, u32 y);
u32 GetX();
u32 GetY();
void SetFontSize(float fsz);
void AddSpacingLines(float num_lines);
void PrintLine(const char *str);
void PrintFormatLine(const char *format, ...);
void Print(const char *str);
void PrintFormat(const char *format, ...);
void PrintMonospaceU64(u64 x);
void PrintMonospaceU32(u32 x);
void PrintMonospaceBlank(u32 width);
}

View file

@ -23,9 +23,7 @@
#include <atmosphere.h>
#include <stratosphere.hpp>
#include "fatal_types.hpp"
#include "fatal_private.hpp"
#include "fatal_user.hpp"
#include "fatal_service.hpp"
#include "fatal_config.hpp"
#include "fatal_repair.hpp"
#include "fatal_font.hpp"
@ -123,24 +121,19 @@ void __appExit(void) {
int main(int argc, char **argv)
{
/* Load settings from set:sys. */
InitializeFatalConfig();
/* Load shared font. */
if (R_FAILED(FontManager::InitializeSharedFont())) {
std::abort();
}
R_ASSERT(sts::fatal::srv::font::InitializeSharedFont());
/* Check whether we should throw fatal due to repair process. */
CheckRepairStatus();
sts::fatal::srv::CheckRepairStatus();
/* TODO: What's a good timeout value to use here? */
/* Create waitable manager. */
static auto s_server_manager = WaitableManager(1);
/* Create services. */
s_server_manager.AddWaitable(new ServiceServer<PrivateService>("fatal:p", 4));
s_server_manager.AddWaitable(new ServiceServer<UserService>("fatal:u", 4));
s_server_manager.AddWaitable(GetFatalSettingsEvent());
s_server_manager.AddWaitable(new ServiceServer<sts::fatal::srv::PrivateService>("fatal:p", 4));
s_server_manager.AddWaitable(new ServiceServer<sts::fatal::srv::UserService>("fatal:u", 4));
s_server_manager.AddWaitable(sts::fatal::srv::GetFatalDirtyEvent());
/* Loop forever, servicing our services. */
s_server_manager.Process();

View file

@ -14,93 +14,104 @@
* 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"
#include "fatal_service_for_self.hpp"
static bool InRepairWithoutVolHeld() {
if (GetRuntimeFirmwareVersion() < FirmwareVersion_300) {
return false;
}
namespace sts::fatal::srv {
bool in_repair;
if (R_FAILED(setsysGetFlag(SetSysFlag_InRepairProcessEnable, &in_repair)) || !in_repair) {
return false;
}
namespace {
{
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;
bool IsInRepair() {
/* Before firmware 3.0.0, this wasn't implemented. */
if (GetRuntimeFirmwareVersion() < FirmwareVersion_300) {
return false;
}
/* Sleep for 100 ms. */
svcSleepThread(100000000UL);
bool in_repair;
return R_SUCCEEDED(setsysGetFlag(SetSysFlag_InRepairProcessEnable, &in_repair)) && in_repair;
}
bool IsInRepairWithoutVolHeld() {
if (IsInRepair()) {
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(1'000'000'000ul);
while (!timeout_helper.TimedOut()) {
GpioValue val;
if (R_FAILED(gpioPadGetValue(&vol_btn, &val)) || val != GpioValue_Low) {
return true;
}
/* Sleep for 100 ms. */
svcSleepThread(100'000'000ul);
}
}
return false;
}
bool NeedsRunTimeReviser() {
/* Before firmware 5.0.0, this wasn't implemented. */
if (GetRuntimeFirmwareVersion() < FirmwareVersion_300) {
return false;
}
bool requires_time_reviser;
return R_SUCCEEDED(setsysGetFlag(SetSysFlag_RequiresRunRepairTimeReviser, &requires_time_reviser)) && requires_time_reviser;
}
bool IsTimeReviserCartridgeInserted() {
FsGameCardHandle gc_hnd;
u8 gc_attr;
{
FsDeviceOperator devop;
if (R_FAILED(fsOpenDeviceOperator(&devop))) {
return false;
}
/* 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 false;
}
/* 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 false;
}
}
/* Check that the gamecard is a repair tool. */
return (gc_attr & FsGameCardAttribute_Repair) == FsGameCardAttribute_Repair;
}
bool IsInRepairWithoutTimeReviserCartridge() {
return NeedsRunTimeReviser() && IsTimeReviserCartridgeInserted();
}
}
void CheckRepairStatus() {
if (IsInRepairWithoutVolHeld()) {
ThrowFatalForSelf(ResultFatalInRepairWithoutVolHeld);
}
if (IsInRepairWithoutTimeReviserCartridge()) {
ThrowFatalForSelf(ResultFatalInRepairWithoutTimeReviserCartridge);
}
}
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(ResultFatalInRepairWithoutVolHeld);
}
if (InRepairWithoutTimeReviserCartridge()) {
ThrowFatalForSelf(ResultFatalInRepairWithoutTimeReviserCartridge);
}
}

View file

@ -18,4 +18,8 @@
#include <switch.h>
#include <stratosphere.hpp>
void CheckRepairStatus();
namespace sts::fatal::srv {
void CheckRepairStatus();
}

View file

@ -0,0 +1,161 @@
/*
* Copyright (c) 2018-2019 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 "fatal_config.hpp"
#include "fatal_debug.hpp"
#include "fatal_service.hpp"
#include "fatal_service_for_self.hpp"
#include "fatal_event_manager.hpp"
#include "fatal_task.hpp"
namespace sts::fatal::srv {
namespace {
/* Service Context. */
class ServiceContext {
private:
ThrowContext context;
FatalEventManager event_manager;
bool has_thrown;
private:
Result TrySetHasThrown() {
if (this->has_thrown) {
return ResultFatalAlreadyThrown;
}
this->has_thrown = true;
return ResultSuccess;
}
public:
ServiceContext() {
this->context.ClearState();
R_ASSERT(eventCreate(&this->context.erpt_event, false));
R_ASSERT(eventCreate(&this->context.battery_event, false));
this->has_thrown = false;
}
Result GetEvent(Handle *out) {
return this->event_manager.GetEvent(out);
}
Result ThrowFatal(u32 error_code, u64 process_id) {
return this->ThrowFatalWithCpuContext(error_code, process_id, FatalType_ErrorReportAndErrorScreen, {});
}
Result ThrowFatalWithPolicy(u32 error_code, u64 process_id, FatalType policy) {
return this->ThrowFatalWithCpuContext(error_code, process_id, policy, {});
}
Result ThrowFatalWithCpuContext(u32 error_code, u64 process_id, FatalType policy, const CpuContext &cpu_ctx);
};
/* Context global. */
ServiceContext g_context;
/* Throw implementation. */
Result ServiceContext::ThrowFatalWithCpuContext(u32 error_code, u64 process_id, FatalType policy, const CpuContext &cpu_ctx) {
/* We don't support Error Report only fatals. */
if (policy == FatalType_ErrorReport) {
return ResultSuccess;
}
/* Note that we've thrown fatal. */
R_TRY(this->TrySetHasThrown());
/* At this point we have exclusive access to this->context. */
this->context.error_code = error_code;
this->context.cpu_ctx = cpu_ctx;
/* Cap the stack trace to a sane limit. */
if (cpu_ctx.architecture == CpuContext::Architecture_Aarch64) {
this->context.cpu_ctx.aarch64_ctx.stack_trace_size = std::max(size_t(this->context.cpu_ctx.aarch64_ctx.stack_trace_size), aarch64::CpuContext::MaxStackTraceDepth);
} else {
this->context.cpu_ctx.aarch32_ctx.stack_trace_size = std::max(size_t(this->context.cpu_ctx.aarch32_ctx.stack_trace_size), aarch32::CpuContext::MaxStackTraceDepth);
}
/* Get title id. */
pm::info::GetTitleId(&this->context.title_id, process_id);
this->context.is_creport = (this->context.title_id == ncm::TitleId::Creport);
if (!this->context.is_creport) {
/* On firmware version 2.0.0, use debugging SVCs to collect information. */
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) {
fatal::srv::TryCollectDebugInformation(&this->context, process_id);
}
} else {
/* We received info from creport. Parse title id from afsr0. */
if (cpu_ctx.architecture == CpuContext::Architecture_Aarch64) {
this->context.title_id = cpu_ctx.aarch64_ctx.GetTitleIdForAtmosphere();
} else {
this->context.title_id = cpu_ctx.aarch32_ctx.GetTitleIdForAtmosphere();
}
}
/* Decide whether to generate a report. */
this->context.generate_error_report = (policy == FatalType_ErrorReportAndErrorScreen);
/* Adjust error code (2000-0000 -> 2162-0002). */
if (this->context.error_code == ResultSuccess) {
this->context.error_code = ResultErrSystemModuleAborted;
}
switch (policy) {
case FatalType_ErrorReportAndErrorScreen:
case FatalType_ErrorScreen:
/* Signal that we're throwing. */
this->event_manager.SignalEvents();
if (GetFatalConfig().ShouldTransitionToFatal()) {
RunTasks(&this->context);
}
break;
default:
/* N aborts here. Should we just return an error code? */
std::abort();
}
return ResultSuccess;
}
}
Result ThrowFatalForSelf(Result error_code) {
u64 process_id = 0;
R_ASSERT(svcGetProcessId(&process_id, CUR_PROCESS_HANDLE));
return g_context.ThrowFatalWithPolicy(static_cast<u32>(error_code), process_id, FatalType_ErrorScreen);
}
Result UserService::ThrowFatal(u32 error, PidDescriptor pid_desc) {
return g_context.ThrowFatal(error, pid_desc.pid);
}
Result UserService::ThrowFatalWithPolicy(u32 error, PidDescriptor pid_desc, FatalType policy) {
return g_context.ThrowFatalWithPolicy(error, pid_desc.pid, policy);
}
Result UserService::ThrowFatalWithCpuContext(u32 error, PidDescriptor pid_desc, FatalType policy, InBuffer<u8> _ctx) {
if (_ctx.num_elements < sizeof(CpuContext)) {
return g_context.ThrowFatalWithPolicy(error, pid_desc.pid, policy);
} else {
return g_context.ThrowFatalWithCpuContext(error, pid_desc.pid, policy, *reinterpret_cast<const CpuContext *>(_ctx.buffer));
}
}
Result PrivateService::GetFatalEvent(Out<CopiedHandle> out_h) {
return g_context.GetEvent(out_h.GetHandlePointer());
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2018-2019 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>
namespace sts::fatal::srv {
class UserService final : public IServiceObject {
private:
enum class CommandId {
ThrowFatal = 0,
ThrowFatalWithPolicy = 1,
ThrowFatalWithCpuContext = 2,
};
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 {
MAKE_SERVICE_COMMAND_META(UserService, ThrowFatal),
MAKE_SERVICE_COMMAND_META(UserService, ThrowFatalWithPolicy),
MAKE_SERVICE_COMMAND_META(UserService, ThrowFatalWithCpuContext),
};
};
class PrivateService final : public IServiceObject {
private:
enum class CommandId {
GetFatalEvent = 0,
};
private:
/* Actual commands. */
Result GetFatalEvent(Out<CopiedHandle> out_h);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(PrivateService, GetFatalEvent),
};
};
}

View file

@ -16,9 +16,9 @@
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "fatal_types.hpp"
namespace sts::fatal::srv {
Result ThrowFatalForSelf(u32 error);
Result ThrowFatalImpl(u32 error, u64 pid, FatalType policy, FatalCpuContext *cpu_ctx);
Result ThrowFatalForSelf(Result error_code);
}

View file

@ -15,7 +15,7 @@
*/
#include <switch.h>
#include "fatal_types.hpp"
#include "fatal_task.hpp"
#include "fatal_task_error_report.hpp"
@ -24,40 +24,64 @@
#include "fatal_task_clock.hpp"
#include "fatal_task_power.hpp"
namespace sts::fatal::srv {
static constexpr size_t MaxTasks = 8;
static HosThread g_task_threads[MaxTasks];
static size_t g_num_threads = 0;
namespace {
class TaskThread {
NON_COPYABLE(TaskThread);
private:
static constexpr int TaskThreadPriority = 15;
private:
HosThread thread;
private:
static void RunTaskImpl(void *arg) {
ITask *task = reinterpret_cast<ITask *>(arg);
static void RunTaskThreadFunc(void *arg) {
IFatalTask *task = reinterpret_cast<IFatalTask *>(arg);
if (R_FAILED(task->Run())) {
/* TODO: Log task failure, somehow? */
}
}
public:
TaskThread() { /* ... */ }
void StartTask(ITask *task) {
R_ASSERT(this->thread.Initialize(&RunTaskImpl, task, task->GetStackSize(), TaskThreadPriority));
R_ASSERT(this->thread.Start());
}
};
class TaskManager {
NON_COPYABLE(TaskManager);
private:
static constexpr size_t MaxTasks = 8;
private:
TaskThread task_threads[MaxTasks];
size_t task_count = 0;
public:
TaskManager() { /* ... */ }
void StartTask(ITask *task) {
if (this->task_count >= MaxTasks) {
std::abort();
}
this->task_threads[this->task_count++].StartTask(task);
}
};
/* Global task manager. */
TaskManager g_task_manager;
if (R_FAILED(task->Run())) {
/* TODO: Log task failure, somehow? */
}
/* Finish. */
}
static void RunTask(IFatalTask *task, u32 stack_size = 0x4000) {
if (g_num_threads >= MaxTasks) {
std::abort();
void RunTasks(const ThrowContext *ctx) {
g_task_manager.StartTask(GetErrorReportTask(ctx));
g_task_manager.StartTask(GetPowerControlTask(ctx));
g_task_manager.StartTask(GetShowFatalTask(ctx));
g_task_manager.StartTask(GetStopSoundTask(ctx));
g_task_manager.StartTask(GetBacklightControlTask(ctx));
g_task_manager.StartTask(GetAdjustClockTask(ctx));
g_task_manager.StartTask(GetPowerButtonObserveTask(ctx));
g_task_manager.StartTask(GetStateTransitionStopTask(ctx));
}
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

@ -17,16 +17,29 @@
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "fatal_types.hpp"
#include "fatal_service.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;
};
namespace sts::fatal::srv {
void RunFatalTasks(FatalThrowContext *ctx, u64 title_id, bool error_report, Event *erpt_event, Event *battery_event);
class ITask {
public:
static constexpr size_t DefaultStackSize = 0x1000;
protected:
const ThrowContext *context = nullptr;
public:
void Initialize(const ThrowContext *context) {
this->context = context;
}
virtual Result Run() = 0;
virtual const char *GetName() const = 0;
virtual size_t GetStackSize() const {
return DefaultStackSize;
}
};
void RunTasks(const ThrowContext *ctx);
}

View file

@ -14,41 +14,70 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include "fatal_task_clock.hpp"
Result AdjustClockTask::AdjustClockForModule(PcvModule module, u32 hz) {
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_800) {
/* On 8.0.0+, convert to module id + use clkrst API. */
PcvModuleId module_id;
R_TRY(pcvGetModuleId(&module_id, module));
namespace sts::fatal::srv {
ClkrstSession session;
R_TRY(clkrstOpenSession(&session, module_id, 3));
ON_SCOPE_EXIT { clkrstCloseSession(&session); };
R_TRY(clkrstSetClockRate(&session, hz));
} else {
/* On 1.0.0-7.0.1, use pcv API. */
R_TRY(pcvSetClockRate(module, hz));
namespace {
/* Task definition. */
class AdjustClockTask : public ITask {
private:
Result AdjustClockForModule(PcvModule module, u32 hz);
Result AdjustClock();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "AdjustClockTask";
}
};
/* Task global. */
AdjustClockTask g_adjust_clock_task;
/* Task implementation. */
Result AdjustClockTask::AdjustClockForModule(PcvModule module, u32 hz) {
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_800) {
/* On 8.0.0+, convert to module id + use clkrst API. */
PcvModuleId module_id;
R_TRY(pcvGetModuleId(&module_id, module));
ClkrstSession session;
R_TRY(clkrstOpenSession(&session, module_id, 3));
ON_SCOPE_EXIT { clkrstCloseSession(&session); };
R_TRY(clkrstSetClockRate(&session, hz));
} else {
/* On 1.0.0-7.0.1, use pcv API. */
R_TRY(pcvSetClockRate(module, hz));
}
return ResultSuccess;
}
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;
R_TRY(AdjustClockForModule(PcvModule_CpuBus, CPU_CLOCK_1020MHZ));
R_TRY(AdjustClockForModule(PcvModule_GPU, GPU_CLOCK_307MHZ));
R_TRY(AdjustClockForModule(PcvModule_EMC, EMC_CLOCK_1331MHZ));
return ResultSuccess;
}
Result AdjustClockTask::Run() {
return AdjustClock();
}
}
ITask *GetAdjustClockTask(const ThrowContext *ctx) {
g_adjust_clock_task.Initialize(ctx);
return &g_adjust_clock_task;
}
return ResultSuccess;
}
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;
R_TRY(AdjustClockForModule(PcvModule_CpuBus, CPU_CLOCK_1020MHZ));
R_TRY(AdjustClockForModule(PcvModule_GPU, GPU_CLOCK_307MHZ));
R_TRY(AdjustClockForModule(PcvModule_EMC, EMC_CLOCK_1331MHZ));
return ResultSuccess;
}
Result AdjustClockTask::Run() {
return AdjustClock();
}

View file

@ -15,18 +15,10 @@
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "fatal_task.hpp"
class AdjustClockTask : public IFatalTask {
private:
Result AdjustClockForModule(PcvModule module, u32 hz);
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";
}
};
namespace sts::fatal::srv {
ITask *GetAdjustClockTask(const ThrowContext *ctx);
}

View file

@ -17,133 +17,141 @@
#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"
#include "fatal_task_error_report.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);
}
namespace sts::fatal::srv {
bool ErrorReportTask::GetCurrentTime(u64 *out) {
*out = 0;
namespace {
/* Verify that pcv isn't dead. */
{
bool has_time_service;
DoWithSmSession([&]() {
Handle dummy;
if (R_SUCCEEDED(smRegisterService(&dummy, "time:s", false, 0x20))) {
svcCloseHandle(dummy);
has_time_service = false;
} else {
has_time_service = true;
}
});
if (!has_time_service) {
return false;
/* Helpers. */
void TryEnsureReportDirectories() {
mkdir("sdmc:/atmosphere", S_IRWXU);
mkdir("sdmc:/atmosphere/fatal_reports", S_IRWXU);
mkdir("sdmc:/atmosphere/fatal_reports/dumps", S_IRWXU);
}
}
/* Try to get the current time. */
bool success = true;
DoWithSmSession([&]() {
success &= R_SUCCEEDED(timeInitialize());
});
if (success) {
success &= R_SUCCEEDED(timeGetCurrentTime(TimeType_LocalSystemClock, out));
timeExit();
}
return success;
}
bool TryGetCurrentTimestamp(u64 *out) {
/* Clear output. */
*out = 0;
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]);
/* Check if we have time service. */
{
bool has_time_service = false;
if (R_FAILED(sm::HasService(&has_time_service, sm::ServiceName::Encode("time:s"))) || !has_time_service) {
return false;
}
}
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]);
/* Try to get the current time. */
{
sm::ScopedServiceHolder<timeInitialize, timeExit> time_holder;
return time_holder && R_SUCCEEDED(timeGetCurrentTime(TimeType_LocalSystemClock, out));
}
} 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]);
}
/* Task definition. */
class ErrorReportTask : public ITask {
private:
void SaveReportToSdCard();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "WriteErrorReport";
}
};
/* Task globals. */
ErrorReportTask g_error_report_task;
/* Task Implementation. */
void ErrorReportTask::SaveReportToSdCard() {
char file_path[FS_MAX_PATH];
/* Try to Ensure path exists. */
TryEnsureReportDirectories();
/* Get a timestamp. */
u64 timestamp;
if (!TryGetCurrentTimestamp(&timestamp)) {
timestamp = svcGetSystemTick();
}
/* Open report file. */
snprintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/%011lu_%016lx.log", timestamp, static_cast<u64>(this->context->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->context->error_code, R_MODULE(this->context->error_code), R_DESCRIPTION(this->context->error_code));
fprintf(f_report, "Title ID: %016lx\n", static_cast<u64>(this->context->title_id));
if (strlen(this->context->proc_name)) {
fprintf(f_report, "Process Name: %s\n", this->context->proc_name);
}
fprintf(f_report, u8"Firmware: %s (Atmosphère %u.%u.%u-%s)\n", GetFatalConfig().GetFirmwareVersion().display_version, CURRENT_ATMOSPHERE_VERSION, GetAtmosphereGitRevision());
if (this->context->cpu_ctx.architecture == CpuContext::Architecture_Aarch32) {
fprintf(f_report, "General Purpose Registers:\n");
for (size_t i = 0; i <= aarch32::RegisterName_PC; i++) {
if (this->context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i))) {
fprintf(f_report, " %3s: %08x\n", aarch32::CpuContext::RegisterNameStrings[i], this->context->cpu_ctx.aarch32_ctx.r[i]);
}
}
fprintf(f_report, "Start Address: %08x\n", this->context->cpu_ctx.aarch32_ctx.base_address);
fprintf(f_report, "Stack Trace:\n");
for (unsigned int i = 0; i < this->context->cpu_ctx.aarch32_ctx.stack_trace_size; i++) {
fprintf(f_report, " ReturnAddress[%02u]: %08x\n", i, this->context->cpu_ctx.aarch32_ctx.stack_trace[i]);
}
} else {
fprintf(f_report, "General Purpose Registers:\n");
for (size_t i = 0; i <= aarch64::RegisterName_PC; i++) {
if (this->context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i))) {
fprintf(f_report, " %3s: %016lx\n", aarch64::CpuContext::RegisterNameStrings[i], this->context->cpu_ctx.aarch64_ctx.x[i]);
}
}
fprintf(f_report, "Start Address: %016lx\n", this->context->cpu_ctx.aarch64_ctx.base_address);
fprintf(f_report, "Stack Trace:\n");
for (unsigned int i = 0; i < this->context->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
fprintf(f_report, " ReturnAddress[%02u]: %016lx\n", i, this->context->cpu_ctx.aarch64_ctx.stack_trace[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->context->stack_dump_size) {
snprintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/dumps/%011lu_%016lx.bin", timestamp, static_cast<u64>(this->context->title_id));
FILE *f_stackdump = fopen(file_path, "wb");
if (f_stackdump == NULL) { return; }
ON_SCOPE_EXIT { fclose(f_stackdump); };
fwrite(this->context->stack_dump, this->context->stack_dump_size, 1, f_stackdump);
fflush(f_stackdump);
}
}
Result ErrorReportTask::Run() {
if (this->context->generate_error_report) {
/* Here, Nintendo creates an error report with erpt. AMS will not do that. */
}
/* Save report to SD card. */
if (!this->context->is_creport) {
this->SaveReportToSdCard();
}
/* Signal we're done with our job. */
eventFire(const_cast<Event *>(&this->context->erpt_event));
return ResultSuccess;
}
}
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);
ITask *GetErrorReportTask(const ThrowContext *ctx) {
g_error_report_task.Initialize(ctx);
return &g_error_report_task;
}
}
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 ResultSuccess;
}

View file

@ -15,22 +15,10 @@
*/
#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";
}
};
namespace sts::fatal::srv {
ITask *GetErrorReportTask(const ThrowContext *ctx);
}

View file

@ -14,149 +14,199 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include "fatal_task_power.hpp"
#include "fatal_config.hpp"
#include "fatal_task_power.hpp"
bool PowerControlTask::TryShutdown() {
/* Set a timeout of 30 seconds. */
TimeoutHelper timeout_helper(30000000000UL);
bool cancel_shutdown = false;
PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal;
namespace sts::fatal::srv {
while (true) {
if (timeout_helper.TimedOut()) {
break;
}
namespace {
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;
}
/* Task types. */
class PowerControlTask : public ITask {
private:
bool TryShutdown();
void MonitorBatteryState();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "PowerControlTask";
}
break;
default:
break;
};
class PowerButtonObserveTask : public ITask {
private:
void WaitForPowerButton();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "PowerButtonObserveTask";
}
};
class StateTransitionStopTask : public ITask {
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "StateTransitionStopTask";
}
};
/* Task globals. */
PowerControlTask g_power_control_task;
PowerButtonObserveTask g_power_button_observe_task;
StateTransitionStopTask g_state_transition_stop_task;
/* Task Implementations. */
bool PowerControlTask::TryShutdown() {
/* Set a timeout of 30 seconds. */
TimeoutHelper timeout_helper(30'000'000'000ul);
bool perform_shutdown = true;
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) {
perform_shutdown = false;
break;
}
/* Query voltage state every 5 seconds, for 30 seconds. */
svcSleepThread(5'000'000'000ul);
}
if (perform_shutdown) {
bpcShutdownSystem();
}
return perform_shutdown;
}
/* Query voltage state every 5 seconds. */
svcSleepThread(5000000000UL);
}
}
void PowerControlTask::MonitorBatteryState() {
PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal;
void PowerButtonObserveTask::WaitForPowerButton() {
/* Wait up to a second for error report generation to finish. */
eventWait(this->erpt_event, TimeoutHelper::NsToTick(1000000000UL));
/* 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(const_cast<Event *>(&this->context->erpt_event), TimeoutHelper::NsToTick(1'000'000'000ul));
this->TryShutdown();
return;
}
/* Force a reboot after some time if kiosk unit. */
const FatalConfig *config = GetFatalConfig();
TimeoutHelper reboot_helper(config->quest_reboot_interval_second * 1000000000UL);
/* Signal we've checked the battery at least once. */
eventFire(const_cast<Event *>(&this->context->battery_event));
TimeoutHelper auto_reboot_helper(config->fatal_auto_reboot_interval * 1000000);
/* Loop querying voltage state every 5 seconds. */
while (true) {
if (R_FAILED(psmGetBatteryVoltageState(&bv_state))) {
bv_state = PsmBatteryVoltageState_NeedsShutdown;
}
bool check_vol_up = true, check_vol_down = true;
GpioPadSession vol_up_btn, vol_down_btn;
if (R_FAILED(gpioOpenSession(&vol_up_btn, GpioPadName_ButtonVolUp))) {
check_vol_up = false;
}
if (R_FAILED(gpioOpenSession(&vol_down_btn, GpioPadName_ButtonVolDown))) {
check_vol_down = false;
}
switch (bv_state) {
case PsmBatteryVoltageState_NeedsShutdown:
case PsmBatteryVoltageState_NeedsSleep:
{
bool shutdown = this->TryShutdown();
if (shutdown) {
return;
}
}
break;
default:
break;
}
/* Ensure we close on early return. */
ON_SCOPE_EXIT { if (check_vol_up) { gpioPadClose(&vol_up_btn); } };
ON_SCOPE_EXIT { if (check_vol_down) { gpioPadClose(&vol_down_btn); } };
/* Set direction input. */
if (check_vol_up) {
gpioPadSetDirection(&vol_up_btn, GpioDirection_Input);
}
if (check_vol_down) {
gpioPadSetDirection(&vol_down_btn, GpioDirection_Input);
}
BpcSleepButtonState state;
GpioValue val;
while (true) {
if (config->is_auto_reboot_enabled && auto_reboot_helper.TimedOut() ) {
bpcRebootSystem();
return;
svcSleepThread(5'000'000'000ul);
}
}
if (check_vol_up && R_SUCCEEDED(gpioPadGetValue(&vol_up_btn, &val)) && val == GpioValue_Low) {
bpcRebootSystem();
void PowerButtonObserveTask::WaitForPowerButton() {
/* Wait up to a second for error report generation to finish. */
eventWait(const_cast<Event *>(&this->context->erpt_event), TimeoutHelper::NsToTick(1'000'000'000ul));
/* Force a reboot after some time if kiosk unit. */
const auto &config = GetFatalConfig();
TimeoutHelper quest_reboot_helper(config.GetQuestRebootTimeoutInterval());
TimeoutHelper fatal_reboot_helper(config.GetFatalRebootTimeoutInterval());
bool check_vol_up = true, check_vol_down = true;
GpioPadSession vol_up_btn, vol_down_btn;
if (R_FAILED(gpioOpenSession(&vol_up_btn, GpioPadName_ButtonVolUp))) {
check_vol_up = false;
}
if (R_FAILED(gpioOpenSession(&vol_down_btn, GpioPadName_ButtonVolDown))) {
check_vol_down = false;
}
/* Ensure we close on early return. */
ON_SCOPE_EXIT { if (check_vol_up) { gpioPadClose(&vol_up_btn); } };
ON_SCOPE_EXIT { if (check_vol_down) { gpioPadClose(&vol_down_btn); } };
/* Set direction input. */
if (check_vol_up) {
gpioPadSetDirection(&vol_up_btn, GpioDirection_Input);
}
if (check_vol_down) {
gpioPadSetDirection(&vol_down_btn, GpioDirection_Input);
}
BpcSleepButtonState state;
GpioValue val;
while (true) {
if ((config.IsFatalRebootEnabled() && fatal_reboot_helper.TimedOut()) ||
(check_vol_up && R_SUCCEEDED(gpioPadGetValue(&vol_up_btn, &val)) && val == GpioValue_Low) ||
(check_vol_down && R_SUCCEEDED(gpioPadGetValue(&vol_down_btn, &val)) && val == GpioValue_Low) ||
(R_SUCCEEDED(bpcGetSleepButtonState(&state)) && state == BpcSleepButtonState_Held) ||
(config.IsQuest() && quest_reboot_helper.TimedOut())) {
/* If any of the above conditions succeeded, we should reboot. */
bpcRebootSystem();
return;
}
/* Wait 100 ms between button checks. */
svcSleepThread(100'000'000ul);
}
}
if (check_vol_down && R_SUCCEEDED(gpioPadGetValue(&vol_down_btn, &val)) && val == GpioValue_Low) {
bpcRebootSystem();
Result PowerControlTask::Run() {
this->MonitorBatteryState();
return ResultSuccess;
}
if ((R_SUCCEEDED(bpcGetSleepButtonState(&state)) && state == BpcSleepButtonState_Held) || (config->quest_flag && reboot_helper.TimedOut())) {
bpcRebootSystem();
return;
Result PowerButtonObserveTask::Run() {
this->WaitForPowerButton();
return ResultSuccess;
}
Result StateTransitionStopTask::Run() {
/* Nintendo ignores the output of this call... */
spsmPutErrorState();
return ResultSuccess;
}
/* Wait 100 ms between button checks. */
svcSleepThread(100000000UL);
}
}
Result PowerControlTask::Run() {
MonitorBatteryState();
return ResultSuccess;
}
ITask *GetPowerControlTask(const ThrowContext *ctx) {
g_power_control_task.Initialize(ctx);
return &g_power_control_task;
}
Result PowerButtonObserveTask::Run() {
WaitForPowerButton();
return ResultSuccess;
}
ITask *GetPowerButtonObserveTask(const ThrowContext *ctx) {
g_power_button_observe_task.Initialize(ctx);
return &g_power_button_observe_task;
}
ITask *GetStateTransitionStopTask(const ThrowContext *ctx) {
g_state_transition_stop_task.Initialize(ctx);
return &g_state_transition_stop_task;
}
Result StateTransitionStopTask::Run() {
/* Nintendo ignores the output of this call... */
spsmPutErrorState();
return ResultSuccess;
}

View file

@ -15,43 +15,12 @@
*/
#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";
}
};
namespace sts::fatal::srv {
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";
}
};
ITask *GetPowerControlTask(const ThrowContext *ctx);
ITask *GetPowerButtonObserveTask(const ThrowContext *ctx);
ITask *GetStateTransitionStopTask(const ThrowContext *ctx);
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

@ -14,378 +14,434 @@
* 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;
namespace sts::fatal::srv {
static constexpr u32 FatalScreenWidthAlignedBytes = (FatalScreenWidth * FatalScreenBpp + 63) & ~63;
static constexpr u32 FatalScreenWidthAligned = FatalScreenWidthAlignedBytes / FatalScreenBpp;
/* Include Atmosphere logo into its own anonymous namespace. */
u32 GetPixelOffset(uint32_t x, uint32_t y)
{
u32 tmp_pos;
namespace {
tmp_pos = ((y & 127) / 16) + (x/32*8) + ((y/16/8)*(((FatalScreenWidthAligned/2)/16*8)));
tmp_pos *= 16*16 * 4;
#include "fatal_ams_logo.inc"
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;
}
namespace {
/* Screen definitions. */
constexpr u32 FatalScreenWidth = 1280;
constexpr u32 FatalScreenHeight = 720;
constexpr u32 FatalScreenBpp = 2;
constexpr u32 FatalLayerZ = 100;
constexpr u32 FatalScreenWidthAlignedBytes = (FatalScreenWidth * FatalScreenBpp + 63) & ~63;
constexpr u32 FatalScreenWidthAligned = FatalScreenWidthAlignedBytes / FatalScreenBpp;
/* Pixel calculation helper. */
constexpr u32 GetPixelOffset(u32 x, u32 y) {
u32 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;
}
/* Task definitions. */
class ShowFatalTask : public ITask {
private:
ViDisplay display;
ViLayer layer;
NWindow win;
Framebuffer fb;
private:
Result SetupDisplayInternal();
Result SetupDisplayExternal();
Result PrepareScreenForDrawing();
Result ShowFatal();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "ShowFatal";
}
virtual size_t GetStackSize() const override {
return 0x8000;
}
};
class BacklightControlTask : public ITask {
private:
void TurnOnBacklight();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "BacklightControlTask";
}
};
/* Task globals. */
ShowFatalTask g_show_fatal_task;
BacklightControlTask g_backlight_control_task;
/* Task implementations. */
Result ShowFatalTask::SetupDisplayInternal() {
ViDisplay temp_display;
/* Try to open the display. */
R_TRY_CATCH(viOpenDisplay("Internal", &temp_display)) {
R_CATCH(ResultViNotFound) {
return ResultSuccess;
}
} R_END_TRY_CATCH;
/* Guarantee we close the display. */
ON_SCOPE_EXIT { viCloseDisplay(&temp_display); };
/* Turn on the screen. */
R_TRY(viSetDisplayPowerState(&temp_display, ViPowerState_On));
/* Set alpha to 1.0f. */
R_TRY(viSetDisplayAlpha(&temp_display, 1.0f));
Result ShowFatalTask::SetupDisplayInternal() {
ViDisplay display;
/* Try to open the display. */
R_TRY_CATCH(viOpenDisplay("Internal", &display)) {
R_CATCH(ResultViNotFound) {
return ResultSuccess;
}
} R_END_TRY_CATCH;
/* Guarantee we close the display. */
ON_SCOPE_EXIT { viCloseDisplay(&display); };
Result ShowFatalTask::SetupDisplayExternal() {
ViDisplay temp_display;
/* Try to open the display. */
R_TRY_CATCH(viOpenDisplay("External", &temp_display)) {
R_CATCH(ResultViNotFound) {
return ResultSuccess;
}
} R_END_TRY_CATCH;
/* Turn on the screen. */
R_TRY(viSetDisplayPowerState(&display, ViPowerState_On));
/* Guarantee we close the display. */
ON_SCOPE_EXIT { viCloseDisplay(&temp_display); };
/* Set alpha to 1.0f. */
R_TRY(viSetDisplayAlpha(&display, 1.0f));
/* Set alpha to 1.0f. */
R_TRY(viSetDisplayAlpha(&temp_display, 1.0f));
return ResultSuccess;
}
Result ShowFatalTask::SetupDisplayExternal() {
ViDisplay display;
/* Try to open the display. */
R_TRY_CATCH(viOpenDisplay("External", &display)) {
R_CATCH(ResultViNotFound) {
return ResultSuccess;
}
} R_END_TRY_CATCH;
/* Guarantee we close the display. */
ON_SCOPE_EXIT { viCloseDisplay(&display); };
Result ShowFatalTask::PrepareScreenForDrawing() {
/* Connect to vi. */
R_TRY(viInitialize(ViServiceType_Manager));
/* Set alpha to 1.0f. */
R_TRY(viSetDisplayAlpha(&display, 1.0f));
/* Close other content. */
viSetContentVisibility(false);
return ResultSuccess;
}
/* Setup the two displays. */
R_TRY(SetupDisplayInternal());
R_TRY(SetupDisplayExternal());
Result ShowFatalTask::PrepareScreenForDrawing() {
/* Connect to vi. */
R_TRY(viInitialize(ViServiceType_Manager));
/* Open the default display. */
R_TRY(viOpenDefaultDisplay(&this->display));
/* Close other content. */
viSetContentVisibility(false);
/* Reset the display magnification to its default value. */
u32 display_width, display_height;
R_TRY(viGetDisplayLogicalResolution(&this->display, &display_width, &display_height));
/* Setup the two displays. */
R_TRY(SetupDisplayInternal());
R_TRY(SetupDisplayExternal());
/* viSetDisplayMagnification was added in 3.0.0. */
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) {
R_TRY(viSetDisplayMagnification(&this->display, 0, 0, display_width, display_height));
}
/* Open the default display. */
R_TRY(viOpenDefaultDisplay(&this->display));
/* Create layer to draw to. */
R_TRY(viCreateLayer(&this->display, &this->layer));
/* Reset the display magnification to its default value. */
u32 display_width, display_height;
R_TRY(viGetDisplayLogicalResolution(&this->display, &display_width, &display_height));
/* 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;
/* viSetDisplayMagnification was added in 3.0.0. */
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) {
R_TRY(viSetDisplayMagnification(&this->display, 0, 0, display_width, display_height));
}
const float layer_x = static_cast<float>((display_width - layer_width) / 2);
const float layer_y = static_cast<float>((display_height - layer_height) / 2);
/* Create layer to draw to. */
R_TRY(viCreateLayer(&this->display, &this->layer));
R_TRY(viSetLayerSize(&this->layer, layer_width, layer_height));
/* 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;
/* Set the layer's Z at display maximum, to be above everything else .*/
R_TRY(viSetLayerZ(&this->layer, FatalLayerZ));
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;
/* Center the layer in the screen. */
R_TRY(viSetLayerPosition(&this->layer, layer_x, layer_y));
R_TRY(viSetLayerSize(&this->layer, layer_width, layer_height));
/* Create framebuffer. */
R_TRY(nwindowCreateFromLayer(&this->win, &this->layer));
R_TRY(framebufferCreate(&this->fb, &this->win, raw_width, raw_height, PIXEL_FORMAT_RGB_565, 1));
}
/* Set the layer's Z at display maximum, to be above everything else .*/
/* NOTE: Fatal hardcodes 100 here. */
if (R_SUCCEEDED(viGetDisplayMaximumZ(&this->display, &layer_z))) {
R_TRY(viSetLayerZ(&this->layer, layer_z));
return ResultSuccess;
}
/* Center the layer in the screen. */
R_TRY(viSetLayerPosition(&this->layer, layer_x, layer_y));
Result ShowFatalTask::ShowFatal() {
const FatalConfig &config = GetFatalConfig();
/* Create framebuffer. */
R_TRY(nwindowCreateFromLayer(&this->win, &this->layer));
R_TRY(framebufferCreate(&this->fb, &this->win, raw_width, raw_height, PIXEL_FORMAT_RGB_565, 1));
}
/* Prepare screen for drawing. */
DoWithSmSession([&]() {
R_ASSERT(PrepareScreenForDrawing());
});
return ResultSuccess;
}
/* Dequeue a buffer. */
u16 *tiled_buf = reinterpret_cast<u16 *>(framebufferBegin(&this->fb, NULL));
if (tiled_buf == nullptr) {
return ResultFatalNullGraphicsBuffer;
}
Result ShowFatalTask::ShowFatal() {
const FatalConfig *config = GetFatalConfig();
/* Let the font manager know about our framebuffer. */
font::ConfigureFontFramebuffer(tiled_buf, GetPixelOffset);
font::SetFontColor(0xFFFF);
/* Prepare screen for drawing. */
DoWithSmSession([&]() {
if (R_FAILED(PrepareScreenForDrawing())) {
std::abort();
}
});
/* Draw a background. */
for (size_t i = 0; i < this->fb.fb_size / sizeof(*tiled_buf); i++) {
tiled_buf[i] = 0x39C9;
}
/* Dequeue a buffer. */
u16 *tiled_buf = reinterpret_cast<u16 *>(framebufferBegin(&this->fb, NULL));
if (tiled_buf == nullptr) {
return ResultFatalNullGraphicsBuffer;
}
/* Draw the atmosphere logo in the bottom right corner. */
for (size_t y = 0; y < AtmosphereLogoHeight; y++) {
for (size_t x = 0; x < AtmosphereLogoWidth; x++) {
tiled_buf[GetPixelOffset(FatalScreenWidth - AtmosphereLogoWidth - 32 + x, 32 + y)] = AtmosphereLogoData[y * AtmosphereLogoWidth + x];
}
}
/* 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);
if (this->ctx->error_code != ResultAtmosphereVersionMismatch) {
FontManager::Print(config->error_desc);
} else {
/* Print a special message for atmosphere version mismatch. */
FontManager::Print(u8"Atmosphère version mismatch detected.\n\n"
u8"Please press the POWER Button to restart the console normally, or a VOL button\n"
u8"to reboot to a payload (or RCM, if none is present). If you are unable to\n"
u8"restart the console, hold the POWER Button for 12 seconds to turn the console off.\n\n"
u8"Please ensure that all Atmosphère components are updated.\n"
u8"github.com/Atmosphere-NX/Atmosphere/releases\n");
}
/* 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]);
/* TODO: Actually draw meaningful shit here. */
font::SetPosition(32, 64);
font::SetFontSize(16.0f);
font::PrintFormat(config.GetErrorMessage(), R_MODULE(this->context->error_code), R_DESCRIPTION(this->context->error_code), this->context->error_code);
font::AddSpacingLines(0.5f);
font::PrintFormatLine("Title: %016lX", static_cast<u64>(this->context->title_id));
font::AddSpacingLines(0.5f);
font::PrintFormatLine(u8"Firmware: %s (Atmosphère %u.%u.%u-%s)", config.GetFirmwareVersion().display_version, CURRENT_ATMOSPHERE_VERSION, GetAtmosphereGitRevision());
font::AddSpacingLines(1.5f);
if (this->context->error_code != ResultAtmosphereVersionMismatch) {
font::Print(config.GetErrorDescription());
} else {
FontManager::PrintMonospaceBlank(8);
/* Print a special message for atmosphere version mismatch. */
font::Print(u8"Atmosphère version mismatch detected.\n\n"
u8"Please press the POWER Button to restart the console normally, or a VOL button\n"
u8"to reboot to a payload (or RCM, if none is present). If you are unable to\n"
u8"restart the console, hold the POWER Button for 12 seconds to turn the console off.\n\n"
u8"Please ensure that all Atmosphère components are updated.\n"
u8"github.com/Atmosphere-NX/Atmosphere/releases\n");
}
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)]);
/* Add a line. */
for (size_t x = 32; x < FatalScreenWidth - 32; x++) {
tiled_buf[GetPixelOffset(x, font::GetY())] = 0xFFFF;
}
font::AddSpacingLines(1.5f);
u32 backtrace_y = font::GetY();
u32 backtrace_x = 0;
/* Note architecutre. */
const bool is_aarch32 = this->context->cpu_ctx.architecture == CpuContext::Architecture_Aarch32;
/* Print GPRs. */
font::SetFontSize(14.0f);
font::Print("General Purpose Registers ");
{
font::SetPosition(font::GetX() + 2, font::GetY());
u32 x = font::GetX();
font::Print("PC: ");
font::SetPosition(x + 47, font::GetY());
}
if (is_aarch32) {
font::PrintMonospaceU32(this->context->cpu_ctx.aarch32_ctx.pc);
} else {
FontManager::PrintMonospaceBlank(8);
font::PrintMonospaceU64(this->context->cpu_ctx.aarch64_ctx.pc);
}
font::PrintLine("");
font::SetPosition(32, font::GetY());
font::AddSpacingLines(0.5f);
if (is_aarch32) {
for (size_t i = 0; i < (aarch32::RegisterName_GeneralPurposeCount / 2); i++) {
u32 x = font::GetX();
font::PrintFormat("%s:", aarch32::CpuContext::RegisterNameStrings[i]);
font::SetPosition(x + 47, font::GetY());
if (this->context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i))) {
font::PrintMonospaceU32(this->context->cpu_ctx.aarch32_ctx.r[i]);
} else {
font::PrintMonospaceBlank(8);
}
font::Print(" ");
x = font::GetX();
font::PrintFormat("%s:", aarch32::CpuContext::RegisterNameStrings[i + (aarch32::RegisterName_GeneralPurposeCount / 2)]);
font::SetPosition(x + 47, font::GetY());
if (this->context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i + (aarch32::RegisterName_GeneralPurposeCount / 2)))) {
font::PrintMonospaceU32(this->context->cpu_ctx.aarch32_ctx.r[i + (aarch32::RegisterName_GeneralPurposeCount / 2)]);
} else {
font::PrintMonospaceBlank(8);
}
if (i == (aarch32::RegisterName_GeneralPurposeCount / 2) - 1) {
font::Print(" ");
backtrace_x = font::GetX();
}
font::PrintLine("");
font::SetPosition(32, font::GetY());
}
} else {
for (size_t i = 0; i < aarch64::RegisterName_GeneralPurposeCount / 2; i++) {
u32 x = font::GetX();
font::PrintFormat("%s:", aarch64::CpuContext::RegisterNameStrings[i]);
font::SetPosition(x + 47, font::GetY());
if (this->context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i))) {
font::PrintMonospaceU64(this->context->cpu_ctx.aarch64_ctx.x[i]);
} else {
font::PrintMonospaceBlank(16);
}
font::Print(" ");
x = font::GetX();
font::PrintFormat("%s:", aarch64::CpuContext::RegisterNameStrings[i + (aarch64::RegisterName_GeneralPurposeCount / 2)]);
font::SetPosition(x + 47, font::GetY());
if (this->context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i + (aarch64::RegisterName_GeneralPurposeCount / 2)))) {
font::PrintMonospaceU64(this->context->cpu_ctx.aarch64_ctx.x[i + (aarch64::RegisterName_GeneralPurposeCount / 2)]);
} else {
font::PrintMonospaceBlank(16);
}
if (i == (aarch64::RegisterName_GeneralPurposeCount / 2) - 1) {
font::Print(" ");
backtrace_x = font::GetX();
}
font::PrintLine("");
font::SetPosition(32, font::GetY());
}
}
if (i == (NumAarch32Gprs / 2) - 1) {
FontManager::Print(" ");
backtrace_x = FontManager::GetX();
/* Print Backtrace. */
u32 bt_size;
if (is_aarch32) {
bt_size = this->context->cpu_ctx.aarch32_ctx.stack_trace_size;
} else {
bt_size = this->context->cpu_ctx.aarch64_ctx.stack_trace_size;
}
FontManager::PrintLine("");
FontManager::SetPosition(32, FontManager::GetY());
font::SetPosition(backtrace_x, backtrace_y);
if (bt_size == 0) {
if (is_aarch32) {
font::Print("Start Address: ");
font::PrintMonospaceU32(this->context->cpu_ctx.aarch32_ctx.base_address);
font::PrintLine("");
} else {
font::Print("Start Address: ");
font::PrintMonospaceU64(this->context->cpu_ctx.aarch64_ctx.base_address);
font::PrintLine("");
}
} else {
if (is_aarch32) {
font::Print("Backtrace - Start Address: ");
font::PrintMonospaceU32(this->context->cpu_ctx.aarch32_ctx.base_address);
font::PrintLine("");
font::AddSpacingLines(0.5f);
for (u32 i = 0; i < aarch32::CpuContext::MaxStackTraceDepth / 2; i++) {
u32 bt_cur = 0, bt_next = 0;
if (i < this->context->cpu_ctx.aarch32_ctx.stack_trace_size) {
bt_cur = this->context->cpu_ctx.aarch32_ctx.stack_trace[i];
}
if (i + aarch32::CpuContext::MaxStackTraceDepth / 2 < this->context->cpu_ctx.aarch32_ctx.stack_trace_size) {
bt_next = this->context->cpu_ctx.aarch32_ctx.stack_trace[i + aarch32::CpuContext::MaxStackTraceDepth / 2];
}
if (i < this->context->cpu_ctx.aarch32_ctx.stack_trace_size) {
u32 x = font::GetX();
font::PrintFormat("BT[%02d]: ", i);
font::SetPosition(x + 72, font::GetY());
font::PrintMonospaceU32(bt_cur);
font::Print(" ");
}
if (i + aarch32::CpuContext::MaxStackTraceDepth / 2 < this->context->cpu_ctx.aarch32_ctx.stack_trace_size) {
u32 x = font::GetX();
font::PrintFormat("BT[%02d]: ", i + aarch32::CpuContext::MaxStackTraceDepth / 2);
font::SetPosition(x + 72, font::GetY());
font::PrintMonospaceU32(bt_next);
}
font::PrintLine("");
font::SetPosition(backtrace_x, font::GetY());
}
} else {
font::Print("Backtrace - Start Address: ");
font::PrintMonospaceU64(this->context->cpu_ctx.aarch64_ctx.base_address);
font::PrintLine("");
font::AddSpacingLines(0.5f);
for (u32 i = 0; i < aarch64::CpuContext::MaxStackTraceDepth / 2; i++) {
u64 bt_cur = 0, bt_next = 0;
if (i < this->context->cpu_ctx.aarch64_ctx.stack_trace_size) {
bt_cur = this->context->cpu_ctx.aarch64_ctx.stack_trace[i];
}
if (i + aarch64::CpuContext::MaxStackTraceDepth / 2 < this->context->cpu_ctx.aarch64_ctx.stack_trace_size) {
bt_next = this->context->cpu_ctx.aarch64_ctx.stack_trace[i + aarch64::CpuContext::MaxStackTraceDepth / 2];
}
if (i < this->context->cpu_ctx.aarch64_ctx.stack_trace_size) {
u32 x = font::GetX();
font::PrintFormat("BT[%02d]: ", i);
font::SetPosition(x + 72, font::GetY());
font::PrintMonospaceU64(bt_cur);
font::Print(" ");
}
if (i + aarch64::CpuContext::MaxStackTraceDepth / 2 < this->context->cpu_ctx.aarch64_ctx.stack_trace_size) {
u32 x = font::GetX();
font::PrintFormat("BT[%02d]: ", i + aarch64::CpuContext::MaxStackTraceDepth / 2);
font::SetPosition(x + 72, font::GetY());
font::PrintMonospaceU64(bt_next);
}
font::PrintLine("");
font::SetPosition(backtrace_x, font::GetY());
}
}
}
/* Enqueue the buffer. */
framebufferEnd(&fb);
return ResultSuccess;
}
} 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();
}
Result ShowFatalTask::Run() {
/* Don't show the fatal error screen until we've verified the battery is okay. */
eventWait(const_cast<Event *>(&this->context->battery_event), U64_MAX);
FontManager::PrintLine("");
FontManager::SetPosition(32, FontManager::GetY());
return ShowFatal();
}
void BacklightControlTask::TurnOnBacklight() {
lblSwitchBacklightOn(0);
}
Result BacklightControlTask::Run() {
TurnOnBacklight();
return ResultSuccess;
}
}
/* 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;
ITask *GetShowFatalTask(const ThrowContext *ctx) {
g_show_fatal_task.Initialize(ctx);
return &g_show_fatal_task;
}
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());
}
}
ITask *GetBacklightControlTask(const ThrowContext *ctx) {
g_backlight_control_task.Initialize(ctx);
return &g_backlight_control_task;
}
/* Enqueue the buffer. */
framebufferEnd(&fb);
return ResultSuccess;
}
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 ResultSuccess;
}

View file

@ -15,37 +15,11 @@
*/
#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";
}
};
namespace sts::fatal::srv {
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";
}
};
ITask *GetShowFatalTask(const ThrowContext *ctx);
ITask *GetBacklightControlTask(const ThrowContext *ctx);
}

View file

@ -14,60 +14,86 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include "fatal_task_sound.hpp"
namespace sts::fatal::srv {
void StopSoundTask::StopSound() {
/* Talk to the ALC5639 over I2C, and disable audio output. */
{
I2cSession audio;
if (R_SUCCEEDED(i2cOpenSession(&audio, I2cDevice_Alc5639))) {
struct {
u16 dev;
u8 val;
} __attribute__((packed)) cmd;
static_assert(sizeof(cmd) == 3, "I2C command definition!");
namespace {
cmd.dev = 0xC801;
cmd.val = 200;
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
/* Task definition. */
class StopSoundTask : public ITask {
private:
void StopSound();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "SoundTask";
}
};
cmd.dev = 0xC802;
cmd.val = 200;
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
/* Task global. */
StopSoundTask g_stop_sound_task;
cmd.dev = 0xC802;
cmd.val = 200;
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
/* Task implementation. */
void StopSoundTask::StopSound() {
/* Talk to the ALC5639 over I2C, and disable audio output. */
{
I2cSession audio;
if (R_SUCCEEDED(i2cOpenSession(&audio, I2cDevice_Alc5639))) {
ON_SCOPE_EXIT { i2csessionClose(&audio); };
for (u16 dev = 97; dev <= 102; dev++) {
cmd.dev = dev;
cmd.val = 0;
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
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))) {
ON_SCOPE_EXIT { gpioPadClose(&audio); };
/* 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);
}
}
}
Result StopSoundTask::Run() {
StopSound();
return ResultSuccess;
}
}
/* 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);
}
ITask *GetStopSoundTask(const ThrowContext *ctx) {
g_stop_sound_task.Initialize(ctx);
return &g_stop_sound_task;
}
}
Result StopSoundTask::Run() {
StopSound();
return ResultSuccess;
}

View file

@ -15,17 +15,10 @@
*/
#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";
}
};
namespace sts::fatal::srv {
ITask *GetStopSoundTask(const ThrowContext *ctx);
}

View file

@ -1,121 +0,0 @@
/*
* Copyright (c) 2018-2019 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 ResultFatalAlreadyThrown;
}
g_thrown = true;
return ResultSuccess;
}
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) {
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;
}
/* Cap the stack trace size at a sane limit. */
/* TODO: Better to set to zero, in order to manually collect debug info ourselves instead? */
if (cpu_ctx->is_aarch32) {
ctx.cpu_ctx.aarch32_ctx.stack_trace_size = std::max(ctx.cpu_ctx.aarch32_ctx.stack_trace_size, static_cast<u32>(Aarch32CpuContext::MaxStackTraceDepth));
} else {
ctx.cpu_ctx.aarch64_ctx.stack_trace_size = std::max(ctx.cpu_ctx.aarch64_ctx.stack_trace_size, static_cast<u32>(Aarch64CpuContext::MaxStackTraceDepth));
}
} else {
std::memset(&ctx.cpu_ctx, 0, sizeof(ctx.cpu_ctx));
}
/* Reassign this unconditionally, for convenience. */
cpu_ctx = &ctx.cpu_ctx;
/* Get config. */
const FatalConfig *config = GetFatalConfig();
/* Get title id. On failure, it'll be zero. */
sts::ncm::TitleId title_id = sts::ncm::TitleId::Invalid;
sts::pm::info::GetTitleId(&title_id, pid);
ctx.is_creport = title_id == sts::ncm::TitleId::Creport;
/* 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 = sts::ncm::TitleId{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->aarch64_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. */
R_TRY(SetThrown());
/* 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, static_cast<u64>(title_id), policy == FatalType_ErrorReportAndErrorScreen, &erpt_event, &battery_event);
} else {
/* If flag is not set, don't show the fatal screen. */
return ResultSuccess;
}
}
break;
default:
/* N aborts here. Should we just return an error code? */
std::abort();
}
return ResultSuccess;
}

View file

@ -1,159 +0,0 @@
/*
* Copyright (c) 2018-2019 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>
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

@ -1,37 +0,0 @@
/*
* Copyright (c) 2018-2019 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

@ -1,41 +0,0 @@
/*
* Copyright (c) 2018-2019 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 UserService final : public IServiceObject {
private:
enum class CommandId {
ThrowFatal = 0,
ThrowFatalWithPolicy = 1,
ThrowFatalWithCpuContext = 2,
};
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 {
MAKE_SERVICE_COMMAND_META(UserService, ThrowFatal),
MAKE_SERVICE_COMMAND_META(UserService, ThrowFatalWithPolicy),
MAKE_SERVICE_COMMAND_META(UserService, ThrowFatalWithCpuContext),
};
};

View file

@ -45,6 +45,7 @@
#include "stratosphere/svc.hpp"
#include "stratosphere/cfg.hpp"
#include "stratosphere/fatal.hpp"
#include "stratosphere/hid.hpp"
#include "stratosphere/ncm.hpp"
#include "stratosphere/pm.hpp"

View file

@ -14,10 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#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());
}
#include "fatal/fatal_types.hpp"

View file

@ -0,0 +1,340 @@
/*
* Copyright (c) 2018-2019 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 "../defines.hpp"
#include "../results.hpp"
#include "../ncm/ncm_types.hpp"
namespace sts::fatal {
namespace aarch64 {
enum RegisterName {
RegisterName_X0 = 0,
RegisterName_X1 = 1,
RegisterName_X2 = 2,
RegisterName_X3 = 3,
RegisterName_X4 = 4,
RegisterName_X5 = 5,
RegisterName_X6 = 6,
RegisterName_X7 = 7,
RegisterName_X8 = 8,
RegisterName_X9 = 9,
RegisterName_X10 = 10,
RegisterName_X11 = 11,
RegisterName_X12 = 12,
RegisterName_X13 = 13,
RegisterName_X14 = 14,
RegisterName_X15 = 15,
RegisterName_X16 = 16,
RegisterName_X17 = 17,
RegisterName_X18 = 18,
RegisterName_X19 = 19,
RegisterName_X20 = 20,
RegisterName_X21 = 21,
RegisterName_X22 = 22,
RegisterName_X23 = 23,
RegisterName_X24 = 24,
RegisterName_X25 = 25,
RegisterName_X26 = 26,
RegisterName_X27 = 27,
RegisterName_X28 = 28,
RegisterName_FP = 29,
RegisterName_LR = 30,
RegisterName_SP = 31,
RegisterName_PC = 32,
RegisterName_GeneralPurposeCount,
RegisterName_PState = 33,
RegisterName_Afsr0 = 34,
RegisterName_Afsr1 = 35,
RegisterName_Esr = 36,
RegisterName_Far = 37,
RegisterName_Count,
};
struct CpuContext {
using RegisterType = u64;
static constexpr size_t MaxStackTraceDepth = 0x20;
static constexpr const char *RegisterNameStrings[RegisterName_Count] = {
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",
u8"PC",
u8"PState",
u8"Afsr0",
u8"Afsr1",
u8"Esr",
u8"Far",
};
/* Registers, exception context. N left names for these fields in fatal .rodata. */
union {
struct {
union {
RegisterType x[RegisterName_GeneralPurposeCount];
struct {
RegisterType _x[RegisterName_FP];
RegisterType fp;
RegisterType lr;
RegisterType sp;
RegisterType pc;
};
};
RegisterType pstate;
RegisterType afsr0;
RegisterType afsr1;
RegisterType esr;
RegisterType far;
};
RegisterType registers[RegisterName_Count];
};
/* Misc. */
RegisterType stack_trace[MaxStackTraceDepth];
RegisterType base_address;
RegisterType register_set_flags;
u32 stack_trace_size;
void ClearState() {
std::memset(this, 0, sizeof(*this));
}
void SetTitleIdForAtmosphere(ncm::TitleId title_id) {
/* Right now, we mux title ID in through afsr when creport. */
/* TODO: Better way to do this? */
this->afsr0 = static_cast<RegisterType>(title_id);
}
ncm::TitleId GetTitleIdForAtmosphere() const {
return ncm::TitleId{this->afsr0};
}
void SetRegisterValue(RegisterName name, RegisterType value) {
this->registers[name] = value;
this->register_set_flags |= (RegisterType(1) << name);
}
bool HasRegisterValue(RegisterName name) const {
return this->register_set_flags & (RegisterType(1) << name);
}
void SetBaseAddress(RegisterType base_addr) {
this->base_address = base_addr;
}
};
}
namespace aarch32 {
enum RegisterName {
RegisterName_R0 = 0,
RegisterName_R1 = 1,
RegisterName_R2 = 2,
RegisterName_R3 = 3,
RegisterName_R4 = 4,
RegisterName_R5 = 5,
RegisterName_R6 = 6,
RegisterName_R7 = 7,
RegisterName_R8 = 8,
RegisterName_R9 = 9,
RegisterName_R10 = 10,
RegisterName_FP = 11,
RegisterName_IP = 12,
RegisterName_LR = 13,
RegisterName_SP = 14,
RegisterName_PC = 15,
RegisterName_GeneralPurposeCount,
RegisterName_PState = 16,
RegisterName_Afsr0 = 17,
RegisterName_Afsr1 = 18,
RegisterName_Esr = 29,
RegisterName_Far = 20,
RegisterName_Count,
};
struct CpuContext {
using RegisterType = u32;
static constexpr size_t MaxStackTraceDepth = 0x20;
static constexpr const char *RegisterNameStrings[RegisterName_Count] = {
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",
u8"PState",
u8"Afsr0",
u8"Afsr1",
u8"Esr",
u8"Far",
};
/* Registers, exception context. N left names for these fields in fatal .rodata. */
union {
struct {
union {
RegisterType r[RegisterName_GeneralPurposeCount];
struct {
RegisterType _x[RegisterName_FP];
RegisterType fp;
RegisterType ip;
RegisterType lr;
RegisterType sp;
RegisterType pc;
};
};
RegisterType pstate;
RegisterType afsr0;
RegisterType afsr1;
RegisterType esr;
RegisterType far;
};
RegisterType registers[RegisterName_Count];
};
/* Misc. Yes, stack_trace_size is really laid out differently than aarch64... */
RegisterType stack_trace[MaxStackTraceDepth];
u32 stack_trace_size;
RegisterType base_address;
RegisterType register_set_flags;
void ClearState() {
std::memset(this, 0, sizeof(*this));
}
void SetTitleIdForAtmosphere(ncm::TitleId title_id) {
/* Right now, we mux title ID in through afsr when creport. */
/* TODO: Better way to do this? */
this->afsr0 = static_cast<RegisterType>(static_cast<u64>(title_id) >> 0);
this->afsr1 = static_cast<RegisterType>(static_cast<u64>(title_id) >> 32);
}
ncm::TitleId GetTitleIdForAtmosphere() const {
return ncm::TitleId{(static_cast<u64>(this->afsr1) << 32ul) | (static_cast<u64>(this->afsr0) << 0ul)};
}
void SetRegisterValue(RegisterName name, RegisterType value) {
this->registers[name] = value;
this->register_set_flags |= (RegisterType(1) << name);
}
bool HasRegisterValue(RegisterName name) const {
return this->register_set_flags & (RegisterType(1) << name);
}
void SetBaseAddress(RegisterType base_addr) {
this->base_address = base_addr;
}
};
}
struct CpuContext {
enum Architecture {
Architecture_Aarch64 = 0,
Architecture_Aarch32 = 1,
};
union {
aarch64::CpuContext aarch64_ctx;
aarch32::CpuContext aarch32_ctx;
};
Architecture architecture;
u32 type;
void ClearState() {
std::memset(this, 0, sizeof(*this));
}
};
static_assert(std::is_pod<aarch64::CpuContext>::value && sizeof(aarch64::CpuContext) == 0x248, "aarch64::CpuContext definition!");
static_assert(std::is_pod<aarch32::CpuContext>::value && sizeof(aarch32::CpuContext) == 0xE0, "aarch32::CpuContext definition!");
static_assert(std::is_pod<CpuContext>::value && sizeof(CpuContext) == 0x250, "CpuContext definition!");
namespace srv {
struct ThrowContext {
u32 error_code;
ncm::TitleId title_id;
char proc_name[0xD];
bool is_creport;
CpuContext cpu_ctx;
bool generate_error_report;
Event erpt_event;
Event battery_event;
size_t stack_dump_size;
u8 stack_dump[0x100];
void ClearState() {
std::memset(this, 0, sizeof(*this));
}
};
}
}

View file

@ -23,6 +23,7 @@
#include "results/creport_results.hpp"
#include "results/debug_results.hpp"
#include "results/dmnt_results.hpp"
#include "results/err_results.hpp"
#include "results/fatal_results.hpp"
#include "results/fs_results.hpp"
#include "results/hipc_results.hpp"

View file

@ -16,18 +16,8 @@
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
class PrivateService final : public IServiceObject {
private:
enum class CommandId {
GetFatalEvent = 0,
};
private:
/* Actual commands. */
Result GetFatalEvent(Out<CopiedHandle> out_h);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(PrivateService, GetFatalEvent),
};
};
static constexpr u32 Module_Err = 162;
static constexpr Result ResultErrApplicationAborted = MAKERESULT(Module_Err, 1);
static constexpr Result ResultErrSystemModuleAborted = MAKERESULT(Module_Err, 2);