From 76dadff3029ef1d5203ea1949dface0e14b07ac6 Mon Sep 17 00:00:00 2001 From: Conrad Meyer Date: Sat, 26 Oct 2019 12:22:00 -0700 Subject: [PATCH] libexecinfo: Add enhanced backtrace_ctx(3) API backtrace_ctx(3) extends backtrace(3) in two ways: * Support is added for unwinding from ucontext_t contexts[1]. * Support is added for collecting stackpointers in addition to instruction pointers. The new interface is documented in the backtrace.3 manual page. Appropriate tests are added to the existing netbsd contrib tests for the library. [1]: (Re-)initializing a unw_cursor_t from a ucontext_t requires operating system and architecture-specific code. A FreeBSD implementation is provided for AMD64 and IA32 in the new signalctx.c. --- contrib/libexecinfo/backtrace.3 | 73 +++++++++++++-- contrib/libexecinfo/execinfo.h | 6 ++ contrib/libexecinfo/signalctx.c | 66 +++++++++++++ contrib/libexecinfo/signalctx.h | 4 + contrib/libexecinfo/unwind.c | 161 ++++++++++++++++++++++++++------ lib/libexecinfo/Makefile | 4 +- 6 files changed, 280 insertions(+), 34 deletions(-) create mode 100644 contrib/libexecinfo/signalctx.c create mode 100644 contrib/libexecinfo/signalctx.h diff --git a/contrib/libexecinfo/backtrace.3 b/contrib/libexecinfo/backtrace.3 index 061a68aa93a7..93fb17d29a10 100644 --- a/contrib/libexecinfo/backtrace.3 +++ b/contrib/libexecinfo/backtrace.3 @@ -28,7 +28,7 @@ .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd December 12, 2015 +.Dd October 26, 2019 .Dt BACKTRACE 3 .Os .Sh NAME @@ -40,6 +40,8 @@ .In execinfo.h .Ft size_t .Fn backtrace "void **addrlist" "size_t len" +.Ft int +.Fn backtrace_ctx "const ucontext_t *uc" "void **addrlist" "size_t *len" "int flags" .Ft "char **" .Fn backtrace_symbols "void * const *addrlist" "size_t len" .Ft int @@ -60,6 +62,44 @@ The number of frames found (which can be fewer than .Fa len ) is returned. .Pp +Similarly, +.Fn backtrace_ctx +unwinds a stack and stores per-frame metadata into the supplied +.Fa addrlist +array of length +.Fa *len . +If +.Fa uc +is +.Dv NULL , +it unwinds the caller's stack. +If +.Fa uc +is not +.Dv NULL , +it unwinds the stack described by the +.Vt ucontext_t . +If it is successful, the number of filled array elements is stored to +.Fa *len . +The following +.Fa flags +are accepted: +.Bl -tag -width ".Dv BTC_FETCH_SP" -offset indent +.It Dv BTC_FETCH_SP +If present, gather both the program counter and canonical frame address (CFA) +of each frame traversed. +The program counter is stored at +.Fa addrlist Ns [ Va frame No * Dv 2 No + Dv 0 Ns ] +and the CFA is stored at +.Fa addrlist Ns [ Va frame No * Dv 2 No + Dv 1 Ns ] . +.Fa *len +must be a multiple of two. +.Pp +Caveat: in this mode, the resulting array cannot be consumed directly by the +.Fn backtrace_symbols +family of functions. +.El +.Pp The .Fn backtrace_symbols_fmt function takes an array of previously filled addresses from @@ -118,6 +158,12 @@ on failure. The .Fn backtrace function returns the number of elements that were filled in the backtrace. +.Pp +.Fn backtrace_ctx +returns zero if successful, or an +.Va errno +(positive) or libunwind (negative) integer value on failure. +.Pp The .Fn backtrace_symbols and @@ -130,6 +176,21 @@ Diagnostic output may also be produced by the ELF symbol lookup functions. .Sh SEE ALSO .Xr dladdr 3 , .Xr elf 3 +.Sh STANDARDS +These functions are non-standard. +.Pp +A similar family of functions are provided by GNU libc. +They use +.Vt int +instead of +.Vt size_t +parameters. +.Pp +.Fn backtrace_ctx , +.Fn backtrace_symbols_fmt , +and +.Fn backtrace_symbols_fd_fmt +are BSD extensions to the GNU libc API. .Sh HISTORY The .Fn backtrace @@ -147,9 +208,9 @@ Because these functions use this is a separate library instead of being part of libc/libutil so that no library dependencies are introduced. .It -The Linux versions of the functions (there are no _fmt variants) use -.Ft int -instead of -.Ft size_t -arguments. +Currently, +.Fn backtrace_ctx +only supports unwinding +.Vt ucontext_t +contexts on AMD64 and IA-32. .El diff --git a/contrib/libexecinfo/execinfo.h b/contrib/libexecinfo/execinfo.h index 22460967e83c..bce9bd19ada9 100644 --- a/contrib/libexecinfo/execinfo.h +++ b/contrib/libexecinfo/execinfo.h @@ -35,7 +35,13 @@ #include __BEGIN_DECLS +struct __ucontext; +enum backtrace_ctx_flags { + BTC_FETCH_SP = 0x00000001, +}; + size_t backtrace(void **, size_t); +int backtrace_ctx(const struct __ucontext *, void **, size_t *, int); char **backtrace_symbols(void *const *, size_t); int backtrace_symbols_fd(void *const *, size_t, int); char **backtrace_symbols_fmt(void *const *, size_t, const char *); diff --git a/contrib/libexecinfo/signalctx.c b/contrib/libexecinfo/signalctx.c new file mode 100644 index 000000000000..6be05fa7fc97 --- /dev/null +++ b/contrib/libexecinfo/signalctx.c @@ -0,0 +1,66 @@ +#include +#include + +#include + +#include "signalctx.h" + +#define UNWRAP(x) do { \ + rc = x; \ + if (rc < 0) \ + return rc; \ +} while (0) + +int +__unw_cursor_reinit_ctx(unw_cursor_t *cursor, const struct __ucontext *uc) +{ +#if defined(__amd64__) || defined(__i386__) +#define HAVE_UCONTEXT_IMPL + int rc; +#else + (void)cursor; + (void)uc; + return ENOSYS; +#endif + +#if defined(__amd64__) + + UNWRAP(unw_set_reg(cursor, UNW_X86_64_RDI, uc->uc_mcontext.mc_rdi)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_RSI, uc->uc_mcontext.mc_rsi)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_RDX, uc->uc_mcontext.mc_rdx)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_RCX, uc->uc_mcontext.mc_rcx)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_R8, uc->uc_mcontext.mc_r8)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_R9, uc->uc_mcontext.mc_r9)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_RAX, uc->uc_mcontext.mc_rax)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_RBX, uc->uc_mcontext.mc_rbx)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_RBP, uc->uc_mcontext.mc_rbp)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_R10, uc->uc_mcontext.mc_r10)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_R11, uc->uc_mcontext.mc_r11)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_R12, uc->uc_mcontext.mc_r12)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_R13, uc->uc_mcontext.mc_r13)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_R14, uc->uc_mcontext.mc_r14)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_R15, uc->uc_mcontext.mc_r15)); + UNWRAP(unw_set_reg(cursor, UNW_X86_64_RSP, uc->uc_mcontext.mc_rsp)); + UNWRAP(unw_set_reg(cursor, UNW_REG_SP, uc->uc_mcontext.mc_rsp)); + /* Do IP last, as it kicks off reprocessing. */ + UNWRAP(unw_set_reg(cursor, UNW_REG_IP, uc->uc_mcontext.mc_rip)); + +#elif defined(__i386__) + + UNWRAP(unw_set_reg(cursor, UNW_X86_EAX, uc->uc_mcontext.mc_eax)); + UNWRAP(unw_set_reg(cursor, UNW_X86_ECX, uc->uc_mcontext.mc_ecx)); + UNWRAP(unw_set_reg(cursor, UNW_X86_EDX, uc->uc_mcontext.mc_edx)); + UNWRAP(unw_set_reg(cursor, UNW_X86_EBX, uc->uc_mcontext.mc_ebx)); + UNWRAP(unw_set_reg(cursor, UNW_X86_EBP, uc->uc_mcontext.mc_ebp)); + UNWRAP(unw_set_reg(cursor, UNW_X86_ESI, uc->uc_mcontext.mc_esi)); + UNWRAP(unw_set_reg(cursor, UNW_X86_EDI, uc->uc_mcontext.mc_edi)); + + UNWRAP(unw_set_reg(cursor, UNW_REG_SP, uc->uc_mcontext.mc_esp)); + /* Do IP last, as it kicks off reprocessing. */ + UNWRAP(unw_set_reg(cursor, UNW_REG_IP, uc->uc_mcontext.mc_eip)); +#endif + +#ifdef HAVE_UCONTEXT_IMPL + return 0; +#endif +} diff --git a/contrib/libexecinfo/signalctx.h b/contrib/libexecinfo/signalctx.h new file mode 100644 index 000000000000..01c9b97ce9c5 --- /dev/null +++ b/contrib/libexecinfo/signalctx.h @@ -0,0 +1,4 @@ +#pragma once + +struct __ucontext; +int __unw_cursor_reinit_ctx(unw_cursor_t *, const struct __ucontext *); diff --git a/contrib/libexecinfo/unwind.c b/contrib/libexecinfo/unwind.c index 3b489168ebaa..fa2d7a0216a5 100644 --- a/contrib/libexecinfo/unwind.c +++ b/contrib/libexecinfo/unwind.c @@ -28,47 +28,154 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ +/* + * Some portions loosely derived from llvm-libunwind UnwindLevel1-gcc-ext.c, + * part of the LLVM Project, under the terms of the Apache License v2.0 with + * LLVM Exceptions. + */ + #include #include + +#include +#include #include -#include "unwind.h" +#include +#ifdef _LIBUNWIND_ARM_EHABI +#include +#endif + #include "execinfo.h" +#include "signalctx.h" -struct tracer_context { - void **arr; - size_t len; - size_t n; -}; +/* + * Most architectures can just use the normal libunwind APIs. ARM EHABI is + * special and requires more handholding. + */ +#ifndef _LIBUNWIND_ARM_EHABI +# define EX_INIT do { } while (0) +# define EX_PTR NULL -static _Unwind_Reason_Code -tracer(struct _Unwind_Context *ctx, void *arg) +static inline int +bt_unw_step_wrapper(void *dummy __unused, unw_cursor_t *cursor) { - struct tracer_context *t = arg; - if (t->n == (size_t)~0) { - /* Skip backtrace frame */ - t->n = 0; + return unw_step(cursor); +} + +#else /* _LIBUNWIND_ARM_EHABI */ + +/* For ARM EHABI, a fake exception is used to unwind the stack. */ +# define EX_INIT \ + _Unwind_Exception mockex = { \ + .exception_class = 0x554e574642534400, /* "UNWFBSD\0" */\ + } +# define EX_PTR (&mockex) + +static int +bt_unw_step_wrapper(_Unwind_Exception *ex, unw_cursor_t *cursor) +{ + unw_proc_info_t frameInfo; + __personality_routine handler; + + if (unw_get_proc_info(cursor, &frameInfo) != UNW_ESUCCESS) + return 0; + + // Update the pr_cache in the mock exception object. + ex->pr_cache.fnstart = frameInfo.start_ip; + ex->pr_cache.ehtp = (void *)frameInfo.unwind_info; + ex->pr_cache.additional = frameInfo.flags; + + // Get and call the personality function to unwind the frame. + handler = (void *)frameInfo.handler; + if (handler == NULL) + return 0; + if (handler(_US_VIRTUAL_UNWIND_FRAME | _US_FORCE_UNWIND, ex, + (void *)cursor) != _URC_CONTINUE_UNWIND) return 0; + return 1; +} +#endif + +static inline bool +cursor_in_backtrace(unw_cursor_t *cursor) +{ + unw_proc_info_t info; + + if (unw_get_proc_info(cursor, &info) == UNW_ESUCCESS) + return (void *)(intptr_t)info.start_ip == (void *)&backtrace; + return false; +} + +#define BT_VALID_FLAGS (BTC_FETCH_SP) +int +backtrace_ctx(const struct __ucontext *uc, void **arr, size_t *len, int flags) +{ + unw_cursor_t cursor; + unw_context_t unwc; + unw_word_t ip, sp; + size_t n; + bool firstloop; + int rc; + EX_INIT; + + if (flags & ~BT_VALID_FLAGS) + return EINVAL; + if ((flags & BTC_FETCH_SP) != 0 && *len % 2 != 0) + return EINVAL; + + rc = unw_getcontext(&unwc); + if (rc != 0) + return rc; + rc = unw_init_local(&cursor, &unwc); + if (rc != 0) + return rc; + + if (uc != NULL) { + rc = __unw_cursor_reinit_ctx(&cursor, uc); + if (rc != 0) + return rc; + } + + firstloop = true; + for (n = 0; n < *len; n++) { + /* Get next frame (skip over first frame, backtrace_ctx). */ + rc = bt_unw_step_wrapper(EX_PTR, &cursor); + if (rc <= 0) + break; + + if (firstloop) { + firstloop = false; + /* Skip 2nd frame if backtrace() wrapper called us. */ + if (cursor_in_backtrace(&cursor)) + continue; + } + + /* Fetch and store IP, and if requested, SP. */ + rc = unw_get_reg(&cursor, UNW_REG_IP, &ip); + if (rc != 0) + return rc; + arr[n++] = (void *)ip; + if (BTC_FETCH_SP) { + rc = unw_get_reg(&cursor, UNW_REG_SP, &sp); + if (rc != 0) + return rc; + arr[n++] = (void *)sp; + } } - if (t->n < t->len) - t->arr[t->n++] = (void *)_Unwind_GetIP(ctx); - else - return _URC_END_OF_STACK; - return _URC_NO_REASON; + if (n > 0) + arr[--n] = NULL; /* Skip frame below __start */ + *len = n; + return 0; } size_t backtrace(void **arr, size_t len) { - struct tracer_context ctx; - - ctx.arr = arr; - ctx.len = len; - ctx.n = (size_t)~0; - - _Unwind_Backtrace(tracer, &ctx); - if (ctx.n != (size_t)~0 && ctx.n > 0) - ctx.arr[--ctx.n] = NULL; /* Skip frame below __start */ + int rc; - return ctx.n; + rc = backtrace_ctx(NULL, arr, &len, 0); + if (rc == 0) + return len; + return 0; } diff --git a/lib/libexecinfo/Makefile b/lib/libexecinfo/Makefile index d130d5795918..d20d523617e6 100644 --- a/lib/libexecinfo/Makefile +++ b/lib/libexecinfo/Makefile @@ -10,8 +10,10 @@ SHLIB_MAJOR= 1 .PATH: ${SRCTOP}/contrib/libexecinfo INCS= execinfo.h -SRCS= backtrace.c symtab.c unwind.c +SRCS= backtrace.c symtab.c unwind.c signalctx.c CFLAGS+= -I${SRCTOP}/contrib/libunwind/include +# Must match up with libgcc_eh/Makefile.inc. +CFLAGS+= -D_LIBUNWIND_IS_NATIVE_ONLY LIBADD= elf -- 2.23.0