678 lines
22 KiB
C++
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, ®s_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, ®s_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;
|
|
} |