summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2020-07-12 17:49:42 +0100
committerEdward Thomson <ethomson@edwardthomson.com>2020-11-21 21:53:29 +0000
commitcb4b3bdf923c9ebc66f500d7cc484e6f453ebf0a (patch)
tree11a5f3cb6331f7d07ec9ce550a04b4923ba012dd /src
parenta6d151bfed31490fbd34e79db59177a47ec9ee5e (diff)
downloadlibgit2-cb4b3bdf923c9ebc66f500d7cc484e6f453ebf0a.tar.gz
win32: "crtdbg" is now "leakcheck"
msvc crtdbg is a mouthfull that is not particularly indicative of what it does. Let's rename it to "win32 leakcheck".
Diffstat (limited to 'src')
-rw-r--r--src/alloc.c11
-rw-r--r--src/allocators/win32_crtdbg.c11
-rw-r--r--src/libgit2.c1
-rw-r--r--src/win32/w32_crtdbg_stacktrace.h127
-rw-r--r--src/win32/w32_leakcheck.c (renamed from src/win32/w32_crtdbg_stacktrace.c)240
-rw-r--r--src/win32/w32_leakcheck.h256
-rw-r--r--src/win32/w32_stack.c188
-rw-r--r--src/win32/w32_stack.h140
8 files changed, 476 insertions, 498 deletions
diff --git a/src/alloc.c b/src/alloc.c
index 6972e7b59..eedf85aa7 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -12,8 +12,7 @@
#include "allocators/win32_crtdbg.h"
#if defined(GIT_MSVC_CRTDBG)
-# include "win32/w32_stack.h"
-# include "win32/w32_crtdbg_stacktrace.h"
+# include "win32/w32_leakcheck.h"
#endif
git_allocator git__allocator;
@@ -30,16 +29,16 @@ static int setup_default_allocator(void)
#if defined(GIT_MSVC_CRTDBG)
static void allocator_global_shutdown(void)
{
- git_win32__crtdbg_stacktrace_cleanup();
- git_win32__stack_cleanup();
+ git_win32_leakcheck_stacktrace_cleanup();
+ git_win32_leakcheck_stack_cleanup();
}
#endif
int git_allocator_global_init(void)
{
#if defined(GIT_MSVC_CRTDBG)
- git_win32__crtdbg_stacktrace_init();
- git_win32__stack_init();
+ git_win32_leakcheck_stacktrace_init();
+ git_win32_leakcheck_stack_init();
if (git_runtime_shutdown_register(allocator_global_shutdown) < 0)
return -1;
diff --git a/src/allocators/win32_crtdbg.c b/src/allocators/win32_crtdbg.c
index c726268a8..c542b0c54 100644
--- a/src/allocators/win32_crtdbg.c
+++ b/src/allocators/win32_crtdbg.c
@@ -9,26 +9,25 @@
#if defined(GIT_MSVC_CRTDBG)
-#include "win32/w32_stack.h"
-#include "win32/w32_crtdbg_stacktrace.h"
+#include "win32/w32_leakcheck.h"
static void *crtdbg__malloc(size_t len, const char *file, int line)
{
- void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
+ void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line);
if (!ptr) git_error_set_oom();
return ptr;
}
static void *crtdbg__calloc(size_t nelem, size_t elsize, const char *file, int line)
{
- void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
+ void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line);
if (!ptr) git_error_set_oom();
return ptr;
}
static char *crtdbg__strdup(const char *str, const char *file, int line)
{
- char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
+ char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line);
if (!ptr) git_error_set_oom();
return ptr;
}
@@ -68,7 +67,7 @@ static char *crtdbg__substrdup(const char *start, size_t n, const char *file, in
static void *crtdbg__realloc(void *ptr, size_t size, const char *file, int line)
{
- void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
+ void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line);
if (!new_ptr) git_error_set_oom();
return new_ptr;
}
diff --git a/src/libgit2.c b/src/libgit2.c
index 316d89365..2e9cb8510 100644
--- a/src/libgit2.c
+++ b/src/libgit2.c
@@ -31,7 +31,6 @@
#include "transports/smart.h"
#include "transports/http.h"
#include "transports/ssh.h"
-#include "win32/w32_stack.h"
#ifdef GIT_OPENSSL
# include <openssl/err.h>
diff --git a/src/win32/w32_crtdbg_stacktrace.h b/src/win32/w32_crtdbg_stacktrace.h
deleted file mode 100644
index d65154bab..000000000
--- a/src/win32/w32_crtdbg_stacktrace.h
+++ /dev/null
@@ -1,127 +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.
- */
-#ifndef INCLUDE_win32_w32_crtdbg_stacktrace_h__
-#define INCLUDE_win32_w32_crtdbg_stacktrace_h__
-
-#include "common.h"
-
-#if defined(GIT_MSVC_CRTDBG)
-
-#include <stdlib.h>
-#include <crtdbg.h>
-
-#include "git2/errors.h"
-#include "strnlen.h"
-
-/* MSVC CRTDBG memory leak reporting.
- *
- * We DO NOT use the "_CRTDBG_MAP_ALLOC" macro described in the MSVC
- * documentation because all allocs/frees in libgit2 already go through
- * the "git__" routines defined in this file. Simply using the normal
- * reporting mechanism causes all leaks to be attributed to a routine
- * here in util.h (ie, the actual call to calloc()) rather than the
- * caller of git__calloc().
- *
- * Therefore, we declare a set of "git__crtdbg__" routines to replace
- * the corresponding "git__" routines and re-define the "git__" symbols
- * as macros. This allows us to get and report the file:line info of
- * the real caller.
- *
- * We DO NOT replace the "git__free" routine because it needs to remain
- * a function pointer because it is used as a function argument when
- * setting up various structure "destructors".
- *
- * We also DO NOT use the "_CRTDBG_MAP_ALLOC" macro because it causes
- * "free" to be remapped to "_free_dbg" and this causes problems for
- * structures which define a field named "free".
- *
- * Finally, CRTDBG must be explicitly enabled and configured at program
- * startup. See tests/main.c for an example.
- */
-
-/**
- * Initialize our memory leak tracking and de-dup data structures.
- * This should ONLY be called by git_libgit2_init().
- */
-void git_win32__crtdbg_stacktrace_init(void);
-
-/**
- * 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__crtdbg_stacktrace_cleanup(void);
-
-/**
- * Checkpoint options.
- */
-typedef enum git_win32__crtdbg_stacktrace_options {
- /**
- * Set checkpoint marker.
- */
- GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK = (1 << 0),
-
- /**
- * Dump leaks since last checkpoint marker.
- * May not be combined with __LEAKS_TOTAL.
- *
- * Note that this may generate false positives for global TLS
- * error state and other global caches that aren't cleaned up
- * until the thread/process terminates. So when using this
- * around a region of interest, also check the final (at exit)
- * dump before digging into leaks reported here.
- */
- GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK = (1 << 1),
-
- /**
- * Dump leaks since init. May not be combined
- * with __LEAKS_SINCE_MARK.
- */
- GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL = (1 << 2),
-
- /**
- * Suppress printing during dumps.
- * Just return leak count.
- */
- GIT_WIN32__CRTDBG_STACKTRACE__QUIET = (1 << 3),
-
-} git_win32__crtdbg_stacktrace_options;
-
-/**
- * Checkpoint memory state and/or dump unique stack traces of
- * current memory leaks.
- *
- * @return number of unique leaks (relative to requested starting
- * point) or error.
- */
-GIT_EXTERN(int) git_win32__crtdbg_stacktrace__dump(
- git_win32__crtdbg_stacktrace_options opt,
- const char *label);
-
-/**
- * Construct stacktrace and append it to the global buffer.
- * Return pointer to start of this string. On any error or
- * lack of buffer space, just return the given file buffer
- * so it will behave as usual.
- *
- * This should ONLY be called by our internal memory allocations
- * routines.
- */
-const char *git_win32__crtdbg_stacktrace(int skip, const char *file);
-
-#endif
-#endif
diff --git a/src/win32/w32_crtdbg_stacktrace.c b/src/win32/w32_leakcheck.c
index 22a353d58..4fc3ec489 100644
--- a/src/win32/w32_crtdbg_stacktrace.c
+++ b/src/win32/w32_leakcheck.c
@@ -5,12 +5,192 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
-#include "w32_crtdbg_stacktrace.h"
+#include "w32_leakcheck.h"
#if defined(GIT_MSVC_CRTDBG)
-#include "w32_stack.h"
-#define CRTDBG_STACKTRACE__UID_LEN (15)
+#include "Windows.h"
+#include "Dbghelp.h"
+#include "win32/posix.h"
+#include "hash.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;
+}
+
+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;
+ }
+}
+
+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
@@ -20,20 +200,20 @@
* give to the CRT malloc routines.
*/
typedef struct {
- char uid[CRTDBG_STACKTRACE__UID_LEN + 1];
-} git_win32__crtdbg_stacktrace__uid;
+ 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__crtdbg_stacktrace__uid uid; /* must be first */
- git_win32__stack__raw_data raw_data;
+ 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__crtdbg_stacktrace__row;
+} git_win32_leakcheck_stacktrace_row;
static CRITICAL_SECTION g_crtdbg_stacktrace_cs;
@@ -58,8 +238,8 @@ static CRITICAL_SECTION g_crtdbg_stacktrace_cs;
*/
#define MY_ROW_LIMIT (2 * 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 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 */
@@ -76,18 +256,18 @@ static bool g_transient_leaks_since_mark = false; /* payload for hook */
*/
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;
+ 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__stack_compare(d1, &r2->raw_data));
+ 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__crtdbg_stacktrace__row * insert_unique(
- const git_win32__stack__raw_data *pdata)
+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) {
@@ -206,7 +386,7 @@ static void dump_summary(const char *label)
g_cs_rows[k].count_allocs);
my_output(buf);
- if (git_win32__stack_format(
+ if (git_win32_leakcheck_stack_format(
buf, sizeof(buf), &g_cs_rows[k].raw_data,
NULL, NULL) >= 0) {
my_output(buf);
@@ -219,7 +399,7 @@ static void dump_summary(const char *label)
fflush(stderr);
}
-void git_win32__crtdbg_stacktrace_init(void)
+void git_win32_leakcheck_stacktrace_init(void)
{
InitializeCriticalSection(&g_crtdbg_stacktrace_cs);
@@ -238,8 +418,8 @@ void git_win32__crtdbg_stacktrace_init(void)
LeaveCriticalSection(&g_crtdbg_stacktrace_cs);
}
-int git_win32__crtdbg_stacktrace__dump(
- git_win32__crtdbg_stacktrace_options opt,
+int git_win32_leakcheck_stacktrace_dump(
+ git_win32_leakcheck_stacktrace_options opt,
const char *label)
{
_CRT_REPORT_HOOK old;
@@ -248,10 +428,10 @@ int git_win32__crtdbg_stacktrace__dump(
#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);
+ 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.");
@@ -301,7 +481,7 @@ int git_win32__crtdbg_stacktrace__dump(
return r;
}
-void git_win32__crtdbg_stacktrace_cleanup(void)
+void git_win32_leakcheck_stacktrace_cleanup(void)
{
/* At shutdown/cleanup, dump cummulative leak info
* with everything since startup. This might generate
@@ -310,20 +490,20 @@ void git_win32__crtdbg_stacktrace_cleanup(void)
* positives for resources previously reported during
* checkpoints.
*/
- git_win32__crtdbg_stacktrace__dump(
- GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL,
+ git_win32_leakcheck_stacktrace_dump(
+ GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL,
"CLEANUP");
DeleteCriticalSection(&g_crtdbg_stacktrace_cs);
}
-const char *git_win32__crtdbg_stacktrace(int skip, const char *file)
+const char *git_win32_leakcheck_stacktrace(int skip, const char *file)
{
- git_win32__stack__raw_data new_data;
- git_win32__crtdbg_stacktrace__row *row;
+ git_win32_leakcheck_stack_raw_data new_data;
+ git_win32_leakcheck_stacktrace_row *row;
const char * result = file;
- if (git_win32__stack_capture(&new_data, skip+1) < 0)
+ if (git_win32_leakcheck_stack_capture(&new_data, skip+1) < 0)
return result;
EnterCriticalSection(&g_crtdbg_stacktrace_cs);
diff --git a/src/win32/w32_leakcheck.h b/src/win32/w32_leakcheck.h
new file mode 100644
index 000000000..518f5ad7d
--- /dev/null
+++ b/src/win32/w32_leakcheck.h
@@ -0,0 +1,256 @@
+/*
+ * 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.
+ */
+
+#ifndef INCLUDE_win32_leakcheck_h__
+#define INCLUDE_win32_leakcheck_h__
+
+#include "common.h"
+
+#if defined(GIT_MSVC_CRTDBG)
+
+#include <stdlib.h>
+#include <crtdbg.h>
+
+#include "git2/errors.h"
+#include "strnlen.h"
+
+/* Stack frames (for stack tracing, below) */
+
+/**
+ * This type defines a callback to be used to augment a C stacktrace
+ * with "aux" data. This can be used, for example, to allow LibGit2Sharp
+ * (or other interpreted consumer libraries) to give us C# stacktrace
+ * data for the PInvoke.
+ *
+ * This callback will be called during crtdbg-instrumented allocs.
+ *
+ * @param aux_id [out] A returned "aux_id" representing a unique
+ * (de-duped at the C# layer) stacktrace. "aux_id" 0 is reserved
+ * to mean no aux stacktrace data.
+ */
+typedef void (*git_win32_leakcheck_stack_aux_cb_alloc)(unsigned int *aux_id);
+
+/**
+ * This type defines a callback to be used to augment the output of
+ * a stacktrace. This will be used to request the C# layer format
+ * the C# stacktrace associated with "aux_id" into the provided
+ * buffer.
+ *
+ * This callback will be called during leak reporting.
+ *
+ * @param aux_id The "aux_id" key associated with a stacktrace.
+ * @param aux_msg A buffer where a formatted message should be written.
+ * @param aux_msg_len The size of the buffer.
+ */
+typedef void (*git_win32_leakcheck_stack_aux_cb_lookup)(unsigned int aux_id, char *aux_msg, size_t aux_msg_len);
+
+/**
+ * Register an "aux" data provider to augment our C stacktrace data.
+ *
+ * This can be used, for example, to allow LibGit2Sharp (or other
+ * interpreted consumer libraries) to give us the C# stacktrace of
+ * the PInvoke.
+ *
+ * If you choose to use this feature, it should be registered during
+ * initialization and not changed for the duration of the process.
+ */
+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);
+
+/**
+ * Maximum number of stackframes to record for a
+ * single stacktrace.
+ */
+#define GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES 30
+
+/**
+ * Wrapper containing the raw unprocessed stackframe
+ * data for a single stacktrace and any "aux_id".
+ *
+ * I put the aux_id first so leaks will be sorted by it.
+ * So, for example, if a specific callstack in C# leaks
+ * a repo handle, all of the pointers within the associated
+ * repo pointer will be grouped together.
+ */
+typedef struct {
+ unsigned int aux_id;
+ unsigned int nr_frames;
+ void *frames[GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES];
+} git_win32_leakcheck_stack_raw_data;
+
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+
+/**
+ * Capture raw stack trace data for the current process/thread.
+ *
+ * @param skip Number of initial frames to skip. Pass 0 to
+ * begin with the caller of this routine. Pass 1 to begin
+ * with its caller. And so on.
+ */
+int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip);
+
+/**
+ * Compare 2 raw stacktraces with the usual -1,0,+1 result.
+ * This includes any "aux_id" values in the comparison, so that
+ * our de-dup is also "aux" context relative.
+ */
+int git_win32_leakcheck_stack_compare(
+ git_win32_leakcheck_stack_raw_data *d1,
+ git_win32_leakcheck_stack_raw_data *d2);
+
+/**
+ * Format raw stacktrace data into buffer WITHOUT using any mallocs.
+ *
+ * @param prefix String written before each frame; defaults to "\t".
+ * @param suffix String written after each frame; defaults to "\n".
+ */
+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);
+
+/**
+ * Convenience routine to capture and format stacktrace into
+ * a buffer WITHOUT using any mallocs. This is primarily a
+ * wrapper for testing.
+ *
+ * @param skip Number of initial frames to skip. Pass 0 to
+ * begin with the caller of this routine. Pass 1 to begin
+ * with its caller. And so on.
+ * @param prefix String written before each frame; defaults to "\t".
+ * @param suffix String written after each frame; defaults to "\n".
+ */
+int git_win32_leakcheck_stack(
+ char * pbuf, size_t buf_len,
+ int skip,
+ const char *prefix, const char *suffix);
+
+/* Stack tracing */
+
+/* MSVC CRTDBG memory leak reporting.
+ *
+ * We DO NOT use the "_CRTDBG_MAP_ALLOC" macro described in the MSVC
+ * documentation because all allocs/frees in libgit2 already go through
+ * the "git__" routines defined in this file. Simply using the normal
+ * reporting mechanism causes all leaks to be attributed to a routine
+ * here in util.h (ie, the actual call to calloc()) rather than the
+ * caller of git__calloc().
+ *
+ * Therefore, we declare a set of "git__crtdbg__" routines to replace
+ * the corresponding "git__" routines and re-define the "git__" symbols
+ * as macros. This allows us to get and report the file:line info of
+ * the real caller.
+ *
+ * We DO NOT replace the "git__free" routine because it needs to remain
+ * a function pointer because it is used as a function argument when
+ * setting up various structure "destructors".
+ *
+ * We also DO NOT use the "_CRTDBG_MAP_ALLOC" macro because it causes
+ * "free" to be remapped to "_free_dbg" and this causes problems for
+ * structures which define a field named "free".
+ *
+ * Finally, CRTDBG must be explicitly enabled and configured at program
+ * startup. See tests/main.c for an example.
+ */
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * Checkpoint options.
+ */
+typedef enum git_win32_leakcheck_stacktrace_options {
+ /**
+ * Set checkpoint marker.
+ */
+ GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK = (1 << 0),
+
+ /**
+ * Dump leaks since last checkpoint marker.
+ * May not be combined with _LEAKS_TOTAL.
+ *
+ * Note that this may generate false positives for global TLS
+ * error state and other global caches that aren't cleaned up
+ * until the thread/process terminates. So when using this
+ * around a region of interest, also check the final (at exit)
+ * dump before digging into leaks reported here.
+ */
+ GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK = (1 << 1),
+
+ /**
+ * Dump leaks since init. May not be combined
+ * with _LEAKS_SINCE_MARK.
+ */
+ GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL = (1 << 2),
+
+ /**
+ * Suppress printing during dumps.
+ * Just return leak count.
+ */
+ GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET = (1 << 3),
+
+} git_win32_leakcheck_stacktrace_options;
+
+/**
+ * Checkpoint memory state and/or dump unique stack traces of
+ * current memory leaks.
+ *
+ * @return number of unique leaks (relative to requested starting
+ * point) or error.
+ */
+int git_win32_leakcheck_stacktrace_dump(
+ git_win32_leakcheck_stacktrace_options opt,
+ const char *label);
+
+/**
+ * Construct stacktrace and append it to the global buffer.
+ * Return pointer to start of this string. On any error or
+ * lack of buffer space, just return the given file buffer
+ * so it will behave as usual.
+ *
+ * This should ONLY be called by our internal memory allocations
+ * routines.
+ */
+const char *git_win32_leakcheck_stacktrace(int skip, const char *file);
+
+#endif
+#endif
diff --git a/src/win32/w32_stack.c b/src/win32/w32_stack.c
deleted file mode 100644
index 78c78dbbd..000000000
--- a/src/win32/w32_stack.c
+++ /dev/null
@@ -1,188 +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_stack.h"
-
-#if defined(GIT_MSVC_CRTDBG)
-#include "Windows.h"
-#include "Dbghelp.h"
-#include "win32/posix.h"
-#include "hash.h"
-
-static bool g_win32_stack_initialized = false;
-static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE;
-static git_win32__stack__aux_cb_alloc g_aux_cb_alloc = NULL;
-static git_win32__stack__aux_cb_lookup g_aux_cb_lookup = NULL;
-
-int git_win32__stack__set_aux_cb(
- git_win32__stack__aux_cb_alloc cb_alloc,
- git_win32__stack__aux_cb_lookup cb_lookup)
-{
- g_aux_cb_alloc = cb_alloc;
- g_aux_cb_lookup = cb_lookup;
-
- return 0;
-}
-
-void git_win32__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;
- }
-}
-
-void git_win32__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__stack_capture(git_win32__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__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__stack_compare(
- git_win32__stack__raw_data *d1,
- git_win32__stack__raw_data *d2)
-{
- return memcmp(d1, d2, sizeof(*d1));
-}
-
-int git_win32__stack_format(
- char *pbuf, size_t buf_len,
- const git_win32__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__stack(
- char * pbuf, size_t buf_len,
- int skip,
- const char *prefix, const char *suffix)
-{
- git_win32__stack__raw_data data;
- int error;
-
- if ((error = git_win32__stack_capture(&data, skip)) < 0)
- return error;
- if ((error = git_win32__stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0)
- return error;
- return 0;
-}
-
-#endif
diff --git a/src/win32/w32_stack.h b/src/win32/w32_stack.h
deleted file mode 100644
index c2565c228..000000000
--- a/src/win32/w32_stack.h
+++ /dev/null
@@ -1,140 +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.
- */
-
-#ifndef INCLUDE_win32_w32_stack_h__
-#define INCLUDE_win32_w32_stack_h__
-
-#include "common.h"
-
-#if defined(GIT_MSVC_CRTDBG)
-
-/**
- * This type defines a callback to be used to augment a C stacktrace
- * with "aux" data. This can be used, for example, to allow LibGit2Sharp
- * (or other interpreted consumer libraries) to give us C# stacktrace
- * data for the PInvoke.
- *
- * This callback will be called during crtdbg-instrumented allocs.
- *
- * @param aux_id [out] A returned "aux_id" representing a unique
- * (de-duped at the C# layer) stacktrace. "aux_id" 0 is reserved
- * to mean no aux stacktrace data.
- */
-typedef void (*git_win32__stack__aux_cb_alloc)(unsigned int *aux_id);
-
-/**
- * This type defines a callback to be used to augment the output of
- * a stacktrace. This will be used to request the C# layer format
- * the C# stacktrace associated with "aux_id" into the provided
- * buffer.
- *
- * This callback will be called during leak reporting.
- *
- * @param aux_id The "aux_id" key associated with a stacktrace.
- * @param aux_msg A buffer where a formatted message should be written.
- * @param aux_msg_len The size of the buffer.
- */
-typedef void (*git_win32__stack__aux_cb_lookup)(unsigned int aux_id, char *aux_msg, size_t aux_msg_len);
-
-/**
- * Register an "aux" data provider to augment our C stacktrace data.
- *
- * This can be used, for example, to allow LibGit2Sharp (or other
- * interpreted consumer libraries) to give us the C# stacktrace of
- * the PInvoke.
- *
- * If you choose to use this feature, it should be registered during
- * initialization and not changed for the duration of the process.
- */
-GIT_EXTERN(int) git_win32__stack__set_aux_cb(
- git_win32__stack__aux_cb_alloc cb_alloc,
- git_win32__stack__aux_cb_lookup cb_lookup);
-
-/**
- * Maximum number of stackframes to record for a
- * single stacktrace.
- */
-#define GIT_WIN32__STACK__MAX_FRAMES 30
-
-/**
- * Wrapper containing the raw unprocessed stackframe
- * data for a single stacktrace and any "aux_id".
- *
- * I put the aux_id first so leaks will be sorted by it.
- * So, for example, if a specific callstack in C# leaks
- * a repo handle, all of the pointers within the associated
- * repo pointer will be grouped together.
- */
-typedef struct {
- unsigned int aux_id;
- unsigned int nr_frames;
- void *frames[GIT_WIN32__STACK__MAX_FRAMES];
-} git_win32__stack__raw_data;
-
-
-/**
- * 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__stack_init(void);
-
-/**
- * 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__stack_cleanup(void);
-
-
-/**
- * Capture raw stack trace data for the current process/thread.
- *
- * @param skip Number of initial frames to skip. Pass 0 to
- * begin with the caller of this routine. Pass 1 to begin
- * with its caller. And so on.
- */
-int git_win32__stack_capture(git_win32__stack__raw_data *pdata, int skip);
-
-/**
- * Compare 2 raw stacktraces with the usual -1,0,+1 result.
- * This includes any "aux_id" values in the comparison, so that
- * our de-dup is also "aux" context relative.
- */
-int git_win32__stack_compare(
- git_win32__stack__raw_data *d1,
- git_win32__stack__raw_data *d2);
-
-/**
- * Format raw stacktrace data into buffer WITHOUT using any mallocs.
- *
- * @param prefix String written before each frame; defaults to "\t".
- * @param suffix String written after each frame; defaults to "\n".
- */
-int git_win32__stack_format(
- char *pbuf, size_t buf_len,
- const git_win32__stack__raw_data *pdata,
- const char *prefix, const char *suffix);
-
-/**
- * Convenience routine to capture and format stacktrace into
- * a buffer WITHOUT using any mallocs. This is primarily a
- * wrapper for testing.
- *
- * @param skip Number of initial frames to skip. Pass 0 to
- * begin with the caller of this routine. Pass 1 to begin
- * with its caller. And so on.
- * @param prefix String written before each frame; defaults to "\t".
- * @param suffix String written after each frame; defaults to "\n".
- */
-int git_win32__stack(
- char * pbuf, size_t buf_len,
- int skip,
- const char *prefix, const char *suffix);
-
-#endif /* GIT_MSVC_CRTDBG */
-#endif