From 962fa0a69020004f8e827200249a4edb2d201a9a Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Wed, 14 Nov 2018 03:23:28 -0800 Subject: [PATCH] fatal: automatically collect backtrace for callers. --- stratosphere/fatal/fatal.json | 11 +- stratosphere/fatal/source/fatal_debug.cpp | 256 ++++++++++++++++++ stratosphere/fatal/source/fatal_debug.hpp | 149 ++++++++++ stratosphere/fatal/source/fatal_font.cpp | 8 + stratosphere/fatal/source/fatal_font.hpp | 1 + .../fatal/source/fatal_task_screen.cpp | 41 ++- stratosphere/fatal/source/fatal_throw.cpp | 22 +- stratosphere/fatal/source/fatal_types.hpp | 3 +- stratosphere/fatal/source/fatal_user.cpp | 9 +- 9 files changed, 481 insertions(+), 19 deletions(-) create mode 100644 stratosphere/fatal/source/fatal_debug.cpp create mode 100644 stratosphere/fatal/source/fatal_debug.hpp diff --git a/stratosphere/fatal/fatal.json b/stratosphere/fatal/fatal.json index 380b2b85f..61f6ca88f 100644 --- a/stratosphere/fatal/fatal.json +++ b/stratosphere/fatal/fatal.json @@ -12,7 +12,7 @@ "is_64_bit": true, "address_space_type": 3, "filesystem_access": { - "permissions": "0x0000000000100000" + "permissions": "0xFFFFFFFFFFFFFFFF" }, "service_access": ["bpc", "bpc:c", "erpt:c", "fsp-srv", "gpio", "i2c", "lbl", "lm", "nvdrv:s", "pcv", "pl:u", "pm:info", "psm", "set", "set:sys", "spsm", "vi:m", "vi:s"], "service_host": ["fatal:p", "fatal:u"], @@ -76,7 +76,14 @@ "svcReplyAndReceive": "0x43", "svcReplyAndReceiveWithUserBuffer": "0x44", "svcCreateEvent": "0x45", - "svcReadWriteRegister": "0x4E" + "svcReadWriteRegister": "0x4E", + "svcDebugActiveProcess": "0x60", + "svcGetDebugEvent": "0x63", + "svcGetThreadList": "0x66", + "svcGetDebugThreadContext": "0x67", + "svcQueryDebugProcessMemory": "0x69", + "svcReadDebugProcessMemory": "0x6a", + "svcGetDebugThreadParam": "0x6d" } }, { "type": "min_kernel_version", diff --git a/stratosphere/fatal/source/fatal_debug.cpp b/stratosphere/fatal/source/fatal_debug.cpp new file mode 100644 index 000000000..46ed8df13 --- /dev/null +++ b/stratosphere/fatal/source/fatal_debug.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include "fatal_debug.hpp" +#include "fatal_config.hpp" + +static bool IsAddressReadable(Handle debug_handle, u64 address, u64 size, MemoryInfo *o_mi) { + MemoryInfo mi; + u32 pi; + + if (o_mi == NULL) { + o_mi = &mi; + } + + if (R_FAILED(svcQueryDebugProcessMemory(o_mi, &pi, debug_handle, address))) { + return false; + } + + /* Must be readable */ + if ((o_mi->perm & Perm_R) != Perm_R) { + return false; + } + + /* Must have space for both userdata address and userdata size. */ + if (address < o_mi->addr || o_mi->addr + o_mi->size < address + size) { + return false; + } + + return true; +} + +static bool CheckThreadIsFatalCaller(FatalThrowContext *ctx, u64 debug_handle, u64 thread_id, u64 thread_tls_addr, ThreadContext *thread_ctx) { + /* Verify that the thread is running or waiting. */ + { + u64 _; + u32 thread_state; + if (R_FAILED(svcGetDebugThreadParam(&_, &thread_state, debug_handle, thread_id, DebugThreadParam_State))) { + return false; + } + + if (thread_state > 1) { + return false; + } + } + + /* Get the thread context. */ + if (R_FAILED(svcGetDebugThreadContext(thread_ctx, debug_handle, thread_id, 0xF))) { + return false; + } + + /* Check if PC is readable. */ + if (!IsAddressReadable(debug_handle, thread_ctx->pc.x, sizeof(u32), NULL)) { + return false; + } + + /* Try to read the current instruction. */ + u32 insn; + if (R_FAILED(svcReadDebugProcessMemory(&insn, debug_handle, thread_ctx->pc.x, sizeof(insn)))) { + return false; + } + + /* If the instruction isn't svcSendSyncRequest, it's not the fatal caller. */ + if (insn != 0xD4000421) { + return false; + } + + /* The fatal caller will have readable tls. */ + if (!IsAddressReadable(debug_handle, thread_tls_addr, 0x100, NULL)) { + return false; + } + + /* Read in the fatal caller's tls. */ + u8 thread_tls[0x100]; + if (R_FAILED(svcReadDebugProcessMemory(thread_tls, debug_handle, thread_tls_addr, sizeof(thread_tls)))) { + return false; + } + + /* Replace our tls with the fatal caller's. */ + std::memcpy(armGetTls(), thread_tls, sizeof(thread_tls)); + + /* Parse the command that the thread sent. */ + { + IpcParsedCommand r; + if (R_FAILED(ipcParse(&r))) { + return false; + } + + /* Fatal command takes in a PID, only one buffer max. */ + if (!r.HasPid || r.NumStatics || r.NumStaticsOut || r.NumHandles) { + return false; + } + + struct { + u32 magic; + u32 version; + u64 cmd_id; + u32 err_code; + } *raw = (decltype(raw))(r.Raw); + + if (raw->magic != SFCI_MAGIC) { + return false; + } + + if (raw->cmd_id > 2) { + return false; + } + + if (raw->cmd_id != 2 && r.NumBuffers) { + return false; + } + + if (raw->err_code != ctx->error_code) { + return false; + } + } + + /* We found our caller. */ + return true; +} + +void TryCollectDebugInformation(FatalThrowContext *ctx, u64 pid) { + Handle debug_handle; + if (R_SUCCEEDED(svcDebugActiveProcess(&debug_handle, pid))) { + /* Ensure we close the debugged process. */ + ON_SCOPE_EXIT { svcCloseHandle(debug_handle); }; + + /* First things first, check if process is 64 bits, and get list of thread infos. */ + std::unordered_map 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; + got_attach_process = true; + } else if (d.type == DebugEventType::AttachThread) { + thread_id_to_tls[d.info.attach_thread.thread_id] = d.info.attach_thread.tls_address; + } + } + + if (!got_attach_process) { + return; + } + } + + /* TODO: Try to collect information on 32-bit fatals. This shouldn't really matter for any real use case. */ + if (ctx->cpu_ctx.is_aarch32) { + return; + } + + /* Welcome to hell. */ + bool found_fatal_caller = false; + u64 thread_id = 0; + ThreadContext thread_ctx; + { + /* We start by trying to get a list of threads. */ + u32 thread_count; + u64 thread_ids[0x60]; + if (R_FAILED(svcGetThreadList(&thread_count, thread_ids, 0x60, debug_handle))) { + return; + } + + /* We need to locate the thread that's called fatal. */ + for (u32 i = 0; i < thread_count; i++) { + const u64 cur_thread_id = thread_ids[i]; + if (thread_id_to_tls.find(cur_thread_id) == thread_id_to_tls.end()) { + continue; + } + + if (CheckThreadIsFatalCaller(ctx, debug_handle, cur_thread_id, thread_id_to_tls[cur_thread_id], &thread_ctx)) { + thread_id = cur_thread_id; + found_fatal_caller = true; + break; + } + } + if (!found_fatal_caller) { + return; + } + } + if (R_FAILED(svcGetDebugThreadContext(&thread_ctx, debug_handle, thread_id, 0xF))) { + return; + } + + /* So we found our caller. */ + for (u32 i = 0; i < 29; i++) { + /* GetDebugThreadContext won't give us any of these registers, because thread is in SVC :( */ + ctx->has_gprs[i] = false; + } + for (u32 i = 29; i < NumAarch64Gprs; i++) { + ctx->has_gprs[i] = true; + } + ctx->cpu_ctx.aarch64_ctx.fp = thread_ctx.fp; + ctx->cpu_ctx.aarch64_ctx.lr = thread_ctx.lr; + ctx->cpu_ctx.aarch64_ctx.sp = thread_ctx.sp; + ctx->cpu_ctx.aarch64_ctx.pc = thread_ctx.pc.x; + + + /* Parse a stack trace. */ + u64 cur_fp = thread_ctx.fp; + for (unsigned int i = 0; i < sizeof(ctx->cpu_ctx.aarch64_ctx.stack_trace)/sizeof(u64); i++) { + /* Validate the current frame. */ + if (cur_fp == 0 || (cur_fp & 0xF)) { + break; + } + + /* Read a new frame. */ + StackFrame cur_frame; + if (R_FAILED(svcReadDebugProcessMemory(&cur_frame, debug_handle, cur_fp, sizeof(StackFrame)))) { + break; + } + + /* Advance to the next frame. */ + ctx->cpu_ctx.aarch64_ctx.stack_trace[ctx->cpu_ctx.aarch64_ctx.stack_trace_size++] = cur_frame.lr; + cur_fp = cur_frame.fp; + } + + /* Parse the starting address. */ + { + u64 guess = thread_ctx.pc.x; + MemoryInfo mi; + u32 pi; + if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess)) || mi.perm != Perm_Rx) { + return; + } + + /* Iterate backwards until we find the memory before the code region. */ + while (mi.addr > 0) { + if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess))) { + return; + } + + if (mi.type == MemType_Unmapped) { + /* Code region will be at the end of the unmapped region preceding it. */ + ctx->cpu_ctx.aarch64_ctx.start_address = mi.addr + mi.size; + break; + } + + guess -= 4; + } + } + } +} \ No newline at end of file diff --git a/stratosphere/fatal/source/fatal_debug.hpp b/stratosphere/fatal/source/fatal_debug.hpp new file mode 100644 index 000000000..8a7c4ec3d --- /dev/null +++ b/stratosphere/fatal/source/fatal_debug.hpp @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "fatal_types.hpp" + +void TryCollectDebugInformation(FatalThrowContext *ctx, u64 pid); + +struct StackFrame { + u64 fp; + u64 lr; +}; + +struct AttachProcessInfo { + u64 title_id; + u64 process_id; + char name[0xC]; + u32 flags; + u64 user_exception_context_address; /* 5.0.0+ */ +}; + +struct AttachThreadInfo { + u64 thread_id; + u64 tls_address; + u64 entrypoint; +}; + +/* TODO: ExitProcessInfo */ +/* TODO: ExitThreadInfo */ + +enum class DebugExceptionType : u32 { + UndefinedInstruction = 0, + InstructionAbort = 1, + DataAbort = 2, + AlignmentFault = 3, + DebuggerAttached = 4, + BreakPoint = 5, + UserBreak = 6, + DebuggerBreak = 7, + BadSvc = 8, + UnknownNine = 9, +}; + +static inline const char *GetDebugExceptionTypeStr(DebugExceptionType type) { + switch (type) { + case DebugExceptionType::UndefinedInstruction: + return "Undefined Instruction"; + case DebugExceptionType::InstructionAbort: + return "Instruction Abort"; + case DebugExceptionType::DataAbort: + return "Data Abort"; + case DebugExceptionType::AlignmentFault: + return "Alignment Fault"; + case DebugExceptionType::DebuggerAttached: + return "Debugger Attached"; + case DebugExceptionType::BreakPoint: + return "Break Point"; + case DebugExceptionType::UserBreak: + return "User Break"; + case DebugExceptionType::DebuggerBreak: + return "Debugger Break"; + case DebugExceptionType::BadSvc: + return "Bad Svc"; + case DebugExceptionType::UnknownNine: + return "Unknown Nine"; + default: + return "Unknown"; + } +} + +struct UndefinedInstructionInfo { + u32 insn; +}; + +struct DataAbortInfo { + u64 address; +}; + +struct AlignmentFaultInfo { + u64 address; +}; + +struct UserBreakInfo { + u64 break_reason; + u64 address; + u64 size; +}; + +struct BadSvcInfo { + u32 id; +}; + +union SpecificExceptionInfo { + UndefinedInstructionInfo undefined_instruction; + DataAbortInfo data_abort; + AlignmentFaultInfo alignment_fault; + UserBreakInfo user_break; + BadSvcInfo bad_svc; + u64 raw; +}; + +struct ExceptionInfo { + DebugExceptionType type; + u64 address; + SpecificExceptionInfo specific; +}; + + +enum class DebugEventType : u32 { + AttachProcess = 0, + AttachThread = 1, + ExitProcess = 2, + ExitThread = 3, + Exception = 4 +}; + +union DebugInfo { + AttachProcessInfo attach_process; + AttachThreadInfo attach_thread; + ExceptionInfo exception; +}; + +struct DebugEventInfo { + DebugEventType type; + u32 flags; + u64 thread_id; + union { + DebugInfo info; + u64 _[0x40/sizeof(u64)]; + }; +}; + +static_assert(sizeof(DebugEventInfo) >= 0x50, "Incorrect DebugEventInfo definition!"); \ No newline at end of file diff --git a/stratosphere/fatal/source/fatal_font.cpp b/stratosphere/fatal/source/fatal_font.cpp index b86cf4edf..452bff06d 100644 --- a/stratosphere/fatal/source/fatal_font.cpp +++ b/stratosphere/fatal/source/fatal_font.cpp @@ -163,6 +163,14 @@ void FontManager::PrintMonospaceU32(u32 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; diff --git a/stratosphere/fatal/source/fatal_font.hpp b/stratosphere/fatal/source/fatal_font.hpp index 5bd79dc14..acd1bca63 100644 --- a/stratosphere/fatal/source/fatal_font.hpp +++ b/stratosphere/fatal/source/fatal_font.hpp @@ -41,4 +41,5 @@ class FontManager { static void PrintFormat(const char *format, ...); static void PrintMonospaceU64(u64 x); static void PrintMonospaceU32(u32 x); + static void PrintMonospaceBlank(u32 width); }; \ No newline at end of file diff --git a/stratosphere/fatal/source/fatal_task_screen.cpp b/stratosphere/fatal/source/fatal_task_screen.cpp index 94fa1bacb..66e39b69c 100644 --- a/stratosphere/fatal/source/fatal_task_screen.cpp +++ b/stratosphere/fatal/source/fatal_task_screen.cpp @@ -225,21 +225,40 @@ Result ShowFatalTask::ShowFatal() { /* Print GPRs. */ FontManager::SetFontSize(14.0f); - FontManager::PrintLine("General Purpose Registers"); + 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()); - FontManager::Print("0x"); - FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.r[i]); + if (this->ctx->has_gprs[i]) { + FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.r[i]); + } else { + FontManager::PrintMonospaceBlank(8); + } FontManager::Print(" "); x = FontManager::GetX(); FontManager::PrintFormat("%s:", Aarch32GprNames[i + (NumAarch32Gprs / 2)]); FontManager::SetPosition(x + 47, FontManager::GetY()); - FontManager::Print("0x"); - FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.r[i + (NumAarch32Gprs / 2)]); + if (this->ctx->has_gprs[i + (NumAarch32Gprs / 2)]) { + FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.r[i + (NumAarch32Gprs / 2)]); + } else { + FontManager::PrintMonospaceBlank(8); + } if (i == (NumAarch32Gprs / 2) - 1) { FontManager::Print(" "); @@ -254,12 +273,20 @@ Result ShowFatalTask::ShowFatal() { u32 x = FontManager::GetX(); FontManager::PrintFormat("%s:", Aarch64GprNames[i]); FontManager::SetPosition(x + 47, FontManager::GetY()); - FontManager::PrintMonospaceU64(this->ctx->cpu_ctx.aarch64_ctx.x[i]); + 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()); - FontManager::PrintMonospaceU64(this->ctx->cpu_ctx.aarch64_ctx.x[i + (NumAarch64Gprs / 2)]); + 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(" "); diff --git a/stratosphere/fatal/source/fatal_throw.cpp b/stratosphere/fatal/source/fatal_throw.cpp index dbd6eba3b..8dbd15e0e 100644 --- a/stratosphere/fatal/source/fatal_throw.cpp +++ b/stratosphere/fatal/source/fatal_throw.cpp @@ -19,6 +19,7 @@ #include "fatal_event_manager.hpp" #include "fatal_task.hpp" #include "fatal_config.hpp" +#include "fatal_debug.hpp" static bool g_thrown = false; @@ -34,17 +35,25 @@ static Result SetThrown() { Result ThrowFatalForSelf(u32 error) { u64 pid = 0; - FatalCpuContext ctx = {0}; svcGetProcessId(&pid, CUR_PROCESS_HANDLE); - return ThrowFatalImpl(error, pid, FatalType_ErrorScreen, &ctx); + return ThrowFatalImpl(error, pid, FatalType_ErrorScreen, nullptr); } Result ThrowFatalImpl(u32 error, u64 pid, FatalType policy, FatalCpuContext *cpu_ctx) { Result rc = 0; FatalThrowContext ctx; ctx.error_code = error; - ctx.cpu_ctx = *cpu_ctx; + if (cpu_ctx != nullptr) { + ctx.cpu_ctx = *cpu_ctx; + /* Assume if we're provided a context that it's complete. */ + for (u32 i = 0; i < NumAarch64Gprs; i++) { + ctx.has_gprs[i] = true; + } + } else { + std::memset(&ctx.cpu_ctx, 0, sizeof(ctx.cpu_ctx)); + cpu_ctx = &ctx.cpu_ctx; + } /* Get config. */ const FatalConfig *config = GetFatalConfig(); @@ -59,6 +68,13 @@ Result ThrowFatalImpl(u32 error, u64 pid, FatalType policy, FatalCpuContext *cpu title_id = cpu_ctx->aarch64_ctx.afsr0; } + /* Atmosphere extension: automatic debug info collection. */ + if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200 && !ctx.is_creport) { + if ((cpu_ctx->is_aarch32 && cpu_ctx->aarch32_ctx.stack_trace_size == 0) || (!cpu_ctx->is_aarch32 && cpu_ctx->aarch32_ctx.stack_trace_size == 0)) { + TryCollectDebugInformation(&ctx, pid); + } + } + switch (policy) { case FatalType_ErrorReport: /* TODO: Don't write an error report. */ diff --git a/stratosphere/fatal/source/fatal_types.hpp b/stratosphere/fatal/source/fatal_types.hpp index 99be9c524..b0635ec9f 100644 --- a/stratosphere/fatal/source/fatal_types.hpp +++ b/stratosphere/fatal/source/fatal_types.hpp @@ -99,6 +99,7 @@ struct FatalCpuContext { struct FatalThrowContext { u32 error_code; bool is_creport; + bool has_gprs[NumAarch64Gprs]; FatalCpuContext cpu_ctx; }; @@ -129,6 +130,7 @@ static constexpr const char *Aarch64GprNames[NumAarch64Gprs] = { u8"X18", u8"X19", u8"X20", + u8"X21", u8"X22", u8"X23", u8"X24", @@ -139,7 +141,6 @@ static constexpr const char *Aarch64GprNames[NumAarch64Gprs] = { u8"FP", u8"LR", u8"SP", - u8"PC", }; static constexpr const char *Aarch32GprNames[NumAarch32Gprs] = { diff --git a/stratosphere/fatal/source/fatal_user.cpp b/stratosphere/fatal/source/fatal_user.cpp index 98b081494..520971bf5 100644 --- a/stratosphere/fatal/source/fatal_user.cpp +++ b/stratosphere/fatal/source/fatal_user.cpp @@ -21,19 +21,16 @@ #include "fatal_task.hpp" Result UserService::ThrowFatal(u32 error, PidDescriptor pid_desc) { - FatalCpuContext ctx = {0}; - return ThrowFatalImpl(error, pid_desc.pid, FatalType_ErrorReportAndErrorScreen, &ctx); + return ThrowFatalImpl(error, pid_desc.pid, FatalType_ErrorReportAndErrorScreen, nullptr); } Result UserService::ThrowFatalWithPolicy(u32 error, PidDescriptor pid_desc, FatalType policy) { - FatalCpuContext ctx = {0}; - return ThrowFatalImpl(error, pid_desc.pid, policy, &ctx); + return ThrowFatalImpl(error, pid_desc.pid, policy, nullptr); } Result UserService::ThrowFatalWithCpuContext(u32 error, PidDescriptor pid_desc, FatalType policy, InBuffer _ctx) { if (_ctx.num_elements < sizeof(FatalCpuContext)) { - FatalCpuContext ctx = {0}; - return ThrowFatalImpl(error, pid_desc.pid, policy, &ctx); + return ThrowFatalImpl(error, pid_desc.pid, policy, nullptr); } else { return ThrowFatalImpl(error, pid_desc.pid, policy, reinterpret_cast(_ctx.buffer)); }