Backport debug functions
[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_ENTRIES 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 int fr_debugger_present = -1;
64
65 /** Stub callback to see if the SIGTRAP handler is overriden
66  *
67  * @param signum signal raised.
68  */
69 static void _sigtrap_handler(UNUSED int signum)
70 {
71     fr_debugger_present = 0;
72     signal(SIGTRAP, SIG_DFL);
73 }
74
75 /** Break in GDB (if were running under GDB)
76  *
77  * If the server is running under GDB this will raise a SIGTRAP which
78  * will pause the running process.
79  *
80  * If the server is not running under GDB then this will do nothing.
81  */
82 void fr_debug_break(void)
83 {
84     if (fr_debugger_present == -1) {
85         fr_debugger_present = 0;
86         signal(SIGTRAP, _sigtrap_handler);
87         raise(SIGTRAP);
88     } else if (fr_debugger_present == 1) {
89         raise(SIGTRAP);
90     }
91 }
92
93 #ifdef HAVE_EXECINFO_H
94 /** Generate a backtrace for an object during destruction
95  *
96  * If this is the first entry being inserted
97  */
98 static int _fr_do_bt(fr_bt_marker_t *marker)
99 {
100         fr_bt_info_t *bt;
101
102         if (!fr_assert(marker->obj) || !fr_assert(marker->cbuff)) {
103                 return -1;
104         }
105
106         bt = talloc_zero(marker->cbuff, fr_bt_info_t);
107         if (!bt) {
108                 return -1;
109         }
110         bt->count = backtrace(bt->frames, MAX_BT_FRAMES);
111         fr_cbuff_rp_insert(marker->cbuff, bt);
112
113         return 0;
114 }
115
116 /** Print backtrace entry for a given object
117  *
118  * @param cbuff to search in.
119  * @param obj pointer to original object
120  */
121 void backtrace_print(fr_cbuff_t *cbuff, void *obj)
122 {
123         fr_bt_info_t *p;
124         bool found = false;
125         int i = 0;
126         char **frames;
127
128         while ((p = fr_cbuff_rp_next(cbuff, NULL))) {
129                 if ((p == obj) || !obj) {
130                         found = true;
131                         frames = backtrace_symbols(p->frames, p->count);
132
133                         fprintf(stderr, "Stacktrace for: %p\n", p);
134                         for (i = 0; i < p->count; i++) {
135                                 fprintf(stdout, "%s\n", frames[i]);
136                         }
137
138                         /* We were only asked to look for one */
139                         if (obj) {
140                                 return;
141                         }
142                 }
143         };
144
145         if (!found) {
146                 fprintf(stderr, "No backtrace available for %p", obj);
147         }
148 }
149
150 /** Inserts a backtrace marker into the provided context
151  *
152  * Allows for maximum laziness and will initialise a circular buffer if one has not already been created.
153  *
154  * Code augmentation should look something like:
155 @verbatim
156         // Create a static cbuffer pointer, the first call to backtrace_attach will initialise it
157         static fr_cbuff *my_obj_bt;
158
159         my_obj_t *alloc_my_obj(TALLOC_CTX *ctx) {
160                 my_obj_t *this;
161
162                 this = talloc(ctx, my_obj_t);
163
164                 // Attach backtrace marker to object
165                 backtrace_attach(&my_obj_bt, this);
166
167                 return this;
168         }
169 @endverbatim
170  *
171  * Then, later when a double free occurs:
172 @verbatim
173         (gdb) call backtrace_print(&my_obj_bt, <pointer to double freed memory>)
174 @endverbatim
175  *
176  * which should print a limited backtrace to stderr. Note, this backtrace will not include any argument
177  * values, but should at least show the code path taken.
178  *
179  * @param cbuff this should be a pointer to a static *fr_cbuff.
180  * @param obj we want to generate a backtrace for.
181  */
182 fr_bt_marker_t *fr_backtrace_attach(fr_cbuff_t **cbuff, TALLOC_CTX *obj)
183 {
184         fr_bt_marker_t *marker;
185
186         if (*cbuff == NULL) {
187                 PTHREAD_MUTEX_LOCK(&fr_debug_init);
188                 /* Check again now we hold the mutex - eww*/
189                 if (*cbuff == NULL) {
190                         TALLOC_CTX *ctx;
191
192                         ctx = fr_autofree_ctx();
193                         *cbuff = fr_cbuff_alloc(ctx, MAX_BT_ENTRIES, true);
194                 }
195                 PTHREAD_MUTEX_UNLOCK(&fr_debug_init);
196         }
197
198         marker = talloc(obj, fr_bt_marker_t);
199         if (!marker) {
200                 return NULL;
201         }
202
203         marker->obj = (void *) obj;
204         marker->cbuff = *cbuff;
205
206         talloc_set_destructor(marker, _fr_do_bt);
207
208         return marker;
209 }
210 #else
211 void backtrace_print(UNUSED fr_cbuff_t *cbuff, UNUSED void *obj)
212 {
213         fr_perror("Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo");
214 }
215 fr_bt_marker_t *fr_backtrace_attach(UNUSED fr_cbuff_t **cbuff, UNUSED TALLOC_CTX *obj)
216 {
217         fr_perror("Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo");
218         abort();
219 }
220 #endif /* ifdef HAVE_EXECINFO_H */