From 714ef6740c850209cadffb7c70e8b6d6ed5c37a9 Mon Sep 17 00:00:00 2001 From: Arran Cudbard-Bell Date: Sun, 13 Jul 2025 18:21:41 -0600 Subject: [PATCH] Add support for libbacktrace --- Make.inc.in | 2 + configure | 43 ++++ configure.ac | 43 ++++ src/include/autoconf.h.in | 3 + src/lib/util/backtrace.c | 349 +++++++++++++++++++++++++++++ src/lib/util/backtrace.h | 45 ++++ src/lib/util/debug.c | 213 +----------------- src/lib/util/debug.h | 10 +- src/lib/util/libfreeradius-util.mk | 33 ++- 9 files changed, 523 insertions(+), 218 deletions(-) create mode 100644 src/lib/util/backtrace.c create mode 100644 src/lib/util/backtrace.h diff --git a/Make.inc.in b/Make.inc.in index e00a9f8c9a..107fb15d3d 100644 --- a/Make.inc.in +++ b/Make.inc.in @@ -181,6 +181,8 @@ LIBFREERADIUS_SERVER := libfreeradius-internal.a libfreeradius-tls.a endif LIBFREERADIUS_SERVER += libfreeradius-server.a libfreeradius-unlang.a +WITH_BACKTRACE = @WITH_BACKTRACE@ + ifneq ($(WITH_OPENSSL_MD5),) WITH_OPENSSL_DIGEST = 1 CFLAGS += -DWITH_OPENSSL_MD5 diff --git a/configure b/configure index bf2cca4066..082af199d1 100755 --- a/configure +++ b/configure @@ -665,6 +665,7 @@ OPENSSL_LIBS CPP LIBREADLINE_PREFIX LIBREADLINE +WITH_BACKTRACE KQUEUE_LDFLAGS KQUEUE_LIBS TALLOC_LDFLAGS @@ -830,6 +831,7 @@ with_systemd_include_dir with_talloc_lib_dir with_talloc_include_dir with_regex +with_libbacktrace with_libcap enable_year2038 ' @@ -1566,6 +1568,7 @@ Optional Packages: available(default=yes) --with-pcap use pcap library for the RADIUS sniffer. (default=yes) --with-collectdclient use collectd client. (default=yes) + --with-libbacktrace use libbacktrace to print backtraces with line numbers. (default=yes) --with-libcap use libcap for debugger checks. (default=yes) Some influential environment variables: @@ -3319,6 +3322,7 @@ else allow_core_dumps="no" fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking build commit" >&5 printf %s "checking build commit... " >&6; } RADIUSD_VERSION_COMMIT=`./version.sh commit` @@ -5896,6 +5900,7 @@ then : *) fuzzer=yes CFLAGS="$CFLAGS -g3" + ;; esac fi @@ -8952,6 +8957,44 @@ printf "%s\n" "$as_me: WARNING: collectdclient library not found. Use --with-col LIBS="${old_LIBS}" fi + +# Check whether --with-libbacktrace was given. +if test ${with_libbacktrace+y} +then : + withval=$with_libbacktrace; case "$withval" in + no) + WITH_BACKTRACE=no + ;; + yes) + if ! test -f src/lib/backtrace/configure || git submodule update --init src/lib/backtrace > /dev/null 2>&1; then + as_fn_error $? "--with-libbacktrace=yes requires the backtrace submodule to be checked out. Please run 'git submodule update --init src/lib/backtrace'." "$LINENO" 5 + fi + WITH_BACKTRACE=yes + ;; + *) + if test -f src/lib/backtrace/configure; then + WITH_BACKTRACE=yes + else + WITH_BACKTRACE=no + fi + esac + +fi + + +if test "x$WITH_BACKTRACE" = xyes; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: building with libbacktrace, this will be statically linked" >&5 +printf "%s\n" "$as_me: building with libbacktrace, this will be statically linked" >&6;} + +printf "%s\n" "#define HAVE_BACKTRACE 1" >>confdefs.h + + WITH_BACKTRACE=$WITH_BACKTRACE + +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: building without libbacktrace" >&5 +printf "%s\n" "$as_me: building without libbacktrace" >&6;} +fi + WITH_LIBCAP=yes # Check whether --with-libcap was given. diff --git a/configure.ac b/configure.ac index 3e4b2224bb..5b5b9f6a98 100644 --- a/configure.ac +++ b/configure.ac @@ -1190,6 +1190,49 @@ if test "x$WITH_COLLECTDCLIENT" = xyes; then LIBS="${old_LIBS}" fi +dnl # +dnl # extra argument: --with-libbacktrace +dnl # +AC_ARG_WITH(libbacktrace, +[ --with-libbacktrace use libbacktrace to print backtraces with line numbers. (default=yes)], +[ case "$withval" in + no) + WITH_BACKTRACE=no + ;; + yes) + if ! test -f src/lib/backtrace/configure || git submodule update --init src/lib/backtrace > /dev/null 2>&1; then + AC_MSG_ERROR([--with-libbacktrace=yes requires the backtrace submodule to be checked out. Please run 'git submodule update --init src/lib/backtrace'.]) + fi + WITH_BACKTRACE=yes + ;; + dnl # Build based on whether the submodule was checked out + *) + if test -f src/lib/backtrace/configure; then + WITH_BACKTRACE=yes + else + WITH_BACKTRACE=no + fi + esac ] +) + +dnl # +dnl # Check for libbacktrace +dnl # +dnl # This is a weird one because it's intended to be bundled with the code that +dnl # uses it and statically linked. There's no guarantee the user checked out +dnl # or was able to check out the submodule though, so we still need to fail +dnl # gracefully. +dnl # +if test "x$WITH_BACKTRACE" = xyes; then + AC_MSG_NOTICE([building with libbacktrace, this will be statically linked]) + AC_DEFINE(HAVE_BACKTRACE, 1, + [Define to 1 if you have the `backtrace' library (-lbacktrace).] + ) + AC_SUBST([WITH_BACKTRACE], $WITH_BACKTRACE) +else + AC_MSG_NOTICE([building without libbacktrace]) +fi + dnl # dnl # extra argument: --with-libcap dnl # diff --git a/src/include/autoconf.h.in b/src/include/autoconf.h.in index dd4322b3e0..808e76065c 100644 --- a/src/include/autoconf.h.in +++ b/src/include/autoconf.h.in @@ -35,6 +35,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_ARPA_INET_H +/* Define to 1 if you have the `backtrace' library (-lbacktrace). */ +#undef HAVE_BACKTRACE + /* Define to 1 if you have the 'bindat' function. */ #undef HAVE_BINDAT diff --git a/src/lib/util/backtrace.c b/src/lib/util/backtrace.c new file mode 100644 index 0000000000..b78591af42 --- /dev/null +++ b/src/lib/util/backtrace.c @@ -0,0 +1,349 @@ +#include + +#include +#include +#include +#include + +#ifdef HAVE_BACKTRACE +# include + +struct backtrace_state *backtrace_state = NULL; //!< Backtrace state for the backtrace functions + ///< This is initialised to be thread-safe, so we only need one. + +/** Used when building without libbacktrace to record frame information + */ +typedef struct { + char const *library; //!< Backtrace library name. + char const *filename; //!< Backtrace file. + char const *function; //!< Backtrace function. + bool function_guess; //!< Whether dladdr guessed the function. + //!< This is true if the function name is not in the + //!< symbol table, but was guessed from the program counter. + unsigned int lineno; //!< Backtrace line number. + unsigned int frameno; //!< Backtrace frame number. + uintptr_t pc; //!< Backtrace program counter. +} fr_bt_info_frame_t; +#elif defined(HAVE_EXECINFO) +# include +#endif + +# ifndef MAX_BT_FRAMES +# define MAX_BT_FRAMES 128 +# endif +# ifndef MAX_BT_CBUFF +# define MAX_BT_CBUFF 1048576 //!< Should be a power of 2 +# endif + +static pthread_mutex_t fr_backtrace_lock = PTHREAD_MUTEX_INITIALIZER; + +typedef struct { + void *obj; //!< Memory address of the block of allocated memory. +#ifdef HAVE_BACKTRACE + fr_bt_info_frame_t *frames[MAX_BT_FRAMES]; //!< Backtrace frame data +#else + void *frames[MAX_BT_FRAMES]; //!< Backtrace frame data +#endif + int count; //!< Number of frames stored +} fr_bt_info_t; + +struct fr_bt_marker { + void *obj; //!< Pointer to the parent object, this is our needle + //!< when we iterate over the contents of the circular buffer. + fr_fring_t *fring; //!< Where we temporarily store the backtraces +}; + +#ifdef HAVE_BACKTRACE +/** Log faults from libbacktrace + * + */ +static void _backtrace_error(UNUSED void *data, const char *msg, int errnum) +{ + FR_FAULT_LOG("Backtrace error: %s (%d)", msg, errnum); +} + +static void backtrace_info_sanitise(fr_bt_info_frame_t *info) +{ + Dl_info dl_info; + + if (dladdr((void *)info->pc, &dl_info) != 0) { + info->library = dl_info.dli_fname; + if (!info->function) { + info->function = dl_info.dli_sname; + info->function_guess = true; + } + } +} + +static void backtrace_info_print(fr_bt_info_frame_t *frame, int fd, bool trim_path) +{ + if (!frame->library && !frame->filename) { + dprintf(fd, "%u: @ 0x%lx\n", + frame->frameno, + (unsigned long)frame->pc); + return; + } + else if (!frame->filename) { + dprintf(fd, "%u: %s %s() @ 0x%lx\n", + frame->frameno, + trim_path ? fr_filename(frame->library) : frame->library, + frame->function, + (unsigned long)frame->pc); + return; + } + dprintf(fd, "%u: %s:%d %s()%s @ 0x%lx\n", + frame->frameno, + trim_path ? fr_filename_common_trim(frame->filename, TOP_SRCDIR) : frame->filename, + frame->lineno, + frame->function, + frame->function_guess ? "?" : "", + (unsigned long)frame->pc); +} + +static int _backtrace_info_record(void *data, uintptr_t pc, + const char *filename, int lineno, + const char *function) +{ + fr_bt_info_t *info = talloc_get_type_abort(data, fr_bt_info_t); + fr_bt_info_frame_t *frame; + + if (info->count >= (int)NUM_ELEMENTS(info->frames)) return 0; + + frame = talloc_zero(info, fr_bt_info_frame_t); + if (!frame) return -1; + + frame->filename = talloc_strdup(frame, filename); + frame->function = talloc_strdup(frame, function); + frame->lineno = lineno; + frame->frameno = info->count; + frame->pc = pc; + + backtrace_info_sanitise(frame); + + info->frames[info->count++] = frame; + + return 0; +} + +static void backtrace_record(fr_bt_info_t *info) +{ + backtrace_full(backtrace_state, 0, _backtrace_info_record, _backtrace_error, info); +} + +static int _backtrace_print(void *data, uintptr_t pc, + const char *filename, int lineno, + const char *function) +{ + unsigned int *frame_no = ((unsigned int *)data); + fr_bt_info_frame_t frame = { + .filename = filename, + .lineno = lineno, + .function = function, + .frameno = *frame_no, + .pc = pc, + }; + + backtrace_info_sanitise(&frame); + backtrace_info_print(&frame, fr_fault_log_fd, true); + + (*frame_no)++; + return 0; +} + +void fr_backtrace(void) +{ + unsigned int frame = 0; + + if (fr_fault_log_fd >= 0) { + FR_FAULT_LOG("Backtrace:"); + backtrace_full(backtrace_state, 0, _backtrace_print, _backtrace_error, &frame); + } +} +#elif defined(HAVE_EXECINFO) +void fr_backtrace(void) +{ + /* + * Produce a simple backtrace - They're very basic but at least give us an + * idea of the area of the code we hit the issue in. + * + * See below in fr_fault_setup() and + * https://sourceware.org/bugzilla/show_bug.cgi?id=16159 + * for why we only print backtraces in debug builds if we're using GLIBC. + */ +#if (!defined(NDEBUG) || !defined(__GNUC__)) + if (fr_fault_log_fd >= 0) { + size_t frame_count; + void *stack[MAX_BT_FRAMES]; + + frame_count = backtrace(stack, MAX_BT_FRAMES); + + FR_FAULT_LOG("Backtrace of last %zu frames:", frame_count); + + backtrace_symbols_fd(stack, frame_count, fr_fault_log_fd); + } +#endif + return; +} +#else +void fr_backtrace(void) +{ + return; +} +#endif + +#if defined(HAVE_BACKTRACE) || defined(HAVE_EXECINFO) +/** Print backtrace entry for a given object + * + * @param fring to search in. + * @param obj pointer to original object + */ +void fr_backtrace_print(fr_fring_t *fring, void *obj) +{ + fr_bt_info_t *p; + bool found = false; + + while ((p = fr_fring_next(fring))) { + if ((p->obj == obj) || !obj) { + found = true; + + fprintf(stderr, "Stacktrace for: %p\n", p->obj); +#ifdef HAVE_BACKTRACE + { + int i; + + for (i = 0; i < p->count; i++) { + backtrace_info_print(p->frames[i], fr_fault_log_fd, true); + } + } +#else + backtrace_symbols_fd(p->frames, p->count, fr_fault_log_fd); +#endif + } + } + + if (!found) { + fprintf(stderr, "No backtrace available for %p", obj); + } +} + +/** Generate a backtrace for an object + * + * If this is the first entry being inserted + */ +static int _backtrace_do(fr_bt_marker_t *marker) +{ + fr_bt_info_t *bt; + + if (!fr_cond_assert(marker->obj) || !fr_cond_assert(marker->fring)) return -1; + + bt = talloc_zero(NULL, fr_bt_info_t); + if (!bt) return -1; + + bt->obj = marker->obj; +#ifdef HAVE_BACKTRACE + +#else + bt->count = backtrace(bt->frames, MAX_BT_FRAMES); +#endif + fr_fring_overwrite(marker->fring, bt); + + return 0; +} + +/** Inserts a backtrace marker into the provided context + * + * Allows for maximum laziness and will initialise a circular buffer if one has not already been created. + * + * Code augmentation should look something like: +@verbatim + // Create a static fring pointer, the first call to backtrace_attach will initialise it + static fr_fring_t *my_obj_bt; + + my_obj_t *alloc_my_obj(TALLOC_CTX *ctx) { + my_obj_t *this; + + this = talloc(ctx, my_obj_t); + + // Attach backtrace marker to object + backtrace_attach(&my_obj_bt, this); + + return this; + } +@endverbatim + * + * Then, later when a double free occurs: +@verbatim + (gdb) call backtrace_print(&my_obj_bt, ) +@endverbatim + * + * which should print a limited backtrace to stderr. Note, this backtrace will not include any argument + * values, but should at least show the code path taken. + * + * @param fring this should be a pointer to a static *fr_fring_buffer. + * @param obj we want to generate a backtrace for. + */ +fr_bt_marker_t *fr_backtrace_attach(fr_fring_t **fring, TALLOC_CTX *obj) +{ + fr_bt_marker_t *marker; + + if (*fring == NULL) { + pthread_mutex_lock(&fr_backtrace_lock); + if (*fring == NULL) *fring = fr_fring_alloc(NULL, MAX_BT_CBUFF, true); + pthread_mutex_unlock(&fr_backtrace_lock); + } + + marker = talloc(obj, fr_bt_marker_t); + if (!marker) { + return NULL; + } + + marker->obj = (void *) obj; + marker->fring = *fring; + + fprintf(stderr, "Backtrace attached to %s %p\n", talloc_get_name(obj), obj); + /* + * Generate the backtrace for memory allocation + */ + _backtrace_do(marker); + talloc_set_destructor(marker, _backtrace_do); + + return marker; +} +#else +fr_bt_marker_t *fr_backtrace_attach(UNUSED fr_fring_t **fring, UNUSED TALLOC_CTX *obj) +{ + fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo, or libbacktrace\n"); + abort(); +} +#endif + +void fr_backtrace_init( +#ifndef HAVE_BACKTRACE + UNUSED +#endif + char const *program) +{ +#ifdef HAVE_BACKTRACE + /* + * Initialise the state for libbacktrace. As per the docs + * these resources can never be freed, and should be ignore + * in any leak tracking code. + */ + backtrace_state = backtrace_create_state(program, 1, _backtrace_error, NULL); +#elif defined(HAVE_EXECINFO) && defined(__GNUC__) && !defined(NDEBUG) + /* + * We need to pre-load lgcc_s, else we can get into a deadlock + * in fr_fault, as backtrace() attempts to dlopen it. + * + * Apparently there's a performance impact of loading lgcc_s, + * so only do it if this is a debug build. + * + * See: https://sourceware.org/bugzilla/show_bug.cgi?id=16159 + */ + { + void *stack[10]; + + backtrace(stack, 10); + } +#endif +} diff --git a/src/lib/util/backtrace.h b/src/lib/util/backtrace.h new file mode 100644 index 0000000000..bc885e92db --- /dev/null +++ b/src/lib/util/backtrace.h @@ -0,0 +1,45 @@ +#pragma once +/* + * This program 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** Functions to help with cleanup + * + * Allows for printing backtraces of memory allocations or after crashes + * + * @file lib/util/backtrace.h + * + * @copyright 2025 Arran Cudbard-Bell (a.cudbardb@freeradius.org) + */ +RCSIDH(backtrace_h, "$Id$") + +#include + +#ifdef __cplusplus +extern "C" { +#endif +typedef struct fr_bt_marker fr_bt_marker_t; + +void fr_backtrace_init(char const *program); + +void fr_backtrace_print(fr_fring_t *fring, void *obj); + +fr_bt_marker_t *fr_backtrace_attach(fr_fring_t **fring, TALLOC_CTX *obj); + +void fr_backtrace(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/lib/util/debug.c b/src/lib/util/debug.c index 2bc8e68317..f02f27e9ed 100644 --- a/src/lib/util/debug.c +++ b/src/lib/util/debug.c @@ -21,6 +21,7 @@ * @copyright 2013 The FreeRADIUS server project * @copyright 2013 Arran Cudbard-Bell (a.cudbardb@freeradius.org) */ +#include #include #include #include @@ -36,14 +37,6 @@ # include #endif -/* - * runtime backtrace functions are not POSIX but are included in - * glibc, OSX >= 10.5 and various BSDs - */ -#ifdef HAVE_EXECINFO -# include -#endif - #ifdef HAVE_SYS_PRCTL_H # include #endif @@ -70,36 +63,13 @@ #include #endif -#ifdef HAVE_EXECINFO -# ifndef MAX_BT_FRAMES -# define MAX_BT_FRAMES 128 -# endif -# ifndef MAX_BT_CBUFF -# define MAX_BT_CBUFF 1048576 //!< Should be a power of 2 -# endif - -static pthread_mutex_t fr_debug_init = PTHREAD_MUTEX_INITIALIZER; - -typedef struct { - void *obj; //!< Memory address of the block of allocated memory. - void *frames[MAX_BT_FRAMES]; //!< Backtrace frame data - int count; //!< Number of frames stored -} fr_bt_info_t; - -struct fr_bt_marker { - void *obj; //!< Pointer to the parent object, this is our needle - //!< when we iterate over the contents of the circular buffer. - fr_fring_t *fring; //!< Where we temporarily store the backtraces -}; -#endif - static char panic_action[512]; //!< The command to execute when panicking. static fr_fault_cb_t panic_cb = NULL; //!< Callback to execute whilst panicking, before the //!< panic_action. static bool dump_core; //!< Whether we should drop a core on fatal signals. -static int fr_fault_log_fd = STDERR_FILENO; //!< Where to write debug output. +int fr_fault_log_fd = STDERR_FILENO; //!< Where to write debug output. fr_debug_state_t fr_debug_state = DEBUGGER_STATE_UNKNOWN; //!< Whether we're attached to by a debugger. @@ -580,124 +550,6 @@ void fr_debug_break(bool always) } } -#ifdef HAVE_EXECINFO -/** Print backtrace entry for a given object - * - * @param fring to search in. - * @param obj pointer to original object - */ -void backtrace_print(fr_fring_t *fring, void *obj) -{ - fr_bt_info_t *p; - bool found = false; - - while ((p = fr_fring_next(fring))) { - if ((p->obj == obj) || !obj) { - found = true; - - fprintf(stderr, "Stacktrace for: %p\n", p->obj); - backtrace_symbols_fd(p->frames, p->count, STDERR_FILENO); - } - } - - if (!found) { - fprintf(stderr, "No backtrace available for %p", obj); - } -} - -/** Generate a backtrace for an object - * - * If this is the first entry being inserted - */ -int fr_backtrace_do(fr_bt_marker_t *marker) -{ - fr_bt_info_t *bt; - - if (!fr_cond_assert(marker->obj) || !fr_cond_assert(marker->fring)) return -1; - - bt = talloc_zero(NULL, fr_bt_info_t); - if (!bt) return -1; - - bt->obj = marker->obj; - bt->count = backtrace(bt->frames, MAX_BT_FRAMES); - - fr_fring_overwrite(marker->fring, bt); - - return 0; -} - -/** Inserts a backtrace marker into the provided context - * - * Allows for maximum laziness and will initialise a circular buffer if one has not already been created. - * - * Code augmentation should look something like: -@verbatim - // Create a static fringer pointer, the first call to backtrace_attach will initialise it - static fr_fring_t *my_obj_bt; - - my_obj_t *alloc_my_obj(TALLOC_CTX *ctx) { - my_obj_t *this; - - this = talloc(ctx, my_obj_t); - - // Attach backtrace marker to object - backtrace_attach(&my_obj_bt, this); - - return this; - } -@endverbatim - * - * Then, later when a double free occurs: -@verbatim - (gdb) call backtrace_print(&my_obj_bt, ) -@endverbatim - * - * which should print a limited backtrace to stderr. Note, this backtrace will not include any argument - * values, but should at least show the code path taken. - * - * @param fring this should be a pointer to a static *fr_fring_buffer. - * @param obj we want to generate a backtrace for. - */ -fr_bt_marker_t *fr_backtrace_attach(fr_fring_t **fring, TALLOC_CTX *obj) -{ - fr_bt_marker_t *marker; - - if (*fring == NULL) { - pthread_mutex_lock(&fr_debug_init); - /* Check again now we hold the mutex - eww*/ - if (*fring == NULL) *fring = fr_fring_alloc(NULL, MAX_BT_CBUFF, true); - pthread_mutex_unlock(&fr_debug_init); - } - - marker = talloc(obj, fr_bt_marker_t); - if (!marker) { - return NULL; - } - - marker->obj = (void *) obj; - marker->fring = *fring; - - fprintf(stderr, "Backtrace attached to %s %p\n", talloc_get_name(obj), obj); - /* - * Generate the backtrace for memory allocation - */ - fr_backtrace_do(marker); - talloc_set_destructor(marker, fr_backtrace_do); - - return marker; -} -#else -void backtrace_print(UNUSED fr_fring_t *fring, UNUSED void *obj) -{ - fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n"); -} -fr_bt_marker_t *fr_backtrace_attach(UNUSED fr_fring_t **fring, UNUSED TALLOC_CTX *obj) -{ - fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n"); - abort(); -} -#endif /* ifdef HAVE_EXECINFO */ - static int _panic_on_free(UNUSED char *foo) { fr_fault(SIGABRT); @@ -939,35 +791,6 @@ static int fr_fault_check_permissions(void) return 0; } -/** Split out so it can be sprinkled throughout the server and called via a debugger - * - */ -void fr_fault_backtrace(void) -{ - - /* - * Produce a simple backtrace - They're very basic but at least give us an - * idea of the area of the code we hit the issue in. - * - * See below in fr_fault_setup() and - * https://sourceware.org/bugzilla/show_bug.cgi?id=16159 - * for why we only print backtraces in debug builds if we're using GLIBC. - */ -#if defined(HAVE_EXECINFO) && (!defined(NDEBUG) || !defined(__GNUC__)) - if (fr_fault_log_fd >= 0) { - size_t frame_count; - void *stack[MAX_BT_FRAMES]; - - frame_count = backtrace(stack, MAX_BT_FRAMES); - - FR_FAULT_LOG("Backtrace of last %zu frames:", frame_count); - - backtrace_symbols_fd(stack, frame_count, fr_fault_log_fd); - } -#endif - return; -} - /** Prints a simple backtrace (if execinfo is available) and calls panic_action if set. * * @param sig caught @@ -1006,7 +829,7 @@ NEVER_RETURNS void fr_fault(int sig) */ if (panic_cb && (panic_cb(sig) < 0)) goto finish; - fr_fault_backtrace(); + fr_backtrace(); /* No panic action set... */ if (panic_action[0] == '\0') { @@ -1101,16 +924,7 @@ static void _fr_talloc_fault_simple(char const *reason) { FR_FAULT_LOG("talloc abort: %s\n", reason); -#if defined(HAVE_EXECINFO) && (!defined(NDEBUG) || !defined(__GNUC__)) - if (fr_fault_log_fd >= 0) { - size_t frame_count; - void *stack[MAX_BT_FRAMES]; - - frame_count = backtrace(stack, MAX_BT_FRAMES); - FR_FAULT_LOG("Backtrace of last %zu frames:", frame_count); - backtrace_symbols_fd(stack, frame_count, fr_fault_log_fd); - } -#endif + fr_backtrace(); abort(); } @@ -1194,7 +1008,6 @@ int fr_log_talloc_report(TALLOC_CTX const *ctx) return 0; } - static int _disable_null_tracking(UNUSED bool *p) { talloc_disable_null_tracking(); @@ -1356,23 +1169,7 @@ int fr_fault_setup(TALLOC_CTX *ctx, char const *cmd, char const *program) mallopt(M_CHECK_ACTION, 3); # endif #endif - -#if defined(HAVE_EXECINFO) && defined(__GNUC__) && !defined(NDEBUG) - /* - * We need to pre-load lgcc_s, else we can get into a deadlock - * in fr_fault, as backtrace() attempts to dlopen it. - * - * Apparently there's a performance impact of loading lgcc_s, - * so only do it if this is a debug build. - * - * See: https://sourceware.org/bugzilla/show_bug.cgi?id=16159 - */ - { - void *stack[10]; - - backtrace(stack, 10); - } -#endif + fr_backtrace_init(program); } setup = true; diff --git a/src/lib/util/debug.h b/src/lib/util/debug.h index 3b81bd497a..c9131dd710 100644 --- a/src/lib/util/debug.h +++ b/src/lib/util/debug.h @@ -44,6 +44,7 @@ typedef enum { DEBUGGER_STATE_ATTACHED = 1 //!< We can't attach, it's likely a debugger is already tracing. } fr_debug_state_t; +extern int fr_fault_log_fd; extern fr_debug_state_t fr_debug_state; #define FR_FAULT_LOG(_fmt, ...) fr_fault_log(_fmt "\n", ## __VA_ARGS__) @@ -61,7 +62,6 @@ extern fr_debug_state_t fr_debug_state; * - < 0 on failure. */ typedef int (*fr_fault_cb_t)(int signum); -typedef struct fr_bt_marker fr_bt_marker_t; int fr_get_lsan_state(void); @@ -73,12 +73,6 @@ char const *fr_debug_state_to_msg(fr_debug_state_t state); void fr_debug_break(bool always); -void backtrace_print(fr_fring_t *fring, void *obj); - -int fr_backtrace_do(fr_bt_marker_t *marker); - -fr_bt_marker_t *fr_backtrace_attach(fr_fring_t **fring, TALLOC_CTX *obj); - void fr_panic_on_free(TALLOC_CTX *ctx); int fr_set_dumpable_init(void); @@ -89,8 +83,6 @@ int fr_reset_dumpable(void); int fr_log_talloc_report(TALLOC_CTX const *ctx); -void fr_fault_backtrace(void); - void fr_fault(int sig); void fr_talloc_fault_setup(void); diff --git a/src/lib/util/libfreeradius-util.mk b/src/lib/util/libfreeradius-util.mk index f9e21d14e9..ff99d27c9d 100644 --- a/src/lib/util/libfreeradius-util.mk +++ b/src/lib/util/libfreeradius-util.mk @@ -12,6 +12,7 @@ endif SOURCES := \ atexit.c \ + backtrace.c \ base16.c \ base32.c \ base64.c \ @@ -112,13 +113,43 @@ endif HEADERS := $(subst src/lib/,,$(wildcard src/lib/util/*.h)) -SRC_CFLAGS := -DNO_ASSERT -I$(top_builddir)/src +SRC_CFLAGS := -DNO_ASSERT -DTOP_SRCDIR=\"${top_srcdir}\" -I$(top_builddir)/src # System libraries discovered by our top level configure script, links things # like pthread and the regexp libraries. TGT_LDLIBS := $(LIBS) $(PCAP_LIBS) TGT_LDFLAGS := $(LDFLAGS) $(PCAP_LDFLAGS) +# libbacktrace is checked out as a submodule and linked statically into libfreeradius-util +# as it's the only library that uses it. Other libraries should not use it directly but +# instead add the functionality they need to libfreeradius-util. +ifeq "$(WITH_BACKTRACE)" "yes" +HEADERS += $(top_srcdir)/src/lib/backtrace/backtrace.h +TGT_PREREQS += libbacktrace.la +TGT_LDLIBS += '-lbacktrace' +TGT_LDFLAGS += -L$(top_builddir)/build/lib/local/.libs + +# Actually call the 'sub'-make to build libbacktrace. +src/lib/backtrace/.libs/libbacktrace.a: + $(MAKE) -C $(top_srcdir)/src/lib/backtrace + +# We need to do this so jlibtool can find the library. +build/lib/.libs/libbacktrace.a: src/lib/backtrace/.libs/libbacktrace.a + cp $< $@ + +# Boilermake needs this target to exist +build/lib/libbacktrace.la: src/lib/backtrace/libbacktrace.la build/lib/.libs/libbacktrace.a + cp $< $@ + +# We need to do this so jlibtool can find the library. +build/lib/local/.libs/libbacktrace.a: src/lib/backtrace/.libs/libbacktrace.a + cp $< $@ + +# Boilermake needs this target to exist +build/lib/local/libbacktrace.la: src/lib/backtrace/libbacktrace.la build/lib/local/.libs/libbacktrace.a + cp $< $@ +endif + ifeq "$(TARGET_IS_WASM)" "yes" SRC_CFLAGS += -sMAIN_MODULE=1 -sUSE_PTHREADS=1 TGT_LDFLAGS += --no-entry -sALLOW_MEMORY_GROWTH=1 -sFORCE_FILESYSTEM=1 -sEXPORT_ALL=1 -sLINKABLE=1 -sMODULARIZE=1 -sEXPORT_ES6=1 -sEXPORT_NAME=libfreeradiusUtil -sEXPORTED_RUNTIME_METHODS=ccall,cwrap,setValue,getValue --preload-file=$(top_builddir)/share/dictionary@/share/dictionary -- 2.47.2