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 
32 using namespace basis;
33 
34 namespace application {
35 
36 const int MAX_STACK_DEPTH = 2000;
37  // beyond that many stack frames, we will simply refuse to add any more.
38 
39 const int MAX_TEXT_FIELD = 1024;
40  // the most space we allow the class, function, and file to take up.
41 
42 const char *emptiness_note = "Empty Stack\n";
44 
45 const int STACK_TRACKER_ELEMS = 200;
47 
48 // uncomment for noisier debugging logging.
49 //#define DEBUG_CALLSTACK_TRACKER
50 
52 
53 basis::mutex &callstack_tracker::__callstack_tracker_synchronizer()
54 {
55  static basis::mutex __global_synch_callstacks;
56  return __global_synch_callstacks;
57 }
58 
60 
61 class tracker_hashing : public std::unordered_map<pid_t, callstack_tracker *>
62 {
63 public:
64  tracker_hashing() : unordered_map<pid_t, callstack_tracker *>() {}
65 };
66 
67 tracker_hashing &__tracker_hashing()
68 {
69  static tracker_hashing __global_tracker;
70  return __global_tracker;
71 }
72 
74 
79 {
80  auto_synchronizer l(callstack_tracker::__callstack_tracker_synchronizer());
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 
121 class callstack_records
122 {
123 public:
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 
138 callstack_tracker::callstack_tracker()
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 
154 bool 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 
362 frame_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 
376 void 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