feisty meow concerns codebase 2.140
callstack_tracker.cpp
Go to the documentation of this file.
1/*
2*
3* Name : callstack_tracker
4* Author : Chris Koeritz
5*
6*
7* Copyright (c) 2007-$now By Author. This program is free software; you can
8* redistribute it and/or modify it under the terms of the GNU General Public
9* License as published by the Free Software Foundation; either version 2 of
10* the License or (at your option) any later version. This is online at:
11* http://www.fsf.org/copyleft/gpl.html
12* Please send any updates to: fred@gruntose.com
13*/
14
15#ifdef ENABLE_CALLSTACK_TRACKING
16
17// note: this object cannot be constructed when the memory_checker is still
18// tracking memory leaks. it must be disabled so that this object can
19// construct without being tracked, which causes an infinite loop. the code
20// in the basis extern support takes care of that for us.
21
22#include "callstack_tracker.h"
23
24#include <basis/functions.h>
25
26#include <malloc.h>
27#include <stdio.h>
28#include <string.h>
29#include <unistd.h>
30#include <unordered_map>
31
32using namespace basis;
33
34namespace application {
35
36const int MAX_STACK_DEPTH = 2000;
37 // beyond that many stack frames, we will simply refuse to add any more.
38
39const int MAX_TEXT_FIELD = 1024;
40 // the most space we allow the class, function, and file to take up.
41
42const char *emptiness_note = "Empty Stack\n";
44
45const int STACK_TRACKER_ELEMS = 200;
47
48// uncomment for noisier debugging logging.
49//#define DEBUG_CALLSTACK_TRACKER
50
52
54{
55 static basis::mutex __global_synch_callstacks;
56 return __global_synch_callstacks;
57}
58
60
61class tracker_hashing : public std::unordered_map<pid_t, callstack_tracker *>
62{
63public:
64 tracker_hashing() : unordered_map<pid_t, callstack_tracker *>() {}
65};
66
67tracker_hashing &__tracker_hashing()
68{
69 static tracker_hashing __global_tracker;
70 return __global_tracker;
71}
72
74
79{
81
82 tracker_hashing &thc = __tracker_hashing();
84 pid_t my_thread_id = gettid();
85#ifdef DEBUG_CALLSTACK_TRACKER
86 printf("looking for thread id %ld in callstack lists...\n", (long)my_thread_id);
87#endif
88 if (auto seeker = thc.find(my_thread_id); seeker != thc.end()) {
89 my_thread_id = seeker->first;
90 pointy = seeker->second;
91#ifdef DEBUG_CALLSTACK_TRACKER
92 printf("found existing callstack_tracker for thread %ld\n", (long)my_thread_id);
93#endif
94 } else {
95#ifdef ENABLE_MEMORY_HOOK
96 program_wide_memories().disable();
97 /* we don't want infinite loops tracking the call stack during this object's construction. */
98//hmmm: does that disable the progwide memories for the whole program or just for this thread?
99// and what does that entail exactly?
100// did it actually fix the problem we saw?
101#endif
102#ifdef DEBUG_CALLSTACK_TRACKER
103 printf("adding new callstack_tracker for thread %ld\n", (long)my_thread_id);
104#endif
105 pointy = new callstack_tracker;
106 thc.insert({my_thread_id, pointy});
107#ifdef ENABLE_MEMORY_HOOK
108 program_wide_memories().enable();
109#endif
110 }
111
112//hmmm: now scan for dead threads here!
113// costly though! have to iterate across all the ids.
114// how about doing it on a timed basis?
115
116 return *pointy;
117}
118
120
121class callstack_records
122{
123public:
124 frame_tracking_instance _records[MAX_STACK_DEPTH + 2]; // fudging room.
125};
126
128
129/*
130 our current depth gives us our position in the array. we define our
131 stack as starting at element zero, which is a null stack entry and
132 corresponds to a depth of zero also. then, when the stack depth is one,
133 we actually have an element in place and it resides at index 1. this
134 scheme allows us to have an update to the line number just do nothing when
135 there is no current stack.
136*/
137
139: _bt(new callstack_records),
140 _depth(0),
141 _frames_in(0),
142 _frames_out(0),
143 _highest(0),
144 _unusable(false)
145{
146}
147
149{
150 _unusable = true;
151 WHACK(_bt);
152}
153
154bool callstack_tracker::push_frame(const char *class_name, const char *func,
155 const char *file, int line)
156{
158#ifdef DEBUG_CALLSTACK_TRACKER
159 printf(">> in, callstack pushframe depth=%d\n", _depth);
160#endif
161 if (_unusable) return false;
162 if (_depth >= MAX_STACK_DEPTH) {
163 // too many frames already.
164 printf("callstack_tracker::push_frame: past limit at class=%s func=%s "
165 "file=%s line=%d\n", class_name, func, file, line);
166 return false;
167 }
168 _depth++;
169 if (_depth > _highest) _highest = _depth;
170 _frames_in += 1;
171 _bt->_records[_depth].assign(class_name, func, file, line);
172#ifdef DEBUG_CALLSTACK_TRACKER
173 printf("<< out, callstack pushframe depth=%d\n", _depth);
174#endif
175 return true;
176}
177
179{
181#ifdef DEBUG_CALLSTACK_TRACKER
182 printf(">> in, callstack popframe depth=%d\n", _depth);
183#endif
184 if (_unusable) return false;
185 if (_depth <= 0) {
186 // how inappropriate of them; we have no frames.
187 _depth = 0; // we don't lose anything useful by forcing it to be zero.
188 printf("callstack_tracker::pop_frame stack underflow!\n");
189 return false;
190 }
191 _bt->_records[_depth].clean();
192 _depth--;
193 _frames_out += 1;
194#ifdef DEBUG_CALLSTACK_TRACKER
195 printf("<< out, callstack popframe depth=%d\n", _depth);
196#endif
197 return true;
198}
199
201{
203
204 if (_unusable) return false;
205 if (!_depth) return false; // not as serious, but pretty weird.
206 _bt->_records[_depth]._line = line;
207 return true;
208}
209
210// helpful macro makes sure we stay within our buffer for string storage of the stack trace.
211#define CHECK_SPACE_IN_BUFFER(desired_chunk) \
212 /* being slightly paranoid about the space, but we don't want any buffer overflows. */ \
213 if (space_used_in_buffer + desired_chunk >= full_size_needed - 4) { \
214 printf("callstack_tracker::full_trace: failure in size estimation--we would have blown out of the buffer"); \
215 return to_return; \
216 } else { \
217 space_used_in_buffer += desired_chunk; \
218 }
219
221{
223
224 if (_unusable) return strdup("");
225 int full_size_needed = full_trace_size();
226 char *to_return = (char *)malloc(full_size_needed);
227#ifdef DEBUG_CALLSTACK_TRACKER
228 printf("fulltrace allocated %d bytes for trace.\n", full_size_needed);
229#endif
230 to_return[0] = '\0';
231 if (!_depth) {
232 strcat(to_return, emptiness_note);
233 return to_return;
234 }
235
236 const int initial_len = MAX_TEXT_FIELD + 8;
237 char temp[initial_len];
238 int space_left_in_line; // the space provided for one text line.
239
240 int space_used_in_buffer = 0;
241 /* tracks whether we're getting close to the buffer limit. technically, this
242 should not happen, since we calculated the space ahead of time... but it is good
243 to ensure we don't overflow the buffer in case we were not accurate. */
244
245 // start at top most active frame and go down towards bottom most.
246 for (int i = _depth; i >= 1; i--) {
248 strcat(to_return, "\t"); // we left space for this and \n at end.
249 space_left_in_line = initial_len; // reset our counter per line now.
250 temp[0] = '\0';
251 int len_class = strlen(_bt->_records[i]._class);
252 int len_func = strlen(_bt->_records[i]._func);
253 if (space_left_in_line > len_class + len_func + 6) {
254 space_left_in_line -= len_class + len_func + 6;
255 sprintf(temp, "\"%s::%s\", ", _bt->_records[i]._class,
256 _bt->_records[i]._func);
257 CHECK_SPACE_IN_BUFFER(strlen(temp));
258 strcat(to_return, temp);
259 }
260
261 temp[0] = '\0';
262 int len_file = strlen(_bt->_records[i]._file);
263 if (space_left_in_line > len_file + 4) {
264 space_left_in_line -= len_file + 4;
265 sprintf(temp, "\"%s\", ", _bt->_records[i]._file);
266 CHECK_SPACE_IN_BUFFER(strlen(temp));
267 strcat(to_return, temp);
268 }
269
270 temp[0] = '\0';
271 sprintf(temp, "\"line=%d\"", _bt->_records[i]._line);
272 int len_line = strlen(temp);
273 if (space_left_in_line > len_line) {
274 space_left_in_line -= len_line;
275 CHECK_SPACE_IN_BUFFER(strlen(temp));
276 strcat(to_return, temp);
277 }
278
280 strcat(to_return, "\n"); // we left space for this already.
281 }
282
283 return to_return;
284}
285
287{
289
290 if (_unusable) return 0;
291 if (!_depth) return strlen(emptiness_note) + 14; // liberal allocation.
292 int to_return = 28; // another hollywood style excess.
293 for (int i = _depth; i >= 1; i--) {
294 int this_line = 0; // add up parts for just this item.
295
296 // all of these additions are completely dependent on how it's done above.
297
298 int len_class = strlen(_bt->_records[i]._class);
299 int len_func = strlen(_bt->_records[i]._func);
300 this_line += len_class + len_func + 6;
301
302 int len_file = strlen(_bt->_records[i]._file);
303 this_line += len_file + 4;
304
305 this_line += 32; // extra space for line number and such.
306
307 // limit it like we did above; we will use the lesser size value.
308 if (this_line < MAX_TEXT_FIELD + 8) to_return += this_line;
309 else to_return += MAX_TEXT_FIELD + 8;
310 }
311 return to_return;
312}
313
315
317 const char *func, const char *file, int line, bool add_frame)
318: _frame_involved(add_frame),
319 _class(class_name? strdup(class_name) : NULL_POINTER),
320 _func(func? strdup(func) : NULL_POINTER),
321 _file(file? strdup(file) : NULL_POINTER),
322 _line(line)
323{
324 if (_frame_involved) {
325#ifdef DEBUG_CALLSTACK_TRACKER
326 printf(">> in, frame_tracking_instance ctor, class=%s func=%s\n", class_name, func);
327#endif
328 thread_wide_stack_trace().push_frame(class_name, func, file, line);
329#ifdef DEBUG_CALLSTACK_TRACKER
330 printf("<< out, frame_tracking_instance ctor\n");
331#endif
332 }
333}
334
336 (const frame_tracking_instance &to_copy)
337: _frame_involved(false), // copies don't get a right to this.
338 _class(to_copy._class? strdup(to_copy._class) : NULL_POINTER),
339 _func(to_copy._func? strdup(to_copy._func) : NULL_POINTER),
340 _file(to_copy._file? strdup(to_copy._file) : NULL_POINTER),
341 _line(to_copy._line)
342{
343}
344
346
348{
349 if (_frame_involved) {
350#ifdef DEBUG_CALLSTACK_TRACKER
351 printf("frame_tracking_instance clean\n");
352#endif
354 }
355 _frame_involved = false;
356 free(_class); _class = NULL_POINTER;
357 free(_func); _func = NULL_POINTER;
358 free(_file); _file = NULL_POINTER;
359 _line = 0;
360}
361
362frame_tracking_instance &frame_tracking_instance::operator =
363 (const frame_tracking_instance &to_copy)
364{
365#ifdef DEBUG_CALLSTACK_TRACKER
366 printf(">> in, frametrackinst assignment\n");
367#endif
368 if (this == &to_copy) return *this;
369 assign(to_copy._class, to_copy._func, to_copy._file, to_copy._line);
370#ifdef DEBUG_CALLSTACK_TRACKER
371 printf("<< out, frametrackinst assignment\n");
372#endif
373 return *this;
374}
375
376void frame_tracking_instance::assign(const char *class_name, const char *func,
377 const char *file, int line)
378{
379 clean();
380 _frame_involved = false; // copies don't get a right to this.
381 _class = class_name? strdup(class_name) : NULL_POINTER;
382 _func = func? strdup(func) : NULL_POINTER;
383 _file = file? strdup(file) : NULL_POINTER;
384 _line = line;
385}
386
388{
389#ifdef DEBUG_CALLSTACK_TRACKER
390 printf(">> in, frame_tracking_instance update line num\n");
391#endif
393#ifdef DEBUG_CALLSTACK_TRACKER
394 printf("<< out, frame_tracking_instance update line num\n");
395#endif
396}
397
398} // namespace
399
400#endif // ENABLE_CALLSTACK_TRACKING
401
#define CHECK_SPACE_IN_BUFFER(desired_chunk)
This object can provide a backtrace at runtime of the invoking methods.
bool push_frame(const char *class_name, const char *func, const char *file, int line)
adds a new stack from for the "class_name" in "function" at the "line".
bool pop_frame()
removes the last callstack frame off from our tracking.
char * full_trace() const
provides the current stack trace in a newly malloc'd string.
bool update_line(int line)
sets the line number within the current stack frame.
static basis::mutex & __callstack_tracker_synchronizer()
protects concurrent access.
int full_trace_size() const
this returns an estimated number of bytes needed for the full_trace().
a small object that represents a stack trace in progress.
frame_tracking_instance(const char *class_name="", const char *func="", const char *file="", int line=0, bool add_frame=false)
as an automatic variable, this can hang onto frame information.
~frame_tracking_instance()
releases the information and this stack frame in the tracker.
void clean()
throws out our accumulated memory and pops frame if applicable.
char * _file
newly allocated copies.
bool _frame_involved
has this object been added to the tracker?
void assign(const char *class_name, const char *func, const char *file, int line)
similar to assignment operator but doesn't require an object.
auto_synchronizer simplifies concurrent code by automatically unlocking.
Definition mutex.h:113
#define NULL_POINTER
The value representing a pointer to nothing.
Definition definitions.h:32
#define program_wide_memories()
Implements an application lock to ensure only one is running at once.
callstack_tracker & thread_wide_stack_trace()
the provider of thread-wide (single instance per thread) callstack_trackers.
const int MAX_TEXT_FIELD
const char * emptiness_note
what we show when the stack is empty.
tracker_hashing & __tracker_hashing()
void update_current_stack_frame_line_number(int line)
sets the line number for the current frame in the global stack trace.
const int MAX_STACK_DEPTH
const int STACK_TRACKER_ELEMS
fairly generous thread allotment for callstacks, before we start bucketing.
The guards collection helps in testing preconditions and reporting errors.
Definition array.h:30
void WHACK(contents *&ptr)
deletion with clearing of the pointer.
Definition functions.h:121