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 
30 using namespace basis;
31 
32 namespace application {
33 
34 const int MAX_STACK_DEPTH = 2000;
35  // beyond that many stack frames, we will simply refuse to add any more.
36 
37 const int MAX_TEXT_FIELD = 1024;
38  // the most space we allow the class, function, and file to take up.
39 
40 const char *emptiness_note = "Empty Stack\n";
42 
44 
45 basis::mutex &callstack_tracker::__callstack_tracker_synchronizer()
46 {
47  thread_local basis::mutex __global_synch_callstacks;
48  return __global_synch_callstacks;
49 }
50 
52 
54 
60 {
61  auto_synchronizer l(callstack_tracker::__callstack_tracker_synchronizer());
62 
63  thread_local callstack_tracker *_hidden_trace = NULL_POINTER;
64  if (!_hidden_trace) {
65 #ifdef ENABLE_MEMORY_HOOK
66  program_wide_memories().disable();
67  /* we don't want infinite loops tracking the call stack during this object's construction. */
68 //hmmm: does that disable the progwide memories for the whole program or just for this thread?
69 // and what does that entail exactly?
70 // did it actually fix the problem we saw?
71 #endif
72  _hidden_trace = new callstack_tracker;
73 #ifdef ENABLE_MEMORY_HOOK
74  program_wide_memories().enable();
75 #endif
76  }
77  return *_hidden_trace;
78 }
79 
81 
82 class callstack_records
83 {
84 public:
85  frame_tracking_instance _records[MAX_STACK_DEPTH + 2]; // fudging room.
86 };
87 
89 
90 // our current depth gives us our position in the array. we define our
91 // stack as starting at element zero, which is a null stack entry and
92 // corresponds to a depth of zero also. then, when the stack depth is one,
93 // we actually have an element in place and it resides at index 1. this
94 // scheme allows us to have an update to the line number just do nothing when
95 // there is no current stack.
96 
97 callstack_tracker::callstack_tracker()
98 : _bt(new callstack_records),
99  _depth(0),
100  _frames_in(0),
101  _frames_out(0),
102  _highest(0),
103  _unusable(false)
104 {
105 }
106 
108 {
109  _unusable = true;
110  WHACK(_bt);
111 }
112 
113 bool callstack_tracker::push_frame(const char *class_name, const char *func,
114  const char *file, int line)
115 {
117 
118 //printf("callstack pushframe depth=%d in\n", _depth);
119  if (_unusable) return false;
120  if (_depth >= MAX_STACK_DEPTH) {
121  // too many frames already.
122  printf("callstack_tracker::push_frame: past limit at class=%s func=%s "
123  "file=%s line=%d\n", class_name, func, file, line);
124  return false;
125  }
126  _depth++;
127  if (_depth > _highest) _highest = _depth;
128  _frames_in += 1;
129  _bt->_records[_depth].assign(class_name, func, file, line);
130 //printf("callstack pushframe depth=%d out\n", _depth);
131  return true;
132 }
133 
135 {
137 
138 //printf("callstack popframe depth=%d in\n", _depth);
139  if (_unusable) return false;
140  if (_depth <= 0) {
141  // how inappropriate of them; we have no frames.
142  _depth = 0; // we don't lose anything useful by forcing it to be zero.
143  printf("callstack_tracker::pop_frame stack underflow!\n");
144  return false;
145  }
146  _bt->_records[_depth].clean();
147  _depth--;
148  _frames_out += 1;
149 //printf("callstack popframe depth=%d out\n", _depth);
150  return true;
151 }
152 
154 {
156 
157  if (_unusable) return false;
158  if (!_depth) return false; // not as serious, but pretty weird.
159  _bt->_records[_depth]._line = line;
160  return true;
161 }
162 
163 // helpful macro makes sure we stay within our buffer for string storage of the stack trace.
164 #define CHECK_SPACE_IN_BUFFER(desired_chunk) \
165  /* being slightly paranoid about the space, but we don't want any buffer overflows. */ \
166  if (space_used_in_buffer + desired_chunk >= full_size_needed - 4) { \
167  printf("callstack_tracker::full_trace: failure in size estimation--we would have blown out of the buffer"); \
168  return to_return; \
169  } else { \
170  space_used_in_buffer += desired_chunk; \
171  }
172 
174 {
176 
177  if (_unusable) return strdup("");
178  int full_size_needed = full_trace_size();
179  char *to_return = (char *)malloc(full_size_needed);
180 //printf("fulltrace allocated %d bytes for trace.\n", full_size_needed);
181  to_return[0] = '\0';
182  if (!_depth) {
183  strcat(to_return, emptiness_note);
184  return to_return;
185  }
186 
187  const int initial_len = MAX_TEXT_FIELD + 8;
188  char temp[initial_len];
189  int space_left_in_line; // the space provided for one text line.
190 
191  int space_used_in_buffer = 0;
192  /* tracks whether we're getting close to the buffer limit. technically, this
193  should not happen, since we calculated the space ahead of time... but it is good
194  to ensure we don't overflow the buffer in case we were not accurate. */
195 
196  // start at top most active frame and go down towards bottom most.
197  for (int i = _depth; i >= 1; i--) {
199  strcat(to_return, "\t"); // we left space for this and \n at end.
200  space_left_in_line = initial_len; // reset our counter per line now.
201  temp[0] = '\0';
202  int len_class = strlen(_bt->_records[i]._class);
203  int len_func = strlen(_bt->_records[i]._func);
204  if (space_left_in_line > len_class + len_func + 6) {
205  space_left_in_line -= len_class + len_func + 6;
206  sprintf(temp, "\"%s::%s\", ", _bt->_records[i]._class,
207  _bt->_records[i]._func);
208  CHECK_SPACE_IN_BUFFER(strlen(temp));
209  strcat(to_return, temp);
210  }
211 
212  temp[0] = '\0';
213  int len_file = strlen(_bt->_records[i]._file);
214  if (space_left_in_line > len_file + 4) {
215  space_left_in_line -= len_file + 4;
216  sprintf(temp, "\"%s\", ", _bt->_records[i]._file);
217  CHECK_SPACE_IN_BUFFER(strlen(temp));
218  strcat(to_return, temp);
219  }
220 
221  temp[0] = '\0';
222  sprintf(temp, "\"line=%d\"", _bt->_records[i]._line);
223  int len_line = strlen(temp);
224  if (space_left_in_line > len_line) {
225  space_left_in_line -= len_line;
226  CHECK_SPACE_IN_BUFFER(strlen(temp));
227  strcat(to_return, temp);
228  }
229 
231  strcat(to_return, "\n"); // we left space for this already.
232  }
233 
234  return to_return;
235 }
236 
238 {
240 
241  if (_unusable) return 0;
242  if (!_depth) return strlen(emptiness_note) + 14; // liberal allocation.
243  int to_return = 28; // another hollywood style excess.
244  for (int i = _depth; i >= 1; i--) {
245  int this_line = 0; // add up parts for just this item.
246 
247  // all of these additions are completely dependent on how it's done above.
248 
249  int len_class = strlen(_bt->_records[i]._class);
250  int len_func = strlen(_bt->_records[i]._func);
251  this_line += len_class + len_func + 6;
252 
253  int len_file = strlen(_bt->_records[i]._file);
254  this_line += len_file + 4;
255 
256  this_line += 32; // extra space for line number and such.
257 
258  // limit it like we did above; we will use the lesser size value.
259  if (this_line < MAX_TEXT_FIELD + 8) to_return += this_line;
260  else to_return += MAX_TEXT_FIELD + 8;
261  }
262  return to_return;
263 }
264 
266 
268  const char *func, const char *file, int line, bool add_frame)
269 : _frame_involved(add_frame),
270  _class(class_name? strdup(class_name) : NULL_POINTER),
271  _func(func? strdup(func) : NULL_POINTER),
272  _file(file? strdup(file) : NULL_POINTER),
273  _line(line)
274 {
275  if (_frame_involved) {
276 //printf("frametrackinst ctor in class=%s func=%s\n", class_name, func);
277  thread_wide_stack_trace().push_frame(class_name, func, file, line);
278 //printf("frametrackinst ctor out\n");
279  }
280 }
281 
283  (const frame_tracking_instance &to_copy)
284 : _frame_involved(false), // copies don't get a right to this.
285  _class(to_copy._class? strdup(to_copy._class) : NULL_POINTER),
286  _func(to_copy._func? strdup(to_copy._func) : NULL_POINTER),
287  _file(to_copy._file? strdup(to_copy._file) : NULL_POINTER),
288  _line(to_copy._line)
289 {
290 }
291 
293 
295 {
296  if (_frame_involved) {
297 //printf("frametrackinst clean\n");
299  }
300  _frame_involved = false;
301  free(_class); _class = NULL_POINTER;
302  free(_func); _func = NULL_POINTER;
303  free(_file); _file = NULL_POINTER;
304  _line = 0;
305 }
306 
307 frame_tracking_instance &frame_tracking_instance::operator =
308  (const frame_tracking_instance &to_copy)
309 {
310 //printf("frametrackinst tor = in\n");
311  if (this == &to_copy) return *this;
312  assign(to_copy._class, to_copy._func, to_copy._file, to_copy._line);
313 //printf("frametrackinst tor = out\n");
314  return *this;
315 }
316 
317 void frame_tracking_instance::assign(const char *class_name, const char *func,
318  const char *file, int line)
319 {
320  clean();
321  _frame_involved = false; // copies don't get a right to this.
322  _class = class_name? strdup(class_name) : NULL_POINTER;
323  _func = func? strdup(func) : NULL_POINTER;
324  _file = file? strdup(file) : NULL_POINTER;
325  _line = line;
326 }
327 
329 {
330 //printf("frametrackinst updatelinenum in\n");
332 //printf("frametrackinst updatelinenum out\n");
333 }
334 
335 } // namespace
336 
337 #endif // ENABLE_CALLSTACK_TRACKING
338 
#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 single instance of callstack_tracker.
const int MAX_TEXT_FIELD
const char * emptiness_note
what we show when the stack is empty.
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
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