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