From c34daa26c67f4a879ff541775c099ddc19a1dec5 Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Mon, 2 Dec 2019 01:19:30 +0800 Subject: Add a stack trace printer for the beloved operating system. --- CMakeLists.txt | 8 + qmidiplayer-desktop/main.cpp | 5 + third_party/backtrace-mingw/CMakeLists.txt | 11 + third_party/backtrace-mingw/README.md | 20 ++ third_party/backtrace-mingw/backtrace.c | 457 +++++++++++++++++++++++++++++ third_party/backtrace-mingw/test.c | 23 ++ 6 files changed, 524 insertions(+) create mode 100644 third_party/backtrace-mingw/CMakeLists.txt create mode 100644 third_party/backtrace-mingw/README.md create mode 100644 third_party/backtrace-mingw/backtrace.c create mode 100644 third_party/backtrace-mingw/test.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 92ba7f7..6b92872 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,9 @@ pkg_search_module(fluidsynth REQUIRED fluidsynth>=2.0.0) pkg_search_module(rtmidi REQUIRED rtmidi) option(BUILD_VISUALIZATION "Build visualization plugin" ON) +if(WIN32) + option(BUILD_BACKTRACE "Build backtrace library" OFF) +endif() add_subdirectory(core) add_subdirectory(qmidiplayer-desktop) @@ -24,3 +27,8 @@ add_subdirectory(simple-visualization) if(BUILD_VISUALIZATION) add_subdirectory(visualization) endif() +if(WIN32) +if(BUILD_BACKTRACE) + add_subdirectory(third_party/backtrace-mingw) +endif() +endif() diff --git a/qmidiplayer-desktop/main.cpp b/qmidiplayer-desktop/main.cpp index 37f6338..b1c9149 100644 --- a/qmidiplayer-desktop/main.cpp +++ b/qmidiplayer-desktop/main.cpp @@ -22,11 +22,16 @@ #include #include #ifdef _WIN32 +#include #include #endif int main(int argc,char **argv) { +#ifdef _WIN32 + if(!LoadLibraryA("libbacktrace.dll")) + fputs("Failed to load backtrace library. Stack trace will not be printed if unhandled exception occurs.\n",stderr); +#endif QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QCoreApplication::setApplicationName("qmidiplayer"); QCoreApplication::setApplicationVersion(APP_VERSION); diff --git a/third_party/backtrace-mingw/CMakeLists.txt b/third_party/backtrace-mingw/CMakeLists.txt new file mode 100644 index 0000000..394d420 --- /dev/null +++ b/third_party/backtrace-mingw/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(backtrace SHARED backtrace.c) +find_path(BFD_INCLUDE_DIR NAMES bfd.h) +find_library(BFD_LIBRARIES NAMES bfd) +find_library(IBERTY_LIBRARIES NAMES iberty) +find_package(ZLIB REQUIRED) +target_link_libraries(backtrace + ${BFD_LIBRARIES} + ${IBERTY_LIBRARIES} + ${ZLIB_LIBRARIES} + imagehlp +) diff --git a/third_party/backtrace-mingw/README.md b/third_party/backtrace-mingw/README.md new file mode 100644 index 0000000..77cb879 --- /dev/null +++ b/third_party/backtrace-mingw/README.md @@ -0,0 +1,20 @@ +The original project doesn't come with a readme. + +The code has been slightly tweaked to add support for amd64 and to obtain a +more readable output format. + +Tested with mxe (gcc 5.5.0, binutils 2.28). Obviously you need a shared build. +mxe disables shared versions of bfd and iberty, but actually they work just +fine. Simply remove this line +``` +$(PKG)_BUILD_SHARED = +``` +from their makefiles and change the options for `./configure` +(`--enable-static` to `--disable-static`, `--disable-shared` to +`--enable-shared`). + +[Source project](https://github.com/cloudwu/backtrace-mingw) + +References + +[Printing a Stack Trace with MinGW](http://theorangeduck.com/page/printing-stack-trace-mingw) diff --git a/third_party/backtrace-mingw/backtrace.c b/third_party/backtrace-mingw/backtrace.c new file mode 100644 index 0000000..b9ddcf4 --- /dev/null +++ b/third_party/backtrace-mingw/backtrace.c @@ -0,0 +1,457 @@ +/* + 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; +} + diff --git a/third_party/backtrace-mingw/test.c b/third_party/backtrace-mingw/test.c new file mode 100644 index 0000000..731129f --- /dev/null +++ b/third_party/backtrace-mingw/test.c @@ -0,0 +1,23 @@ +#include + +static void +foo() +{ + int *f=NULL; + *f = 0; +} + +static void +bar() +{ + foo(); +} + +int +main() +{ + LoadLibraryA("backtrace.dll"); + bar(); + + return 0; +} -- cgit v1.2.3