/* 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 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 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 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; }