xref: /netbsd-src/external/mpl/bind/dist/lib/isc/backtrace.c (revision e7ac2a8b5bd66fa2e050809de09a075c36a7014d)
1 /*	$NetBSD: backtrace.c,v 1.4 2020/05/24 19:46:26 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 **)(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