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