Use libbacktrace to generate better leak callstacks.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3986 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
2022-09-24 20:54:54 +00:00
parent 8279ec5e9e
commit eadda41518
77 changed files with 79134 additions and 15 deletions

View File

@ -9,6 +9,7 @@
#include "tests.h"
#include "util.js.h"
#include <backtrace.h>
#include <memcheck.h>
#include <quickjs-libc.h>
#include <quickjs.h>
@ -83,6 +84,8 @@ const command_t k_commands[] = {
{ "private", _tf_command_private, "Check for private messages the SSB database (just an experiment)." },
};
struct backtrace_state* g_backtrace_state;
void shedPrivileges()
{
#if !defined(_WIN32)
@ -684,8 +687,19 @@ static void _do_leak_checks(int sig)
}
#endif
static void _backtrace_error(void* data, const char* message, int errnum)
{
printf("libbacktrace error %d: %s\n", errnum, message);
}
int main(int argc, char* argv[])
{
g_backtrace_state = backtrace_create_state(
argv[0],
0,
_backtrace_error,
NULL);
#if !defined(_WIN32)
prctl(PR_SET_PDEATHSIG, SIGKILL);
#endif

View File

@ -26,7 +26,10 @@
#include "quickjs.h"
#include "quickjs-libc.h"
#include <backtrace.h>
#ifndef _WIN32
#include <execinfo.h>
#include <unistd.h>
#endif
@ -35,6 +38,8 @@ static const char* k_version = "1.0";
static JSClassID _import_class_id;
static int _count;
extern struct backtrace_state* g_backtrace_state;
typedef struct _export_record_t export_record_t;
typedef struct _import_record_t import_record_t;
@ -59,6 +64,8 @@ typedef struct _promise_stack_t
{
uint32_t hash;
const char* stack;
void* cstack[32];
int cstack_count;
int count;
} promise_stack_t;
@ -731,18 +738,79 @@ static JSValue _tf_task_getStats(JSContext* context, JSValueConst this_val, int
return result;
}
typedef struct _backtrace_t
{
JSContext* context;
JSValue array;
int count;
} backtrace_t;
static int _tf_backtrace_callback(void* data, uintptr_t pc, const char* filename, int line_number, const char* function)
{
backtrace_t* bt = data;
JSValue entry = JS_NewObject(bt->context);
JS_SetPropertyStr(bt->context, entry, "pc", JS_NewInt64(bt->context, (int64_t)(intptr_t)pc));
if (filename)
{
JS_SetPropertyStr(bt->context, entry, "filename", JS_NewString(bt->context, filename));
}
JS_SetPropertyStr(bt->context, entry, "line_number", JS_NewInt32(bt->context, line_number));
if (function)
{
JS_SetPropertyStr(bt->context, entry, "function", JS_NewString(bt->context, function));
}
JS_SetPropertyUint32(bt->context, bt->array, bt->count++, entry);
return 0;
}
static void _tf_backtrace_error(void* data, const char* message, int error)
{
backtrace_t* bt = data;
JSValue entry = JS_NewObject(bt->context);
if (message)
{
JS_SetPropertyStr(bt->context, entry, "error", JS_NewString(bt->context, message));
}
JS_SetPropertyStr(bt->context, entry, "code", JS_NewInt32(bt->context, error));
JS_SetPropertyUint32(bt->context, bt->array, bt->count++, entry);
}
static JSValue _tf_task_getDebug(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_task_t* task = JS_GetContextOpaque(context);
JSValue result = JS_NewObject(context);
JSValue promises = JS_NewObject(context);
JSValue promises = JS_NewArray(context);
JS_SetPropertyStr(context, result, "promises", promises);
int j = 0;
for (int i = 0; i < task->_promise_stack_count; i++)
{
if (task->_promise_stacks[i].count)
{
JS_SetPropertyStr(context, promises, task->_promise_stacks[i].stack, JS_NewInt32(context, task->_promise_stacks[i].count));
JSValue entry = JS_NewObject(context);
JS_SetPropertyStr(context, entry, "stack", JS_NewString(context, task->_promise_stacks[i].stack));
if (task->_promise_stacks[i].cstack_count)
{
JSValue cstack = JS_NewArray(context);
backtrace_t bt =
{
.context = context,
.array = cstack,
};
for (int k = 0; k < task->_promise_stacks[i].cstack_count; k++)
{
backtrace_pcinfo(
g_backtrace_state,
(uintptr_t)task->_promise_stacks[i].cstack[k],
_tf_backtrace_callback,
_tf_backtrace_error,
&bt);
}
JS_SetPropertyStr(context, entry, "cstack", cstack);
}
JS_SetPropertyStr(context, entry, "count", JS_NewInt32(context, task->_promise_stacks[i].count));
JS_SetPropertyUint32(context, promises, j++, entry);
}
}
@ -1018,7 +1086,7 @@ static int _promise_stack_compare(const void* a, const void* b)
return *pa < pb->hash ? -1 : *pa > pb->hash ? 1 : 0;
}
static void _add_promise_stack(tf_task_t* task, uint32_t hash, const char* stack)
static void _add_promise_stack(tf_task_t* task, uint32_t hash, const char* stack, void** buffer, int count)
{
int index = tf_util_insert_index(&hash, task->_promise_stacks, task->_promise_stack_count, sizeof(promise_stack_t), _promise_stack_compare);
if (index < task->_promise_stack_count && task->_promise_stacks[index].hash == hash)
@ -1032,7 +1100,8 @@ static void _add_promise_stack(tf_task_t* task, uint32_t hash, const char* stack
{
memmove(task->_promise_stacks + index + 1, task->_promise_stacks + index, sizeof(promise_stack_t) * (task->_promise_stack_count - index));
}
task->_promise_stacks[index] = (promise_stack_t) { .hash = hash, .stack = tf_strdup(stack), .count = 1 };
task->_promise_stacks[index] = (promise_stack_t) { .hash = hash, .stack = tf_strdup(stack), .count = 1, .cstack_count = count };
memcpy(task->_promise_stacks[index].cstack, buffer, sizeof(void*) * count);
task->_promise_stack_count++;
}
}
@ -1058,11 +1127,12 @@ static void _tf_task_free_promise(tf_task_t* task, promiseid_t id)
}
}
uint32_t fnv32a(const char* buffer, uint32_t start)
uint32_t fnv32a(const void* buffer, int length, uint32_t start)
{
uint32_t result = 0x811c9dc5;
for (const char* p = buffer; *p; p++) {
result ^= *p;
for (int i = 0; i < length; i++)
{
result ^= ((const uint8_t*)buffer)[i];
result += (result << 1) + (result << 4) + (result << 7) + (result << 8) + (result << 24);
}
return result;
@ -1073,9 +1143,16 @@ JSValue tf_task_allocate_promise(tf_task_t* task, promiseid_t* out_promise)
JSValue error = JS_ThrowInternalError(task->_context, "promise callstack");
JSValue exception = JS_GetException(task->_context);
JSValue stack_value = JS_GetPropertyStr(task->_context, exception, "stack");
const char* stack = JS_ToCString(task->_context, stack_value);
uint32_t stack_hash = fnv32a(stack, 0);
_add_promise_stack(task, stack_hash, stack);
size_t length = 0;
const char* stack = JS_ToCStringLen(task->_context, &length, stack_value);
uint32_t stack_hash = fnv32a((const void*)stack, (int)length, 0);
int count = 0;
void* buffer[32];
#ifndef _WIN32
count = backtrace(buffer, sizeof(buffer) / sizeof(*buffer));
stack_hash = fnv32a((const void*)buffer, sizeof(void*) * count, stack_hash);
#endif
_add_promise_stack(task, stack_hash, stack, buffer, count);
JS_FreeCString(task->_context, stack);
JS_FreeValue(task->_context, stack_value);
JS_FreeValue(task->_context, exception);