From 387bf403da1e19840931ffe7fd6eb7983c3a6634 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 8 Sep 2015 00:49:34 +0100 Subject: [PATCH 1/8] GCC/Unix: Try to demangle C++ function names in crashlog stack trace. Change format of stack trace lines to improve readability. --- config.lib | 31 +++++++++++++++++++++++++++++++ src/os/unix/crashlog_unix.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/config.lib b/config.lib index 07985a4f83..5c7c994267 100644 --- a/config.lib +++ b/config.lib @@ -1554,6 +1554,37 @@ make_cflags_and_ldflags() { if [ "$os" != "CYGWIN" ] && [ "$os" != "HAIKU" ] && [ "$os" != "OPENBSD" ] && [ "$os" != "MINGW" ] && [ "$os" != "MORPHOS" ] && [ "$os" != "OSX" ] && [ "$os" != "DOS" ] && [ "$os" != "WINCE" ] && [ "$os" != "PSP" ] && [ "$os" != "OS2" ]; then LIBS="$LIBS -lpthread" fi + if [ "$os" != "CYGWIN" ] && [ "$os" != "HAIKU" ] && [ "$os" != "MINGW" ] && [ "$os" != "MORPHOS" ] && [ "$os" != "OSX" ] && [ "$os" != "DOS" ] && [ "$os" != "WINCE" ] && [ "$os" != "PSP" ] && [ "$os" != "OS2" ]; then + "$cc_host" -o /dev/null -x c++ - -ldl 2> /dev/null << EOL + #include + int main() { + Dl_info info; + return dladdr(0, &info); + } +EOL + if [ $? -ne 0 ]; then + log 1 "checking libdl... no" + else + log 1 "checking libdl... found" + LIBS="$LIBS -ldl" + CFLAGS="$CFLAGS -DWITH_DL" + fi + + "$cc_host" -o /dev/null -x c++ - -lstdc++ 2> /dev/null << EOL + #include + int main() { + int status = -1; + char *demangled = abi::__cxa_demangle("test", 0, 0, &status); + return 0; + } +EOL + if [ $? -ne 0 ]; then + log 1 "checking abi::__cxa_demangle... no" + else + log 1 "checking abi::__cxa_demangle... found" + CFLAGS="$CFLAGS -DWITH_DEMANGLE" + fi + fi if [ "$os" != "CYGWIN" ] && [ "$os" != "HAIKU" ] && [ "$os" != "MINGW" ] && [ "$os" != "DOS" ] && [ "$os" != "WINCE" ]; then LIBS="$LIBS -lc" diff --git a/src/os/unix/crashlog_unix.cpp b/src/os/unix/crashlog_unix.cpp index 47de057f7e..1bcbe63fde 100644 --- a/src/os/unix/crashlog_unix.cpp +++ b/src/os/unix/crashlog_unix.cpp @@ -22,6 +22,12 @@ #if defined(__GLIBC__) /* Execinfo (and thus making stacktraces) is a GNU extension */ # include +#if defined(WITH_DL) +# include +#endif +#if defined(WITH_DEMANGLE) +# include +#endif #elif defined(SUNOS) # include # include @@ -114,6 +120,26 @@ class CrashLogUnix : public CrashLog { char **messages = backtrace_symbols(trace, trace_size); for (int i = 0; i < trace_size; i++) { +#if defined(WITH_DL) + Dl_info info; + int dladdr_result = dladdr(trace[i], &info); + if (dladdr_result && info.dli_sname) { + int status = -1; + char *demangled = NULL; +#if defined(WITH_DEMANGLE) + demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status); +#endif + const char *name = (demangled != NULL && status == 0) ? demangled : info.dli_sname; + buffer += seprintf(buffer, last, " [%02i] %*p %-40s %s + 0x%zx\n", i, int(2 + sizeof(void*) * 2), + trace[i], info.dli_fname, name, (char *)trace[i] - (char *)info.dli_saddr); + free(demangled); + continue; + } else if (dladdr_result && info.dli_fname) { + buffer += seprintf(buffer, last, " [%02i] %*p %-40s + 0x%zx\n", i, int(2 + sizeof(void*) * 2), + trace[i], info.dli_fname, (char *)trace[i] - (char *)info.dli_fbase); + continue; + } +#endif buffer += seprintf(buffer, last, " [%02i] %s\n", i, messages[i]); } free(messages); From 5b755050b828b5a57be928ebf65e10293be921ee Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 8 Sep 2015 20:29:39 +0100 Subject: [PATCH 2/8] Use libbfd on Unix to get more symbols, line numbers, etc. for backtraces. --- config.lib | 27 ++++++++- src/os/unix/crashlog_unix.cpp | 110 +++++++++++++++++++++++++++++++--- 2 files changed, 127 insertions(+), 10 deletions(-) diff --git a/config.lib b/config.lib index 5c7c994267..2d3e437ca5 100644 --- a/config.lib +++ b/config.lib @@ -1555,7 +1555,7 @@ make_cflags_and_ldflags() { LIBS="$LIBS -lpthread" fi if [ "$os" != "CYGWIN" ] && [ "$os" != "HAIKU" ] && [ "$os" != "MINGW" ] && [ "$os" != "MORPHOS" ] && [ "$os" != "OSX" ] && [ "$os" != "DOS" ] && [ "$os" != "WINCE" ] && [ "$os" != "PSP" ] && [ "$os" != "OS2" ]; then - "$cc_host" -o /dev/null -x c++ - -ldl 2> /dev/null << EOL + "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.libdl -x c++ - -ldl 2> /dev/null << EOL #include int main() { Dl_info info; @@ -1569,8 +1569,9 @@ EOL LIBS="$LIBS -ldl" CFLAGS="$CFLAGS -DWITH_DL" fi + rm tmp.config.libdl - "$cc_host" -o /dev/null -x c++ - -lstdc++ 2> /dev/null << EOL + "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.demangle -x c++ - -lstdc++ 2> /dev/null << EOL #include int main() { int status = -1; @@ -1584,6 +1585,28 @@ EOL log 1 "checking abi::__cxa_demangle... found" CFLAGS="$CFLAGS -DWITH_DEMANGLE" fi + rm tmp.config.demangle + + "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.bfd -x c++ - -lbfd 2> /dev/null << EOL + #define PACKAGE 1 + #define PACKAGE_VERSION 1 + #include + int main() { + bfd_init(); + unsigned int size; + asymbol *syms = 0; + long symcount = bfd_read_minisymbols((bfd *) 0, false, (void**) &syms, &size); + return 0; + } +EOL + if [ $? -ne 0 ]; then + log 1 "checking libbfd... no" + else + log 1 "checking libbfd... found" + LIBS="$LIBS -lbfd" + CFLAGS="$CFLAGS -DWITH_BFD" + fi + rm tmp.config.bfd fi if [ "$os" != "CYGWIN" ] && [ "$os" != "HAIKU" ] && [ "$os" != "MINGW" ] && [ "$os" != "DOS" ] && [ "$os" != "WINCE" ]; then diff --git a/src/os/unix/crashlog_unix.cpp b/src/os/unix/crashlog_unix.cpp index 1bcbe63fde..803433427e 100644 --- a/src/os/unix/crashlog_unix.cpp +++ b/src/os/unix/crashlog_unix.cpp @@ -28,6 +28,9 @@ #if defined(WITH_DEMANGLE) # include #endif +#if defined(WITH_BFD) +# include +#endif #elif defined(SUNOS) # include # include @@ -39,6 +42,73 @@ #include "../../safeguards.h" +#if defined(WITH_BFD) +struct line_info { + bfd_vma addr; + bfd *abfd; + asymbol **syms; + long sym_count; + const char *file_name; + const char *function_name; + bfd_vma function_addr; + unsigned int line; + bool found; + + line_info(bfd_vma addr_) : addr(addr_), abfd(NULL), syms(NULL), sym_count(0), + file_name(NULL), function_name(NULL), function_addr(0), line(0), found(false) {} + + ~line_info() + { + free(syms); + if (abfd != NULL) bfd_close(abfd); + } +}; + +static void find_address_in_section(bfd *abfd, asection *section, void *data) +{ + line_info *info = static_cast(data); + if (info->found) return; + + if ((bfd_get_section_flags(abfd, section) & SEC_ALLOC) == 0) return; + + bfd_vma vma = bfd_get_section_vma(abfd, section); + if (info->addr < vma) return; + + bfd_size_type size = bfd_section_size(abfd, section); + if (info->addr >= vma + size) return; + + info->found = bfd_find_nearest_line(abfd, section, info->syms, info->addr - vma, + &(info->file_name), &(info->function_name), &(info->line)); + + if (info->found) { + for (long i = 0; i < info->sym_count; i++) { + asymbol *sym = info->syms[i]; + if (sym->flags & (BSF_LOCAL | BSF_GLOBAL) && strcmp(sym->name, info->function_name) == 0) { + info->function_addr = sym->value + vma; + } + } + } +} + +void lookup_addr_bfd(const char *obj_file_name, line_info &info) +{ + info.abfd = bfd_openr(obj_file_name, NULL); + + if (info.abfd == NULL) return; + + if (!bfd_check_format(info.abfd, bfd_object) || (bfd_get_file_flags(info.abfd) & HAS_SYMS) == 0) return; + + unsigned int size; + info.sym_count = bfd_read_minisymbols(info.abfd, false, (void**) &(info.syms), &size); + if (info.sym_count <= 0) { + info.sym_count = bfd_read_minisymbols(info.abfd, true, (void**) &(info.syms), &size); + } + if (info.sym_count <= 0) return; + + bfd_map_over_sections(info.abfd, find_address_in_section, &info); +} +#endif + /** * Unix implementation for the crash logger. */ @@ -115,6 +185,9 @@ class CrashLogUnix : public CrashLog { { buffer += seprintf(buffer, last, "Stacktrace:\n"); #if defined(__GLIBC__) +#if defined(WITH_BFD) + bfd_init(); +#endif void *trace[64]; int trace_size = backtrace(trace, lengthof(trace)); @@ -123,22 +196,43 @@ class CrashLogUnix : public CrashLog { #if defined(WITH_DL) Dl_info info; int dladdr_result = dladdr(trace[i], &info); - if (dladdr_result && info.dli_sname) { + const char *func_name = info.dli_sname; + void *func_addr = info.dli_saddr; + const char *file_name = NULL; + unsigned int line_num = 0; +#if defined(WITH_BFD) + /* subtract one to get the line before the return address, i.e. the function call line */ + line_info bfd_info(reinterpret_cast(trace[i]) - 1); + if (dladdr_result && info.dli_fname) { + lookup_addr_bfd(info.dli_fname, bfd_info); + if (bfd_info.file_name != NULL) file_name = bfd_info.file_name; + if (bfd_info.function_name != NULL) func_name = bfd_info.function_name; + if (bfd_info.function_addr != 0) func_addr = reinterpret_cast(bfd_info.function_addr); + line_num = bfd_info.line; + } +#endif + bool ok = true; + const int ptr_str_size = (2 + sizeof(void*) * 2); + if (dladdr_result && func_name) { int status = -1; char *demangled = NULL; #if defined(WITH_DEMANGLE) - demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status); + demangled = abi::__cxa_demangle(func_name, NULL, 0, &status); #endif - const char *name = (demangled != NULL && status == 0) ? demangled : info.dli_sname; - buffer += seprintf(buffer, last, " [%02i] %*p %-40s %s + 0x%zx\n", i, int(2 + sizeof(void*) * 2), - trace[i], info.dli_fname, name, (char *)trace[i] - (char *)info.dli_saddr); + const char *name = (demangled != NULL && status == 0) ? demangled : func_name; + buffer += seprintf(buffer, last, " [%02i] %*p %-40s %s + 0x%zx\n", i, ptr_str_size, + trace[i], info.dli_fname, name, (char *)trace[i] - (char *)func_addr); free(demangled); - continue; } else if (dladdr_result && info.dli_fname) { - buffer += seprintf(buffer, last, " [%02i] %*p %-40s + 0x%zx\n", i, int(2 + sizeof(void*) * 2), + buffer += seprintf(buffer, last, " [%02i] %*p %-40s + 0x%zx\n", i, ptr_str_size, trace[i], info.dli_fname, (char *)trace[i] - (char *)info.dli_fbase); - continue; + } else { + ok = false; } + if (file_name != NULL) { + buffer += seprintf(buffer, last, "%*s%s:%u\n", 7 + ptr_str_size, "", file_name, line_num); + } + if (ok) continue; #endif buffer += seprintf(buffer, last, " [%02i] %s\n", i, messages[i]); } From c5bc04c4b81b4bab89c2786b563abbb1fa2562ef Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 8 Sep 2015 20:30:06 +0100 Subject: [PATCH 3/8] Compile with -g1 if using libbfd to get line numbers. --- config.lib | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config.lib b/config.lib index 2d3e437ca5..5302aa79d6 100644 --- a/config.lib +++ b/config.lib @@ -1605,6 +1605,9 @@ EOL log 1 "checking libbfd... found" LIBS="$LIBS -lbfd" CFLAGS="$CFLAGS -DWITH_BFD" + if [ $enable_debug -lt 1 ]; then + CFLAGS="$CFLAGS -g1" + fi fi rm tmp.config.bfd fi From 99d8d1afa61996559e842da16f0d4afc8125cf6e Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 9 Sep 2015 00:09:07 +0100 Subject: [PATCH 4/8] Enable existing MSVC stack backtrace code on MinGW where supported. The existing code seems to work fine, though symbol lookup in openttd.exe does not work. --- config.lib | 21 +++++++++++++++++++++ src/os/windows/crashlog_win.cpp | 11 ++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/config.lib b/config.lib index 5302aa79d6..a9ceaf6a82 100644 --- a/config.lib +++ b/config.lib @@ -1612,6 +1612,27 @@ EOL rm tmp.config.bfd fi + if [ "$os" = "MINGW" ]; then + "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.dbghelp -x c++ - 2> /dev/null << EOL + #include + #include + int main() { + STACKFRAME64 frame; + IMAGEHLP_SYMBOL64 *sym_info; + IMAGEHLP_MODULE64 module; + IMAGEHLP_LINE64 line; + return 0; + } +EOL + if [ $? -ne 0 ]; then + log 1 "checking dbghelp... no" + else + log 1 "checking dbghelp... found" + CFLAGS="$CFLAGS -DWITH_DBGHELP" + fi + rm -f tmp.config.dbghelp + fi + if [ "$os" != "CYGWIN" ] && [ "$os" != "HAIKU" ] && [ "$os" != "MINGW" ] && [ "$os" != "DOS" ] && [ "$os" != "WINCE" ]; then LIBS="$LIBS -lc" fi diff --git a/src/os/windows/crashlog_win.cpp b/src/os/windows/crashlog_win.cpp index 1abb0e725c..484e4ee3fc 100644 --- a/src/os/windows/crashlog_win.cpp +++ b/src/os/windows/crashlog_win.cpp @@ -49,12 +49,13 @@ class CrashLogWindows : public CrashLog { /* virtual */ char *LogRegisters(char *buffer, const char *last) const; /* virtual */ char *LogModules(char *buffer, const char *last) const; public: -#if defined(_MSC_VER) +#if defined(_MSC_VER) || defined(WITH_DBGHELP) /* virtual */ int WriteCrashDump(char *filename, const char *filename_last) const; char *AppendDecodedStacktrace(char *buffer, const char *last) const; #else char *AppendDecodedStacktrace(char *buffer, const char *last) const { return buffer; } -#endif /* _MSC_VER */ +#endif /* _MSC_VER || WITH_DBGHELP */ + /** Buffer for the generated crash log */ char crashlog[65536]; @@ -321,10 +322,14 @@ static char *PrintModuleInfo(char *output, const char *last, HMODULE mod) return buffer + seprintf(buffer, last, "\n"); } +#if defined(_MSC_VER) || defined(WITH_DBGHELP) #if defined(_MSC_VER) #pragma warning(disable:4091) +#endif #include +#if defined(_MSC_VER) #pragma warning(default:4091) +#endif char *CrashLogWindows::AppendDecodedStacktrace(char *buffer, const char *last) const { @@ -479,7 +484,7 @@ char *CrashLogWindows::AppendDecodedStacktrace(char *buffer, const char *last) c } return ret; } -#endif /* _MSC_VER */ +#endif /* _MSC_VER || WITH_DBGHELP */ extern bool CloseConsoleLogIfActive(); static void ShowCrashlogWindow(); From 3a093c4b637081e9ae7cde5551362397f1f67b6e Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 9 Sep 2015 02:14:06 +0100 Subject: [PATCH 5/8] Use libbfd as a fallback for backtrace symbol lookup on MinGW. This will try to demangle. If bfd_find_nearest_line returns a file name but nothing else (debugging info is turned off), scan the symbol table for the previous symbol and use it if it looks OKish. --- config.lib | 59 +++++++++++++++++++-------- source.list | 1 + src/crashlog.cpp | 70 +++++++++++++++++++++++++++++++++ src/crashlog_bfd.h | 45 +++++++++++++++++++++ src/os/unix/crashlog_unix.cpp | 70 +-------------------------------- src/os/windows/crashlog_win.cpp | 35 +++++++++++++++++ 6 files changed, 196 insertions(+), 84 deletions(-) create mode 100644 src/crashlog_bfd.h diff --git a/config.lib b/config.lib index a9ceaf6a82..a1f5ac6a08 100644 --- a/config.lib +++ b/config.lib @@ -1571,22 +1571,6 @@ EOL fi rm tmp.config.libdl - "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.demangle -x c++ - -lstdc++ 2> /dev/null << EOL - #include - int main() { - int status = -1; - char *demangled = abi::__cxa_demangle("test", 0, 0, &status); - return 0; - } -EOL - if [ $? -ne 0 ]; then - log 1 "checking abi::__cxa_demangle... no" - else - log 1 "checking abi::__cxa_demangle... found" - CFLAGS="$CFLAGS -DWITH_DEMANGLE" - fi - rm tmp.config.demangle - "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.bfd -x c++ - -lbfd 2> /dev/null << EOL #define PACKAGE 1 #define PACKAGE_VERSION 1 @@ -1629,10 +1613,53 @@ EOL else log 1 "checking dbghelp... found" CFLAGS="$CFLAGS -DWITH_DBGHELP" + + "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.bfd -x c++ - -lbfd -liberty -lintl 2> /dev/null << EOL + #define PACKAGE 1 + #define PACKAGE_VERSION 1 + #include + int main() { + bfd_init(); + unsigned int size; + asymbol *syms = 0; + long symcount = bfd_read_minisymbols((bfd *) 0, false, (void**) &syms, &size); + return 0; + } +EOL + if [ $? -ne 0 ]; then + log 1 "checking libbfd... no" + else + log 1 "checking libbfd... found" + LIBS="$LIBS -lbfd -liberty -lintl" + CFLAGS="$CFLAGS -DWITH_BFD" + + if [ $enable_debug -lt 1 ]; then + CFLAGS="$CFLAGS -g1" + fi + fi + rm -f tmp.config.bfd fi rm -f tmp.config.dbghelp fi + if [ "$os" != "CYGWIN" ] && [ "$os" != "HAIKU" ] && [ "$os" != "MORPHOS" ] && [ "$os" != "OSX" ] && [ "$os" != "DOS" ] && [ "$os" != "WINCE" ] && [ "$os" != "PSP" ] && [ "$os" != "OS2" ]; then + "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.demangle -x c++ - -lstdc++ 2> /dev/null << EOL + #include + int main() { + int status = -1; + char *demangled = abi::__cxa_demangle("test", 0, 0, &status); + return 0; + } +EOL + if [ $? -ne 0 ]; then + log 1 "checking abi::__cxa_demangle... no" + else + log 1 "checking abi::__cxa_demangle... found" + CFLAGS="$CFLAGS -DWITH_DEMANGLE" + fi + rm tmp.config.demangle + fi + if [ "$os" != "CYGWIN" ] && [ "$os" != "HAIKU" ] && [ "$os" != "MINGW" ] && [ "$os" != "DOS" ] && [ "$os" != "WINCE" ]; then LIBS="$LIBS -lc" fi diff --git a/source.list b/source.list index df35cdd26e..2277a22139 100644 --- a/source.list +++ b/source.list @@ -165,6 +165,7 @@ console_internal.h console_type.h cpu.h crashlog.h +crashlog_bfd.h currency.h date_func.h date_gui.h diff --git a/src/crashlog.cpp b/src/crashlog.cpp index 7ddc2131f9..19a5898623 100644 --- a/src/crashlog.cpp +++ b/src/crashlog.cpp @@ -11,6 +11,7 @@ #include "stdafx.h" #include "crashlog.h" +#include "crashlog_bfd.h" #include "gamelog.h" #include "date_func.h" #include "map_func.h" @@ -493,3 +494,72 @@ bool CrashLog::MakeCrashLog() const if (SoundDriver::GetInstance() != NULL) SoundDriver::GetInstance()->Stop(); if (VideoDriver::GetInstance() != NULL) VideoDriver::GetInstance()->Stop(); } + +#if defined(WITH_BFD) +sym_info_bfd::sym_info_bfd(bfd_vma addr_) : addr(addr_), abfd(NULL), syms(NULL), sym_count(0), + file_name(NULL), function_name(NULL), function_addr(0), line(0), found(false) {} + +sym_info_bfd::~sym_info_bfd() +{ + free(syms); + if (abfd != NULL) bfd_close(abfd); +} + +static void find_address_in_section(bfd *abfd, asection *section, void *data) +{ + sym_info_bfd *info = static_cast(data); + if (info->found) return; + + if ((bfd_get_section_flags(abfd, section) & SEC_ALLOC) == 0) return; + + bfd_vma vma = bfd_get_section_vma(abfd, section); + if (info->addr < vma) return; + + bfd_size_type size = bfd_section_size(abfd, section); + if (info->addr >= vma + size) return; + + info->found = bfd_find_nearest_line(abfd, section, info->syms, info->addr - vma, + &(info->file_name), &(info->function_name), &(info->line)); + + if (info->found && info->function_name) { + for (long i = 0; i < info->sym_count; i++) { + asymbol *sym = info->syms[i]; + if (sym->flags & (BSF_LOCAL | BSF_GLOBAL) && strcmp(sym->name, info->function_name) == 0) { + info->function_addr = sym->value + vma; + } + } + } else if (info->found) { + bfd_vma target = info->addr - vma; + bfd_vma best_diff = size; + for (long i = 0; i < info->sym_count; i++) { + asymbol *sym = info->syms[i]; + if (!(sym->flags & (BSF_LOCAL | BSF_GLOBAL))) continue; + if (sym->value > target) continue; + bfd_vma diff = target - sym->value; + if (diff < best_diff) { + best_diff = diff; + info->function_name = sym->name; + info->function_addr = sym->value + vma; + } + } + } +} + +void lookup_addr_bfd(const char *obj_file_name, sym_info_bfd &info) +{ + info.abfd = bfd_openr(obj_file_name, NULL); + + if (info.abfd == NULL) return; + + if (!bfd_check_format(info.abfd, bfd_object) || (bfd_get_file_flags(info.abfd) & HAS_SYMS) == 0) return; + + unsigned int size; + info.sym_count = bfd_read_minisymbols(info.abfd, false, (void**) &(info.syms), &size); + if (info.sym_count <= 0) { + info.sym_count = bfd_read_minisymbols(info.abfd, true, (void**) &(info.syms), &size); + } + if (info.sym_count <= 0) return; + + bfd_map_over_sections(info.abfd, find_address_in_section, &info); +} +#endif diff --git a/src/crashlog_bfd.h b/src/crashlog_bfd.h new file mode 100644 index 0000000000..b9af007b4f --- /dev/null +++ b/src/crashlog_bfd.h @@ -0,0 +1,45 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file crashlog_bfd.h Definitions for utility functions for using libbfd as part of logging a crash */ + +#ifndef CRASHLOG_BFD_H +#define CRASHLOG_BFD_H + +#if defined(WITH_BFD) +/* this is because newer versions of libbfd insist on seeing these, even though they aren't used for anything */ +#define PACKAGE 1 +#define PACKAGE_VERSION 1 +#include +#undef PACKAGE +#undef PACKAGE_VERSION +#endif + +#if defined(WITH_BFD) +struct sym_info_bfd; +void lookup_addr_bfd(const char *obj_file_name, sym_info_bfd &info); + +struct sym_info_bfd { + bfd_vma addr; + bfd *abfd; + asymbol **syms; + long sym_count; + const char *file_name; + const char *function_name; + bfd_vma function_addr; + unsigned int line; + bool found; + + sym_info_bfd(bfd_vma addr_); + ~sym_info_bfd(); +}; + +#endif + +#endif /* CRASHLOG_BFD_H */ diff --git a/src/os/unix/crashlog_unix.cpp b/src/os/unix/crashlog_unix.cpp index 803433427e..1e1354c003 100644 --- a/src/os/unix/crashlog_unix.cpp +++ b/src/os/unix/crashlog_unix.cpp @@ -11,6 +11,7 @@ #include "../../stdafx.h" #include "../../crashlog.h" +#include "../../crashlog_bfd.h" #include "../../string_func.h" #include "../../gamelog.h" #include "../../saveload/saveload.h" @@ -42,73 +43,6 @@ #include "../../safeguards.h" -#if defined(WITH_BFD) -struct line_info { - bfd_vma addr; - bfd *abfd; - asymbol **syms; - long sym_count; - const char *file_name; - const char *function_name; - bfd_vma function_addr; - unsigned int line; - bool found; - - line_info(bfd_vma addr_) : addr(addr_), abfd(NULL), syms(NULL), sym_count(0), - file_name(NULL), function_name(NULL), function_addr(0), line(0), found(false) {} - - ~line_info() - { - free(syms); - if (abfd != NULL) bfd_close(abfd); - } -}; - -static void find_address_in_section(bfd *abfd, asection *section, void *data) -{ - line_info *info = static_cast(data); - if (info->found) return; - - if ((bfd_get_section_flags(abfd, section) & SEC_ALLOC) == 0) return; - - bfd_vma vma = bfd_get_section_vma(abfd, section); - if (info->addr < vma) return; - - bfd_size_type size = bfd_section_size(abfd, section); - if (info->addr >= vma + size) return; - - info->found = bfd_find_nearest_line(abfd, section, info->syms, info->addr - vma, - &(info->file_name), &(info->function_name), &(info->line)); - - if (info->found) { - for (long i = 0; i < info->sym_count; i++) { - asymbol *sym = info->syms[i]; - if (sym->flags & (BSF_LOCAL | BSF_GLOBAL) && strcmp(sym->name, info->function_name) == 0) { - info->function_addr = sym->value + vma; - } - } - } -} - -void lookup_addr_bfd(const char *obj_file_name, line_info &info) -{ - info.abfd = bfd_openr(obj_file_name, NULL); - - if (info.abfd == NULL) return; - - if (!bfd_check_format(info.abfd, bfd_object) || (bfd_get_file_flags(info.abfd) & HAS_SYMS) == 0) return; - - unsigned int size; - info.sym_count = bfd_read_minisymbols(info.abfd, false, (void**) &(info.syms), &size); - if (info.sym_count <= 0) { - info.sym_count = bfd_read_minisymbols(info.abfd, true, (void**) &(info.syms), &size); - } - if (info.sym_count <= 0) return; - - bfd_map_over_sections(info.abfd, find_address_in_section, &info); -} -#endif - /** * Unix implementation for the crash logger. */ @@ -202,7 +136,7 @@ class CrashLogUnix : public CrashLog { unsigned int line_num = 0; #if defined(WITH_BFD) /* subtract one to get the line before the return address, i.e. the function call line */ - line_info bfd_info(reinterpret_cast(trace[i]) - 1); + sym_info_bfd bfd_info(reinterpret_cast(trace[i]) - 1); if (dladdr_result && info.dli_fname) { lookup_addr_bfd(info.dli_fname, bfd_info); if (bfd_info.file_name != NULL) file_name = bfd_info.file_name; diff --git a/src/os/windows/crashlog_win.cpp b/src/os/windows/crashlog_win.cpp index 484e4ee3fc..461c0d27a5 100644 --- a/src/os/windows/crashlog_win.cpp +++ b/src/os/windows/crashlog_win.cpp @@ -11,6 +11,7 @@ #include "../../stdafx.h" #include "../../crashlog.h" +#include "../../crashlog_bfd.h" #include "win32.h" #include "../../core/alloc_func.hpp" #include "../../core/math_func.hpp" @@ -20,6 +21,9 @@ #include "../../gamelog.h" #include "../../saveload/saveload.h" #include "../../video/video_driver.hpp" +#if defined(WITH_DEMANGLE) +#include +#endif #include #include @@ -413,11 +417,13 @@ char *CrashLogWindows::AppendDecodedStacktrace(char *buffer, const char *last) c /* Get module name. */ const char *mod_name = "???"; + const char *image_name = NULL; IMAGEHLP_MODULE64 module; module.SizeOfStruct = sizeof(module); if (proc.pSymGetModuleInfo64(hCur, frame.AddrPC.Offset, &module)) { mod_name = module.ModuleName; + image_name = module.ImageName; } /* Print module and instruction pointer. */ @@ -434,6 +440,35 @@ char *CrashLogWindows::AppendDecodedStacktrace(char *buffer, const char *last) c if (proc.pSymGetLineFromAddr64(hCur, frame.AddrPC.Offset, &line_offs, &line)) { buffer += seprintf(buffer, last, " (%s:%d)", line.FileName, line.LineNumber); } + } else if (image_name != NULL) { +#if defined (WITH_BFD) + /* subtract one to get the line before the return address, i.e. the function call line */ + sym_info_bfd bfd_info(reinterpret_cast(frame.AddrPC.Offset) - 1); + lookup_addr_bfd(image_name, bfd_info); + if (bfd_info.function_name != NULL) { + const char *func_name = bfd_info.function_name; +#if defined(WITH_DEMANGLE) + int status = -1; + char *demangled = abi::__cxa_demangle(func_name, NULL, 0, &status); + if (demangled != NULL && status == 0) { + func_name = demangled; + } +#endif + bool symbol_ok = strncmp(func_name, ".rdata$", 7) != 0 && strncmp(func_name, ".debug_loc", 10) != 0; + if (symbol_ok) { + buffer += seprintf(buffer, last, " %s", func_name); + } +#if defined(WITH_DEMANGLE) + free(demangled); +#endif + if (symbol_ok && bfd_info.function_addr) { + buffer += seprintf(buffer, last, " + %I64u", reinterpret_cast(frame.AddrPC.Offset) - bfd_info.function_addr); + } + } + if (bfd_info.file_name != NULL) { + buffer += seprintf(buffer, last, " (%s:%d)", bfd_info.file_name, bfd_info.line); + } +#endif } buffer += seprintf(buffer, last, "\n"); } From b9f57654150bc9e5b2e60b65e26d4eecd56e3ac1 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 9 Sep 2015 02:29:19 +0100 Subject: [PATCH 6/8] Use `rm -f` instead of `rm` for removing configure build temporaries. --- config.lib | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.lib b/config.lib index a1f5ac6a08..a428d3c116 100644 --- a/config.lib +++ b/config.lib @@ -1569,7 +1569,7 @@ EOL LIBS="$LIBS -ldl" CFLAGS="$CFLAGS -DWITH_DL" fi - rm tmp.config.libdl + rm -f tmp.config.libdl "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.bfd -x c++ - -lbfd 2> /dev/null << EOL #define PACKAGE 1 @@ -1593,7 +1593,7 @@ EOL CFLAGS="$CFLAGS -g1" fi fi - rm tmp.config.bfd + rm -f tmp.config.bfd fi if [ "$os" = "MINGW" ]; then @@ -1657,7 +1657,7 @@ EOL log 1 "checking abi::__cxa_demangle... found" CFLAGS="$CFLAGS -DWITH_DEMANGLE" fi - rm tmp.config.demangle + rm -f tmp.config.demangle fi if [ "$os" != "CYGWIN" ] && [ "$os" != "HAIKU" ] && [ "$os" != "MINGW" ] && [ "$os" != "DOS" ] && [ "$os" != "WINCE" ]; then From eaf9aa6571e62bff7bc52b8647a7cef6b682f95c Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 9 Sep 2015 18:28:03 +0100 Subject: [PATCH 7/8] Unix crash log: handle SIGSEGVs while backtracing. Add documentation. --- src/os/unix/crashlog_unix.cpp | 78 ++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/src/os/unix/crashlog_unix.cpp b/src/os/unix/crashlog_unix.cpp index 1e1354c003..7d04b641cb 100644 --- a/src/os/unix/crashlog_unix.cpp +++ b/src/os/unix/crashlog_unix.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #if defined(__GLIBC__) /* Execinfo (and thus making stacktraces) is a GNU extension */ @@ -43,6 +44,21 @@ #include "../../safeguards.h" +#if defined(__GLIBC__) && defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) +#pragma GCC diagnostic ignored "-Wclobbered" +#endif + +#if defined(__GLIBC__) +static char *logStacktraceSavedBuffer; +static jmp_buf logStacktraceJmpBuf; + +static void LogStacktraceSigSegvHandler(int sig) +{ + signal(SIGSEGV, SIG_DFL); + longjmp(logStacktraceJmpBuf, 1); +} +#endif + /** * Unix implementation for the crash logger. */ @@ -115,17 +131,60 @@ class CrashLogUnix : public CrashLog { } #endif + /** + * Get a stack backtrace of the current thread's stack. + * + * This has several modes/options, the most full-featured/complex of which is GLIBC mode. + * + * This gets the backtrace using backtrace() and backtrace_symbols(). + * backtrace() is prone to crashing if the stack is invalid. + * Also these functions freely use malloc which is not technically OK in a signal handler, as + * malloc is not re-entrant. + * For that reason, set up another SIGSEGV handler to handle the case where we trigger a SIGSEGV + * during the course of getting the backtrace. + * + * If libdl is present, try to use that to get the section file name and possibly the symbol + * name/address instead of using the string from backtrace_symbols(). + * If libdl and libbfd are present, try to use that to get the symbol name/address using the + * section file name returned from libdl. This is becuase libbfd also does line numbers, + * and knows about more symbols than libdl does. + * If demangling support is available, try to demangle whatever symbol name we got back. + * If we could find a symbol address from libdl or libbfd, show the offset from that to the frame address. + * + * Note that GCC complains about 'buffer' being clobbered by the longjmp. + * This is not an issue as we save/restore it explicitly, so silence the warning. + */ /* virtual */ char *LogStacktrace(char *buffer, const char *last) const { buffer += seprintf(buffer, last, "Stacktrace:\n"); + #if defined(__GLIBC__) -#if defined(WITH_BFD) - bfd_init(); -#endif + logStacktraceSavedBuffer = buffer; + + if (setjmp(logStacktraceJmpBuf) != 0) { + buffer = logStacktraceSavedBuffer; + buffer += seprintf(buffer, last, "\nSomething went seriously wrong when attempting to decode the stacktrace (SIGSEGV in signal handler)\n"); + buffer += seprintf(buffer, last, "This is probably due to either: a crash caused by an attempt to call an invalid function\n"); + buffer += seprintf(buffer, last, "pointer, some form of stack corruption, or an attempt was made to call malloc recursively.\n\n"); + return buffer; + } + + signal(SIGSEGV, LogStacktraceSigSegvHandler); + sigset_t sigs; + sigset_t oldsigs; + sigemptyset(&sigs); + sigaddset(&sigs, SIGSEGV); + sigprocmask(SIG_UNBLOCK, &sigs, &oldsigs); + void *trace[64]; int trace_size = backtrace(trace, lengthof(trace)); char **messages = backtrace_symbols(trace, trace_size); + +#if defined(WITH_BFD) + bfd_init(); +#endif /* WITH_BFD */ + for (int i = 0; i < trace_size; i++) { #if defined(WITH_DL) Dl_info info; @@ -144,7 +203,7 @@ class CrashLogUnix : public CrashLog { if (bfd_info.function_addr != 0) func_addr = reinterpret_cast(bfd_info.function_addr); line_num = bfd_info.line; } -#endif +#endif /* WITH_BFD */ bool ok = true; const int ptr_str_size = (2 + sizeof(void*) * 2); if (dladdr_result && func_name) { @@ -152,7 +211,7 @@ class CrashLogUnix : public CrashLog { char *demangled = NULL; #if defined(WITH_DEMANGLE) demangled = abi::__cxa_demangle(func_name, NULL, 0, &status); -#endif +#endif /* WITH_DEMANGLE */ const char *name = (demangled != NULL && status == 0) ? demangled : func_name; buffer += seprintf(buffer, last, " [%02i] %*p %-40s %s + 0x%zx\n", i, ptr_str_size, trace[i], info.dli_fname, name, (char *)trace[i] - (char *)func_addr); @@ -167,10 +226,15 @@ class CrashLogUnix : public CrashLog { buffer += seprintf(buffer, last, "%*s%s:%u\n", 7 + ptr_str_size, "", file_name, line_num); } if (ok) continue; -#endif +#endif /* WITH_DL */ buffer += seprintf(buffer, last, " [%02i] %s\n", i, messages[i]); } free(messages); + + signal(SIGSEGV, SIG_DFL); + sigprocmask(SIG_SETMASK, &oldsigs, NULL); + +/* end of __GLIBC__ */ #elif defined(SUNOS) ucontext_t uc; if (getcontext(&uc) != 0) { @@ -180,6 +244,8 @@ class CrashLogUnix : public CrashLog { StackWalkerParams wp = { &buffer, last, 0 }; walkcontext(&uc, &CrashLogUnix::SunOSStackWalker, &wp); + +/* end of SUNOS */ #else buffer += seprintf(buffer, last, " Not supported.\n"); #endif From d7853db2fd84b49f5e48649f74669272f4ef3fb6 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 9 Sep 2015 20:19:26 +0100 Subject: [PATCH 8/8] Add configure switches for libbfd and bfd extra debug info. --- config.lib | 98 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/config.lib b/config.lib index a428d3c116..eda27564f8 100644 --- a/config.lib +++ b/config.lib @@ -95,6 +95,8 @@ set_default() { with_nforenum="1" with_grfcodec="1" with_sse="1" + with_libbfd="1" + with_bfd_extra_debug="1" save_params_array=" build @@ -172,6 +174,8 @@ set_default() { with_grfcodec with_nforenum with_sse + with_libbfd + with_bfd_extra_debug CC CXX CFLAGS CXXFLAGS LDFLAGS CFLAGS_BUILD CXXFLAGS_BUILD LDFLAGS_BUILD" } @@ -465,6 +469,14 @@ detect_params() { --with-sse) with_sse="1";; --with-sse=*) with_sse="$optarg";; + --without-libbfd) with_libbfd="0";; + --with-libbfd) with_libbfd="1";; + --with-libbfd=*) with_libbfd="$optarg";; + + --without-bfd-extra-debug) with_bfd_extra_debug="0";; + --with-bfd-extra-debug) with_bfd_extra_debug="1";; + --with-bfd-extra-debug=*) with_bfd_extra_debug="$optarg";; + CC=* | --CC=*) CC="$optarg";; CXX=* | --CXX=*) CXX="$optarg";; CFLAGS=* | --CFLAGS=*) CFLAGS="$optarg";; @@ -1571,29 +1583,31 @@ EOL fi rm -f tmp.config.libdl - "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.bfd -x c++ - -lbfd 2> /dev/null << EOL - #define PACKAGE 1 - #define PACKAGE_VERSION 1 - #include - int main() { - bfd_init(); - unsigned int size; - asymbol *syms = 0; - long symcount = bfd_read_minisymbols((bfd *) 0, false, (void**) &syms, &size); - return 0; - } + if [ "$with_libbfd" = "1" ]; then + "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.bfd -x c++ - -lbfd 2> /dev/null << EOL + #define PACKAGE 1 + #define PACKAGE_VERSION 1 + #include + int main() { + bfd_init(); + unsigned int size; + asymbol *syms = 0; + long symcount = bfd_read_minisymbols((bfd *) 0, false, (void**) &syms, &size); + return 0; + } EOL - if [ $? -ne 0 ]; then - log 1 "checking libbfd... no" - else - log 1 "checking libbfd... found" - LIBS="$LIBS -lbfd" - CFLAGS="$CFLAGS -DWITH_BFD" - if [ $enable_debug -lt 1 ]; then - CFLAGS="$CFLAGS -g1" + if [ $? -ne 0 ]; then + log 1 "checking libbfd... no" + else + log 1 "checking libbfd... found" + LIBS="$LIBS -lbfd" + CFLAGS="$CFLAGS -DWITH_BFD" + if [ $enable_debug -lt 1 ] && [ "$with_bfd_extra_debug" = "1" ]; then + CFLAGS="$CFLAGS -g1" + fi fi + rm -f tmp.config.bfd fi - rm -f tmp.config.bfd fi if [ "$os" = "MINGW" ]; then @@ -1614,30 +1628,32 @@ EOL log 1 "checking dbghelp... found" CFLAGS="$CFLAGS -DWITH_DBGHELP" - "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.bfd -x c++ - -lbfd -liberty -lintl 2> /dev/null << EOL - #define PACKAGE 1 - #define PACKAGE_VERSION 1 - #include - int main() { - bfd_init(); - unsigned int size; - asymbol *syms = 0; - long symcount = bfd_read_minisymbols((bfd *) 0, false, (void**) &syms, &size); - return 0; - } + if [ "$with_libbfd" = "1" ]; then + "$cc_host" $CFLAGS $LDFLAGS -o tmp.config.bfd -x c++ - -lbfd -liberty -lintl 2> /dev/null << EOL + #define PACKAGE 1 + #define PACKAGE_VERSION 1 + #include + int main() { + bfd_init(); + unsigned int size; + asymbol *syms = 0; + long symcount = bfd_read_minisymbols((bfd *) 0, false, (void**) &syms, &size); + return 0; + } EOL - if [ $? -ne 0 ]; then - log 1 "checking libbfd... no" - else - log 1 "checking libbfd... found" - LIBS="$LIBS -lbfd -liberty -lintl" - CFLAGS="$CFLAGS -DWITH_BFD" + if [ $? -ne 0 ]; then + log 1 "checking libbfd... no" + else + log 1 "checking libbfd... found" + LIBS="$LIBS -lbfd -liberty -lintl" + CFLAGS="$CFLAGS -DWITH_BFD" - if [ $enable_debug -lt 1 ]; then - CFLAGS="$CFLAGS -g1" + if [ $enable_debug -lt 1 ] && [ "$with_bfd_extra_debug" = "1" ]; then + CFLAGS="$CFLAGS -g1" + fi fi + rm -f tmp.config.bfd fi - rm -f tmp.config.bfd fi rm -f tmp.config.dbghelp fi @@ -3652,6 +3668,8 @@ showhelp() { echo " --without-grfcodec disable usage of grfcodec and re-generation of base sets" echo " --without-threads disable threading support" echo " --without-sse disable SSE support (x86/x86_64 only)" + echo " --without-libbfd disable libbfd support, used for improved crash logs (MinGW and Unix/glibc only)" + echo " --without-bfd-extra-debug disable extra debugging information when using libbfd (MinGW and Unix/glibc only)" echo "" echo "Some influential environment variables:" echo " CC C compiler command"