summaryrefslogtreecommitdiff
path: root/src/win32/w32_leakcheck.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/win32/w32_leakcheck.c')
-rw-r--r--src/win32/w32_leakcheck.c581
1 files changed, 0 insertions, 581 deletions
diff --git a/src/win32/w32_leakcheck.c b/src/win32/w32_leakcheck.c
deleted file mode 100644
index 5c8425be3..000000000
--- a/src/win32/w32_leakcheck.c
+++ /dev/null
@@ -1,581 +0,0 @@
-/*
- * Copyright (C) the libgit2 contributors. All rights reserved.
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-
-#include "w32_leakcheck.h"
-
-#if defined(GIT_WIN32_LEAKCHECK)
-
-#include "Windows.h"
-#include "Dbghelp.h"
-#include "win32/posix.h"
-#include "hash.h"
-#include "runtime.h"
-
-/* Stack frames (for stack tracing, below) */
-
-static bool g_win32_stack_initialized = false;
-static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE;
-static git_win32_leakcheck_stack_aux_cb_alloc g_aux_cb_alloc = NULL;
-static git_win32_leakcheck_stack_aux_cb_lookup g_aux_cb_lookup = NULL;
-
-int git_win32_leakcheck_stack_set_aux_cb(
- git_win32_leakcheck_stack_aux_cb_alloc cb_alloc,
- git_win32_leakcheck_stack_aux_cb_lookup cb_lookup)
-{
- g_aux_cb_alloc = cb_alloc;
- g_aux_cb_lookup = cb_lookup;
-
- return 0;
-}
-
-/**
- * Load symbol table data. This should be done in the primary
- * thread at startup (under a lock if there are other threads
- * active).
- */
-void git_win32_leakcheck_stack_init(void)
-{
- if (!g_win32_stack_initialized) {
- g_win32_stack_process = GetCurrentProcess();
- SymSetOptions(SYMOPT_LOAD_LINES);
- SymInitialize(g_win32_stack_process, NULL, TRUE);
- g_win32_stack_initialized = true;
- }
-}
-
-/**
- * Cleanup symbol table data. This should be done in the
- * primary thead at shutdown (under a lock if there are other
- * threads active).
- */
-void git_win32_leakcheck_stack_cleanup(void)
-{
- if (g_win32_stack_initialized) {
- SymCleanup(g_win32_stack_process);
- g_win32_stack_process = INVALID_HANDLE_VALUE;
- g_win32_stack_initialized = false;
- }
-}
-
-int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip)
-{
- if (!g_win32_stack_initialized) {
- git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized.");
- return GIT_ERROR;
- }
-
- memset(pdata, 0, sizeof(*pdata));
- pdata->nr_frames = RtlCaptureStackBackTrace(
- skip+1, GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES, pdata->frames, NULL);
-
- /* If an "aux" data provider was registered, ask it to capture
- * whatever data it needs and give us an "aux_id" to it so that
- * we can refer to it later when reporting.
- */
- if (g_aux_cb_alloc)
- (g_aux_cb_alloc)(&pdata->aux_id);
-
- return 0;
-}
-
-int git_win32_leakcheck_stack_compare(
- git_win32_leakcheck_stack_raw_data *d1,
- git_win32_leakcheck_stack_raw_data *d2)
-{
- return memcmp(d1, d2, sizeof(*d1));
-}
-
-int git_win32_leakcheck_stack_format(
- char *pbuf, size_t buf_len,
- const git_win32_leakcheck_stack_raw_data *pdata,
- const char *prefix, const char *suffix)
-{
-#define MY_MAX_FILENAME 255
-
- /* SYMBOL_INFO has char FileName[1] at the end. The docs say to
- * to malloc it with extra space for your desired max filename.
- */
- struct {
- SYMBOL_INFO symbol;
- char extra[MY_MAX_FILENAME + 1];
- } s;
-
- IMAGEHLP_LINE64 line;
- size_t buf_used = 0;
- unsigned int k;
- char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */
- size_t detail_len;
-
- if (!g_win32_stack_initialized) {
- git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized.");
- return GIT_ERROR;
- }
-
- if (!prefix)
- prefix = "\t";
- if (!suffix)
- suffix = "\n";
-
- memset(pbuf, 0, buf_len);
-
- memset(&s, 0, sizeof(s));
- s.symbol.MaxNameLen = MY_MAX_FILENAME;
- s.symbol.SizeOfStruct = sizeof(SYMBOL_INFO);
-
- memset(&line, 0, sizeof(line));
- line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
-
- for (k=0; k < pdata->nr_frames; k++) {
- DWORD64 frame_k = (DWORD64)pdata->frames[k];
- DWORD dwUnused;
-
- if (SymFromAddr(g_win32_stack_process, frame_k, 0, &s.symbol) &&
- SymGetLineFromAddr64(g_win32_stack_process, frame_k, &dwUnused, &line)) {
- const char *pslash;
- const char *pfile;
-
- pslash = strrchr(line.FileName, '\\');
- pfile = ((pslash) ? (pslash+1) : line.FileName);
- p_snprintf(detail, sizeof(detail), "%s%s:%d> %s%s",
- prefix, pfile, line.LineNumber, s.symbol.Name, suffix);
- } else {
- /* This happens when we cross into another module.
- * For example, in CLAR tests, this is typically
- * the CRT startup code. Just print an unknown
- * frame and continue.
- */
- p_snprintf(detail, sizeof(detail), "%s??%s", prefix, suffix);
- }
- detail_len = strlen(detail);
-
- if (buf_len < (buf_used + detail_len + 1)) {
- /* we don't have room for this frame in the buffer, so just stop. */
- break;
- }
-
- memcpy(&pbuf[buf_used], detail, detail_len);
- buf_used += detail_len;
- }
-
- /* "aux_id" 0 is reserved to mean no aux data. This is needed to handle
- * allocs that occur before the aux callbacks were registered.
- */
- if (pdata->aux_id > 0) {
- p_snprintf(detail, sizeof(detail), "%saux_id: %d%s",
- prefix, pdata->aux_id, suffix);
- detail_len = strlen(detail);
- if ((buf_used + detail_len + 1) < buf_len) {
- memcpy(&pbuf[buf_used], detail, detail_len);
- buf_used += detail_len;
- }
-
- /* If an "aux" data provider is still registered, ask it to append its detailed
- * data to the end of ours using the "aux_id" it gave us when this de-duped
- * item was created.
- */
- if (g_aux_cb_lookup)
- (g_aux_cb_lookup)(pdata->aux_id, &pbuf[buf_used], (buf_len - buf_used - 1));
- }
-
- return GIT_OK;
-}
-
-int git_win32_leakcheck_stack(
- char * pbuf, size_t buf_len,
- int skip,
- const char *prefix, const char *suffix)
-{
- git_win32_leakcheck_stack_raw_data data;
- int error;
-
- if ((error = git_win32_leakcheck_stack_capture(&data, skip)) < 0)
- return error;
- if ((error = git_win32_leakcheck_stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0)
- return error;
- return 0;
-}
-
-/* Strack tracing */
-
-#define STACKTRACE_UID_LEN (15)
-
-/**
- * The stacktrace of an allocation can be distilled
- * to a unique id based upon the stackframe pointers
- * and ignoring any size arguments. We will use these
- * UIDs as the (char const*) __FILE__ argument we
- * give to the CRT malloc routines.
- */
-typedef struct {
- char uid[STACKTRACE_UID_LEN + 1];
-} git_win32_leakcheck_stacktrace_uid;
-
-/**
- * All mallocs with the same stacktrace will be de-duped
- * and aggregated into this row.
- */
-typedef struct {
- git_win32_leakcheck_stacktrace_uid uid; /* must be first */
- git_win32_leakcheck_stack_raw_data raw_data;
- unsigned int count_allocs; /* times this alloc signature seen since init */
- unsigned int count_allocs_at_last_checkpoint; /* times since last mark */
- unsigned int transient_count_leaks; /* sum of leaks */
-} git_win32_leakcheck_stacktrace_row;
-
-static CRITICAL_SECTION g_crtdbg_stacktrace_cs;
-
-/**
- * CRTDBG memory leak tracking takes a "char const * const file_name"
- * and stores the pointer in the heap data (instead of allocing a copy
- * for itself). Normally, this is not a problem, since we usually pass
- * in __FILE__. But I'm going to lie to it and pass in the address of
- * the UID in place of the file_name. Also, I do not want to alloc the
- * stacktrace data (because we are called from inside our alloc routines).
- * Therefore, I'm creating a very large static pool array to store row
- * data. This also eliminates the temptation to realloc it (and move the
- * UID pointers).
- *
- * And to efficiently look for duplicates we need an index on the rows
- * so we can bsearch it. Again, without mallocing.
- *
- * If we observe more than MY_ROW_LIMIT unique malloc signatures, we
- * fall through and use the traditional __FILE__ processing and don't
- * try to de-dup them. If your testing hits this limit, just increase
- * it and try again.
- */
-
-#define MY_ROW_LIMIT (2 * 1024 * 1024)
-static git_win32_leakcheck_stacktrace_row g_cs_rows[MY_ROW_LIMIT];
-static git_win32_leakcheck_stacktrace_row *g_cs_index[MY_ROW_LIMIT];
-
-static unsigned int g_cs_end = MY_ROW_LIMIT;
-static unsigned int g_cs_ins = 0; /* insertion point == unique allocs seen */
-static unsigned int g_count_total_allocs = 0; /* number of allocs seen */
-static unsigned int g_transient_count_total_leaks = 0; /* number of total leaks */
-static unsigned int g_transient_count_dedup_leaks = 0; /* number of unique leaks */
-static bool g_limit_reached = false; /* had allocs after we filled row table */
-
-static unsigned int g_checkpoint_id = 0; /* to better label leak checkpoints */
-static bool g_transient_leaks_since_mark = false; /* payload for hook */
-
-/**
- * Compare function for bsearch on g_cs_index table.
- */
-static int row_cmp(const void *v1, const void *v2)
-{
- git_win32_leakcheck_stack_raw_data *d1 = (git_win32_leakcheck_stack_raw_data*)v1;
- git_win32_leakcheck_stacktrace_row *r2 = (git_win32_leakcheck_stacktrace_row *)v2;
-
- return (git_win32_leakcheck_stack_compare(d1, &r2->raw_data));
-}
-
-/**
- * Unique insert the new data into the row and index tables.
- * We have to sort by the stackframe data itself, not the uid.
- */
-static git_win32_leakcheck_stacktrace_row * insert_unique(
- const git_win32_leakcheck_stack_raw_data *pdata)
-{
- size_t pos;
- if (git__bsearch(g_cs_index, g_cs_ins, pdata, row_cmp, &pos) < 0) {
- /* Append new unique item to row table. */
- 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);
-
- /* Insert pointer to it into the proper place in the index table. */
- 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];
-}
-
-/**
- * Hook function to receive leak data from the CRT. (This includes
- * both "<file_name>:(<line_number>)" data, but also each of the
- * various headers and fields.
- *
- * Scan this for the special "##<pos>" UID forms that we substituted
- * for the "<file_name>". Map <pos> back to the row data and
- * increment its leak count.
- *
- * See https://msdn.microsoft.com/en-us/library/74kabxyx.aspx
- *
- * We suppress the actual crtdbg output.
- */
-static int __cdecl report_hook(int nRptType, char *szMsg, int *retVal)
-{
- static int hook_result = TRUE; /* FALSE to get stock dump; TRUE to suppress. */
- unsigned int pos;
-
- *retVal = 0; /* do not invoke debugger */
-
- 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;
-}
-
-/**
- * Write leak data to all of the various places we need.
- * We force the caller to sprintf() the message first
- * because we want to avoid fprintf() because it allocs.
- */
-static void my_output(const char *buf)
-{
- fwrite(buf, strlen(buf), 1, stderr);
- OutputDebugString(buf);
-}
-
-/**
- * For each row with leaks, dump a stacktrace for it.
- */
-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_leakcheck_stack_format(
- buf, sizeof(buf), &g_cs_rows[k].raw_data,
- NULL, NULL) >= 0) {
- my_output(buf);
- }
-
- my_output("\n");
- }
- }
-
- fflush(stderr);
-}
-
-/**
- * Initialize our memory leak tracking and de-dup data structures.
- * This should ONLY be called by git_libgit2_init().
- */
-void git_win32_leakcheck_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_leakcheck_stacktrace_dump(
- git_win32_leakcheck_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_LEAKCHECK_STACKTRACE_SET_MARK);
- bool b_leaks_since_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK);
- bool b_leaks_total = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL);
- bool b_quiet = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET);
-
- if (b_leaks_since_mark && b_leaks_total) {
- git_error_set(GIT_ERROR_INVALID, "cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL.");
- return GIT_ERROR;
- }
- if (!b_set_mark && !b_leaks_since_mark && !b_leaks_total) {
- git_error_set(GIT_ERROR_INVALID, "nothing to do.");
- return GIT_ERROR;
- }
-
- EnterCriticalSection(&g_crtdbg_stacktrace_cs);
-
- if (b_leaks_since_mark || b_leaks_total) {
- /* All variables with "transient" in the name are per-dump counters
- * and reset before each dump. This lets us handle checkpoints.
- */
- 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;
-}
-
-/**
- * Shutdown our memory leak tracking and dump summary data.
- * This should ONLY be called by git_libgit2_shutdown().
- *
- * We explicitly call _CrtDumpMemoryLeaks() during here so
- * that we can compute summary data for the leaks. We print
- * the stacktrace of each unique leak.
- *
- * This cleanup does not happen if the app calls exit()
- * without calling the libgit2 shutdown code.
- *
- * This info we print here is independent of any automatic
- * reporting during exit() caused by _CRTDBG_LEAK_CHECK_DF.
- * Set it in your app if you also want traditional reporting.
- */
-void git_win32_leakcheck_stacktrace_cleanup(void)
-{
- /* At shutdown/cleanup, dump cumulative leak info
- * with everything since startup. This might generate
- * extra noise if the caller has been doing checkpoint
- * dumps, but it might also eliminate some false
- * positives for resources previously reported during
- * checkpoints.
- */
- git_win32_leakcheck_stacktrace_dump(
- GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL,
- "CLEANUP");
-
- DeleteCriticalSection(&g_crtdbg_stacktrace_cs);
-}
-
-const char *git_win32_leakcheck_stacktrace(int skip, const char *file)
-{
- git_win32_leakcheck_stack_raw_data new_data;
- git_win32_leakcheck_stacktrace_row *row;
- const char * result = file;
-
- if (git_win32_leakcheck_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;
-}
-
-static void git_win32_leakcheck_global_shutdown(void)
-{
- git_win32_leakcheck_stacktrace_cleanup();
- git_win32_leakcheck_stack_cleanup();
-}
-
-bool git_win32_leakcheck_has_leaks(void)
-{
- return (g_transient_count_total_leaks > 0);
-}
-
-int git_win32_leakcheck_global_init(void)
-{
- git_win32_leakcheck_stacktrace_init();
- git_win32_leakcheck_stack_init();
-
- return git_runtime_shutdown_register(git_win32_leakcheck_global_shutdown);
-}
-
-#else
-
-int git_win32_leakcheck_global_init(void)
-{
- return 0;
-}
-
-#endif