Although the existing backtrace works correctly on Linux, on
FreeBSD it frequently generates a backtrace with completely
wrong function names. (FreeBSD 11.2, current latest version).
For example, making htsp_build_dvrentry crash with SEGV, it
would either not generate a stacktrace or would generate a
backtrace of:
-pthread_sigmask
-pthread_getspecific
-service_remove_unseen
-htsp_get_subscription_status
-htsp_init
-tcp_server_done
-tvhthread_create.
...instead of the correct backtrace of:
-<signal>
-htsp_build_dvrentry
-htsp_method_async
-htsp_read_loop
-htsp_serve...
So on FreeBSD only, we use libunwind to generate the
backtrace and function names. We explicitly make
libunwind and libexecinfo mutually exclusive since
FreeBSD has both.
Line are logged similar to:
CRASH: htsp_build_dvrentry+5d (ip=
11f659d sp=
7fffd8bc3930)
Note that it does not have line numbers since the addr2line
does not appear to work on FreeBSD (even with the original
backtrace code).
An example of the problem with the old backtrace code using
the frame from htsp_method_async from within the tvheadend
traphandler after the retrieval of the stack frames:
(gdb) print frames
$38 = {0x806473954, 0x806472eb2, 0x7ffffffff193, 0x11f1638 <htsp_method_async+1640>, 0x11fe400 <htsp_read_loop+880>, 0x11f58e6 <htsp_serve+502>, 0x11b9b11 <tcp_server_start+401>,
0x11af45e <thread_wrapper+302>, 0x80646dc06, 0x0 <repeats 91 times>}
(gdb) print dladdr(0x11f1638, &dli) <--- addr of htsp_method_async from frame 4.
$39 = 1 <--- success
(gdb) print dli
$40 = {dli_fname = 0x7fffffffef97 ".../build.freebsd/tvheadend", dli_fbase = 0x1021000, dli_sname = 0x1044f91 "service_remove_unseen", <--- but wrong name
dli_saddr = 0x11eff80 <service_remove_unseen>} <--- and this is nearest symbol address
(gdb) print htsp_method_async+1640
$41 = (htsmsg_t *(*)(htsp_connection_t *, htsmsg_t *)) 0x11f1638 <htsp_method_async+1640> <---but gdb knows the original address is htsp_method_async
(gdb) print service_remove_unseen
$42 = {void (const char *, int)} 0x11eff80 <service_remove_unseen> <--- and gdb knows sevice_remove_unseen is at the dli_saddr.
By contrast, with libunwind, we get:
(gdb) print buf
$50 = "htsp_method_async", '\000' <repeats 110 times> <--- libunwind detected correct function name
(gdb) where 10 <--- even though our signal has been delivered on its own stack
#0 traphandler_libunwind () at src/trap.c:162
#1 0x000000000120cf06 in traphandler (sig=11, si=0x7fffdbbdb860, UC=0x7fffdbbdb4f0) at src/trap.c:221
#2 0x0000000806673954 in ?? ()
#3 0x0000000000000000 in ?? ()
(gdb) print ip
$51 =
18814904
(gdb) disass
18814904 <--- and gdb knows that ip address is for the same method as libunwind detected
Dump of assembler code for function htsp_method_async:
0x00000000011f1150 <+0>: push %rbp
# Valiate compiler
check_cc || die 'No C compiler found'
-check_cc_header execinfo
+# We only want libunwind on non-linux plaforms where we have had bad
+# stack traces from existing implementation.
+if [ ${PLATFORM} = "freebsd" ]; then
+ check_cc_header libunwind
+ # If we don't have libunwind then fallback to execinfo.
+ if ! enabled libunwind
+ then
+ check_cc_header execinfo
+ fi
+
+else
+ check_cc_header execinfo
+fi
check_cc_option mmx
check_cc_option sse2
check_cc_optionW unused-result
fi
+if enabled libunwind; then
+ LDFLAGS="$LDFLAGS -lunwind"
+fi
#
# Gzip
#
#include <execinfo.h>
#include <dlfcn.h>
#endif
+#if ENABLE_LIBUNWIND
+#define UNW_LOCAL_ONLY
+#include <libunwind.h>
+#endif
+
#include <stdio.h>
#include <stdarg.h>
/**
*
*/
+#ifndef ENABLE_LIBUNWIND
#if ENABLE_EXECINFO
static int
addr2lineresolve(const char *binary, intptr_t addr, char *buf0, size_t buflen)
return 0;
}
#endif /* ENABLE_EXECINFO */
+#endif
-
+#if ENABLE_LIBUNWIND
+static void
+traphandler_libunwind()
+{
+ tvhlog_spawn(LOG_ALERT, LS_CRASH, "STACKTRACE");
+ unw_cursor_t cursor;
+ unw_context_t uc;
+ unw_word_t ip, sp;
+ char buf[128];
+
+ unw_getcontext(&uc);
+ unw_init_local(&cursor, &uc);
+ while (unw_step(&cursor) > 0) {
+ unw_get_reg(&cursor, UNW_REG_IP, &ip);
+ unw_get_reg(&cursor, UNW_REG_SP, &sp);
+ unw_word_t offp = 0;
+ const int pn = unw_get_proc_name(&cursor, buf, sizeof buf, &offp);
+ if (pn) *buf=0;
+ tvhlog_spawn (LOG_ALERT, LS_CRASH, "%s+%lx (ip=%lx sp=%lx)", buf, (long)offp, (long)ip, (long)sp);
+ }
+}
+#endif
static void
traphandler(int sig, siginfo_t *si, void *UC)
#endif
tvhlog_spawn(LOG_ALERT, LS_CRASH, "%s", tmpbuf);
+ /* Although libunwind is supported on Linux, our existing unwind
+ * handler is better (supplies line numbers). But, on FreeBSD, the
+ * libunwind provides better diagnostics since our existing unwind
+ * handler often terminates without a trace, or does not provide
+ * correct function name mapping.
+ */
+#if ENABLE_LIBUNWIND
+ traphandler_libunwind();
+ return;
+#endif
+
#if ENABLE_EXECINFO
tvhlog_spawn(LOG_ALERT, LS_CRASH, "STACKTRACE");