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