The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
backtrace.c
Go to the documentation of this file.
1#include <dlfcn.h>
2
3#include <freeradius-devel/util/backtrace.h>
4#include <freeradius-devel/util/debug.h>
5#include <freeradius-devel/util/fring.h>
6#include <freeradius-devel/util/misc.h>
7
8#ifdef HAVE_BACKTRACE
9# include <freeradius-devel/backtrace/backtrace.h>
10
11struct backtrace_state *backtrace_state = NULL; //!< Backtrace state for the backtrace functions
12 ///< This is initialised to be thread-safe, so we only need one.
13
14/** Used when building without libbacktrace to record frame information
15 */
16typedef struct {
17 char const *library; //!< Backtrace library name.
18 char const *filename; //!< Backtrace file.
19 char const *function; //!< Backtrace function.
20 bool function_guess; //!< Whether dladdr guessed the function.
21 //!< This is true if the function name is not in the
22 //!< symbol table, but was guessed from the program counter.
23 unsigned int lineno; //!< Backtrace line number.
24 unsigned int frameno; //!< Backtrace frame number.
25 uintptr_t pc; //!< Backtrace program counter.
26} fr_bt_info_frame_t;
27#elif defined(HAVE_EXECINFO)
28# include <execinfo.h>
29#endif
30
31# ifndef MAX_BT_FRAMES
32# define MAX_BT_FRAMES 128
33# endif
34# ifndef MAX_BT_CBUFF
35# define MAX_BT_CBUFF 1048576 //!< Should be a power of 2
36# endif
37
38static pthread_mutex_t fr_backtrace_lock = PTHREAD_MUTEX_INITIALIZER;
39
40typedef struct {
41 void *obj; //!< Memory address of the block of allocated memory.
42#ifdef HAVE_BACKTRACE
43 fr_bt_info_frame_t *frames[MAX_BT_FRAMES]; //!< Backtrace frame data
44#else
45 void *frames[MAX_BT_FRAMES]; //!< Backtrace frame data
46#endif
47 int count; //!< Number of frames stored
49
51 void *obj; //!< Pointer to the parent object, this is our needle
52 //!< when we iterate over the contents of the circular buffer.
53 fr_fring_t *fring; //!< Where we temporarily store the backtraces
54};
55
56#ifdef HAVE_BACKTRACE
57/** Log faults from libbacktrace
58 *
59 */
60static void _backtrace_error(UNUSED void *data, const char *msg, int errnum)
61{
62 FR_FAULT_LOG("Backtrace error: %s (%d)", msg, errnum);
63}
64
65static void backtrace_info_sanitise(fr_bt_info_frame_t *info)
66{
67 Dl_info dl_info;
68
69 if (dladdr((void *)info->pc, &dl_info) != 0) {
70 info->library = dl_info.dli_fname;
71 if (!info->function) {
72 info->function = dl_info.dli_sname;
73 info->function_guess = true;
74 }
75 }
76}
77
78static void backtrace_info_print(fr_bt_info_frame_t *frame, int fd, bool trim_path)
79{
80 if (!frame->library && !frame->filename) {
81 dprintf(fd, "%u: @ 0x%lx\n",
82 frame->frameno,
83 (unsigned long)frame->pc);
84 return;
85 }
86 else if (!frame->filename) {
87 dprintf(fd, "%u: %s %s() @ 0x%lx\n",
88 frame->frameno,
89 trim_path ? fr_filename(frame->library) : frame->library,
90 frame->function,
91 (unsigned long)frame->pc);
92 return;
93 }
94 dprintf(fd, "%u: %s:%d %s()%s @ 0x%lx\n",
95 frame->frameno,
96 trim_path ? fr_filename_common_trim(frame->filename, TOP_SRCDIR) : frame->filename,
97 frame->lineno,
98 frame->function,
99 frame->function_guess ? "?" : "",
100 (unsigned long)frame->pc);
101}
102
103static int _backtrace_info_record(void *data, uintptr_t pc,
104 const char *filename, int lineno,
105 const char *function)
106{
107 fr_bt_info_t *info = talloc_get_type_abort(data, fr_bt_info_t);
108 fr_bt_info_frame_t *frame;
109
110 if (info->count >= (int)NUM_ELEMENTS(info->frames)) return 0;
111
112 frame = talloc_zero(info, fr_bt_info_frame_t);
113 if (!frame) return -1;
114
115 frame->filename = talloc_strdup(frame, filename);
116 frame->function = talloc_strdup(frame, function);
117 frame->lineno = lineno;
118 frame->frameno = info->count;
119 frame->pc = pc;
120
121 backtrace_info_sanitise(frame);
122
123 info->frames[info->count++] = frame;
124
125 return 0;
126}
127
128static void backtrace_record(fr_bt_info_t *info)
129{
130 backtrace_full(backtrace_state, 0, _backtrace_info_record, _backtrace_error, info);
131}
132
133static int _backtrace_print(void *data, uintptr_t pc,
134 const char *filename, int lineno,
135 const char *function)
136{
137 unsigned int *frame_no = ((unsigned int *)data);
138 fr_bt_info_frame_t frame = {
139 .filename = filename,
140 .lineno = lineno,
141 .function = function,
142 .frameno = *frame_no,
143 .pc = pc,
144 };
145
146 backtrace_info_sanitise(&frame);
147 backtrace_info_print(&frame, fr_fault_log_fd, true);
148
149 (*frame_no)++;
150 return 0;
151}
152
153void fr_backtrace(void)
154{
155 unsigned int frame = 0;
156
157 if (fr_fault_log_fd >= 0) {
158 FR_FAULT_LOG("Backtrace:");
159 backtrace_full(backtrace_state, 0, _backtrace_print, _backtrace_error, &frame);
160 }
161}
162#elif defined(HAVE_EXECINFO)
163void fr_backtrace(void)
164{
165 /*
166 * Produce a simple backtrace - They're very basic but at least give us an
167 * idea of the area of the code we hit the issue in.
168 *
169 * See below in fr_fault_setup() and
170 * https://sourceware.org/bugzilla/show_bug.cgi?id=16159
171 * for why we only print backtraces in debug builds if we're using GLIBC.
172 */
173#if (!defined(NDEBUG) || !defined(__GNUC__))
174 if (fr_fault_log_fd >= 0) {
175 size_t frame_count;
176 void *stack[MAX_BT_FRAMES];
177
178 frame_count = backtrace(stack, MAX_BT_FRAMES);
179
180 FR_FAULT_LOG("Backtrace of last %zu frames:", frame_count);
181
182 backtrace_symbols_fd(stack, frame_count, fr_fault_log_fd);
183 }
184#endif
185 return;
186}
187#else
188void fr_backtrace(void)
189{
190 return;
191}
192#endif
193
194#if defined(HAVE_BACKTRACE) || defined(HAVE_EXECINFO)
195/** Print backtrace entry for a given object
196 *
197 * @param fring to search in.
198 * @param obj pointer to original object
199 */
200void fr_backtrace_print(fr_fring_t *fring, void *obj)
201{
202 fr_bt_info_t *p;
203 bool found = false;
204
205 while ((p = fr_fring_next(fring))) {
206 if ((p->obj == obj) || !obj) {
207 found = true;
208
209 fprintf(stderr, "Stacktrace for: %p\n", p->obj);
210#ifdef HAVE_BACKTRACE
211 {
212 int i;
213
214 for (i = 0; i < p->count; i++) {
215 backtrace_info_print(p->frames[i], fr_fault_log_fd, true);
216 }
217 }
218#else
219 backtrace_symbols_fd(p->frames, p->count, fr_fault_log_fd);
220#endif
221 }
222 }
223
224 if (!found) {
225 fprintf(stderr, "No backtrace available for %p", obj);
226 }
227}
228
229/** Generate a backtrace for an object
230 *
231 * If this is the first entry being inserted
232 */
233static int _backtrace_do(fr_bt_marker_t *marker)
234{
235 fr_bt_info_t *bt;
236
237 if (!fr_cond_assert(marker->obj) || !fr_cond_assert(marker->fring)) return -1;
238
239 bt = talloc_zero(NULL, fr_bt_info_t);
240 if (!bt) return -1;
241
242 bt->obj = marker->obj;
243#ifdef HAVE_BACKTRACE
244
245#else
246 bt->count = backtrace(bt->frames, MAX_BT_FRAMES);
247#endif
248 fr_fring_overwrite(marker->fring, bt);
249
250 return 0;
251}
252
253/** Inserts a backtrace marker into the provided context
254 *
255 * Allows for maximum laziness and will initialise a circular buffer if one has not already been created.
256 *
257 * Code augmentation should look something like:
258@verbatim
259 // Create a static fring pointer, the first call to backtrace_attach will initialise it
260 static fr_fring_t *my_obj_bt;
261
262 my_obj_t *alloc_my_obj(TALLOC_CTX *ctx) {
263 my_obj_t *this;
264
265 this = talloc(ctx, my_obj_t);
266
267 // Attach backtrace marker to object
268 backtrace_attach(&my_obj_bt, this);
269
270 return this;
271 }
272@endverbatim
273 *
274 * Then, later when a double free occurs:
275@verbatim
276 (gdb) call backtrace_print(&my_obj_bt, <pointer to double freed memory>)
277@endverbatim
278 *
279 * which should print a limited backtrace to stderr. Note, this backtrace will not include any argument
280 * values, but should at least show the code path taken.
281 *
282 * @param fring this should be a pointer to a static *fr_fring_buffer.
283 * @param obj we want to generate a backtrace for.
284 */
285fr_bt_marker_t *fr_backtrace_attach(fr_fring_t **fring, TALLOC_CTX *obj)
286{
287 fr_bt_marker_t *marker;
288
289 if (*fring == NULL) {
290 pthread_mutex_lock(&fr_backtrace_lock);
291 if (*fring == NULL) *fring = fr_fring_alloc(NULL, MAX_BT_CBUFF, true);
292 pthread_mutex_unlock(&fr_backtrace_lock);
293 }
294
295 marker = talloc(obj, fr_bt_marker_t);
296 if (!marker) {
297 return NULL;
298 }
299
300 marker->obj = (void *) obj;
301 marker->fring = *fring;
302
303 fprintf(stderr, "Backtrace attached to %s %p\n", talloc_get_name(obj), obj);
304 /*
305 * Generate the backtrace for memory allocation
306 */
307 _backtrace_do(marker);
308 talloc_set_destructor(marker, _backtrace_do);
309
310 return marker;
311}
312#else
314{
315 fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo, or libbacktrace\n");
316 abort();
317}
318#endif
319
321#ifndef HAVE_BACKTRACE
322 UNUSED
323#endif
324 char const *program)
325{
326#ifdef HAVE_BACKTRACE
327 /*
328 * Initialise the state for libbacktrace. As per the docs
329 * these resources can never be freed, and should be ignore
330 * in any leak tracking code.
331 */
332 backtrace_state = backtrace_create_state(program, 1, _backtrace_error, NULL);
333#elif defined(HAVE_EXECINFO) && defined(__GNUC__) && !defined(NDEBUG)
334 /*
335 * We need to pre-load lgcc_s, else we can get into a deadlock
336 * in fr_fault, as backtrace() attempts to dlopen it.
337 *
338 * Apparently there's a performance impact of loading lgcc_s,
339 * so only do it if this is a debug build.
340 *
341 * See: https://sourceware.org/bugzilla/show_bug.cgi?id=16159
342 */
343 {
344 void *stack[10];
345
346 backtrace(stack, 10);
347 }
348#endif
349}
log_entry msg
Definition acutest.h:794
void * obj
Memory address of the block of allocated memory.
Definition backtrace.c:41
fr_fring_t * fring
Where we temporarily store the backtraces.
Definition backtrace.c:53
void * frames[MAX_BT_FRAMES]
Backtrace frame data.
Definition backtrace.c:45
void * obj
Pointer to the parent object, this is our needle when we iterate over the contents of the circular bu...
Definition backtrace.c:51
void fr_backtrace(void)
Definition backtrace.c:188
#define MAX_BT_CBUFF
Should be a power of 2.
Definition backtrace.c:35
#define MAX_BT_FRAMES
Definition backtrace.c:32
fr_bt_marker_t * fr_backtrace_attach(UNUSED fr_fring_t **fring, UNUSED TALLOC_CTX *obj)
Definition backtrace.c:313
void fr_backtrace_init(UNUSED char const *program)
Definition backtrace.c:320
int count
Number of frames stored.
Definition backtrace.c:47
static pthread_mutex_t fr_backtrace_lock
Definition backtrace.c:38
void fr_backtrace_print(fr_fring_t *fring, void *obj)
#define UNUSED
Definition build.h:317
#define NUM_ELEMENTS(_t)
Definition build.h:339
int fr_fault_log_fd
Where to write debug output.
Definition debug.c:72
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition debug.h:131
#define FR_FAULT_LOG(_fmt,...)
Definition debug.h:50
int fr_fring_overwrite(fr_fring_t *fring, void *in)
Insert a new item into the circular buffer, freeing the tail if we hit it.
Definition fring.c:120
void * fr_fring_next(fr_fring_t *fring)
Remove an item from the buffer.
Definition fring.c:177
fr_fring_t * fr_fring_alloc(TALLOC_CTX *ctx, uint32_t size, bool lock)
Initialise a ring buffer with fixed element size.
Definition fring.c:78
Standard thread safe circular buffer.
Definition fring.c:36
static char * stack[MAX_STACK]
Definition radmin.c:159
char const * fr_filename_common_trim(char const *path, char const *common)
Get the filename from a path.
Definition misc.c:506
char const * fr_filename(char const *path)
Get the filename from a path.
Definition misc.c:490
char const * program
Definition radiusd.c:85
static fr_slen_t data
Definition value.h:1288