summaryrefslogtreecommitdiffstats
path: root/src/lib/backtrace-string.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/backtrace-string.c')
-rw-r--r--src/lib/backtrace-string.c156
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;
+}