backtrace.c revision 1.1.1.3 1 /* $NetBSD: backtrace.c,v 1.1.1.3 2020/05/24 19:36:45 christos Exp $ */
2
3 /*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * See the COPYRIGHT file distributed with this work for additional
11 * information regarding copyright ownership.
12 */
13
14 /*! \file */
15
16 #include <stdlib.h>
17 #include <string.h>
18 #ifdef HAVE_LIBCTRACE
19 #include <execinfo.h>
20 #endif /* ifdef HAVE_LIBCTRACE */
21
22 #include <isc/backtrace.h>
23 #include <isc/result.h>
24 #include <isc/util.h>
25
26 #ifdef USE_BACKTRACE
27 /*
28 * Getting a back trace of a running process is tricky and highly platform
29 * dependent. Our current approach is as follows:
30 * 1. If the system library supports the "backtrace()" function, use it.
31 * 2. Otherwise, if the compiler is gcc and the architecture is x86_64 or IA64,
32 * then use gcc's (hidden) Unwind_Backtrace() function. Note that this
33 * function doesn't work for C programs on many other architectures.
34 * 3. Otherwise, if the architecture x86 or x86_64, try to unwind the stack
35 * frame following frame pointers. This assumes the executable binary
36 * compiled with frame pointers; this is not always true for x86_64 (rather,
37 * compiler optimizations often disable frame pointers). The validation
38 * checks in getnextframeptr() hopefully rejects bogus values stored in
39 * the RBP register in such a case. If the backtrace function itself crashes
40 * due to this problem, the whole package should be rebuilt with
41 * --disable-backtrace.
42 */
43 #ifdef HAVE_LIBCTRACE
44 #define BACKTRACE_LIBC
45 #elif defined(__GNUC__) && (defined(__x86_64__) || defined(__ia64__))
46 #define BACKTRACE_GCC
47 #elif defined(WIN32)
48 #define BACKTRACE_WIN32
49 #elif defined(__x86_64__) || defined(__i386__)
50 #define BACKTRACE_X86STACK
51 #else /* ifdef HAVE_LIBCTRACE */
52 #define BACKTRACE_DISABLED
53 #endif /* HAVE_LIBCTRACE */
54 #else /* USE_BACKTRACE */
55 #define BACKTRACE_DISABLED
56 #endif /* USE_BACKTRACE */
57
58 #ifdef BACKTRACE_LIBC
59 isc_result_t
60 isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
61 int n;
62
63 /*
64 * Validate the arguments: intentionally avoid using REQUIRE().
65 * See notes in backtrace.h.
66 */
67 if (addrs == NULL || nframes == NULL) {
68 return (ISC_R_FAILURE);
69 }
70
71 /*
72 * backtrace(3) includes this function itself in the address array,
73 * which should be eliminated from the returned sequence.
74 */
75 n = backtrace(addrs, maxaddrs);
76 if (n < 2) {
77 return (ISC_R_NOTFOUND);
78 }
79 n--;
80 memmove(addrs, &addrs[1], sizeof(void *) * n);
81 *nframes = n;
82 return (ISC_R_SUCCESS);
83 }
84 #elif defined(BACKTRACE_GCC)
85 extern int
86 _Unwind_Backtrace(void *fn, void *a);
87 extern void *
88 _Unwind_GetIP(void *ctx);
89
90 typedef struct {
91 void **result;
92 int max_depth;
93 int skip_count;
94 int count;
95 } trace_arg_t;
96
97 static int
98 btcallback(void *uc, void *opq) {
99 trace_arg_t *arg = (trace_arg_t *)opq;
100
101 if (arg->skip_count > 0) {
102 arg->skip_count--;
103 } else {
104 arg->result[arg->count++] = (void *)_Unwind_GetIP(uc);
105 }
106 if (arg->count == arg->max_depth) {
107 return (5); /* _URC_END_OF_STACK */
108 }
109 return (0); /* _URC_NO_REASON */
110 }
111
112 isc_result_t
113 isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
114 trace_arg_t arg;
115
116 /* Argument validation: see above. */
117 if (addrs == NULL || nframes == NULL) {
118 return (ISC_R_FAILURE);
119 }
120
121 arg.skip_count = 1;
122 arg.result = addrs;
123 arg.max_depth = maxaddrs;
124 arg.count = 0;
125 _Unwind_Backtrace(btcallback, &arg);
126
127 *nframes = arg.count;
128
129 return (ISC_R_SUCCESS);
130 }
131 #elif defined(BACKTRACE_WIN32)
132 isc_result_t
133 isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
134 unsigned long ftc = (unsigned long)maxaddrs;
135
136 *nframes = (int)CaptureStackBackTrace(1, ftc, addrs, NULL);
137 return (ISC_R_SUCCESS);
138 }
139 #elif defined(BACKTRACE_X86STACK)
140 #ifdef __x86_64__
141 static unsigned long
142 getrbp(void) {
143 __asm("movq %rbp, %rax\n");
144 }
145 #endif /* ifdef __x86_64__ */
146
147 static void **
148 getnextframeptr(void **sp) {
149 void **newsp = (void **)*sp;
150
151 /*
152 * Perform sanity check for the new frame pointer, derived from
153 * google glog. This can actually be bogus depending on compiler.
154 */
155
156 /* prohibit the stack frames from growing downwards */
157 if (newsp <= sp) {
158 return (NULL);
159 }
160
161 /* A heuristics to reject "too large" frame: this actually happened. */
162 if ((char *)newsp - (char *)sp > 100000) {
163 return (NULL);
164 }
165
166 /*
167 * Not sure if other checks used in glog are needed at this moment.
168 * For our purposes we don't have to consider non-contiguous frames,
169 * for example.
170 */
171
172 return (newsp);
173 }
174
175 isc_result_t
176 isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
177 int i = 0;
178 void **sp;
179
180 /* Argument validation: see above. */
181 if (addrs == NULL || nframes == NULL) {
182 return (ISC_R_FAILURE);
183 }
184
185 #ifdef __x86_64__
186 sp = (void **)getrbp();
187 if (sp == NULL) {
188 return (ISC_R_NOTFOUND);
189 }
190 /*
191 * sp is the frame ptr of this function itself due to the call to
192 * getrbp(), so need to unwind one frame for consistency.
193 */
194 sp = getnextframeptr(sp);
195 #else /* ifdef __x86_64__ */
196 /*
197 * i386: the frame pointer is stored 2 words below the address for the
198 * first argument. Note that the body of this function cannot be
199 * inlined since it depends on the address of the function argument.
200 */
201 sp = (void **)&addrs - 2;
202 #endif /* ifdef __x86_64__ */
203
204 while (sp != NULL && i < maxaddrs) {
205 addrs[i++] = *(sp + 1);
206 sp = getnextframeptr(sp);
207 }
208
209 *nframes = i;
210
211 return (ISC_R_SUCCESS);
212 }
213 #elif defined(BACKTRACE_DISABLED)
214 isc_result_t
215 isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
216 /* Argument validation: see above. */
217 if (addrs == NULL || nframes == NULL) {
218 return (ISC_R_FAILURE);
219 }
220
221 UNUSED(maxaddrs);
222
223 return (ISC_R_NOTIMPLEMENTED);
224 }
225 #endif /* ifdef BACKTRACE_LIBC */
226
227 isc_result_t
228 isc_backtrace_getsymbolfromindex(int idx, const void **addrp,
229 const char **symbolp) {
230 REQUIRE(addrp != NULL && *addrp == NULL);
231 REQUIRE(symbolp != NULL && *symbolp == NULL);
232
233 if (idx < 0 || idx >= isc__backtrace_nsymbols) {
234 return (ISC_R_RANGE);
235 }
236
237 *addrp = isc__backtrace_symtable[idx].addr;
238 *symbolp = isc__backtrace_symtable[idx].symbol;
239 return (ISC_R_SUCCESS);
240 }
241
242 static int
243 symtbl_compare(const void *addr, const void *entryarg) {
244 const isc_backtrace_symmap_t *entry = entryarg;
245 const isc_backtrace_symmap_t *end =
246 &isc__backtrace_symtable[isc__backtrace_nsymbols - 1];
247
248 if (isc__backtrace_nsymbols == 1 || entry == end) {
249 if (addr >= entry->addr) {
250 /*
251 * If addr is equal to or larger than that of the last
252 * entry of the table, we cannot be sure if this is
253 * within a valid range so we consider it valid.
254 */
255 return (0);
256 }
257 return (-1);
258 }
259
260 /* entry + 1 is a valid entry from now on. */
261 if (addr < entry->addr) {
262 return (-1);
263 } else if (addr >= (entry + 1)->addr) {
264 return (1);
265 }
266 return (0);
267 }
268
269 isc_result_t
270 isc_backtrace_getsymbol(const void *addr, const char **symbolp,
271 unsigned long *offsetp) {
272 isc_result_t result = ISC_R_SUCCESS;
273 isc_backtrace_symmap_t *found;
274
275 /*
276 * Validate the arguments: intentionally avoid using REQUIRE().
277 * See notes in backtrace.h.
278 */
279 if (symbolp == NULL || *symbolp != NULL || offsetp == NULL) {
280 return (ISC_R_FAILURE);
281 }
282
283 if (isc__backtrace_nsymbols < 1) {
284 return (ISC_R_NOTFOUND);
285 }
286
287 /*
288 * Search the table for the entry that meets:
289 * entry.addr <= addr < next_entry.addr.
290 */
291 found = bsearch(addr, isc__backtrace_symtable, isc__backtrace_nsymbols,
292 sizeof(isc__backtrace_symtable[0]), symtbl_compare);
293 if (found == NULL) {
294 result = ISC_R_NOTFOUND;
295 } else {
296 *symbolp = found->symbol;
297 *offsetp = (unsigned long)((const char *)addr -
298 (char *)found->addr);
299 }
300
301 return (result);
302 }
303