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