Files
PDModdingCommunity_PD-Loader/source-code/source/fakedll/exception.hpp
T
2019-07-30 23:40:17 +02:00

678 lines
22 KiB
C++

#pragma once
/*
* Unhandled Exception Tracer
* by LINK/2012 <dma_2012@hotmail.com>
*
* This source code is offered for use in the public domain. You may
* use, modify or distribute it freely.
*
* This code is distributed in the hope that it will be useful but
* WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY
* DISCLAIMED. This includes but is not limited to warranties of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <cassert>
#include <DbgHelp.h>
#pragma comment(lib, "Dbghelp.lib")
/*
* Special Note:
* Try not to allocate any memory in this file!
* Allocation after a exception may not be a good idea...
*/
#define LODWORD(_qw) ((DWORD)(_qw))
#define HIDWORD(_qw) ((DWORD)(((_qw) >> 32) & 0xffffffff))
// General constants
static const int sizeof_word = sizeof(void*); // Size of a CPU word (4 bytes on x86)
static const int max_chars_per_print = MAX_PATH + 256; // Max characters per Print() call
static const int symbol_max = 256; // Max size of a symbol (func symbol, var symbol, etc)
static const int max_static_buffer = 4096; // Max static buffer for logging
// Stackdump constants
static const int stackdump_max_words = 60; // max number of CPU words that the stackdump should dump
static const int stackdump_words_per_line = 6; // max CPU words in a single line
static const int stackdump_line_count = (stackdump_max_words / stackdump_words_per_line) + 1;
// Backtrace constants
static const int max_backtrace_ever = 100;
static const int max_backtrace = 20;
// Maximum log size constants
static const int max_logsize_basic = (MAX_PATH + 200); // module path + other text
static const int max_logsize_regs = 32 + (4 * 4 * 28); // info + (regsPerLine * numLines * charsPerReg)
static const int max_logsize_stackdump = 32 + 80 + (stackdump_line_count * 32) + (10 * stackdump_words_per_line * stackdump_line_count);
static const int max_logsize_backtrace = 32 + max_backtrace_ever * (MAX_PATH + symbol_max + 90);
static const int max_logsize_ever = 32 + max_logsize_basic + max_logsize_regs + max_logsize_stackdump + max_logsize_backtrace;
// Internal
class ExceptionTracer;
class StackTrace;
static HMODULE GetModuleFromAddress(LPVOID address);
static const char* GetExceptionCodeString(unsigned int code);
static const char* FindModuleName(HMODULE module, char* output, DWORD size);
static int LogException(char* buffer, size_t max, LPEXCEPTION_POINTERS pException, bool bLogRegisters, bool bLogStack, bool bLogBacktrace);
static LPTOP_LEVEL_EXCEPTION_FILTER PrevFilter = nullptr;
static void(*ExceptionCallback)(const char* buffer) = nullptr;
// Exportable
int InstallExceptionCatcher(void(*OnException)(const char* log));
/*
* ExceptionTrace
* This class is responssible for tracing all possible informations about an LPEXCEPTION_POINTER
*/
class ExceptionTracer
{
public:
ExceptionTracer(char* buffer, size_t max, LPEXCEPTION_POINTERS pException);
void PrintUnhandledException();
void PrintRegisters();
void PrintStackdump();
void PrintBacktrace();
void EnterScope();
void LeaveScope();
void Print(const char* fmt, ...);
void NewLine() { Print("\n%s", spc); }
protected:
EXCEPTION_POINTERS& exception;
EXCEPTION_RECORD& record;
CONTEXT& context;
HMODULE module;
char* buffer; // Logging buffer
size_t len; // Logged length
size_t max; // Maximum we can log in that buffer
char spc[(10 * 4) + 1]; // Scope/spacing buffer, 4 spaces per scope, max 10 scopes
size_t nspc; // Number spaces used up there
};
/*
* StackTracer
* Responssible for backtracing an stack from a context
*/
class StackTracer
{
public:
struct Trace
{
// The following values may be null (any)
HMODULE module; // The module the func related to this frame is located
void* pc; // Program counter at func related to this frame (EIP)
void* ret; // Return address for the frame
void* frame; // The frame address (EBP)
void* stack; // The stack pointer at the frame (ESP)
};
StackTracer(const CONTEXT& context);
Trace* Walk();
private:
Trace trace;
DWORD old_options;
CONTEXT context;
STACKFRAME64 frame;
};
/*
* TheUnhandledExceptionFilter
* Logs an unhandled exception
*/
static LONG CALLBACK TheUnhandledExceptionFilter(LPEXCEPTION_POINTERS pException)
{
// Logs exception into buffer and calls the callback
auto Log = [pException](char* buffer, size_t size, bool reg, bool stack, bool trace)
{
if (LogException(buffer, size, (LPEXCEPTION_POINTERS)pException, reg, stack, trace))
ExceptionCallback(buffer);
};
// Try to make a very descriptive exception, for that we need to malloc a huge buffer...
if (auto buffer = (char*)malloc(max_logsize_ever))
{
Log(buffer, max_logsize_ever, true, true, true);
free(buffer);
}
else
{
// Use a static buffer, no need for any allocation
static const auto size = max_logsize_basic + max_logsize_regs + max_logsize_stackdump;
static char static_buf[size];
static_assert(size <= max_static_buffer, "Static buffer is too big");
Log(buffer = static_buf, sizeof(static_buf), true, true, false);
}
// Continue exception propagation
return (PrevFilter ? PrevFilter(pException) : EXCEPTION_CONTINUE_SEARCH); // I'm not really sure about this return
}
/*
* InstallExceptionCatcher
* Installs a exception handler to call the specified callback when it happens with human readalbe information.
*/
int InstallExceptionCatcher(void(*cb)(const char* log))
{
PrevFilter = SetUnhandledExceptionFilter(TheUnhandledExceptionFilter);
ExceptionCallback = cb;
return 1;
}
/*
* LogException
* Takes an LPEXCEPTION_POINTERS and transforms in a string that is put in the logging steam
*/
static int LogException(char* buffer, size_t max, LPEXCEPTION_POINTERS pException, bool bLogRegisters, bool bLogStack, bool bLogBacktrace)
{
ExceptionTracer trace(buffer, max, pException);
trace.PrintUnhandledException();
trace.EnterScope();
if (bLogRegisters) trace.PrintRegisters();
if (bLogStack) trace.PrintStackdump();
if (bLogBacktrace) trace.PrintBacktrace();
trace.LeaveScope();
return 1;
}
/*
* ExceptionTracer
* Contructs a exception trace object, responssible for tracing informations about an exception
*/
ExceptionTracer::ExceptionTracer(char* buffer, size_t max, LPEXCEPTION_POINTERS pException) :
buffer(buffer), exception(*pException), record(*pException->ExceptionRecord), context(*pException->ContextRecord)
{
this->buffer = buffer;
this->buffer[this->len = 0] = 0;
this->spc[this->nspc = 0] = 0;
this->max = max;
// Acquiere common information that we'll access
this->module = GetModuleFromAddress(record.ExceptionAddress);
}
/*
* Print
* Prints some formated text into the logging buffer
*/
void ExceptionTracer::Print(const char* fmt, ...)
{
va_list va;
va_start(va, fmt);
if ((this->max - this->len) > max_chars_per_print)
this->len += vsprintf(&this->buffer[len], fmt, va);
va_end(va);
}
/*
* EnterScope
* Enters a new scope in the logging buffer (scope is related to indentation)
* This also prints a new line
*/
void ExceptionTracer::EnterScope()
{
nspc += 4;
spc[nspc - 4] = ' ';
spc[nspc - 3] = ' ';
spc[nspc - 2] = ' ';
spc[nspc - 1] = ' ';
spc[nspc - 0] = 0;
NewLine();
}
/*
* LeaveScope
* Leaves the scope
*/
void ExceptionTracer::LeaveScope()
{
assert(nspc > 0);
nspc -= 4;
spc[nspc] = 0;
NewLine();
}
/*
* PrintUnhandledException
* Prints the well known "Unhandled exception at ..." into the logging buffer
*/
void ExceptionTracer::PrintUnhandledException()
{
char module_name[MAX_PATH];
auto dwExceptionCode = record.ExceptionCode;
uintptr_t address = (uintptr_t)record.ExceptionAddress;
// Find out our module name for logging
if (!this->module || !GetModuleFileNameA(this->module, module_name, sizeof(module_name)))
strcpy(module_name, "unknown");
// Log the exception in a similar format similar to debuggers format
Print("Unhandled exception at 0x%p in %s", address, FindModuleName(module, module_name, sizeof(module_name)));
if (module) Print(" (+0x%x)", address - (uintptr_t)(module));
Print(": 0x%X: %s", dwExceptionCode, GetExceptionCodeString(dwExceptionCode));
// If exception is IN_PAGE_ERROR or ACCESS_VIOLATION, we have additional information such as an address
if (dwExceptionCode == EXCEPTION_IN_PAGE_ERROR || dwExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
auto rw = (DWORD)record.ExceptionInformation[0]; // read or write?
auto addr = (ULONG_PTR)record.ExceptionInformation[1]; // which address?
Print(" %s 0x%p",
rw == 0 ? "reading location" : rw == 1 ? "writing location" : rw == 8 ? "DEP at" : "",
addr);
// IN_PAGE_ERROR have another information...
if (dwExceptionCode == EXCEPTION_IN_PAGE_ERROR)
{
NewLine();
Print("Underlying NTSTATUS code that resulted in the exception is 0x%p",
record.ExceptionInformation[2]);
}
}
Print(".");
}
/*
* PrintRegisters
* Prints the content of the assembly registers into the logging buffer
*/
void ExceptionTracer::PrintRegisters()
{
int regs_in_line = 0; // Amount of registers currently printed on this line
// Prints a register, followed by spaces
auto PrintRegister = [this, &regs_in_line](const char* reg_name, size_t reg_value, const char* spaces)
{
Print("%s: 0x%p%s", reg_name, reg_value, spaces);
if (++regs_in_line >= 4) { this->NewLine(); regs_in_line = 0; }
};
auto PrintFloatRegister = [this, &regs_in_line](const char* reg_name, int reg_num, uint32_t reg_value1, uint32_t reg_value2, uint32_t reg_value3, uint32_t reg_value4)
{
Print("%s%02d: 0x%08X 0x%08X 0x%08X 0x%08X [ %f %f %f %f ]", reg_name, reg_num, reg_value1, reg_value2, reg_value3, reg_value4,
*(float*)& reg_value1, *(float*)& reg_value2, *(float*)& reg_value3, *(float*)& reg_value4);
if (++regs_in_line >= 1) { this->NewLine(); regs_in_line = 0; }
};
// Prints a general purposes register
auto PrintIntRegister = [PrintRegister](const char* reg_name, size_t reg_value)
{
PrintRegister(reg_name, reg_value, " ");
};
// Prints a segment register
auto PrintSegRegister = [PrintRegister](const char* reg_name, size_t reg_value)
{
PrintRegister(reg_name, reg_value, " ");
};
Print("Register dump:");
EnterScope();
{
// Print main general purposes registers
if (context.ContextFlags & CONTEXT_INTEGER)
{
#if !_M_X64
PrintIntRegister("EAX", context.Eax);
PrintIntRegister("EBX", context.Ebx);
PrintIntRegister("ECX", context.Ecx);
PrintIntRegister("EDX", context.Edx);
PrintIntRegister("EDI", context.Edi);
PrintIntRegister("ESI", context.Esi);
#else
PrintIntRegister("RAX", context.Rax);
PrintIntRegister("RCX", context.Rcx);
PrintIntRegister("RDX", context.Rdx);
PrintIntRegister("RBX", context.Rbx);
PrintIntRegister("RBP", context.Rbp);
PrintIntRegister("RSI", context.Rsi);
PrintIntRegister("RDI", context.Rdi);
PrintIntRegister("R08", context.R8);
PrintIntRegister("R09", context.R9);
PrintIntRegister("R10", context.R10);
PrintIntRegister("R11", context.R11);
PrintIntRegister("R12", context.R12);
PrintIntRegister("R13", context.R13);
PrintIntRegister("R14", context.R14);
PrintIntRegister("R15", context.R15);
#endif
}
// Print control registers
if (context.ContextFlags & CONTEXT_CONTROL)
{
#if !_M_X64
PrintIntRegister("EBP", context.Ebp);
PrintIntRegister("EIP", context.Eip);
PrintIntRegister("ESP", context.Esp);
PrintIntRegister("EFL", context.EFlags);
this->NewLine(); this->NewLine(); regs_in_line = 0;
PrintSegRegister("CS", context.SegCs);
PrintSegRegister("SS", context.SegSs);
#else
PrintIntRegister("RIP", context.Rip);
PrintIntRegister("RSP", context.Rsp);
PrintIntRegister("EFL", context.EFlags);
this->NewLine(); this->NewLine(); regs_in_line = 0;
PrintSegRegister("CS", context.SegCs);
PrintSegRegister("SS", context.SegSs);
#endif
}
this->NewLine(); regs_in_line = 0;
// Print segment registers
if (context.ContextFlags & CONTEXT_SEGMENTS)
{
PrintSegRegister("GS", context.SegGs);
PrintSegRegister("FS", context.SegFs);
this->NewLine(); regs_in_line = 0;
PrintSegRegister("ES", context.SegEs);
PrintSegRegister("DS", context.SegDs);
}
this->NewLine(); this->NewLine(); regs_in_line = 0;
// Print floating point registers
if (context.ContextFlags & CONTEXT_FLOATING_POINT)
{
for (int i = 0; i < 8; i++)
{
#if !_M_X64
auto f = *(M128A*) & (context.FloatSave.RegisterArea[i * 10]);
PrintFloatRegister("ST", i, LODWORD(f.Low), HIDWORD(f.Low), LODWORD(f.High), HIDWORD(f.High));
#else
PrintFloatRegister("ST", i,
LODWORD(context.FltSave.FloatRegisters[i].Low), HIDWORD(context.FltSave.FloatRegisters[i].Low),
LODWORD(context.FltSave.FloatRegisters[i].High), HIDWORD(context.FltSave.FloatRegisters[i].High));
#endif
}
this->NewLine();
for (int i = 0; i < 16; i++)
{
#if !_M_X64
auto f = *(M128A*) & (context.ExtendedRegisters[(i + 10) * 16]);
PrintFloatRegister("XMM", i, LODWORD(f.Low), HIDWORD(f.Low), LODWORD(f.High), HIDWORD(f.High));
if (i >= 7)
break;
#else
PrintFloatRegister("XMM", i,
LODWORD(context.FltSave.XmmRegisters[i].Low), HIDWORD(context.FltSave.XmmRegisters[i].Low),
LODWORD(context.FltSave.XmmRegisters[i].High), HIDWORD(context.FltSave.XmmRegisters[i].High));
#endif
}
}
}
LeaveScope();
}
/*
* PrintStackdump
* Prints the content of the stack into the logging buffer
*/
void ExceptionTracer::PrintStackdump()
{
// We need the ESP of the exception context to execute a stack dump, make sure we have access to it
if ((context.ContextFlags & CONTEXT_CONTROL) == 0)
return;
static const auto align = sizeof_word; // Stack aligment
static const auto max_words_in_line_magic = stackdump_words_per_line + 10;
MEMORY_BASIC_INFORMATION mbi;
#if !_M_X64
uintptr_t base, bottom, top = (uintptr_t)context.Esp;
#else
uintptr_t base, bottom, top = (uintptr_t)context.Rsp;
#endif
auto words_in_line = max_words_in_line_magic;
// Finds the bottom of the stack from it's base pointer
// Note: mbi will get overriden on this function
auto GetStackBottom = [&mbi](uintptr_t base)
{
VirtualQuery((void*)base, &mbi, sizeof(mbi)); // Find uncommited region of the stack
VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi)); // Find guard page
VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi)); // Find commited region of the stack
auto last = (uintptr_t)mbi.BaseAddress;
return (base + (last - base) + mbi.RegionSize); // base + distanceToLastRegion + lastRegionSize
};
// Prints an CPU word at the specified stack address
auto PrintWord = [this, &words_in_line](uintptr_t addr)
{
if (words_in_line++ >= stackdump_words_per_line)
{
// Print new line only if it's not the first time we enter here (i.e. words_in_line has magical value)
if (words_in_line != max_words_in_line_magic + 1) NewLine();
words_in_line = 1;
Print("0x%p: ", addr);
}
Print(" %p", *(size_t*)addr);
};
Print("Stack dump:");
EnterScope();
{
// Makes sure the pointer at top (ESP) is valid and readable memory
if (VirtualQuery((void*)(top), &mbi, sizeof(mbi))
&& (mbi.State & MEM_COMMIT)
&& (mbi.Protect & (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_READWRITE | PAGE_READONLY)) != 0)
{
base = (uintptr_t)mbi.AllocationBase; // Base of the stack (uncommited)
bottom = GetStackBottom(base); // Bottom of the stack (commited)
// Align the stack top (esp) in a 4 bytes boundary
auto remainder = top % align;
uintptr_t current = remainder ? top + (align - remainder) : top;
// on x86 stack grows downward! (i.e. from bottom to base)
for (int n = 0; n < stackdump_max_words && current < bottom; ++n, current += align)
PrintWord(current);
NewLine();
Print("base: 0x%p top: 0x%p bottom: 0x%p", base, top, bottom);
NewLine();
}
}
LeaveScope();
}
/*
* PrintBacktrace
* Prints a call backtrace into the logging buffer
*/
void ExceptionTracer::PrintBacktrace()
{
StackTracer tracer(this->context);
char module_name[MAX_PATH];
char sym_buffer[sizeof(SYMBOL_INFO) + symbol_max];
int backtrace_count = 0; // Num of frames traced
bool has_symbol_api = false; // True if we have the symbol API available for use
DWORD old_options; // Saves old symbol API options
SYMBOL_INFO& symbol = *(SYMBOL_INFO*)sym_buffer;
symbol.SizeOfStruct = sizeof(SYMBOL_INFO);
symbol.MaxNameLen = symbol_max;
// Tries to get the symbol api
if (SymInitialize(GetCurrentProcess(), 0, TRUE))
{
has_symbol_api = true;
old_options = SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_NO_PROMPTS | SYMOPT_FAIL_CRITICAL_ERRORS);
}
Print("Backtrace (may be wrong):");
EnterScope();
{
// Walks on the stack until there's no frame to trace or we traced 'max_backtrace' frames
while (auto trace = tracer.Walk())
{
if (++backtrace_count >= max_backtrace)
break;
bool has_sym = false; // This EIP has a symbol associated with it?
DWORD64 displacement; // EIP displacement relative to symbol
// If we have access to the symbol api, try to get symbol name from pc (eip)
if (has_symbol_api)
has_sym = trace->pc ? !!SymFromAddr(GetCurrentProcess(), (DWORD64)trace->pc, &displacement, &symbol) : false;
// Print everything up, this.... Ew, this looks awful!
Print(backtrace_count == 1 ? "=>" : " "); // First line should have '=>' to specify where it crashed
Print("0x%p ", trace->pc); // Print EIP at frame
if (has_sym) Print("%s+0x%x ", symbol.Name, (DWORD)displacement); // Print frame func symbol
Print("in %s (+0x%x) ", // Print module
trace->module ? FindModuleName(trace->module, module_name, sizeof(module_name)) : "unknown",
(uintptr_t)(trace->pc) - (uintptr_t)(trace->module) // Module displacement
);
if (trace->frame) Print("(0x%p) ", trace->frame); // Print frame pointer
NewLine();
}
}
LeaveScope();
// Cleanup the symbol api
if (has_symbol_api)
{
SymSetOptions(old_options);
SymCleanup(GetCurrentProcess());
}
}
/*
* GetExceptionCodeString
* Returns an description by an exception code
*/
static const char* GetExceptionCodeString(unsigned int code)
{
switch (code)
{
case EXCEPTION_ACCESS_VIOLATION: return "Access violation";
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "Array bounds exceeded";
case EXCEPTION_BREAKPOINT: return "Breakpoint exception";
case EXCEPTION_DATATYPE_MISALIGNMENT: return "Data type misalignment exception";
case EXCEPTION_FLT_DENORMAL_OPERAND: return "Denormal float operand";
case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "Floating-point division by zero";
case EXCEPTION_FLT_INEXACT_RESULT: return "Floating-point inexact result";
case EXCEPTION_FLT_INVALID_OPERATION: return "Floating-point invalid operation";
case EXCEPTION_FLT_OVERFLOW: return "Floating-point overflow";
case EXCEPTION_FLT_STACK_CHECK: return "Floating-point stack check";
case EXCEPTION_FLT_UNDERFLOW: return "Floating-point underflow";
case EXCEPTION_ILLEGAL_INSTRUCTION: return "Illegal instruction.";
case EXCEPTION_IN_PAGE_ERROR: return "In page error";
case EXCEPTION_INT_DIVIDE_BY_ZERO: return "Integer division by zero";
case EXCEPTION_INT_OVERFLOW: return "Integer overflow";
case EXCEPTION_INVALID_DISPOSITION: return "Invalid disposition";
case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "Non-continuable exception";
case EXCEPTION_PRIV_INSTRUCTION: return "Privileged instruction";
case EXCEPTION_SINGLE_STEP: return "Single step exception";
case EXCEPTION_STACK_OVERFLOW: return "Stack overflow";
default: return "NO_DESCRIPTION";
}
}
/*
* FindModuleName
* Finds module filename or "unknown"
*/
static const char* FindModuleName(HMODULE module, char* output, DWORD maxsize)
{
if (GetModuleFileNameA(module, output, maxsize))
{
// Finds the filename part in the output string
char* filename = strrchr(output, '\\');
if (!filename) filename = strrchr(output, '/');
// If filename found (i.e. output isn't already a filename but full path), make output be filename
if (filename)
{
size_t size = strlen(++filename);
memmove(output, filename, size);
output[size] = 0;
}
}
else
{
// Unknown module
strcpy(output, "unknown");
}
return output;
}
/*
* GetModuleFromAddress
* Finds module handle from some address inside it
*/
static HMODULE GetModuleFromAddress(LPVOID address)
{
HMODULE module;
if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(char*)address, &module))
return module;
return nullptr;
}
/*
* StackTracer
* Constructs the tracer, we basically need to initialize the symbol api
*/
StackTracer::StackTracer(const CONTEXT& context)
{
// Initialise basic values
memset(&this->frame, 0, sizeof(frame));
memcpy(&this->context, &context, sizeof(context));
// Setup the initial frame context
#if !_M_X64
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrPC.Offset = context.Eip;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Ebp;
frame.AddrStack.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.Esp;
#else
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrPC.Offset = context.Rip;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Rbp;
frame.AddrStack.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.Rsp;
#endif
}
/*
* StackTracer::Walk
* Walks on the stack, each walk is one frame of backtrace
* Returns a frame or null if the walk on the park is not possible anymore
*/
StackTracer::Trace* StackTracer::Walk()
{
if (StackWalk64(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), GetCurrentThread(),
&frame, &context, NULL, NULL, NULL, NULL))
{
trace.module = GetModuleFromAddress((void*)frame.AddrPC.Offset);
trace.frame = (void*)frame.AddrFrame.Offset;
trace.stack = (void*)frame.AddrStack.Offset;
trace.pc = (void*)frame.AddrPC.Offset;
trace.ret = (void*)frame.AddrReturn.Offset;
return &trace;
}
return nullptr;
}