1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-14 10:13:05 +02:00
pgbackrest/src/common/stackTrace.c
David Steele 2f6017511f Split log levels into separate header file.
Many modules that use debug.h do not need to do logging so this reduces dependencies for those modules.
2018-05-23 09:45:08 -04:00

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--;
}