2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program 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
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief Various functions to aid in debugging
21 * @copyright 2013 The FreeRADIUS server project
22 * @copyright 2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
24 #include <freeradius-devel/libradius.h>
27 * runtime backtrace functions are not POSIX but are included in
28 * glibc, OSX >= 10.5 and various BSDs
30 #ifdef HAVE_EXECINFO_H
31 # include <execinfo.h>
35 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
36 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
38 #define PTHREAD_MUTEX_LOCK(_x)
39 #define PTHREAD_MUTEX_UNLOCK(_x)
42 #ifdef HAVE_EXECINFO_H
43 # define MAX_BT_FRAMES 128
44 # define MAX_BT_CBUFF 65536 //!< Should be a power of 2
46 # ifdef HAVE_PTHREAD_H
47 static pthread_mutex_t fr_debug_init = PTHREAD_MUTEX_INITIALIZER;
50 typedef struct fr_bt_info {
51 void *obj; //!< Memory address of the block of allocated memory.
52 void *frames[MAX_BT_FRAMES]; //!< Backtrace frame data
53 int count; //!< Number of frames stored
57 void *obj; //!< Pointer to the parent object, this is our needle
58 //!< when we iterate over the contents of the circular buffer.
59 fr_cbuff_t *cbuff; //!< Where we temporarily store the backtraces
63 static char panic_action[512];
64 static int fr_debugger_present = -1;
66 /** Stub callback to see if the SIGTRAP handler is overriden
68 * @param signum signal raised.
70 static void _sigtrap_handler(UNUSED int signum)
72 fr_debugger_present = 0;
73 signal(SIGTRAP, SIG_DFL);
76 /** Break in GDB (if were running under GDB)
78 * If the server is running under GDB this will raise a SIGTRAP which
79 * will pause the running process.
81 * If the server is not running under GDB then this will do nothing.
83 void fr_debug_break(void)
85 if (fr_debugger_present == -1) {
86 fr_debugger_present = 0;
87 signal(SIGTRAP, _sigtrap_handler);
89 } else if (fr_debugger_present == 1) {
94 #ifdef HAVE_EXECINFO_H
95 /** Generate a backtrace for an object during destruction
97 * If this is the first entry being inserted
99 static int _fr_do_bt(fr_bt_marker_t *marker)
103 if (!fr_assert(marker->obj) || !fr_assert(marker->cbuff)) {
107 bt = talloc_zero(marker->cbuff, fr_bt_info_t);
111 bt->count = backtrace(bt->frames, MAX_BT_FRAMES);
112 fr_cbuff_rp_insert(marker->cbuff, bt);
117 /** Print backtrace entry for a given object
119 * @param cbuff to search in.
120 * @param obj pointer to original object
122 void backtrace_print(fr_cbuff_t *cbuff, void *obj)
129 while ((p = fr_cbuff_rp_next(cbuff, NULL))) {
130 if ((p == obj) || !obj) {
132 frames = backtrace_symbols(p->frames, p->count);
134 fprintf(stderr, "Stacktrace for: %p\n", p);
135 for (i = 0; i < p->count; i++) {
136 fprintf(stderr, "%s\n", frames[i]);
139 /* We were only asked to look for one */
147 fprintf(stderr, "No backtrace available for %p", obj);
151 /** Inserts a backtrace marker into the provided context
153 * Allows for maximum laziness and will initialise a circular buffer if one has not already been created.
155 * Code augmentation should look something like:
157 // Create a static cbuffer pointer, the first call to backtrace_attach will initialise it
158 static fr_cbuff *my_obj_bt;
160 my_obj_t *alloc_my_obj(TALLOC_CTX *ctx) {
163 this = talloc(ctx, my_obj_t);
165 // Attach backtrace marker to object
166 backtrace_attach(&my_obj_bt, this);
172 * Then, later when a double free occurs:
174 (gdb) call backtrace_print(&my_obj_bt, <pointer to double freed memory>)
177 * which should print a limited backtrace to stderr. Note, this backtrace will not include any argument
178 * values, but should at least show the code path taken.
180 * @param cbuff this should be a pointer to a static *fr_cbuff.
181 * @param obj we want to generate a backtrace for.
183 fr_bt_marker_t *fr_backtrace_attach(fr_cbuff_t **cbuff, TALLOC_CTX *obj)
185 fr_bt_marker_t *marker;
187 if (*cbuff == NULL) {
188 PTHREAD_MUTEX_LOCK(&fr_debug_init);
189 /* Check again now we hold the mutex - eww*/
190 if (*cbuff == NULL) {
193 ctx = fr_autofree_ctx();
194 *cbuff = fr_cbuff_alloc(ctx, MAX_BT_CBUFF, true);
196 PTHREAD_MUTEX_UNLOCK(&fr_debug_init);
199 marker = talloc(obj, fr_bt_marker_t);
204 marker->obj = (void *) obj;
205 marker->cbuff = *cbuff;
207 talloc_set_destructor(marker, _fr_do_bt);
212 void backtrace_print(UNUSED fr_cbuff_t *cbuff, UNUSED void *obj)
214 fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n");
216 fr_bt_marker_t *fr_backtrace_attach(UNUSED fr_cbuff_t **cbuff, UNUSED TALLOC_CTX *obj)
218 fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n");
221 #endif /* ifdef HAVE_EXECINFO_H */
223 /** Prints a simple backtrace (if execinfo is available) and calls panic_action if set.
227 static void NEVER_RETURNS _fr_fault(int sig)
229 char cmd[sizeof(panic_action) + 20];
233 fprintf(stderr, "FATAL SIGNAL: %s\n", strsignal(sig));
236 * Produce a simple backtrace - They've very basic but at least give us an
237 * idea of the area of the code we hit the issue in.
239 #ifdef HAVE_EXECINFO_H
240 size_t frame_count, i;
241 void *stack[MAX_BT_FRAMES];
244 frame_count = backtrace(stack, MAX_BT_FRAMES);
245 frames = backtrace_symbols(stack, frame_count);
247 fprintf(stderr, "Backtrace of last %zu frames:\n", frame_count);
248 for (i = 0; i < frame_count; i++) {
249 fprintf(stderr, "%s\n", frames[i]);
250 /* Leak the backtrace strings, freeing may lead to undefined behaviour... */
254 /* No panic action set... */
255 if (panic_action[0] == '\0') {
256 fprintf(stderr, "No panic action set\n");
260 /* Substitute %p for the current PID (useful for attaching a debugger) */
261 p = strstr(panic_action, "%p");
263 snprintf(cmd, sizeof(cmd), "%.*s%i%s",
264 (int)(p - panic_action), panic_action, (int)getpid(), p + 2);
266 strlcpy(cmd, panic_action, sizeof(cmd));
269 fprintf(stderr, "Calling: %s\n", cmd);
271 fprintf(stderr, "Panic action exited with %i\n", ret);
276 /** Registers signal handlers to execute panic_action on fatal signal
278 * May be called multiple time to change the panic_action/program.
280 * @param cmd to execute on fault. If present %p will be substituted
281 * for the parent PID before the command is executed, and %e
282 * will be substituted for the currently running program.
283 * @return 0 on success -1 on failure.
285 int fr_fault_setup(char const *cmd, char const *program)
287 static bool setup = false;
291 /* Substitute %e for the current program */
292 p = strstr(cmd, "%e");
294 snprintf(panic_action, sizeof(panic_action), "%.*s%s%s",
295 (int)(p - cmd), cmd, program, p + 2);
297 strlcpy(panic_action, cmd, sizeof(panic_action));
300 *panic_action = '\0';
303 /* Unsure what the side effects of changing the signal handler mid execution might be */
306 if (fr_set_signal(SIGSEGV, _fr_fault) < 0) return -1;
309 if (fr_set_signal(SIGBUS, _fr_fault) < 0) return -1;
312 if (fr_set_signal(SIGABRT, _fr_fault) < 0) return -1;
315 if (fr_set_signal(SIGFPE, _fr_fault) < 0) return -1;