]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add support for libbacktrace
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 14 Jul 2025 00:21:41 +0000 (18:21 -0600)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 14 Jul 2025 01:37:39 +0000 (19:37 -0600)
Make.inc.in
configure
configure.ac
src/include/autoconf.h.in
src/lib/util/backtrace.c [new file with mode: 0644]
src/lib/util/backtrace.h [new file with mode: 0644]
src/lib/util/debug.c
src/lib/util/debug.h
src/lib/util/libfreeradius-util.mk

index e00a9f8c9af2320df2fe7ce95ae3005d22a2a7cf..107fb15d3d163b06f84327a74dd539ab58cdab18 100644 (file)
@@ -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
index bf2cca40667e579a6af26642e04eb6c0be6a59d2..082af199d1b7811c64d455686dd8aa8647bc0941 100755 (executable)
--- 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.
index 3e4b2224bbef544d85dea0aabdf2001f42c5476d..5b5b9f6a988e7a041440d20714ec6794fa566c75 100644 (file)
@@ -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 #
index dd4322b3e0fa75c2b3aba7b2dd923486026cddc3..808e76065c37ac65d7c5b017fc1b8b0effb6da60 100644 (file)
@@ -35,6 +35,9 @@
 /* Define to 1 if you have the <arpa/inet.h> 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 (file)
index 0000000..b78591a
--- /dev/null
@@ -0,0 +1,349 @@
+#include <dlfcn.h>
+
+#include <freeradius-devel/util/backtrace.h>
+#include <freeradius-devel/util/debug.h>
+#include <freeradius-devel/util/fring.h>
+#include <freeradius-devel/util/misc.h>
+
+#ifdef HAVE_BACKTRACE
+#  include <freeradius-devel/backtrace/backtrace.h>
+
+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 <execinfo.h>
+#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, <pointer to double freed memory>)
+@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 (file)
index 0000000..bc885e9
--- /dev/null
@@ -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 <freeradius-devel/util/fring.h>
+
+#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
index 2bc8e68317ff160e0f816d02c28cc102e42ab8f8..f02f27e9ed990d9d3a692742b4c38b608dfa45b4 100644 (file)
@@ -21,6 +21,7 @@
  * @copyright 2013 The FreeRADIUS server project
  * @copyright 2013 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
  */
+#include <freeradius-devel/util/backtrace.h>
 #include <freeradius-devel/util/debug.h>
 #include <freeradius-devel/util/hash.h>
 #include <freeradius-devel/util/strerror.h>
 #  include <malloc.h>
 #endif
 
-/*
- *     runtime backtrace functions are not POSIX but are included in
- *     glibc, OSX >= 10.5 and various BSDs
- */
-#ifdef HAVE_EXECINFO
-#  include <execinfo.h>
-#endif
-
 #ifdef HAVE_SYS_PRCTL_H
 #  include <sys/prctl.h>
 #endif
 #include <sys/sysctl.h>
 #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, <pointer to double freed memory>)
-@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;
 
index 3b81bd497a332061c1a1d6d955ea56935f054a28..c9131dd7107b61dbfee1a141d4824cf670677673 100644 (file)
@@ -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);
index f9e21d14e95f2dc73c8d1edcff5cf7c534975f91..ff99d27c9d87acb872a8a580b4e8e3298795835d 100644 (file)
@@ -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