diff --git a/config.lib b/config.lib index 07985a4f83..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";; @@ -1554,6 +1566,115 @@ 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" $CFLAGS $LDFLAGS -o tmp.config.libdl -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 + rm -f tmp.config.libdl + + 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 ] && [ "$with_bfd_extra_debug" = "1" ]; then + CFLAGS="$CFLAGS -g1" + fi + fi + rm -f tmp.config.bfd + fi + 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" + + 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 [ $enable_debug -lt 1 ] && [ "$with_bfd_extra_debug" = "1" ]; then + CFLAGS="$CFLAGS -g1" + fi + fi + rm -f tmp.config.bfd + fi + 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 -f tmp.config.demangle + fi if [ "$os" != "CYGWIN" ] && [ "$os" != "HAIKU" ] && [ "$os" != "MINGW" ] && [ "$os" != "DOS" ] && [ "$os" != "WINCE" ]; then LIBS="$LIBS -lc" @@ -3547,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" diff --git a/source.list b/source.list index 91e8dd26b8..275ebf1569 100644 --- a/source.list +++ b/source.list @@ -170,6 +170,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 47de057f7e..7d04b641cb 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" @@ -18,10 +19,20 @@ #include #include #include +#include #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 +#if defined(WITH_BFD) +# include +#endif #elif defined(SUNOS) # include # include @@ -33,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. */ @@ -105,18 +131,110 @@ 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__) + 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; + int dladdr_result = dladdr(trace[i], &info); + 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 */ + 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; + 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 /* WITH_BFD */ + 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(func_name, NULL, 0, &status); +#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); + free(demangled); + } else if (dladdr_result && info.dli_fname) { + 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); + } 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 /* 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) { @@ -126,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 diff --git a/src/os/windows/crashlog_win.cpp b/src/os/windows/crashlog_win.cpp index 1abb0e725c..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 @@ -49,12 +53,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 +326,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 { @@ -408,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. */ @@ -429,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"); } @@ -479,7 +519,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();