diff options
Diffstat (limited to 'src/lib/backtrace-string.c')
-rw-r--r-- | src/lib/backtrace-string.c | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/src/lib/backtrace-string.c b/src/lib/backtrace-string.c new file mode 100644 index 0000000..2ac90ec --- /dev/null +++ b/src/lib/backtrace-string.c @@ -0,0 +1,156 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "backtrace-string.h" + +#define MAX_STACK_SIZE 30 +#define BACKTRACE_SKIP_PREFIX "backtrace_" + +#if defined(HAVE_LIBUNWIND) + +#include <libunwind.h> + +static int backtrace_append_unwind(string_t *str) +{ + size_t str_orig_size = str_len(str); + char proc_name[256]; + int ret; + unsigned int fp = 0; + unw_cursor_t c; + unw_context_t ctx; + unw_proc_info_t pip; + bool success = FALSE; + + if ((ret = unw_getcontext(&ctx)) != 0) { + str_printfa(str, "unw_getcontext() failed: %d", ret); + return -1; + } + if ((ret = unw_init_local(&c, &ctx)) != 0) { + str_printfa(str, "unw_init_local() failed: %d", ret); + return -1; + } + + do { + str_printfa(str, "#%d ", fp); + if ((ret = unw_get_proc_info(&c, &pip)) != 0) { + str_printfa(str, "[unw_get_proc_info_failed(): %d]", ret); + } else if (pip.start_ip == 0 || pip.end_ip == 0) { + str_append(str, "[no start/end information]"); + } else if ((ret = unw_get_proc_name(&c, proc_name, sizeof(proc_name), 0)) != 0 && + ret != UNW_ENOMEM) { + str_printfa(str, "[unw_get_proc_name() failed: %d]", ret); + } else if (!success && str_begins(proc_name, BACKTRACE_SKIP_PREFIX)) { + str_truncate(str, str_orig_size); + continue; + } else { + str_append_max(str, proc_name, sizeof(proc_name)); + str_printfa(str, "[0x%08zx]", pip.start_ip); + success = TRUE; + } + str_append(str, " -> "); + fp++; + } while ((ret = unw_step(&c)) > 0); + + /* remove ' -> ' */ + if (str->used > 4) + str_truncate(str, str->used - 4); + return ret == 0 && success ? 0 : -1; +} +#endif + +#if defined(HAVE_BACKTRACE_SYMBOLS) && defined(HAVE_EXECINFO_H) +/* Linux */ +#include <execinfo.h> + +static int backtrace_append_libc(string_t *str) +{ + size_t str_orig_size = str_len(str); + void *stack[MAX_STACK_SIZE]; + char **strings; + int ret, i; + + ret = backtrace(stack, N_ELEMENTS(stack)); + if (ret <= 0) + return -1; + + strings = backtrace_symbols(stack, ret); + for (i = 0; i < ret; i++) { + if (str_len(str) > str_orig_size) + str_append(str, " -> "); + + if (strings == NULL) { + /* out of memory case */ + str_printfa(str, "0x%p", stack[i]); + } else if (str_len(str) != str_orig_size || + !str_begins(strings[i], BACKTRACE_SKIP_PREFIX)) + str_append(str, strings[i]); + } + free(strings); + return 0; +} +#elif defined(HAVE_WALKCONTEXT) && defined(HAVE_UCONTEXT_H) +/* Solaris */ +#include <ucontext.h> + +struct walk_context { + string_t *str; + unsigned int pos; +}; + +static int walk_callback(uintptr_t ptr, int signo ATTR_UNUSED, + void *context) +{ + struct walk_context *ctx = context; + + if (ctx->pos > 0) + str_append(ctx->str, " -> "); + str_printfa(ctx->str, "0x%p", (void *)ptr); + ctx->pos++; + return 0; +} + +static int backtrace_append_libc(string_t *str) +{ + ucontext_t uc; + struct walk_context ctx; + + if (getcontext(&uc) < 0) + return -1; + + ctx.str = str; + ctx.pos = 0; + walkcontext(&uc, walk_callback, &ctx); + return 0; +} +#else +static int backtrace_append_libc(string_t *str ATTR_UNUSED) +{ + return -1; +} +#endif + +int backtrace_append(string_t *str) +{ +#if defined(HAVE_LIBUNWIND) + size_t orig_len = str_len(str); + if (backtrace_append_unwind(str) == 0) + return 0; + /* failed to get useful backtrace. libc's own method is likely + better. */ + str_truncate(str, orig_len); +#endif + return backtrace_append_libc(str); +} + +int backtrace_get(const char **backtrace_r) +{ + string_t *str; + + str = t_str_new(512); + if (backtrace_append(str) < 0) + return -1; + + *backtrace_r = str_c(str); + return 0; +} |