/src/dovecot/src/lib/backtrace-string.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "str.h" |
5 | | #include "backtrace-string.h" |
6 | | |
7 | | #define MAX_STACK_SIZE 30 |
8 | | #define BACKTRACE_SKIP_PREFIX "backtrace_" |
9 | | |
10 | | #if defined(HAVE_LIBUNWIND) |
11 | | |
12 | | #include <libunwind.h> |
13 | | |
14 | | static int backtrace_append_unwind(string_t *str, const char **error_r) |
15 | | { |
16 | | size_t str_orig_size = str_len(str); |
17 | | char proc_name[256]; |
18 | | int ret; |
19 | | unsigned int fp = 0; |
20 | | unw_cursor_t c; |
21 | | unw_context_t ctx; |
22 | | unw_proc_info_t pip; |
23 | | bool success = FALSE; |
24 | | |
25 | | if ((ret = unw_getcontext(&ctx)) != 0) { |
26 | | *error_r = t_strdup_printf("unw_getcontext() failed: %d", ret); |
27 | | return -1; |
28 | | } |
29 | | if ((ret = unw_init_local(&c, &ctx)) != 0) { |
30 | | *error_r = t_strdup_printf("unw_init_local() failed: %d", ret); |
31 | | return -1; |
32 | | } |
33 | | |
34 | | do { |
35 | | str_printfa(str, "#%d ", fp); |
36 | | if ((ret = unw_get_proc_info(&c, &pip)) != 0) { |
37 | | str_printfa(str, "[unw_get_proc_info_failed(): %d]", ret); |
38 | | } else if (pip.start_ip == 0 || pip.end_ip == 0) { |
39 | | str_append(str, "[no start/end information]"); |
40 | | } else if ((ret = unw_get_proc_name(&c, proc_name, sizeof(proc_name), 0)) != 0 && |
41 | | ret != UNW_ENOMEM) { |
42 | | str_printfa(str, "[unw_get_proc_name() failed: %d]", ret); |
43 | | } else if (!success && str_begins_with(proc_name, BACKTRACE_SKIP_PREFIX)) { |
44 | | str_truncate(str, str_orig_size); |
45 | | continue; |
46 | | } else { |
47 | | str_append_max(str, proc_name, sizeof(proc_name)); |
48 | | str_printfa(str, "[0x%08zx]", pip.start_ip); |
49 | | success = TRUE; |
50 | | } |
51 | | str_append(str, " -> "); |
52 | | fp++; |
53 | | } while ((ret = unw_step(&c)) > 0); |
54 | | |
55 | | /* remove ' -> ' */ |
56 | | if (str->used > 4) |
57 | | str_truncate(str, str->used - 4); |
58 | | if (ret < 0) { |
59 | | *error_r = t_strdup_printf("unw_step() failed: %d", ret); |
60 | | return -1; |
61 | | } |
62 | | if (!success) { |
63 | | *error_r = t_strdup_printf("No symbols found (process chrooted?)"); |
64 | | return -1; |
65 | | } |
66 | | return 0; |
67 | | } |
68 | | #endif |
69 | | |
70 | | #if defined(HAVE_BACKTRACE_SYMBOLS) && defined(HAVE_EXECINFO_H) |
71 | | /* Linux */ |
72 | | #include <execinfo.h> |
73 | | |
74 | | static int backtrace_append_libc(string_t *str, const char **error_r) |
75 | 0 | { |
76 | 0 | size_t str_orig_size = str_len(str); |
77 | 0 | void *stack[MAX_STACK_SIZE]; |
78 | 0 | char **strings; |
79 | 0 | int ret, i; |
80 | |
|
81 | 0 | ret = backtrace(stack, N_ELEMENTS(stack)); |
82 | 0 | if (ret <= 0) { |
83 | 0 | *error_r = "backtrace() failed"; |
84 | 0 | return -1; |
85 | 0 | } |
86 | | |
87 | 0 | strings = backtrace_symbols(stack, ret); |
88 | 0 | if (strings == NULL) { |
89 | 0 | *error_r = "backtrace_symbols() failed"; |
90 | 0 | return -1; |
91 | 0 | } |
92 | 0 | for (i = 0; i < ret; i++) { |
93 | 0 | if (str_len(str) > str_orig_size) |
94 | 0 | str_append(str, " -> "); |
95 | |
|
96 | 0 | if (str_len(str) != str_orig_size || |
97 | 0 | !str_begins_with(strings[i], BACKTRACE_SKIP_PREFIX)) { |
98 | | /* String often contains a full path to the binary, |
99 | | followed by the function name. The path causes the |
100 | | backtrace to be excessively large and we don't |
101 | | especially care about it, so just skip over it. */ |
102 | 0 | const char *suffix = strrchr(strings[i], '/'); |
103 | 0 | if (suffix != NULL) |
104 | 0 | suffix++; |
105 | 0 | else |
106 | 0 | suffix = strings[i]; |
107 | 0 | str_append(str, suffix); |
108 | 0 | } |
109 | 0 | } |
110 | 0 | free(strings); |
111 | 0 | return 0; |
112 | 0 | } |
113 | | #elif defined(HAVE_WALKCONTEXT) && defined(HAVE_UCONTEXT_H) |
114 | | /* Solaris */ |
115 | | #include <ucontext.h> |
116 | | |
117 | | struct walk_context { |
118 | | string_t *str; |
119 | | unsigned int pos; |
120 | | }; |
121 | | |
122 | | static int walk_callback(uintptr_t ptr, int signo ATTR_UNUSED, |
123 | | void *context) |
124 | | { |
125 | | struct walk_context *ctx = context; |
126 | | |
127 | | if (ctx->pos > 0) |
128 | | str_append(ctx->str, " -> "); |
129 | | str_printfa(ctx->str, "0x%p", (void *)ptr); |
130 | | ctx->pos++; |
131 | | return 0; |
132 | | } |
133 | | |
134 | | static int backtrace_append_libc(string_t *str, const char **error_r) |
135 | | { |
136 | | ucontext_t uc; |
137 | | struct walk_context ctx; |
138 | | |
139 | | if (getcontext(&uc) < 0) { |
140 | | *error_r = t_strdup_printf("getcontext() failed: %m"); |
141 | | return -1; |
142 | | } |
143 | | |
144 | | ctx.str = str; |
145 | | ctx.pos = 0; |
146 | | walkcontext(&uc, walk_callback, &ctx); |
147 | | return 0; |
148 | | } |
149 | | #else |
150 | | static int |
151 | | backtrace_append_libc(string_t *str ATTR_UNUSED, const char **error_r) |
152 | | { |
153 | | *error_r = "Missing implementation"; |
154 | | return -1; |
155 | | } |
156 | | #endif |
157 | | |
158 | | int backtrace_append(string_t *str, const char **error_r) |
159 | 0 | { |
160 | | #if defined(HAVE_LIBUNWIND) |
161 | | size_t orig_len = str_len(str); |
162 | | if (backtrace_append_unwind(str, error_r) == 0) |
163 | | return 0; |
164 | | /* failed to get useful backtrace. libc's own method is likely |
165 | | better. */ |
166 | | str_truncate(str, orig_len); |
167 | | #endif |
168 | 0 | return backtrace_append_libc(str, error_r); |
169 | 0 | } |
170 | | |
171 | | int backtrace_get(const char **backtrace_r, const char **error_r) |
172 | 0 | { |
173 | 0 | string_t *str; |
174 | |
|
175 | 0 | str = t_str_new(512); |
176 | 0 | if (backtrace_append(str, error_r) < 0) |
177 | 0 | return -1; |
178 | | |
179 | 0 | *backtrace_r = str_c(str); |
180 | 0 | return 0; |
181 | 0 | } |