Use backtrace in crash handler on POSIX platforms

Non-ancient versions of GCC and clang should ship it, and in contrast
to the <execinfo.h> backtrace_symbols() it also works with
-fvisibility=hidden
This commit is contained in:
Daniel Gibson 2021-05-12 07:56:57 +02:00
parent 64b21fcd0c
commit 227fe5fc92
2 changed files with 105 additions and 3 deletions

View file

@ -198,7 +198,24 @@ if(MSVC)
message(SEND_ERROR "MFC ('Microsoft Foundation Classes for C++') couldn't be found, but is needed for TOOLS!")
message(FATAL_ERROR "If you're using VS2013, you'll also need the 'Multibyte MFC Library for Visual Studio 2013': https://www.microsoft.com/en-us/download/details.aspx?id=40770 (VS2015 and 2017 include that in the default MFC package)")
endif()
endif()
else() # not MSVC
if(NOT WIN32)
# libbacktrace support - TODO: might work with MinGW? we don't have a crash handler for win32 though..
include(CheckCSourceCompiles)
set(CMAKE_REQUIRED_LIBRARIES backtrace)
check_c_source_compiles( "#include <backtrace.h>
int main() { backtrace_create_state(NULL, 0, NULL, NULL); return 0; }" HAVE_LIBBACKTRACE )
if(HAVE_LIBBACKTRACE)
set(sys_libs ${sys_libs} backtrace)
add_definitions(-DD3_HAVE_LIBBACKTRACE)
message(STATUS "Using libbacktrace")
endif()
endif() # NOT WIN32
endif() # not MSVC
# compiler specific flags
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID STREQUAL "Clang")

View file

@ -384,11 +384,83 @@ static const int crashSigs[] = { SIGILL, SIGABRT, SIGFPE, SIGSEGV }
static const char* crashSigNames[] = { "SIGILL", "SIGABRT", "SIGFPE", "SIGSEGV" };
#if ( defined(__linux__) && defined(__GLIBC__) ) || defined(__FreeBSD__) || defined(__APPLE__)
// TODO: https://github.com/ianlancetaylor/libbacktrace looks interesting and also supports windows apparently
#define D3_HAVE_BACKTRACE
#include <execinfo.h>
#endif
#ifdef D3_HAVE_LIBBACKTRACE
// non-ancient versions of GCC and clang include libbacktrace
// for ancient versions it can be built from https://github.com/ianlancetaylor/libbacktrace
#include <backtrace.h>
#include <cxxabi.h> // for demangling C++ symbols
static struct backtrace_state *bt_state = NULL;
static void bt_error_callback( void *data, const char *msg, int errnum )
{
printf("libbacktrace ERROR: %d - %s\n", errnum, msg);
}
static void bt_syminfo_callback( void *data, uintptr_t pc, const char *symname,
uintptr_t symval, uintptr_t symsize )
{
if (symname != NULL) {
int status;
// FIXME: sucks that __cxa_demangle() insists on using malloc().. but so does printf()
char* name = abi::__cxa_demangle(symname, NULL, NULL, &status);
if (name != NULL) {
symname = name;
}
printf(" %zu %s\n", pc, symname);
free(name);
} else {
printf(" %zu (unknown symbol)\n", pc);
}
}
static int bt_pcinfo_callback( void *data, uintptr_t pc, const char *filename, int lineno, const char *function )
{
if (data != NULL) {
int* hadInfo = (int*)data;
*hadInfo = (function != NULL);
}
if (function != NULL) {
int status;
// FIXME: sucks that __cxa_demangle() insists on using malloc()..
char* name = abi::__cxa_demangle(function, NULL, NULL, &status);
if (name != NULL) {
function = name;
}
printf(" %zu %s:%d %s\n", pc, filename, lineno, function);
free(name);
}
return 0;
}
static void bt_error_dummy( void *data, const char *msg, int errnum )
{
//printf("ERROR-DUMMY: %d - %s\n", errnum, msg);
}
static int bt_simple_callback(void *data, uintptr_t pc)
{
int pcInfoWorked = 0;
// if this fails, the executable doesn't have debug info, that's ok (=> use bt_error_dummy())
backtrace_pcinfo(bt_state, pc, bt_pcinfo_callback, bt_error_dummy, &pcInfoWorked);
if (!pcInfoWorked) { // no debug info? use normal symbols instead
// yes, it would be easier to call backtrace_syminfo() in bt_pcinfo_callback() if function == NULL,
// but some libbacktrace versions (e.g. in Ubuntu 18.04's g++-7) don't call bt_pcinfo_callback
// at all if no debug info was available - which is also the reason backtrace_full() can't be used..
backtrace_syminfo(bt_state, pc, bt_syminfo_callback, bt_error_callback, NULL);
}
return 0;
}
#endif
static void signalhandlerCrash(int sig)
{
const char* name = "";
@ -402,7 +474,14 @@ static void signalhandlerCrash(int sig)
// (could use backtrace_symbols_fd() then)
printf("Looks like %s crashed with signal %s (%d) - sorry!\n", ENGINE_VERSION, name, sig);
#ifdef D3_HAVE_BACKTRACE
#ifdef D3_HAVE_LIBBACKTRACE
if (bt_state != NULL) {
int skip = 1; // skip this function in backtrace
backtrace_simple(bt_state, skip, bt_simple_callback, bt_error_callback, NULL);
} else {
printf("(No backtrace because libbacktrace state is NULL)\n");
}
#elif defined(D3_HAVE_BACKTRACE)
// this is partly based on Yamagi Quake II code
void* array[128];
int size = backtrace(array, sizeof(array)/sizeof(array[0]));
@ -465,6 +544,12 @@ static void installSigHandler(int sig, int flags, void (*handler)(int))
void Posix_InitSignalHandlers( void )
{
#ifdef D3_HAVE_LIBBACKTRACE
// can't use idStr here and thus can't use Sys_GetPath(PATH_EXE) => added Posix_GetExePath()
const char* exePath = Posix_GetExePath();
bt_state = backtrace_create_state(exePath[0] ? exePath : NULL, 0, bt_error_callback, NULL);
#endif
for(int i=0; i<sizeof(crashSigs)/sizeof(crashSigs[0]); ++i)
{
installSigHandler(crashSigs[i], SA_RESTART|SA_RESETHAND, signalhandlerCrash);