summaryrefslogtreecommitdiffstats
path: root/lib/isc/backtrace.c
blob: 69488de99158f28c579aca123996493eed60e51e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/*
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 *
 * SPDX-License-Identifier: MPL-2.0
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
 */

/*! \file */

#include <stdlib.h>
#include <string.h>
#ifdef HAVE_LIBCTRACE
#include <execinfo.h>
#endif /* ifdef HAVE_LIBCTRACE */

#include <isc/backtrace.h>
#include <isc/result.h>
#include <isc/util.h>

#ifdef USE_BACKTRACE
/*
 * Getting a back trace of a running process is tricky and highly platform
 * dependent.  Our current approach is as follows:
 * 1. If the system library supports the "backtrace()" function, use it.
 * 2. Otherwise, if the compiler is gcc and the architecture is x86_64 or IA64,
 *    then use gcc's (hidden) Unwind_Backtrace() function.  Note that this
 *    function doesn't work for C programs on many other architectures.
 * 3. Otherwise, if the architecture x86 or x86_64, try to unwind the stack
 *    frame following frame pointers.  This assumes the executable binary
 *    compiled with frame pointers; this is not always true for x86_64 (rather,
 *    compiler optimizations often disable frame pointers).  The validation
 *    checks in getnextframeptr() hopefully rejects bogus values stored in
 *    the RBP register in such a case.  If the backtrace function itself crashes
 *    due to this problem, the whole package should be rebuilt with
 *    --disable-backtrace.
 */
#ifdef HAVE_LIBCTRACE
#define BACKTRACE_LIBC
#elif defined(HAVE_UNWIND_BACKTRACE)
#define BACKTRACE_GCC
#elif defined(WIN32)
#define BACKTRACE_WIN32
#elif defined(__x86_64__) || defined(__i386__)
#define BACKTRACE_X86STACK
#else /* ifdef HAVE_LIBCTRACE */
#define BACKTRACE_DISABLED
#endif /* HAVE_LIBCTRACE */
#else  /* USE_BACKTRACE */
#define BACKTRACE_DISABLED
#endif /* USE_BACKTRACE */

#ifdef BACKTRACE_LIBC
isc_result_t
isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
	int n;

	/*
	 * Validate the arguments: intentionally avoid using REQUIRE().
	 * See notes in backtrace.h.
	 */
	if (addrs == NULL || nframes == NULL) {
		return (ISC_R_FAILURE);
	}

	/*
	 * backtrace(3) includes this function itself in the address array,
	 * which should be eliminated from the returned sequence.
	 */
	n = backtrace(addrs, maxaddrs);
	if (n < 2) {
		return (ISC_R_NOTFOUND);
	}
	n--;
	memmove(addrs, &addrs[1], sizeof(void *) * n);
	*nframes = n;
	return (ISC_R_SUCCESS);
}
#elif defined(BACKTRACE_GCC)
extern int
_Unwind_Backtrace(void *fn, void *a);
extern void *
_Unwind_GetIP(void *ctx);

typedef struct {
	void **result;
	int max_depth;
	int skip_count;
	int count;
} trace_arg_t;

static int
btcallback(void *uc, void *opq) {
	trace_arg_t *arg = (trace_arg_t *)opq;

	if (arg->skip_count > 0) {
		arg->skip_count--;
	} else {
		arg->result[arg->count++] = (void *)_Unwind_GetIP(uc);
	}
	if (arg->count == arg->max_depth) {
		return (5); /* _URC_END_OF_STACK */
	}
	return (0); /* _URC_NO_REASON */
}

isc_result_t
isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
	trace_arg_t arg;

	/* Argument validation: see above. */
	if (addrs == NULL || nframes == NULL) {
		return (ISC_R_FAILURE);
	}

	arg.skip_count = 1;
	arg.result = addrs;
	arg.max_depth = maxaddrs;
	arg.count = 0;
	_Unwind_Backtrace(btcallback, &arg);

	*nframes = arg.count;

	return (ISC_R_SUCCESS);
}
#elif defined(BACKTRACE_WIN32)
isc_result_t
isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
	unsigned long ftc = (unsigned long)maxaddrs;

	*nframes = (int)CaptureStackBackTrace(1, ftc, addrs, NULL);
	return (ISC_R_SUCCESS);
}
#elif defined(BACKTRACE_X86STACK)
#ifdef __x86_64__
static unsigned long
getrbp(void) {
	unsigned long rbp;
	__asm("movq %%rbp, %0\n" : "=r"(rbp));
	return rbp;
}
#endif /* ifdef __x86_64__ */

static void **
getnextframeptr(void **sp) {
	void **newsp = (void **)*sp;

	/*
	 * Perform sanity check for the new frame pointer, derived from
	 * google glog.  This can actually be bogus depending on compiler.
	 */

	/* prohibit the stack frames from growing downwards */
	if (newsp <= sp) {
		return (NULL);
	}

	/* A heuristics to reject "too large" frame: this actually happened. */
	if ((char *)newsp - (char *)sp > 100000) {
		return (NULL);
	}

	/*
	 * Not sure if other checks used in glog are needed at this moment.
	 * For our purposes we don't have to consider non-contiguous frames,
	 * for example.
	 */

	return (newsp);
}

isc_result_t
isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
	int i = 0;
	void **sp;

	/* Argument validation: see above. */
	if (addrs == NULL || nframes == NULL) {
		return (ISC_R_FAILURE);
	}

#ifdef __x86_64__
	sp = (void **)getrbp();
	if (sp == NULL) {
		return (ISC_R_NOTFOUND);
	}
	/*
	 * sp is the frame ptr of this function itself due to the call to
	 * getrbp(), so need to unwind one frame for consistency.
	 */
	sp = getnextframeptr(sp);
#else  /* ifdef __x86_64__ */
	/*
	 * i386: the frame pointer is stored 2 words below the address for the
	 * first argument.  Note that the body of this function cannot be
	 * inlined since it depends on the address of the function argument.
	 */
	sp = (void **)&addrs - 2;
#endif /* ifdef __x86_64__ */

	while (sp != NULL && i < maxaddrs) {
		addrs[i++] = *(sp + 1);
		sp = getnextframeptr(sp);
	}

	*nframes = i;

	return (ISC_R_SUCCESS);
}
#elif defined(BACKTRACE_DISABLED)
isc_result_t
isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
	/* Argument validation: see above. */
	if (addrs == NULL || nframes == NULL) {
		return (ISC_R_FAILURE);
	}

	UNUSED(maxaddrs);

	return (ISC_R_NOTIMPLEMENTED);
}
#endif /* ifdef BACKTRACE_LIBC */

isc_result_t
isc_backtrace_getsymbolfromindex(int idx, const void **addrp,
				 const char **symbolp) {
	REQUIRE(addrp != NULL && *addrp == NULL);
	REQUIRE(symbolp != NULL && *symbolp == NULL);

	if (idx < 0 || idx >= isc__backtrace_nsymbols) {
		return (ISC_R_RANGE);
	}

	*addrp = isc__backtrace_symtable[idx].addr;
	*symbolp = isc__backtrace_symtable[idx].symbol;
	return (ISC_R_SUCCESS);
}

static int
symtbl_compare(const void *addr, const void *entryarg) {
	const isc_backtrace_symmap_t *entry = entryarg;
	const isc_backtrace_symmap_t *end =
		&isc__backtrace_symtable[isc__backtrace_nsymbols - 1];

	if (isc__backtrace_nsymbols == 1 || entry == end) {
		if (addr >= entry->addr) {
			/*
			 * If addr is equal to or larger than that of the last
			 * entry of the table, we cannot be sure if this is
			 * within a valid range so we consider it valid.
			 */
			return (0);
		}
		return (-1);
	}

	/* entry + 1 is a valid entry from now on. */
	if (addr < entry->addr) {
		return (-1);
	} else if (addr >= (entry + 1)->addr) {
		return (1);
	}
	return (0);
}

isc_result_t
isc_backtrace_getsymbol(const void *addr, const char **symbolp,
			unsigned long *offsetp) {
	isc_result_t result = ISC_R_SUCCESS;
	isc_backtrace_symmap_t *found;

	/*
	 * Validate the arguments: intentionally avoid using REQUIRE().
	 * See notes in backtrace.h.
	 */
	if (symbolp == NULL || *symbolp != NULL || offsetp == NULL) {
		return (ISC_R_FAILURE);
	}

	if (isc__backtrace_nsymbols < 1) {
		return (ISC_R_NOTFOUND);
	}

	/*
	 * Search the table for the entry that meets:
	 * entry.addr <= addr < next_entry.addr.
	 */
	found = bsearch(addr, isc__backtrace_symtable, isc__backtrace_nsymbols,
			sizeof(isc__backtrace_symtable[0]), symtbl_compare);
	if (found == NULL) {
		result = ISC_R_NOTFOUND;
	} else {
		*symbolp = found->symbol;
		*offsetp = (unsigned long)((const char *)addr -
					   (char *)found->addr);
	}

	return (result);
}