#include "w32_crtdbg_stacktrace.h"
#if defined(GIT_MSVC_CRTDBG)
#include "w32_stack.h"
#define CRTDBG_STACKTRACE__UID_LEN (15)
typedef struct {
char uid[CRTDBG_STACKTRACE__UID_LEN + 1];
} git_win32__crtdbg_stacktrace__uid;
typedef struct {
git_win32__crtdbg_stacktrace__uid uid;
git_win32__stack__raw_data raw_data;
unsigned int count_allocs;
unsigned int count_allocs_at_last_checkpoint;
unsigned int transient_count_leaks;
} git_win32__crtdbg_stacktrace__row;
static CRITICAL_SECTION g_crtdbg_stacktrace_cs;
#define MY_ROW_LIMIT (1024 * 1024)
static git_win32__crtdbg_stacktrace__row g_cs_rows[MY_ROW_LIMIT];
static git_win32__crtdbg_stacktrace__row *g_cs_index[MY_ROW_LIMIT];
static unsigned int g_cs_end = MY_ROW_LIMIT;
static unsigned int g_cs_ins = 0;
static unsigned int g_count_total_allocs = 0;
static unsigned int g_transient_count_total_leaks = 0;
static unsigned int g_transient_count_dedup_leaks = 0;
static bool g_limit_reached = false;
static unsigned int g_checkpoint_id = 0;
static bool g_transient_leaks_since_mark = false;
static int row_cmp(const void *v1, const void *v2)
{
git_win32__stack__raw_data *d1 = (git_win32__stack__raw_data*)v1;
git_win32__crtdbg_stacktrace__row *r2 = (git_win32__crtdbg_stacktrace__row *)v2;
return (git_win32__stack_compare(d1, &r2->raw_data));
}
static git_win32__crtdbg_stacktrace__row * insert_unique(
const git_win32__stack__raw_data *pdata)
{
size_t pos;
if (git__bsearch(g_cs_index, g_cs_ins, pdata, row_cmp, &pos) < 0) {
memcpy(&g_cs_rows[g_cs_ins].raw_data, pdata, sizeof(*pdata));
sprintf(g_cs_rows[g_cs_ins].uid.uid, "##%08lx", g_cs_ins);
if (pos < g_cs_ins)
memmove(&g_cs_index[pos+1], &g_cs_index[pos], (g_cs_ins - pos)*sizeof(g_cs_index[0]));
g_cs_index[pos] = &g_cs_rows[g_cs_ins];
g_cs_ins++;
}
g_cs_index[pos]->count_allocs++;
return g_cs_index[pos];
}
static int __cdecl report_hook(int nRptType, char *szMsg, int *retVal)
{
static int hook_result = TRUE;
unsigned int pos;
*retVal = 0;
if ((szMsg[0] != '#') || (szMsg[1] != '#'))
return hook_result;
if (sscanf(&szMsg[2], "%08lx", &pos) < 1)
return hook_result;
if (pos >= g_cs_ins)
return hook_result;
if (g_transient_leaks_since_mark) {
if (g_cs_rows[pos].count_allocs == g_cs_rows[pos].count_allocs_at_last_checkpoint)
return hook_result;
}
g_cs_rows[pos].transient_count_leaks++;
if (g_cs_rows[pos].transient_count_leaks == 1)
g_transient_count_dedup_leaks++;
g_transient_count_total_leaks++;
return hook_result;
}
static void my_output(const char *buf)
{
fwrite(buf, strlen(buf), 1, stderr);
OutputDebugString(buf);
}
static void dump_summary(const char *label)
{
unsigned int k;
char buf[10 * 1024];
if (g_transient_count_total_leaks == 0)
return;
fflush(stdout);
fflush(stderr);
my_output("\n");
if (g_limit_reached) {
sprintf(buf,
"LEAK SUMMARY: de-dup row table[%d] filled. Increase MY_ROW_LIMIT.\n",
MY_ROW_LIMIT);
my_output(buf);
}
if (!label)
label = "";
if (g_transient_leaks_since_mark) {
sprintf(buf, "LEAK CHECKPOINT %d: leaks %d unique %d: %s\n",
g_checkpoint_id, g_transient_count_total_leaks, g_transient_count_dedup_leaks, label);
my_output(buf);
} else {
sprintf(buf, "LEAK SUMMARY: TOTAL leaks %d de-duped %d: %s\n",
g_transient_count_total_leaks, g_transient_count_dedup_leaks, label);
my_output(buf);
}
my_output("\n");
for (k = 0; k < g_cs_ins; k++) {
if (g_cs_rows[k].transient_count_leaks > 0) {
sprintf(buf, "LEAK: %s leaked %d of %d times:\n",
g_cs_rows[k].uid.uid,
g_cs_rows[k].transient_count_leaks,
g_cs_rows[k].count_allocs);
my_output(buf);
if (git_win32__stack_format(
buf, sizeof(buf), &g_cs_rows[k].raw_data,
NULL, NULL) >= 0) {
my_output(buf);
}
my_output("\n");
}
}
fflush(stderr);
}
void git_win32__crtdbg_stacktrace_init(void)
{
InitializeCriticalSection(&g_crtdbg_stacktrace_cs);
EnterCriticalSection(&g_crtdbg_stacktrace_cs);
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE);
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
LeaveCriticalSection(&g_crtdbg_stacktrace_cs);
}
int git_win32__crtdbg_stacktrace__dump(
git_win32__crtdbg_stacktrace_options opt,
const char *label)
{
_CRT_REPORT_HOOK old;
unsigned int k;
int r = 0;
#define IS_BIT_SET(o,b) (((o) & (b)) != 0)
bool b_set_mark = IS_BIT_SET(opt, GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK);
bool b_leaks_since_mark = IS_BIT_SET(opt, GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK);
bool b_leaks_total = IS_BIT_SET(opt, GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL);
bool b_quiet = IS_BIT_SET(opt, GIT_WIN32__CRTDBG_STACKTRACE__QUIET);
if (b_leaks_since_mark && b_leaks_total) {
giterr_set(GITERR_INVALID, "cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL.");
return GIT_ERROR;
}
if (!b_set_mark && !b_leaks_since_mark && !b_leaks_total) {
giterr_set(GITERR_INVALID, "nothing to do.");
return GIT_ERROR;
}
EnterCriticalSection(&g_crtdbg_stacktrace_cs);
if (b_leaks_since_mark || b_leaks_total) {
g_transient_count_total_leaks = 0;
g_transient_count_dedup_leaks = 0;
for (k = 0; k < g_cs_ins; k++) {
g_cs_rows[k].transient_count_leaks = 0;
}
}
g_transient_leaks_since_mark = b_leaks_since_mark;
old = _CrtSetReportHook(report_hook);
_CrtDumpMemoryLeaks();
_CrtSetReportHook(old);
if (b_leaks_since_mark || b_leaks_total) {
r = g_transient_count_dedup_leaks;
if (!b_quiet)
dump_summary(label);
}
if (b_set_mark) {
for (k = 0; k < g_cs_ins; k++) {
g_cs_rows[k].count_allocs_at_last_checkpoint = g_cs_rows[k].count_allocs;
}
g_checkpoint_id++;
}
LeaveCriticalSection(&g_crtdbg_stacktrace_cs);
return r;
}
void git_win32__crtdbg_stacktrace_cleanup(void)
{
git_win32__crtdbg_stacktrace__dump(
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL,
"CLEANUP");
DeleteCriticalSection(&g_crtdbg_stacktrace_cs);
}
const char *git_win32__crtdbg_stacktrace(int skip, const char *file)
{
git_win32__stack__raw_data new_data;
git_win32__crtdbg_stacktrace__row *row;
const char * result = file;
if (git_win32__stack_capture(&new_data, skip+1) < 0)
return result;
EnterCriticalSection(&g_crtdbg_stacktrace_cs);
if (g_cs_ins < g_cs_end) {
row = insert_unique(&new_data);
result = row->uid.uid;
} else {
g_limit_reached = true;
}
g_count_total_allocs++;
LeaveCriticalSection(&g_crtdbg_stacktrace_cs);
return result;
}
#endif