/* 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 . 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 #include #include #include #include #include #include #include #include #include #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; 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_get_section_flags(abfd, sec) & SEC_ALLOC)) return; bfd_vma vma = bfd_get_section_vma(abfd, sec); if (data->counter < vma || vma + bfd_get_section_size(sec) <= data->counter) return; bfd_find_nearest_line(abfd, sec, data->symbol, data->counter - vma, &(data->file), &(data->func), &(data->line)); } static void find(struct bfd_ctx * b, DWORD offset, const char **file, const char **func, unsigned *line) { struct find_info data; data.func = NULL; data.symbol = b->symbol; data.counter = offset; 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) { char procname[MAX_PATH]; GetModuleFileNameA(NULL, procname, sizeof procname); struct bfd_ctx *bc = NULL; int err = BFD_ERR_OK; DWORD machine_type = 0; STACKFRAME 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, SymFunctionTableAccess, SymGetModuleBase, 0)) { --depth; if (depth < 0) break; IMAGEHLP_SYMBOL *symbol = (IMAGEHLP_SYMBOL *)symbol_buffer; symbol->SizeOfStruct = (sizeof *symbol) + 255; symbol->MaxNameLength = 254; DWORD module_base = SymGetModuleBase(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); } const char * source_file = NULL; const char * func = NULL; unsigned line = 0; if (bc) { find(bc,frame.AddrPC.Offset,&source_file,&func,&line); } if (source_file == NULL) { DWORD dummy = 0; if (SymGetSymFromAddr(process, frame.AddrPC.Offset, &dummy, symbol)) { source_file = symbol->Name; } else { source_file = "[unknown file]"; } } if (func == NULL) { output_print(ob,"0x%08x from %s in %s %s \n", frame.AddrPC.Offset, module_name, source_file, bfd_errors[err]); } else { output_print(ob,"0x%08x 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%08x: %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()); } fputs(g_output , stderr); 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; }