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