Coverage Report

Created: 2026-10-18 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/util/backtrace.c
Line
Count
Source
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
 */
23
RCSID("$Id: 158ebb8cd6d3658ce7495b509225bdcce746fa5d $")
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
34
static 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
 */
39
typedef 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
0
#    define MAX_BT_FRAMES 128
56
#  endif
57
#  ifndef MAX_BT_CBUFF
58
0
#    define MAX_BT_CBUFF  1048576      //!< Should be a power of 2
59
#  endif
60
61
static pthread_mutex_t fr_backtrace_lock = PTHREAD_MUTEX_INITIALIZER;
62
63
typedef 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
71
} fr_bt_info_t;
72
73
struct fr_bt_marker {
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
 */
83
static void _backtrace_error(UNUSED void *data, const char *msg, int errnum)
84
{
85
  FR_FAULT_LOG("Backtrace error: %s (%d)", msg, errnum);
86
}
87
88
static 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
101
static 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:%u\n",
118
    frame->frameno,
119
    (unsigned long)frame->pc,
120
    trim_path && frame->library ? 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
127
static 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
152
static void backtrace_record(fr_bt_info_t *info)
153
{
154
  backtrace_full(backtrace_state, 0, _backtrace_info_record, _backtrace_error, info);
155
}
156
157
static 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
177
void 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)
187
void fr_backtrace(void)
188
0
{
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
0
#if (!defined(NDEBUG) || !defined(__GNUC__))
198
0
  if (fr_fault_log_fd >= 0) {
199
0
    size_t frame_count;
200
0
    void *stack[MAX_BT_FRAMES];
201
202
0
    frame_count = backtrace(stack, MAX_BT_FRAMES);
203
204
0
    FR_FAULT_LOG("Backtrace of last %zu frames:", frame_count);
205
206
0
    backtrace_symbols_fd(stack, frame_count, fr_fault_log_fd);
207
0
  }
208
0
#endif
209
0
  return;
210
0
}
211
#else
212
void 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
 */
224
void fr_backtrace_print(fr_fring_t *fring, void *obj)
225
0
{
226
0
  fr_bt_info_t *p;
227
0
  bool found = false;
228
229
0
  while ((p = fr_fring_next(fring))) {
230
0
    if ((p->obj == obj) || !obj) {
231
0
      found = true;
232
233
0
      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
0
      backtrace_symbols_fd(p->frames, p->count, fr_fault_log_fd);
244
0
#endif
245
0
    }
246
0
  }
247
248
0
  if (!found) {
249
0
    fprintf(stderr, "No backtrace available for %p", obj);
250
0
  }
251
0
}
252
253
/** Generate a backtrace for an object
254
 *
255
 * If this is the first entry being inserted
256
 */
257
static int _backtrace_do(fr_bt_marker_t *marker)
258
0
{
259
0
  fr_bt_info_t *bt;
260
261
0
  if (!fr_cond_assert(marker->obj) || !fr_cond_assert(marker->fring)) return -1;
262
263
0
  bt = talloc_zero(NULL, fr_bt_info_t);
264
0
  if (!bt) return -1;
265
266
0
  bt->obj = marker->obj;
267
#ifdef HAVE_BACKTRACE
268
  backtrace_record(bt);
269
#else
270
0
  bt->count = backtrace(bt->frames, MAX_BT_FRAMES);
271
0
#endif
272
0
  fr_fring_overwrite(marker->fring, bt);
273
274
0
  return 0;
275
0
}
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
 */
309
fr_bt_marker_t *fr_backtrace_attach(fr_fring_t **fring, TALLOC_CTX *obj)
310
0
{
311
0
  fr_bt_marker_t *marker;
312
313
0
  if (*fring == NULL) {
314
0
    pthread_mutex_lock(&fr_backtrace_lock);
315
0
    if (*fring == NULL) *fring = fr_fring_alloc(NULL, MAX_BT_CBUFF, true);
316
0
    pthread_mutex_unlock(&fr_backtrace_lock);
317
0
  }
318
319
0
  marker = talloc(obj, fr_bt_marker_t);
320
0
  if (!marker) {
321
0
    return NULL;
322
0
  }
323
324
0
  marker->obj = (void *) obj;
325
0
  marker->fring = *fring;
326
327
0
  fprintf(stderr, "Backtrace attached to %s %p\n", talloc_get_name(obj), obj);
328
  /*
329
   *  Generate the backtrace for memory allocation
330
   */
331
0
  _backtrace_do(marker);
332
0
  talloc_set_destructor(marker, _backtrace_do);
333
334
0
  return marker;
335
0
}
336
#else
337
fr_bt_marker_t *fr_backtrace_attach(UNUSED fr_fring_t **fring, UNUSED TALLOC_CTX *obj)
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
344
void fr_backtrace_init(
345
#ifndef HAVE_BACKTRACE
346
      UNUSED
347
#endif
348
      char const *program)
349
56
{
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
56
    {
368
56
      void *stack[10];
369
370
56
      backtrace(stack, 10);
371
56
    }
372
56
#endif
373
56
}