/*
Copyright (c) 2010 ,
Cloud Wu . All rights reserved.
http://www.codingnow.com
Use, modification and distribution are subject to the "New BSD License"
as listed at <url: http://www.opensource.org/licenses/bsd-license.php >.
filename: backtrace.c
compiler: gcc 3.4.5 (mingw-win32)
build command: gcc -O2 -shared -Wall -o backtrace.dll backtrace.c -lbfd -liberty -limagehlp
how to use: Call LoadLibraryA("backtrace.dll"); at beginning of your program .
*/
#define PACKAGE "backtrace"
#define PACKAGE_VERSION "0.1"
#include <windows.h>
#include <excpt.h>
#include <imagehlp.h>
#include <bfd.h>
#include <psapi.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdbool.h>
#define BUFFER_MAX (16*1024)
#define BFD_ERR_OK (0)
#define BFD_ERR_OPEN_FAIL (1)
#define BFD_ERR_BAD_FORMAT (2)
#define BFD_ERR_NO_SYMBOLS (3)
#define BFD_ERR_READ_SYMBOL (4)
static const char *const bfd_errors[] = {
"",
"(Failed to open bfd)",
"(Bad format)",
"(No symbols)",
"(Failed to read symbols)",
};
struct bfd_ctx {
bfd * handle;
asymbol ** symbol;
};
struct bfd_set {
char * name;
struct bfd_ctx * bc;
struct bfd_set *next;
};
struct find_info {
asymbol **symbol;
bfd_vma counter;
bfd_vma base_addr;
const char *file;
const char *func;
unsigned line;
};
struct output_buffer {
char * buf;
size_t sz;
size_t ptr;
};
static const char*
exception_name(DWORD code)
{
switch(code)
{
case EXCEPTION_ACCESS_VIOLATION:
return "access violation";
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
return "array index out of bound";
case EXCEPTION_BREAKPOINT:
return "breakpoint reached";
case EXCEPTION_DATATYPE_MISALIGNMENT:
return "misaligned data access";
case EXCEPTION_FLT_DENORMAL_OPERAND:
return "operand had denormal value";
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
return "floating-point division by zero";
case EXCEPTION_FLT_INEXACT_RESULT:
return "no decimal fraction representation for value";
case EXCEPTION_FLT_INVALID_OPERATION:
return "invalid floating-point operation";
case EXCEPTION_FLT_OVERFLOW:
return "floating-point overflow";
case EXCEPTION_FLT_STACK_CHECK:
return "floating-point stack corruption";
case EXCEPTION_FLT_UNDERFLOW:
return "floating-point underflow";
case EXCEPTION_ILLEGAL_INSTRUCTION:
return "illegal instruction";
case EXCEPTION_IN_PAGE_ERROR:
return "inaccessible page";
case EXCEPTION_INT_DIVIDE_BY_ZERO:
return "integer division by zero";
case EXCEPTION_INT_OVERFLOW:
return "integer overflow";
case EXCEPTION_INVALID_DISPOSITION:
return "documentation says this should never happen";
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
return "can't continue after a noncontinuable exception";
case EXCEPTION_PRIV_INSTRUCTION:
return "attempted to execute a privileged instruction";
case EXCEPTION_SINGLE_STEP:
return "one instruction has been executed";
case EXCEPTION_STACK_OVERFLOW:
return "stack overflow";
}
return "unknown exception";
}
static void
output_init(struct output_buffer *ob, char * buf, size_t sz)
{
ob->buf = buf;
ob->sz = sz;
ob->ptr = 0;
ob->buf[0] = '\0';
}
static void
output_print(struct output_buffer *ob, const char * format, ...)
{
if (ob->sz == ob->ptr)
return;
ob->buf[ob->ptr] = '\0';
va_list ap;
va_start(ap,format);
vsnprintf(ob->buf + ob->ptr, ob->sz - ob->ptr, format, ap);
va_end(ap);
ob->ptr = strlen(ob->buf + ob->ptr) + ob->ptr;
}
static void
lookup_section(bfd *abfd, asection *sec, void *opaque_data)
{
struct find_info *data = opaque_data;
if (data->func)
return;
if (!(bfd_section_flags(sec) & SEC_ALLOC))
return;
bfd_vma vma = bfd_section_vma(sec);
bfd_vma addr = data->counter;
if (addr < vma || addr >= vma + bfd_section_size(sec))
addr -= data->base_addr; //relocated?
if (addr < vma || addr >= vma + bfd_section_size(sec))
return;
bfd_find_nearest_line(abfd, sec, data->symbol, addr - vma, &(data->file), &(data->func), &(data->line));
}
static void
find_sym(struct bfd_ctx * b, DWORD64 offset, DWORD64 base, const char **file, const char **func, unsigned *line)
{
struct find_info data;
data.func = NULL;
data.symbol = b->symbol;
data.counter = offset;
data.base_addr = base;
data.file = NULL;
data.func = NULL;
data.line = 0;
bfd_map_over_sections(b->handle, &lookup_section, &data);
if (file) {
*file = data.file;
}
if (func) {
*func = data.func;
}
if (line) {
*line = data.line;
}
}
static int
init_bfd_ctx(struct bfd_ctx *bc, const char * procname, int *err)
{
bc->handle = NULL;
bc->symbol = NULL;
bfd *b = bfd_openr(procname, 0);
if (!b) {
if(err) { *err = BFD_ERR_OPEN_FAIL; }
return 1;
}
if(!bfd_check_format(b, bfd_object)) {
bfd_close(b);
if(err) { *err = BFD_ERR_BAD_FORMAT; }
return 1;
}
if(!(bfd_get_file_flags(b) & HAS_SYMS)) {
bfd_close(b);
if(err) { *err = BFD_ERR_NO_SYMBOLS; }
return 1;
}
void *symbol_table;
unsigned dummy = 0;
if (bfd_read_minisymbols(b, FALSE, &symbol_table, &dummy) == 0) {
if (bfd_read_minisymbols(b, TRUE, &symbol_table, &dummy) < 0) {
free(symbol_table);
bfd_close(b);
if(err) { *err = BFD_ERR_READ_SYMBOL; }
return 1;
}
}
bc->handle = b;
bc->symbol = symbol_table;
if(err) { *err = BFD_ERR_OK; }
return 0;
}
static void
close_bfd_ctx(struct bfd_ctx *bc)
{
if (bc) {
if (bc->symbol) {
free(bc->symbol);
}
if (bc->handle) {
bfd_close(bc->handle);
}
}
}
static struct bfd_ctx *
get_bc(struct bfd_set *set, const char *procname, int *err)
{
while(set->name) {
if (strcmp(set->name, procname) == 0) {
return set->bc;
}
set = set->next;
}
struct bfd_ctx bc;
if (init_bfd_ctx(&bc, procname, err)) {
return NULL;
}
set->next = calloc(1, sizeof(*set));
set->bc = malloc(sizeof(struct bfd_ctx));
memcpy(set->bc, &bc, sizeof(bc));
set->name = strdup(procname);
return set->bc;
}
static void
release_set(struct bfd_set *set)
{
while(set) {
struct bfd_set * temp = set->next;
free(set->name);
close_bfd_ctx(set->bc);
free(set);
set = temp;
}
}
static void
_backtrace(struct output_buffer *ob, struct bfd_set *set, int depth , LPCONTEXT context)
{
struct bfd_ctx *bc = NULL;
int err = BFD_ERR_OK;
DWORD machine_type = 0;
STACKFRAME64 frame;
memset(&frame, 0, sizeof(frame));
#if defined(_M_IX86) || defined(__i386__)
machine_type = IMAGE_FILE_MACHINE_I386;
frame.AddrPC.Offset = context->Eip;
frame.AddrStack.Offset = context->Esp;
frame.AddrFrame.Offset = context->Ebp;
#elif defined(_M_IX64) || defined(__amd64__)
machine_type = IMAGE_FILE_MACHINE_AMD64;
frame.AddrPC.Offset = context->Rip;
frame.AddrStack.Offset = context->Rsp;
frame.AddrFrame.Offset = context->Rbp;
#else
#error "Unsupported platform"
//IA-64 anybody?
#endif
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrStack.Mode = AddrModeFlat;
frame.AddrFrame.Mode = AddrModeFlat;
HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
char symbol_buffer[sizeof(IMAGEHLP_SYMBOL) + 255];
char module_name_raw[MAX_PATH];
while(StackWalk64(machine_type,
process,
thread,
&frame,
context,
0,
SymFunctionTableAccess64,
SymGetModuleBase64, 0)) {
--depth;
if (depth < 0)
break;
IMAGEHLP_SYMBOL *symbol = (IMAGEHLP_SYMBOL *)symbol_buffer;
symbol->SizeOfStruct = (sizeof *symbol) + 255;
symbol->MaxNameLength = 254;
DWORD64 module_base = SymGetModuleBase64(process, frame.AddrPC.Offset);
const char * module_name = "[unknown module]";
if (module_base &&
GetModuleFileNameA((HINSTANCE)module_base, module_name_raw, MAX_PATH)) {
module_name = module_name_raw;
bc = get_bc(set, module_name, &err);
}
PLOADED_IMAGE exeimg = ImageLoad(module_name, NULL);
DWORD64 imbase = exeimg->FileHeader->OptionalHeader.ImageBase;
ImageUnload(exeimg);
const char * source_file = NULL;
const char * func = NULL;
unsigned line = 0;
if (bc) {
find_sym(bc, frame.AddrPC.Offset, module_base - imbase, &source_file, &func, &line);
}
if (source_file == NULL) {
DWORD64 dummy = 0;
if (SymGetSymFromAddr64(process, frame.AddrPC.Offset, &dummy, symbol)) {
source_file = symbol->Name;
}
else {
source_file = "[unknown file]";
}
}
if (func == NULL) {
output_print(ob, "0x%08llx from %s in %s %s \n",
frame.AddrPC.Offset,
module_name,
source_file,
bfd_errors[err]);
}
else {
output_print(ob, "0x%08llx in %s at %s:%d from %s \n",
frame.AddrPC.Offset,
func,
source_file,
line,
module_name);
}
}
}
static char * g_output = NULL;
static LPTOP_LEVEL_EXCEPTION_FILTER g_prev = NULL;
static LONG WINAPI
exception_filter(LPEXCEPTION_POINTERS info)
{
struct output_buffer ob;
output_init(&ob, g_output, BUFFER_MAX);
if (!SymInitialize(GetCurrentProcess(), 0, TRUE)) {
output_print(&ob,"Failed to init symbol context\n");
}
else {
bfd_init();
PEXCEPTION_RECORD rec = info->ExceptionRecord;
output_print(&ob,"Unhandled exception occured at 0x%08llx: %s.\n",
rec->ExceptionAddress,
exception_name(rec->ExceptionCode)
);
if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR)
if (rec->NumberParameters >= 2) {
const char *op =
rec->ExceptionInformation[0] == 0 ? "read" :
rec->ExceptionInformation[0] == 1 ? "written" : "executed";
output_print(&ob, "The data at memory address 0x%08x could not be %s.\n",
rec->ExceptionInformation[1], op);
}
struct bfd_set *set = calloc(1, sizeof(*set));
_backtrace(&ob, set, 128, info->ContextRecord);
release_set(set);
SymCleanup(GetCurrentProcess());
}
FILE *btf = fopen("backtrace.log", "w");
fputs(g_output, btf);
fclose(btf);
return EXCEPTION_CONTINUE_SEARCH;
}
static void
backtrace_register(void)
{
if (g_output == NULL) {
g_output = malloc(BUFFER_MAX);
g_prev = SetUnhandledExceptionFilter(exception_filter);
}
}
static void
backtrace_unregister(void)
{
if (g_output) {
free(g_output);
SetUnhandledExceptionFilter(g_prev);
g_prev = NULL;
g_output = NULL;
}
}
int
__printf__(const char * format, ...) {
int value;
va_list arg;
va_start(arg, format);
value = vprintf(format, arg);
va_end(arg);
return value;
}
__declspec(dllexport) BOOL WINAPI
DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
{
switch (dwReason) {
case DLL_PROCESS_ATTACH:
backtrace_register();
break;
case DLL_PROCESS_DETACH:
backtrace_unregister();
break;
}
return TRUE;
}