Support libbacktrace on Linux

We've supported libbacktrace as an optional dependency on Windows for a
while now.  At the same time, our backtrace implementation on Linux is
very inefficient.

Optionally use libbacktrace on Linux too, when requested at configure
time.  This is much less code and much faster at runtime, but we retain
the old implementation as well for the time being so that we are not
adding an additional mandatory dependency.

Currently the LIBBACKTRACE option is off by default because installing
libbacktrace is surprisingly difficult to do; most distros do not
package it.  It would be nice to enable it at least for the release
builds so that end users on Linux get faster backtraces.  That should be
possible but I'm not attempting it yet.

Using libbacktrace without debug info is useless, so enabling
LIBBACKTRACE forces -g1 even on release builds in the Makefile.
This commit is contained in:
John Bytheway 2022-07-06 13:27:07 -04:00
parent b3c3acda8d
commit 25f6c3a67d
6 changed files with 92 additions and 33 deletions

View File

@ -25,6 +25,7 @@ concurrency:
# - CMAKE=1
# - LOCALIZE=0
# - LTO=1
# - LIBBACKTRACE=1 (on Linux and Windows)
# - Tests with important mods enabled (Magiclysm)
# - A clang-tidy run
# - SANITIZE=address,undefined
@ -85,7 +86,7 @@ jobs:
cmake: 0
localize: 1
test-stage: 1
native: linux64
native: linux64
archive-success: basic-build
dont_skip_data_only_changes: 1
title: Basic Build and Test (Clang 6, Ubuntu, Curses)
@ -103,6 +104,7 @@ jobs:
localize: 1
gold: 1
lto: 1
libbacktrace: 1
native: linux64
title: GCC 9, Curses, LTO
# ~850MB in a clean build
@ -165,6 +167,7 @@ jobs:
tiles: 1
sound: 1
localize: 1
libbacktrace: 1
title: GCC 11, Ubuntu cross-compile to MinGW-Win64, Tiles, Sound
ldflags: -static-libgcc -static-libstdc++
mxe_target: x86_64-w64-mingw32.static.gcc11
@ -209,6 +212,7 @@ jobs:
NATIVE: ${{ matrix.native }}
GOLD: ${{ matrix.gold }}
LTO: ${{ matrix.lto }}
LIBBACKTRACE: ${{ matrix.libbacktrace }}
RELEASE: ${{ matrix.release }}
ARCHIVE_SUCCESS: ${{ matrix.archive-success }}
CCACHE_LIMIT: ${{ matrix.ccache_limit }}

View File

@ -40,7 +40,7 @@
# make LOCALIZE=0
# Disable backtrace support, not available on all platforms
# make BACKTRACE=0
# Use libbacktrace. Only has effect if BACKTRACE=1. (currently only for MinGW builds)
# Use libbacktrace. Only has effect if BACKTRACE=1. (currently only for MinGW and Linux builds)
# make LIBBACKTRACE=1
# Compile localization files for specified languages
# make localization LANGUAGES="<lang_id_1>[ lang_id_2][ ...]"
@ -398,7 +398,11 @@ ifeq ($(RELEASE), 1)
OTHERS += $(RELEASE_FLAGS)
DEBUG =
ifndef DEBUG_SYMBOLS
DEBUGSYMS =
ifeq ($(LIBBACKTRACE), 1)
DEBUGSYMS = -g1
else
DEBUGSYMS =
endif
endif
DEFINES += -DRELEASE
# Check for astyle or JSON regressions on release builds.
@ -780,9 +784,6 @@ ifeq ($(TARGETSYSTEM),WINDOWS)
LDFLAGS += -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lversion
ifeq ($(BACKTRACE),1)
LDFLAGS += -ldbghelp
ifeq ($(LIBBACKTRACE),1)
LDFLAGS += -lbacktrace
endif
endif
endif
@ -790,6 +791,7 @@ ifeq ($(BACKTRACE),1)
DEFINES += -DBACKTRACE
ifeq ($(LIBBACKTRACE),1)
DEFINES += -DLIBBACKTRACE
LDFLAGS += -lbacktrace
endif
endif

View File

@ -103,6 +103,7 @@ then
-DCMAKE_BUILD_TYPE="$build_type" \
-DTILES=${TILES:-0} \
-DSOUND=${SOUND:-0} \
-DLIBBACKTRACE=${LIBBACKTRACE:-0} \
"${cmake_extra_opts[@]}" \
..
if [ -n "$CATA_CLANG_TIDY" ]
@ -213,7 +214,7 @@ then
# fills the log with nonsense.
TERM=dumb ./gradlew assembleExperimentalRelease -Pj=$num_jobs -Plocalize=false -Pabi_arm_32=false -Pabi_arm_64=true -Pdeps=/home/travis/build/CleverRaven/Cataclysm-DDA/android/app/deps.zip
else
make -j "$num_jobs" RELEASE=1 CCACHE=1 CROSS="$CROSS_COMPILATION" LINTJSON=0
make -j "$num_jobs" RELEASE=1 CCACHE=1 CROSS="$CROSS_COMPILATION" LINTJSON=0 LIBBACKTRACE="$LIBBACKTRACE"
export ASAN_OPTIONS=detect_odr_violation=1
export UBSAN_OPTIONS=print_stacktrace=1

View File

@ -36,8 +36,15 @@ if [[ "$TRAVIS_EVENT_TYPE" == "pull_request" ]]; then
fi
set -x
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
$travis_retry sudo apt-get --yes install parallel
if [[ "$LIBBACKTRACE" == "1" ]]; then
git clone https://github.com/ianlancetaylor/libbacktrace.git
(
cd libbacktrace
git checkout 4d2dd0b172f2c9192f83ba93425f868f2a13c553
./configure
make -j$(nproc)
sudo make install
)
fi
if [ -n "${CODE_COVERAGE}" ]; then

View File

@ -129,12 +129,13 @@ if (TILES)
target_link_libraries(cataclysm-tiles-common setupapi.lib)
if (BACKTRACE)
target_link_libraries(cataclysm-tiles-common dbghelp.lib)
if (LIBBACKTRACE)
target_link_libraries(cataclysm-tiles-common backtrace)
endif ()
endif ()
endif ()
if (LIBBACKTRACE)
target_link_libraries(cataclysm-tiles-common backtrace)
endif ()
if (RELEASE)
install(TARGETS cataclysm-tiles DESTINATION ${BIN_PREFIX})
endif ()
@ -193,12 +194,13 @@ if (CURSES)
target_link_libraries(cataclysm-common version.lib)
if (BACKTRACE)
target_link_libraries(cataclysm-common dbghelp.lib)
if (LIBBACKTRACE)
target_link_libraries(cataclysm-common backtrace)
endif ()
endif ()
endif ()
if (LIBBACKTRACE)
target_link_libraries(cataclysm-tiles-common backtrace)
endif ()
if (RELEASE)
install(TARGETS cataclysm DESTINATION ${BIN_PREFIX})
endif ()

View File

@ -58,7 +58,6 @@
# if defined(_WIN32)
# include <dbghelp.h>
# if defined(LIBBACKTRACE)
# include <backtrace.h>
# include <winnt.h>
# endif
# elif defined(__ANDROID__)
@ -70,6 +69,10 @@
# endif
#endif
#if defined(LIBBACKTRACE)
# include <backtrace.h>
#endif
#if defined(TILES)
#include "sdl_wrappers.h"
#endif // TILES
@ -806,7 +809,7 @@ static std::ostream &operator<<( std::ostream &out, DebugClass cl )
}
#if defined(BACKTRACE)
#if !defined(_WIN32) && !defined(__CYGWIN__) && !defined(__ANDROID__)
#if !defined(_WIN32) && !defined(__CYGWIN__) && !defined(__ANDROID__) && !defined(LIBBACKTRACE)
// Verify that a string is safe for passing as an argument to addr2line.
// In particular, we want to avoid any characters of significance to the shell.
static bool debug_is_safe_string( const char *start, const char *finish )
@ -913,7 +916,7 @@ static cata::optional<uintptr_t> debug_compute_load_offset(
}
#endif
#if defined(_WIN32) && defined(LIBBACKTRACE)
#if defined(LIBBACKTRACE)
// wrap libbacktrace to use std::function instead of function pointers
using bt_error_callback = std::function<void( const char *, int )>;
using bt_full_callback = std::function<int( uintptr_t, const char *, int, const char * )>;
@ -930,6 +933,27 @@ static backtrace_state *bt_create_state( const char *const filename, const int t
const_cast<bt_error_callback *>( &cb ) );
}
#if !defined(_WIN32)
static int bt_full( backtrace_state *const state, int skip, const bt_full_callback &cb_full,
const bt_error_callback &cb_error )
{
using cb_pair = std::pair<const bt_full_callback &, const bt_error_callback &>;
cb_pair cb { cb_full, cb_error };
return backtrace_full( state, skip,
// backtrace callback
[]( void *const data, const uintptr_t pc, const char *const filename,
const int lineno, const char *const function ) -> int {
cb_pair &cb = *reinterpret_cast<cb_pair *>( data );
return cb.first( pc, filename, lineno, function );
},
// error callback
[]( void *const data, const char *const msg, const int errnum ) {
cb_pair &cb = *reinterpret_cast<cb_pair *>( data );
cb.second( msg, errnum );
},
&cb );
}
#else
static int bt_pcinfo( backtrace_state *const state, const uintptr_t pc,
const bt_full_callback &cb_full, const bt_error_callback &cb_error )
{
@ -970,6 +994,7 @@ static int bt_syminfo( backtrace_state *const state, const uintptr_t addr,
&cb );
}
#endif
#endif
#if defined(_WIN32)
class sym_init
@ -1004,12 +1029,12 @@ struct backtrace_module_info_t {
};
static std::map<DWORD64, backtrace_module_info_t> bt_module_info_map;
#endif
#elif !defined(__ANDROID__)
#elif !defined(__ANDROID__) && !defined(LIBBACKTRACE)
static constexpr int bt_cnt = 20;
static void *bt[bt_cnt];
#endif
#if !defined(_WIN32) && !defined(__ANDROID__)
#if !defined(_WIN32) && !defined(__ANDROID__) && !defined(LIBBACKTRACE)
static void write_demangled_frame( std::ostream &out, const char *frame )
{
#if defined(__linux__)
@ -1062,6 +1087,23 @@ static void write_demangled_frame( std::ostream &out, const char *frame )
#if !defined(__ANDROID__)
void debug_write_backtrace( std::ostream &out )
{
#if defined(LIBBACKTRACE)
auto bt_full_print = [&out]( const uintptr_t pc, const char *const filename,
const int lineno, const char *const function ) -> int {
std::string file = filename ? filename : "[unknown src]";
size_t src = file.find( "/src/" );
if( src != std::string::npos )
{
file.erase( 0, src );
file = "" + file;
}
out << "\n 0x" << std::hex << pc << std::dec
<< " " << file << ":" << lineno
<< " " << ( function ? demangle( function ) : "[unknown func]" );
return 0;
};
#endif
#if defined(_WIN32)
if( !sym_init_ ) {
sym_init_ = std::make_unique<sym_init>();
@ -1143,18 +1185,8 @@ void debug_write_backtrace( std::ostream &out )
<< ", msg = " << ( msg ? msg : "[no msg]" )
<< "),";
} );
bt_pcinfo( bt_module_info.state, de_aslr_pc,
// backtrace callback
[&out]( const uintptr_t pc, const char *const filename,
const int lineno, const char *const function ) -> int {
out << "\n (libbacktrace: 0x" << std::hex << pc << std::dec
<< " " << ( filename ? filename : "[unknown src]" )
<< ":" << lineno
<< " " << ( function ? function : "[unknown func]" )
<< "),";
return 0;
},
// error callback
bt_pcinfo( bt_module_info.state, de_aslr_pc, bt_full_print,
// error callback
[&out]( const char *const msg, const int errnum ) {
out << "\n (backtrace_pcinfo failed: errno = " << errnum
<< ", msg = " << ( msg ? msg : "[no msg]" )
@ -1165,7 +1197,18 @@ void debug_write_backtrace( std::ostream &out )
}
out << "\n";
#else
# if defined(__CYGWIN__)
# if defined(LIBBACKTRACE)
auto bt_error = [&out]( const char *err_msg, int errnum ) {
out << "\n libbacktrace error " << errnum << ": " << err_msg;
};
static backtrace_state *bt_state = bt_create_state( nullptr, 0, bt_error );
if( bt_state ) {
bt_full( bt_state, 0, bt_full_print, bt_error );
out << std::endl;
} else {
out << "\n\n Failed to initialize libbacktrace\n";
}
# elif defined(__CYGWIN__)
// BACKTRACE is not supported under CYGWIN!
( void ) out;
# else