1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-04-21 11:57:01 +02:00
pgbackrest/src/common/stackTrace.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

427 lines
14 KiB
C
Raw Normal View History

/***********************************************************************************************************************************
Stack Trace Handler
***********************************************************************************************************************************/
#include "build.auto.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_LIBBACKTRACE
#include <backtrace-supported.h>
#include <backtrace.h>
#endif
#include "common/assert.h"
#include "common/macro.h"
#include "common/stackTrace.h"
/***********************************************************************************************************************************
Max call stack depth
***********************************************************************************************************************************/
#define STACK_TRACE_MAX 128
/***********************************************************************************************************************************
Local variables
***********************************************************************************************************************************/
// Stack trace function data
typedef struct StackTraceData
{
const char *fileName;
const char *functionName;
unsigned int fileLine;
LogLevel functionLogLevel;
unsigned int tryDepth;
bool paramOverflow;
bool paramLog;
char *param;
size_t paramSize;
} StackTraceData;
static struct StackTraceLocal
{
int stackSize; // Stack size
StackTraceData stack[STACK_TRACE_MAX]; // Stack data
char functionParamBuffer[32 * 1024]; // Buffer to hold function parameters
#ifdef HAVE_LIBBACKTRACE
struct backtrace_state *backTraceState; // Backtrace state struct
#endif
} stackTraceLocal;
/**********************************************************************************************************************************/
#ifdef DEBUG
static struct StackTraceTestLocal
{
bool testFlag; // Don't log in parameter logging functions to avoid recursion
} stackTraceTestLocal = {.testFlag = true};
FN_EXTERN void
stackTraceTestStart(void)
{
stackTraceTestLocal.testFlag = true;
}
FN_EXTERN void
stackTraceTestStop(void)
{
stackTraceTestLocal.testFlag = false;
}
FN_EXTERN bool
stackTraceTest(void)
{
return stackTraceTestLocal.testFlag;
}
FN_EXTERN void
stackTraceTestFileLineSet(unsigned int fileLine)
{
ASSERT(stackTraceLocal.stackSize > 0);
stackTraceLocal.stack[stackTraceLocal.stackSize - 1].fileLine = fileLine;
}
#endif
/**********************************************************************************************************************************/
FN_EXTERN LogLevel
stackTracePush(const char *fileName, const char *functionName, LogLevel functionLogLevel)
{
ASSERT(stackTraceLocal.stackSize < STACK_TRACE_MAX - 1);
// Set function info
StackTraceData *data = &stackTraceLocal.stack[stackTraceLocal.stackSize];
*data = (StackTraceData)
{
.fileName = fileName,
.functionName = functionName,
.tryDepth = errorTryDepth(),
};
// Set param pointer
if (stackTraceLocal.stackSize == 0)
{
data->param = stackTraceLocal.functionParamBuffer;
data->functionLogLevel = functionLogLevel;
}
else
{
StackTraceData *dataPrior = &stackTraceLocal.stack[stackTraceLocal.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;
}
stackTraceLocal.stackSize++;
return data->functionLogLevel;
}
/**********************************************************************************************************************************/
static const char *
stackTraceParamIdx(int stackIdx)
{
ASSERT(stackTraceLocal.stackSize > 0);
ASSERT(stackIdx < stackTraceLocal.stackSize);
StackTraceData *data = &stackTraceLocal.stack[stackIdx];
if (data->paramLog)
{
if (data->paramOverflow)
return "buffer full - parameters not available";
if (data->paramSize == 0)
return "void";
return data->param;
}
// If no parameters return the log level required to get them
#define LOG_LEVEL_REQUIRED " log level required for parameters"
return data->functionLogLevel == logLevelTrace ? "trace" LOG_LEVEL_REQUIRED : "debug" LOG_LEVEL_REQUIRED;
}
FN_EXTERN const char *
2023-04-21 15:08:59 +03:00
stackTraceParam(void)
{
return stackTraceParamIdx(stackTraceLocal.stackSize - 1);
}
/**********************************************************************************************************************************/
FN_EXTERN char *
stackTraceParamBuffer(const char *paramName)
{
ASSERT(stackTraceLocal.stackSize > 0);
StackTraceData *data = &stackTraceLocal.stack[stackTraceLocal.stackSize - 1];
size_t paramNameSize = strlen(paramName);
// Make sure that adding this parameter will not overflow the buffer
if ((size_t)(data->param - stackTraceLocal.functionParamBuffer) + data->paramSize + paramNameSize + 4 >
sizeof(stackTraceLocal.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 stackTraceLocal.functionParamBuffer + sizeof(stackTraceLocal.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;
}
/**********************************************************************************************************************************/
FN_EXTERN void
stackTraceParamAdd(size_t bufferSize)
{
ASSERT(stackTraceLocal.stackSize > 0);
StackTraceData *data = &stackTraceLocal.stack[stackTraceLocal.stackSize - 1];
if (!data->paramOverflow)
data->paramSize += bufferSize;
}
/**********************************************************************************************************************************/
FN_EXTERN void
stackTraceParamLog(void)
{
ASSERT(stackTraceLocal.stackSize > 0);
stackTraceLocal.stack[stackTraceLocal.stackSize - 1].paramLog = true;
}
/**********************************************************************************************************************************/
#ifdef DEBUG
FN_EXTERN void
stackTracePop(const char *fileName, const char *functionName, bool test)
{
ASSERT(stackTraceLocal.stackSize > 0);
if (!test || stackTraceTest())
{
stackTraceLocal.stackSize--;
StackTraceData *data = &stackTraceLocal.stack[stackTraceLocal.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);
}
}
#else
FN_EXTERN void
stackTracePop(void)
{
stackTraceLocal.stackSize--;
}
#endif
/***********************************************************************************************************************************
Stack trace format
***********************************************************************************************************************************/
static FN_PRINTF(4, 5) 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;
}
/***********************************************************************************************************************************
Helper to trim off extra path before the src path
***********************************************************************************************************************************/
static const char *
stackTraceTrimSrc(const char *const fileName)
{
const char *const src = strstr(fileName, "src/");
return src == NULL ? fileName : src + 4;
}
/**********************************************************************************************************************************/
#ifdef HAVE_LIBBACKTRACE
typedef struct StackTraceBackData
{
bool firstCall;
bool firstLine;
int stackIdx;
size_t result;
char *const buffer;
const size_t bufferSize;
} StackTraceBackData;
// Callback to add backtrace data when available
static int
stackTraceBackCallback(
void *const dataVoid, const uintptr_t pc, const char *fileName, const int fileLine, const char *const functionName)
{
(void)pc;
StackTraceBackData *const data = dataVoid;
// Catch any unset parameters which indicates the debug data is not available
if (fileName == NULL || fileLine == 0 || functionName == NULL)
{
// If this is the first call then stop because the top of the backtrace must be one of our functions
if (data->firstCall)
return true;
// Else return but do not stop
data->firstCall = false;
return false;
}
// Reset first call
data->firstCall = false;
// If the function name matches combine backtrace data with stack data
if (data->stackIdx >= 0 && strcmp(functionName, stackTraceLocal.stack[data->stackIdx].functionName) == 0)
{
data->result += stackTraceFmt(
data->buffer, data->bufferSize, data->result, "%s%s:%s:%d:(%s)", data->firstLine ? "" : "\n",
stackTraceTrimSrc(stackTraceLocal.stack[data->stackIdx].fileName), functionName, fileLine,
stackTraceParamIdx(data->stackIdx));
data->stackIdx--;
}
// Else just use stack data. Skip any functions in the error module since they are not useful for the user
else
{
fileName = stackTraceTrimSrc(fileName);
if (strcmp(fileName, "common/error/error.c") == 0)
return false;
data->result += stackTraceFmt(
data->buffer, data->bufferSize, data->result, "%s%s:%s:%d:(no parameters available)", data->firstLine ? "" : "\n",
fileName, functionName, fileLine);
}
// Reset first line
data->firstLine = false;
// Stop when the main function has been processed
return strcmp(functionName, "main") == 0;
}
// Dummy error callback. If there is an error just generate the default stack trace.
static void
stackTraceBackErrorCallback(void *data, const char *msg, int errnum)
{
(void)data;
(void)msg;
(void)errnum;
}
#endif
FN_EXTERN size_t
stackTraceToZ(
char *const buffer, const size_t bufferSize, const char *fileName, const char *const functionName, const unsigned int fileLine)
{
#ifdef HAVE_LIBBACKTRACE
// Attempt to use backtrace data
StackTraceBackData data =
{
.firstCall = true,
.firstLine = true,
.stackIdx = stackTraceLocal.stackSize - 1,
.result = 0,
.buffer = buffer,
.bufferSize = bufferSize,
};
if (stackTraceLocal.backTraceState == NULL)
stackTraceLocal.backTraceState = backtrace_create_state(NULL, false, NULL, NULL);
backtrace_full(stackTraceLocal.backTraceState, 2, stackTraceBackCallback, stackTraceBackErrorCallback, &data);
if (data.result != 0)
return data.result;
#endif // HAVE_LIBBACKTRACE
size_t result = 0;
const char *param = "test build required for parameters";
int stackIdx = stackTraceLocal.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
fileName = stackTraceTrimSrc(fileName);
if (stackTraceLocal.stackSize > 0 && strcmp(fileName, stackTraceTrimSrc(stackTraceLocal.stack[stackIdx].fileName)) == 0 &&
strcmp(functionName, stackTraceLocal.stack[stackIdx].functionName) == 0)
{
param = stackTraceParamIdx(stackTraceLocal.stackSize - 1);
stackIdx--;
}
// Output the current function
result = stackTraceFmt(buffer, bufferSize, 0, "%s:%s:%u:(%s)", 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 == stackTraceLocal.stackSize - 1)
result += stackTraceFmt(buffer, bufferSize, result, "\n ... function(s) omitted ...");
// Output the rest of the stack
for (; stackIdx >= 0; stackIdx--)
{
const StackTraceData *const data = &stackTraceLocal.stack[stackIdx];
result += stackTraceFmt(buffer, bufferSize, result, "\n%s:%s", stackTraceTrimSrc(data->fileName), data->functionName);
if (data->fileLine > 0)
result += stackTraceFmt(buffer, bufferSize, result, ":%u", data->fileLine);
result += stackTraceFmt(buffer, bufferSize, result, ":(%s)", stackTraceParamIdx(stackIdx));
}
}
return result;
}
/**********************************************************************************************************************************/
FN_EXTERN void
stackTraceClean(const unsigned int tryDepth, const bool fatal)
{
(void)fatal; // Cleanup is the same for fatal errors
while (stackTraceLocal.stackSize > 0 && stackTraceLocal.stack[stackTraceLocal.stackSize - 1].tryDepth >= tryDepth)
stackTraceLocal.stackSize--;
}