1 /***********************************************************************************************************************************
2 Stack Trace Handler
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5
6 #include <stdarg.h>
7 #include <stdio.h>
8 #include <string.h>
9
10 #ifdef WITH_BACKTRACE
11 #include <backtrace.h>
12 #include <backtrace-supported.h>
13 #endif
14
15 #include "common/assert.h"
16 #include "common/error.h"
17 #include "common/stackTrace.h"
18
19 /***********************************************************************************************************************************
20 Max call stack depth
21 ***********************************************************************************************************************************/
22 #define STACK_TRACE_MAX 128
23
24 /***********************************************************************************************************************************
25 Local variables
26 ***********************************************************************************************************************************/
27 // Stack trace function data
28 typedef struct StackTraceData
29 {
30 const char *fileName;
31 const char *functionName;
32 unsigned int fileLine;
33 LogLevel functionLogLevel;
34 unsigned int tryDepth;
35
36 bool paramOverflow;
37 bool paramLog;
38 char *param;
39 size_t paramSize;
40 } StackTraceData;
41
42 static struct StackTraceLocal
43 {
44 int stackSize; // Stack size
45 StackTraceData stack[STACK_TRACE_MAX]; // Stack data
46 char functionParamBuffer[32 * 1024]; // Buffer to hold function parameters
47 } stackTraceLocal;
48
49 /**********************************************************************************************************************************/
50 #ifdef WITH_BACKTRACE
51
52 static struct StackTraceBackLocal
53 {
54 struct backtrace_state *backTraceState; // Backtrace state struct
55 } stackTraceBackLocal;
56
57 void
stackTraceInit(const char * exe)58 stackTraceInit(const char *exe)
59 {
60 if (stackTraceBackLocal.backTraceState == NULL)
61 stackTraceBackLocal.backTraceState = backtrace_create_state(exe, false, NULL, NULL);
62 }
63
64 static int
backTraceCallback(void * data,uintptr_t pc,const char * filename,int lineno,const char * function)65 backTraceCallback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function)
66 {
67 (void)(data);
68 (void)(pc);
69 (void)(filename);
70 (void)(function);
71
72 if (stackTraceLocal.stackSize > 0)
73 stackTraceLocal.stack[stackTraceLocal.stackSize - 1].fileLine = (unsigned int)lineno;
74
75 return 1;
76 }
77
78 static void
backTraceCallbackError(void * data,const char * msg,int errnum)79 backTraceCallbackError(void *data, const char *msg, int errnum)
80 {
81 (void)data;
82 (void)msg;
83 (void)errnum;
84 }
85
86 #endif
87
88 /**********************************************************************************************************************************/
89 #ifdef DEBUG
90
91 static struct StackTraceTestLocal
92 {
93 bool testFlag; // Don't log in parameter logging functions to avoid recursion
94 } stackTraceTestLocal = {.testFlag = true};
95
96 void
stackTraceTestStart(void)97 stackTraceTestStart(void)
98 {
99 stackTraceTestLocal.testFlag = true;
100 }
101
102 void
stackTraceTestStop(void)103 stackTraceTestStop(void)
104 {
105 stackTraceTestLocal.testFlag = false;
106 }
107
108 bool
stackTraceTest(void)109 stackTraceTest(void)
110 {
111 return stackTraceTestLocal.testFlag;
112 }
113
114 void
stackTraceTestFileLineSet(unsigned int fileLine)115 stackTraceTestFileLineSet(unsigned int fileLine)
116 {
117 ASSERT(stackTraceLocal.stackSize > 0);
118
119 stackTraceLocal.stack[stackTraceLocal.stackSize - 1].fileLine = fileLine;
120 }
121
122 #endif
123
124 /**********************************************************************************************************************************/
125 LogLevel
stackTracePush(const char * fileName,const char * functionName,LogLevel functionLogLevel)126 stackTracePush(const char *fileName, const char *functionName, LogLevel functionLogLevel)
127 {
128 ASSERT(stackTraceLocal.stackSize < STACK_TRACE_MAX - 1);
129
130 // Get line number from backtrace if available
131 #ifdef WITH_BACKTRACE
132 backtrace_full(stackTraceBackLocal.backTraceState, 2, backTraceCallback, backTraceCallbackError, NULL);
133 #endif
134
135 // Set function info
136 StackTraceData *data = &stackTraceLocal.stack[stackTraceLocal.stackSize];
137
138 *data = (StackTraceData)
139 {
140 .fileName = fileName,
141 .functionName = functionName,
142 .tryDepth = errorTryDepth(),
143 };
144
145 // Set param pointer
146 if (stackTraceLocal.stackSize == 0)
147 {
148 data->param = stackTraceLocal.functionParamBuffer;
149 data->functionLogLevel = functionLogLevel;
150 }
151 else
152 {
153 StackTraceData *dataPrior = &stackTraceLocal.stack[stackTraceLocal.stackSize - 1];
154
155 data->param = dataPrior->param + dataPrior->paramSize + 1;
156
157 // Log level cannot be lower than the prior function
158 if (functionLogLevel < dataPrior->functionLogLevel)
159 data->functionLogLevel = dataPrior->functionLogLevel;
160 else
161 data->functionLogLevel = functionLogLevel;
162 }
163
164 stackTraceLocal.stackSize++;
165
166 return data->functionLogLevel;
167 }
168
169 /**********************************************************************************************************************************/
170 static const char *
stackTraceParamIdx(int stackIdx)171 stackTraceParamIdx(int stackIdx)
172 {
173 ASSERT(stackTraceLocal.stackSize > 0);
174 ASSERT(stackIdx < stackTraceLocal.stackSize);
175
176 StackTraceData *data = &stackTraceLocal.stack[stackIdx];
177
178 if (data->paramLog)
179 {
180 if (data->paramOverflow)
181 return "buffer full - parameters not available";
182
183 if (data->paramSize == 0)
184 return "void";
185
186 return data->param;
187 }
188
189 // If no parameters return the log level required to get them
190 #define LOG_LEVEL_REQUIRED " log level required for parameters"
191 return data->functionLogLevel == logLevelTrace ? "trace" LOG_LEVEL_REQUIRED : "debug" LOG_LEVEL_REQUIRED;
192 }
193
194 const char *
stackTraceParam()195 stackTraceParam()
196 {
197 return stackTraceParamIdx(stackTraceLocal.stackSize - 1);
198 }
199
200 /**********************************************************************************************************************************/
201 char *
stackTraceParamBuffer(const char * paramName)202 stackTraceParamBuffer(const char *paramName)
203 {
204 ASSERT(stackTraceLocal.stackSize > 0);
205
206 StackTraceData *data = &stackTraceLocal.stack[stackTraceLocal.stackSize - 1];
207 size_t paramNameSize = strlen(paramName);
208
209 // Make sure that adding this parameter will not overflow the buffer
210 if ((size_t)(data->param - stackTraceLocal.functionParamBuffer) + data->paramSize + paramNameSize + 4 >
211 sizeof(stackTraceLocal.functionParamBuffer) - (STACK_TRACE_PARAM_MAX * 2))
212 {
213 // Set overflow to true
214 data->paramOverflow = true;
215
216 // There's no way to stop the parameter from being formatted so we reserve a space at the end where the format can safely
217 // take place and not disturb the rest of the buffer. Hopefully overflows just won't happen but we need to be prepared in
218 // case of runaway recursion or some other issue that fills the buffer because we don't want a segfault.
219 return stackTraceLocal.functionParamBuffer + sizeof(stackTraceLocal.functionParamBuffer) - STACK_TRACE_PARAM_MAX;
220 }
221
222 // Add a comma if a parameter is already in the list
223 if (data->paramSize != 0)
224 {
225 data->param[data->paramSize++] = ',';
226 data->param[data->paramSize++] = ' ';
227 }
228
229 // Add the parameter name
230 strcpy(data->param + data->paramSize, paramName);
231 data->paramSize += paramNameSize;
232
233 // Add param/value separator
234 data->param[data->paramSize++] = ':';
235 data->param[data->paramSize++] = ' ';
236
237 return data->param + data->paramSize;
238 }
239
240 /**********************************************************************************************************************************/
241 void
stackTraceParamAdd(size_t bufferSize)242 stackTraceParamAdd(size_t bufferSize)
243 {
244 ASSERT(stackTraceLocal.stackSize > 0);
245
246 StackTraceData *data = &stackTraceLocal.stack[stackTraceLocal.stackSize - 1];
247
248 if (!data->paramOverflow)
249 data->paramSize += bufferSize;
250 }
251
252 /**********************************************************************************************************************************/
253 void
stackTraceParamLog(void)254 stackTraceParamLog(void)
255 {
256 ASSERT(stackTraceLocal.stackSize > 0);
257
258 stackTraceLocal.stack[stackTraceLocal.stackSize - 1].paramLog = true;
259 }
260
261 /**********************************************************************************************************************************/
262 #ifdef DEBUG
263
264 void
stackTracePop(const char * fileName,const char * functionName,bool test)265 stackTracePop(const char *fileName, const char *functionName, bool test)
266 {
267 ASSERT(stackTraceLocal.stackSize > 0);
268
269 if (!test || stackTraceTest())
270 {
271 stackTraceLocal.stackSize--;
272
273 StackTraceData *data = &stackTraceLocal.stack[stackTraceLocal.stackSize];
274
275 if (strcmp(data->fileName, fileName) != 0 || strcmp(data->functionName, functionName) != 0)
276 THROW_FMT(AssertError, "popping %s:%s but expected %s:%s", fileName, functionName, data->fileName, data->functionName);
277 }
278 }
279
280 #else
281
282 void
stackTracePop(void)283 stackTracePop(void)
284 {
285 stackTraceLocal.stackSize--;
286 }
287
288 #endif
289
290 /***********************************************************************************************************************************
291 Stack trace format
292 ***********************************************************************************************************************************/
293 __attribute__((format(printf, 4, 5))) static size_t
stackTraceFmt(char * buffer,size_t bufferSize,size_t bufferUsed,const char * format,...)294 stackTraceFmt(char *buffer, size_t bufferSize, size_t bufferUsed, const char *format, ...)
295 {
296 va_list argumentList;
297 va_start(argumentList, format);
298 int result = vsnprintf(
299 buffer + bufferUsed, bufferUsed < bufferSize ? bufferSize - bufferUsed : 0, format, argumentList);
300 va_end(argumentList);
301
302 return (size_t)result;
303 }
304
305 /**********************************************************************************************************************************/
306 size_t
stackTraceToZ(char * buffer,size_t bufferSize,const char * fileName,const char * functionName,unsigned int fileLine)307 stackTraceToZ(char *buffer, size_t bufferSize, const char *fileName, const char *functionName, unsigned int fileLine)
308 {
309 size_t result = 0;
310 const char *param = "test build required for parameters";
311 int stackIdx = stackTraceLocal.stackSize - 1;
312
313 // If the current function passed in is the same as the top function on the stack then use the parameters for that function
314 if (stackTraceLocal.stackSize > 0 && strcmp(fileName, stackTraceLocal.stack[stackIdx].fileName) == 0 &&
315 strcmp(functionName, stackTraceLocal.stack[stackIdx].functionName) == 0)
316 {
317 param = stackTraceParamIdx(stackTraceLocal.stackSize - 1);
318 stackIdx--;
319 }
320
321 // Output the current function
322 result = stackTraceFmt(buffer, bufferSize, 0, "%s:%s:%u:(%s)", fileName, functionName, fileLine, param);
323
324 // Output stack if there is anything on it
325 if (stackIdx >= 0)
326 {
327 // If the function passed in was not at the top of the stack then some functions are missing
328 if (stackIdx == stackTraceLocal.stackSize - 1)
329 result += stackTraceFmt(buffer, bufferSize, result, "\n ... function(s) omitted ...");
330
331 // Output the rest of the stack
332 for (; stackIdx >= 0; stackIdx--)
333 {
334 StackTraceData *data = &stackTraceLocal.stack[stackIdx];
335
336 result += stackTraceFmt(buffer, bufferSize, result, "\n%s:%s", data->fileName, data->functionName);
337
338 if (data->fileLine > 0)
339 result += stackTraceFmt(buffer, bufferSize, result, ":%u", data->fileLine);
340
341 result += stackTraceFmt(buffer, bufferSize, result, ":(%s)", stackTraceParamIdx(stackIdx));
342 }
343 }
344
345 return result;
346 }
347
348 /**********************************************************************************************************************************/
349 void
stackTraceClean(unsigned int tryDepth)350 stackTraceClean(unsigned int tryDepth)
351 {
352 while (stackTraceLocal.stackSize > 0 && stackTraceLocal.stack[stackTraceLocal.stackSize - 1].tryDepth >= tryDepth)
353 stackTraceLocal.stackSize--;
354 }
355