You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-07-15 01:04:37 +02:00
Add stack trace macros to all functions.
Low-level functions only include stack trace in test builds while higher-level functions ship with stack trace built-in. Stack traces include all parameters passed to the function but production builds only create the parameter list when the log level is set high enough, i.e. debug or trace depending on the function.
This commit is contained in:
363
src/common/stackTrace.c
Normal file
363
src/common/stackTrace.c
Normal file
@ -0,0 +1,363 @@
|
||||
/***********************************************************************************************************************************
|
||||
Stack Trace Handler
|
||||
***********************************************************************************************************************************/
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef WITH_BACKTRACE
|
||||
#include <backtrace.h>
|
||||
#include <backtrace-supported.h>
|
||||
#endif
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/error.h"
|
||||
#include "common/log.h"
|
||||
#include "common/stackTrace.h"
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Max call stack depth
|
||||
***********************************************************************************************************************************/
|
||||
#define STACK_TRACE_MAX 128
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Track stack trace
|
||||
***********************************************************************************************************************************/
|
||||
static int stackSize = 0;
|
||||
|
||||
typedef struct StackTraceData
|
||||
{
|
||||
const char *fileName;
|
||||
const char *functionName;
|
||||
unsigned int fileLine;
|
||||
LogLevel functionLogLevel;
|
||||
unsigned int tryDepth;
|
||||
|
||||
char *param;
|
||||
size_t paramSize;
|
||||
bool paramOverflow;
|
||||
bool paramLog;
|
||||
} StackTraceData;
|
||||
|
||||
static StackTraceData stackTrace[STACK_TRACE_MAX];
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Buffer to hold function parameters
|
||||
***********************************************************************************************************************************/
|
||||
static char functionParamBuffer[32 * 1024];
|
||||
|
||||
struct backtrace_state *backTraceState = NULL;
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Backtrace init and callbacks
|
||||
***********************************************************************************************************************************/
|
||||
#ifdef WITH_BACKTRACE
|
||||
|
||||
void
|
||||
stackTraceInit(const char *exe)
|
||||
{
|
||||
if (backTraceState == NULL)
|
||||
backTraceState = backtrace_create_state(exe, false, NULL, NULL);
|
||||
}
|
||||
|
||||
static int
|
||||
backTraceCallback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function)
|
||||
{
|
||||
(void)(data);
|
||||
(void)(pc);
|
||||
(void)(filename);
|
||||
(void)(function);
|
||||
|
||||
if (stackSize > 0)
|
||||
stackTrace[stackSize - 1].fileLine = (unsigned int)lineno;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
backTraceCallbackError(void *data, const char *msg, int errnum)
|
||||
{
|
||||
(void)data;
|
||||
(void)msg;
|
||||
(void)errnum;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Flag to enable/disable test function logging
|
||||
***********************************************************************************************************************************/
|
||||
#ifndef NDEBUG
|
||||
|
||||
bool stackTraceTestFlag = true;
|
||||
|
||||
void
|
||||
stackTraceTestStart()
|
||||
{
|
||||
stackTraceTestFlag = true;
|
||||
}
|
||||
|
||||
void
|
||||
stackTraceTestStop()
|
||||
{
|
||||
stackTraceTestFlag = false;
|
||||
}
|
||||
|
||||
bool
|
||||
stackTraceTest()
|
||||
{
|
||||
return stackTraceTestFlag;
|
||||
}
|
||||
#endif
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Push a new function onto the trace stack
|
||||
***********************************************************************************************************************************/
|
||||
LogLevel
|
||||
stackTracePush(const char *fileName, const char *functionName, LogLevel functionLogLevel)
|
||||
{
|
||||
ASSERT(stackSize < STACK_TRACE_MAX - 1);
|
||||
|
||||
// Get line number from backtrace if available
|
||||
#ifdef WITH_BACKTRACE
|
||||
backtrace_full(backTraceState, 2, backTraceCallback, backTraceCallbackError, NULL);
|
||||
#endif
|
||||
|
||||
// This struct could be holding old trace data so init to zero
|
||||
StackTraceData *data = &stackTrace[stackSize];
|
||||
memset(data, 0, sizeof(StackTraceData));
|
||||
|
||||
// Set function info
|
||||
data->fileName = fileName;
|
||||
data->functionName = functionName;
|
||||
data->tryDepth = errorTryDepth();
|
||||
|
||||
// Set param pointer
|
||||
if (stackSize == 0)
|
||||
{
|
||||
data->param = functionParamBuffer;
|
||||
data->functionLogLevel = functionLogLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
StackTraceData *dataPrior = &stackTrace[stackSize - 1];
|
||||
|
||||
data->param = dataPrior->param + dataPrior->paramSize + 1;
|
||||
|
||||
// Log level cannot be lower than the prior function
|
||||
if (functionLogLevel < dataPrior->functionLogLevel)
|
||||
data->functionLogLevel = dataPrior->functionLogLevel;
|
||||
else
|
||||
data->functionLogLevel = functionLogLevel;
|
||||
}
|
||||
|
||||
stackSize++;
|
||||
|
||||
return data->functionLogLevel;
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Get parameters for the top function on the stack
|
||||
***********************************************************************************************************************************/
|
||||
static const char *
|
||||
stackTraceParamIdx(int stackIdx)
|
||||
{
|
||||
ASSERT_DEBUG(stackSize > 0);
|
||||
ASSERT_DEBUG(stackIdx < stackSize);
|
||||
|
||||
StackTraceData *data = &stackTrace[stackIdx];
|
||||
|
||||
if (data->paramLog)
|
||||
{
|
||||
if (data->paramOverflow)
|
||||
return "!!! buffer overflow - parameters not available !!!";
|
||||
|
||||
if (data->paramSize == 0)
|
||||
return "void";
|
||||
|
||||
return data->param;
|
||||
}
|
||||
|
||||
return "debug log level required for parameters";
|
||||
}
|
||||
|
||||
const char *
|
||||
stackTraceParam()
|
||||
{
|
||||
return stackTraceParamIdx(stackSize - 1);
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Get the next location where a parameter can be added in the param buffer
|
||||
***********************************************************************************************************************************/
|
||||
char *
|
||||
stackTraceParamBuffer(const char *paramName)
|
||||
{
|
||||
ASSERT_DEBUG(stackSize > 0);
|
||||
|
||||
StackTraceData *data = &stackTrace[stackSize - 1];
|
||||
size_t paramNameSize = strlen(paramName);
|
||||
|
||||
// Make sure that adding this parameter will not overflow the buffer
|
||||
if ((size_t)(data->param - functionParamBuffer) + data->paramSize + paramNameSize + 4 >
|
||||
sizeof(functionParamBuffer) - (STACK_TRACE_PARAM_MAX * 2))
|
||||
{
|
||||
// Set overflow to true
|
||||
data->paramOverflow = true;
|
||||
|
||||
// There's no way to stop the parameter from being formatted so we reserve a space at the end where the format can safely
|
||||
// take place and not disturb the rest of the buffer. Hopefully overflows just won't happen but we need to be prepared in
|
||||
// case of runaway recursion or some other issue that fills the buffer because we don't want a segfault.
|
||||
return functionParamBuffer + sizeof(functionParamBuffer) - STACK_TRACE_PARAM_MAX;
|
||||
}
|
||||
|
||||
// Add a comma if a parameter is already in the list
|
||||
if (data->paramSize != 0)
|
||||
{
|
||||
data->param[data->paramSize++] = ',';
|
||||
data->param[data->paramSize++] = ' ';
|
||||
}
|
||||
|
||||
// Add the parameter name
|
||||
strcpy(data->param + data->paramSize, paramName);
|
||||
data->paramSize += paramNameSize;
|
||||
|
||||
// Add param/value separator
|
||||
data->param[data->paramSize++] = ':';
|
||||
data->param[data->paramSize++] = ' ';
|
||||
|
||||
return data->param + data->paramSize;
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Add a parameter to the function on the top of the stack
|
||||
***********************************************************************************************************************************/
|
||||
void
|
||||
stackTraceParamAdd(size_t bufferSize)
|
||||
{
|
||||
ASSERT_DEBUG(stackSize > 0);
|
||||
|
||||
StackTraceData *data = &stackTrace[stackSize - 1];
|
||||
|
||||
if (!data->paramOverflow)
|
||||
data->paramSize += bufferSize;
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Mark that parameters are being logged -- it none appear then the function is void
|
||||
***********************************************************************************************************************************/
|
||||
void
|
||||
stackTraceParamLog()
|
||||
{
|
||||
ASSERT_DEBUG(stackSize > 0);
|
||||
|
||||
stackTrace[stackSize - 1].paramLog = true;
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Pop a function from the stack trace
|
||||
***********************************************************************************************************************************/
|
||||
#ifdef NDEBUG
|
||||
|
||||
void
|
||||
stackTracePop()
|
||||
{
|
||||
stackSize--;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void
|
||||
stackTracePop(const char *fileName, const char *functionName)
|
||||
{
|
||||
ASSERT_DEBUG(stackSize > 0);
|
||||
|
||||
stackSize--;
|
||||
|
||||
StackTraceData *data = &stackTrace[stackSize];
|
||||
|
||||
if (strcmp(data->fileName, fileName) != 0 || strcmp(data->functionName, functionName) != 0)
|
||||
THROW_FMT(AssertError, "popping %s:%s but expected %s:%s", fileName, functionName, data->fileName, data->functionName);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Stack trace format
|
||||
***********************************************************************************************************************************/
|
||||
static size_t
|
||||
stackTraceFmt(char *buffer, size_t bufferSize, size_t bufferUsed, const char *format, ...)
|
||||
{
|
||||
va_list argumentList;
|
||||
va_start(argumentList, format);
|
||||
int result = vsnprintf(
|
||||
buffer + bufferUsed, bufferUsed < bufferSize ? bufferSize - bufferUsed : 0, format, argumentList);
|
||||
va_end(argumentList);
|
||||
|
||||
return (size_t)result;
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Generate the stack trace
|
||||
***********************************************************************************************************************************/
|
||||
size_t
|
||||
stackTraceToZ(char *buffer, size_t bufferSize, const char *fileName, const char *functionName, unsigned int fileLine)
|
||||
{
|
||||
size_t result = 0;
|
||||
const char *param = "test build required for parameters";
|
||||
int stackIdx = stackSize - 1;
|
||||
|
||||
// If the current function passed in is the same as the top function on the stack then use the parameters for that function
|
||||
if (stackSize > 0 && strcmp(fileName, stackTrace[stackIdx].fileName) == 0 &&
|
||||
strcmp(functionName, stackTrace[stackIdx].functionName) == 0)
|
||||
{
|
||||
param = stackTraceParamIdx(stackSize - 1);
|
||||
stackIdx--;
|
||||
}
|
||||
|
||||
// Output the current function
|
||||
result = stackTraceFmt(
|
||||
buffer, bufferSize, 0, "%.*s:%s:%u:(%s)", (int)(strlen(fileName) - 2), fileName, functionName, fileLine, param);
|
||||
|
||||
// Output stack if there is anything on it
|
||||
if (stackIdx >= 0)
|
||||
{
|
||||
// If the function passed in was not at the top of the stack then some functions are missing
|
||||
if (stackIdx == stackSize - 1)
|
||||
result += stackTraceFmt(buffer, bufferSize, result, "\n ... function(s) ommitted ...");
|
||||
|
||||
// Output the rest of the stack
|
||||
for (; stackIdx >= 0; stackIdx--)
|
||||
{
|
||||
StackTraceData *data = &stackTrace[stackIdx];
|
||||
|
||||
result += stackTraceFmt(
|
||||
buffer, bufferSize, result, "\n%.*s:%s"
|
||||
|
||||
#ifdef WITH_BACKTRACE
|
||||
":%u"
|
||||
#endif
|
||||
":(%s)", (int)(strlen(data->fileName) - 2), data->fileName, data->functionName,
|
||||
|
||||
#ifdef WITH_BACKTRACE
|
||||
data->fileLine,
|
||||
#endif
|
||||
|
||||
stackTraceParamIdx(stackIdx));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Clean the stack at and below the try level
|
||||
|
||||
Called by the error to cleanup the stack when an exception occurs.
|
||||
***********************************************************************************************************************************/
|
||||
void
|
||||
stackTraceClean(unsigned int tryDepth)
|
||||
{
|
||||
while (stackSize > 0 && stackTrace[stackSize - 1].tryDepth >= tryDepth)
|
||||
stackSize--;
|
||||
}
|
Reference in New Issue
Block a user