Typo
[freeradius.git] / src / lib / debug.c
1 /*
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.
6  *
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.
11  *
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
15  */
16
17 /**
18  * @file debug.c
19  * @brief Various functions to aid in debugging
20  *
21  * @copyright 2013  The FreeRADIUS server project
22  * @copyright 2013  Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23  */
24 #include <freeradius-devel/libradius.h>
25
26 /*
27  *      runtime backtrace functions are not POSIX but are included in
28  *      glibc, OSX >= 10.5 and various BSDs
29  */
30 #ifdef HAVE_EXECINFO_H
31 #  include <execinfo.h>
32 #endif
33
34 #ifdef HAVE_PTHREAD_H
35 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
36 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
37 #else
38 #define PTHREAD_MUTEX_LOCK(_x)
39 #define PTHREAD_MUTEX_UNLOCK(_x)
40 #endif
41
42 #ifdef HAVE_EXECINFO_H
43 #  define MAX_BT_FRAMES 128
44 #  define MAX_BT_CBUFF  65536                   //!< Should be a power of 2
45
46 #  ifdef HAVE_PTHREAD_H
47 static pthread_mutex_t fr_debug_init = PTHREAD_MUTEX_INITIALIZER;
48 #  endif
49
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
54 } fr_bt_info_t;
55
56 struct fr_bt_marker {
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
60 };
61 #endif
62
63 static char panic_action[512];
64 static int fr_debugger_present = -1;
65
66 /** Stub callback to see if the SIGTRAP handler is overriden
67  *
68  * @param signum signal raised.
69  */
70 static void _sigtrap_handler(UNUSED int signum)
71 {
72     fr_debugger_present = 0;
73     signal(SIGTRAP, SIG_DFL);
74 }
75
76 /** Break in GDB (if were running under GDB)
77  *
78  * If the server is running under GDB this will raise a SIGTRAP which
79  * will pause the running process.
80  *
81  * If the server is not running under GDB then this will do nothing.
82  */
83 void fr_debug_break(void)
84 {
85     if (fr_debugger_present == -1) {
86         fr_debugger_present = 0;
87         signal(SIGTRAP, _sigtrap_handler);
88         raise(SIGTRAP);
89     } else if (fr_debugger_present == 1) {
90         raise(SIGTRAP);
91     }
92 }
93
94 #ifdef HAVE_EXECINFO_H
95 /** Generate a backtrace for an object during destruction
96  *
97  * If this is the first entry being inserted
98  */
99 static int _fr_do_bt(fr_bt_marker_t *marker)
100 {
101         fr_bt_info_t *bt;
102
103         if (!fr_assert(marker->obj) || !fr_assert(marker->cbuff)) {
104                 return -1;
105         }
106
107         bt = talloc_zero(marker->cbuff, fr_bt_info_t);
108         if (!bt) {
109                 return -1;
110         }
111         bt->count = backtrace(bt->frames, MAX_BT_FRAMES);
112         fr_cbuff_rp_insert(marker->cbuff, bt);
113
114         return 0;
115 }
116
117 /** Print backtrace entry for a given object
118  *
119  * @param cbuff to search in.
120  * @param obj pointer to original object
121  */
122 void backtrace_print(fr_cbuff_t *cbuff, void *obj)
123 {
124         fr_bt_info_t *p;
125         bool found = false;
126         int i = 0;
127         char **frames;
128
129         while ((p = fr_cbuff_rp_next(cbuff, NULL))) {
130                 if ((p == obj) || !obj) {
131                         found = true;
132                         frames = backtrace_symbols(p->frames, p->count);
133
134                         fprintf(stderr, "Stacktrace for: %p\n", p);
135                         for (i = 0; i < p->count; i++) {
136                                 fprintf(stderr, "%s\n", frames[i]);
137                         }
138
139                         /* We were only asked to look for one */
140                         if (obj) {
141                                 return;
142                         }
143                 }
144         };
145
146         if (!found) {
147                 fprintf(stderr, "No backtrace available for %p", obj);
148         }
149 }
150
151 /** Inserts a backtrace marker into the provided context
152  *
153  * Allows for maximum laziness and will initialise a circular buffer if one has not already been created.
154  *
155  * Code augmentation should look something like:
156 @verbatim
157         // Create a static cbuffer pointer, the first call to backtrace_attach will initialise it
158         static fr_cbuff *my_obj_bt;
159
160         my_obj_t *alloc_my_obj(TALLOC_CTX *ctx) {
161                 my_obj_t *this;
162
163                 this = talloc(ctx, my_obj_t);
164
165                 // Attach backtrace marker to object
166                 backtrace_attach(&my_obj_bt, this);
167
168                 return this;
169         }
170 @endverbatim
171  *
172  * Then, later when a double free occurs:
173 @verbatim
174         (gdb) call backtrace_print(&my_obj_bt, <pointer to double freed memory>)
175 @endverbatim
176  *
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.
179  *
180  * @param cbuff this should be a pointer to a static *fr_cbuff.
181  * @param obj we want to generate a backtrace for.
182  */
183 fr_bt_marker_t *fr_backtrace_attach(fr_cbuff_t **cbuff, TALLOC_CTX *obj)
184 {
185         fr_bt_marker_t *marker;
186
187         if (*cbuff == NULL) {
188                 PTHREAD_MUTEX_LOCK(&fr_debug_init);
189                 /* Check again now we hold the mutex - eww*/
190                 if (*cbuff == NULL) {
191                         TALLOC_CTX *ctx;
192
193                         ctx = fr_autofree_ctx();
194                         *cbuff = fr_cbuff_alloc(ctx, MAX_BT_CBUFF, true);
195                 }
196                 PTHREAD_MUTEX_UNLOCK(&fr_debug_init);
197         }
198
199         marker = talloc(obj, fr_bt_marker_t);
200         if (!marker) {
201                 return NULL;
202         }
203
204         marker->obj = (void *) obj;
205         marker->cbuff = *cbuff;
206
207         talloc_set_destructor(marker, _fr_do_bt);
208
209         return marker;
210 }
211 #else
212 void backtrace_print(UNUSED fr_cbuff_t *cbuff, UNUSED void *obj)
213 {
214         fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n");
215 }
216 fr_bt_marker_t *fr_backtrace_attach(UNUSED fr_cbuff_t **cbuff, UNUSED TALLOC_CTX *obj)
217 {
218         fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n");
219         abort();
220 }
221 #endif /* ifdef HAVE_EXECINFO_H */
222
223 /** Prints a simple backtrace (if execinfo is available) and calls panic_action if set.
224  *
225  * @param sig caught
226  */
227 static void NEVER_RETURNS _fr_fault(int sig)
228 {
229         char cmd[sizeof(panic_action) + 20];
230         char *p;
231         int ret;
232
233         fprintf(stderr, "FATAL SIGNAL: %s\n", strsignal(sig));
234
235         /*
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.
238          */
239 #ifdef HAVE_EXECINFO_H
240         size_t frame_count, i;
241         void *stack[MAX_BT_FRAMES];
242         char **frames;
243
244         frame_count = backtrace(stack, MAX_BT_FRAMES);
245         frames = backtrace_symbols(stack, frame_count);
246
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... */
251         }
252 #endif
253
254         /* No panic action set... */
255         if (panic_action[0] == '\0') {
256                 fprintf(stderr, "No panic action set\n");
257                 fr_exit_now(1);
258         }
259
260         /* Substitute %p for the current PID (useful for attaching a debugger) */
261         p = strstr(panic_action, "%p");
262         if (p) {
263                 snprintf(cmd, sizeof(cmd), "%.*s%i%s",
264                          (int)(p - panic_action), panic_action, (int)getpid(), p + 2);
265         } else {
266                 strlcpy(cmd, panic_action, sizeof(cmd));
267         }
268
269         fprintf(stderr, "Calling: %s\n", cmd);
270         ret = system(cmd);
271         fprintf(stderr, "Panic action exited with %i\n", ret);
272
273         fr_exit_now(1);
274 }
275
276 /** Registers signal handlers to execute panic_action on fatal signal
277  *
278  * May be called multiple time to change the panic_action/program.
279  *
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.
284  */
285 int fr_fault_setup(char const *cmd, char const *program)
286 {
287         static bool setup = false;
288         char *p;
289
290         if (cmd) {
291                 /* Substitute %e for the current program */
292                 p = strstr(cmd, "%e");
293                 if (p) {
294                         snprintf(panic_action, sizeof(panic_action), "%.*s%s%s",
295                                  (int)(p - cmd), cmd, program, p + 2);
296                 } else {
297                         strlcpy(panic_action, cmd, sizeof(panic_action));
298                 }
299         } else {
300                 *panic_action = '\0';
301         }
302
303         /* Unsure what the side effects of changing the signal handler mid execution might be */
304         if (!setup) {
305 #ifdef SIGSEGV
306                 if (fr_set_signal(SIGSEGV, _fr_fault) < 0) return -1;
307 #endif
308 #ifdef SIGBUS
309                 if (fr_set_signal(SIGBUS, _fr_fault) < 0) return -1;
310 #endif
311 #ifdef SIGABRT
312                 if (fr_set_signal(SIGABRT, _fr_fault) < 0) return -1;
313 #endif
314 #ifdef SIGFPE
315                 if (fr_set_signal(SIGFPE, _fr_fault) < 0) return -1;
316 #endif
317         }
318         setup = true;
319
320         return 0;
321 }
322