mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2024-12-14 10:13:05 +02:00
2f6017511f
Many modules that use debug.h do not need to do logging so this reduces dependencies for those modules.
363 lines
12 KiB
C
363 lines
12 KiB
C
/***********************************************************************************************************************************
|
|
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/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--;
|
|
}
|