mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-03 00:43:41 +00:00
Include stacktrace summary in memory leak output.
This commit is contained in:
parent
4a0313bc4e
commit
93b4272895
@ -336,6 +336,7 @@ IF (MSVC)
|
||||
|
||||
IF (MSVC_CRTDBG)
|
||||
SET(CRT_FLAG_DEBUG "${CRT_FLAG_DEBUG} /DGIT_MSVC_CRTDBG")
|
||||
SET(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES}" "Dbghelp.lib")
|
||||
ENDIF()
|
||||
|
||||
# /Zi - Create debugging information
|
||||
|
@ -46,6 +46,10 @@
|
||||
# ifdef GIT_THREADS
|
||||
# include "win32/pthread.h"
|
||||
# endif
|
||||
# if defined(GIT_MSVC_CRTDBG)
|
||||
# include "win32/w32_stack.h"
|
||||
# include "win32/w32_crtdbg_stacktrace.h"
|
||||
# endif
|
||||
|
||||
#else
|
||||
|
||||
|
18
src/global.c
18
src/global.c
@ -11,7 +11,10 @@
|
||||
#include "git2/global.h"
|
||||
#include "git2/sys/openssl.h"
|
||||
#include "thread-utils.h"
|
||||
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
#include "win32/w32_stack.h"
|
||||
#include "win32/w32_crtdbg_stacktrace.h"
|
||||
#endif
|
||||
|
||||
git_mutex git__mwindow_mutex;
|
||||
|
||||
@ -225,6 +228,11 @@ int git_libgit2_init(void)
|
||||
|
||||
/* Only do work on a 0 -> 1 transition of the refcount */
|
||||
if ((ret = git_atomic_inc(&git__n_inits)) == 1) {
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
git_win32__crtdbg_stacktrace_init();
|
||||
git_win32__stack_init();
|
||||
#endif
|
||||
|
||||
if (synchronized_threads_init() < 0)
|
||||
ret = -1;
|
||||
}
|
||||
@ -254,9 +262,15 @@ int git_libgit2_shutdown(void)
|
||||
while (InterlockedCompareExchange(&_mutex, 1, 0)) { Sleep(0); }
|
||||
|
||||
/* Only do work on a 1 -> 0 transition of the refcount */
|
||||
if ((ret = git_atomic_dec(&git__n_inits)) == 0)
|
||||
if ((ret = git_atomic_dec(&git__n_inits)) == 0) {
|
||||
synchronized_threads_shutdown();
|
||||
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
git_win32__crtdbg_stacktrace_cleanup();
|
||||
git_win32__stack_cleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Exit the lock */
|
||||
InterlockedExchange(&_mutex, 0);
|
||||
|
||||
|
13
src/util.h
13
src/util.h
@ -35,6 +35,7 @@
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <crtdbg.h>
|
||||
#include "win32/w32_crtdbg_stacktrace.h"
|
||||
#endif
|
||||
|
||||
#include "common.h"
|
||||
@ -62,23 +63,24 @@
|
||||
#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1)
|
||||
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
|
||||
GIT_INLINE(void *) git__crtdbg__malloc(size_t len, const char *file, int line)
|
||||
{
|
||||
void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, file, line);
|
||||
void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
|
||||
if (!ptr) giterr_set_oom();
|
||||
return ptr;
|
||||
}
|
||||
|
||||
GIT_INLINE(void *) git__crtdbg__calloc(size_t nelem, size_t elsize, const char *file, int line)
|
||||
{
|
||||
void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, file, line);
|
||||
void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
|
||||
if (!ptr) giterr_set_oom();
|
||||
return ptr;
|
||||
}
|
||||
|
||||
GIT_INLINE(char *) git__crtdbg__strdup(const char *str, const char *file, int line)
|
||||
{
|
||||
char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, file, line);
|
||||
char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
|
||||
if (!ptr) giterr_set_oom();
|
||||
return ptr;
|
||||
}
|
||||
@ -118,7 +120,7 @@ GIT_INLINE(char *) git__crtdbg__substrdup(const char *start, size_t n, const cha
|
||||
|
||||
GIT_INLINE(void *) git__crtdbg__realloc(void *ptr, size_t size, const char *file, int line)
|
||||
{
|
||||
void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, file, line);
|
||||
void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
|
||||
if (!new_ptr) giterr_set_oom();
|
||||
return new_ptr;
|
||||
}
|
||||
@ -126,8 +128,9 @@ GIT_INLINE(void *) git__crtdbg__realloc(void *ptr, size_t size, const char *file
|
||||
GIT_INLINE(void *) git__crtdbg__reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line)
|
||||
{
|
||||
size_t newsize;
|
||||
|
||||
return GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize) ?
|
||||
NULL : _realloc_dbg(ptr, newsize, _NORMAL_BLOCK, file, line);
|
||||
NULL : _realloc_dbg(ptr, newsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
|
||||
}
|
||||
|
||||
GIT_INLINE(void *) git__crtdbg__mallocarray(size_t nelem, size_t elsize, const char *file, int line)
|
||||
|
343
src/win32/w32_crtdbg_stacktrace.c
Normal file
343
src/win32/w32_crtdbg_stacktrace.c
Normal file
@ -0,0 +1,343 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
#include "w32_stack.h"
|
||||
#include "w32_crtdbg_stacktrace.h"
|
||||
|
||||
#define CRTDBG_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[CRTDBG_STACKTRACE__UID_LEN + 1];
|
||||
} git_win32__crtdbg_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;
|
||||
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;
|
||||
|
||||
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 (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; /* 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__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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
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__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) {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
void git_win32__crtdbg_stacktrace_cleanup(void)
|
||||
{
|
||||
/* At shutdown/cleanup, dump cummulative 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__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
|
93
src/win32/w32_crtdbg_stacktrace.h
Normal file
93
src/win32/w32_crtdbg_stacktrace.h
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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_w32_crtdbg_stacktrace_h__
|
||||
#define INCLUDE_w32_crtdbg_stacktrace_h__
|
||||
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
|
||||
/**
|
||||
* 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
|
180
src/win32/w32_stack.c
Normal file
180
src/win32/w32_stack.c
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
#include "Windows.h"
|
||||
#include "Dbghelp.h"
|
||||
#include "win32/posix.h"
|
||||
#include "w32_stack.h"
|
||||
#include "hash.h"
|
||||
|
||||
/**
|
||||
* This is supposedly defined in WinBase.h (from Windows.h) but there were linker issues.
|
||||
*/
|
||||
USHORT WINAPI RtlCaptureStackBackTrace(ULONG, ULONG, PVOID*, PULONG);
|
||||
|
||||
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) {
|
||||
giterr_set(GITERR_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, int 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.
|
||||
* We use a union to do the same thing without mallocing.
|
||||
*/
|
||||
struct {
|
||||
SYMBOL_INFO symbol;
|
||||
char extra[MY_MAX_FILENAME + 1];
|
||||
} s;
|
||||
|
||||
IMAGEHLP_LINE64 line;
|
||||
int buf_used = 0;
|
||||
unsigned int k;
|
||||
char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */
|
||||
int detail_len;
|
||||
|
||||
if (!g_win32_stack_initialized) {
|
||||
giterr_set(GITERR_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;
|
||||
}
|
||||
|
||||
/* If an "aux" data provider was 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, int 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
|
132
src/win32/w32_stack.h
Normal file
132
src/win32/w32_stack.h
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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_w32_stack_h__
|
||||
#define INCLUDE_w32_stack_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.
|
||||
*/
|
||||
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, unsigned int 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".
|
||||
*/
|
||||
typedef struct {
|
||||
void *frames[GIT_WIN32__STACK__MAX_FRAMES];
|
||||
unsigned int nr_frames;
|
||||
unsigned int aux_id;
|
||||
} 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, int 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, int buf_len,
|
||||
int skip,
|
||||
const char *prefix, const char *suffix);
|
||||
|
||||
#endif /* GIT_MSVC_CRTDBG */
|
||||
#endif /* INCLUDE_w32_stack_h__ */
|
@ -142,9 +142,28 @@ void _cl_trace_cb__event_handler(
|
||||
switch (ev) {
|
||||
case CL_TRACE__SUITE_BEGIN:
|
||||
git_trace(GIT_TRACE_TRACE, "\n\n%s\n%s: Begin Suite", HR, suite_name);
|
||||
#if 0 && defined(GIT_MSVC_CRTDBG)
|
||||
git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK,
|
||||
suite_name);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case CL_TRACE__SUITE_END:
|
||||
#if 0 && defined(GIT_MSVC_CRTDBG)
|
||||
/* As an example of checkpointing, dump leaks within this suite.
|
||||
* This may generate false positives for things like the global
|
||||
* TLS error state and maybe the odb cache since they aren't
|
||||
* freed until the global shutdown and outside the scope of this
|
||||
* set of tests.
|
||||
*
|
||||
* This may under-report if the test itself uses a checkpoint.
|
||||
* See tests/trace/windows/stacktrace.c
|
||||
*/
|
||||
git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
|
||||
suite_name);
|
||||
#endif
|
||||
git_trace(GIT_TRACE_TRACE, "\n\n%s: End Suite\n%s", suite_name, HR);
|
||||
break;
|
||||
|
||||
|
19
tests/main.c
19
tests/main.c
@ -1,10 +1,3 @@
|
||||
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
/* Enable MSVC CRTDBG memory leak reporting. See src/util.h for details. */
|
||||
#include <stdlib.h>
|
||||
#include <crtdbg.h>
|
||||
#endif
|
||||
|
||||
#include "clar_libgit2.h"
|
||||
#include "clar_libgit2_trace.h"
|
||||
|
||||
@ -16,18 +9,6 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
int res;
|
||||
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
_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);
|
||||
#endif
|
||||
|
||||
clar_test_init(argc, argv);
|
||||
|
||||
git_libgit2_init();
|
||||
|
151
tests/trace/windows/stacktrace.c
Normal file
151
tests/trace/windows/stacktrace.c
Normal file
@ -0,0 +1,151 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "win32/w32_stack.h"
|
||||
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
static void a(void)
|
||||
{
|
||||
char buf[10000];
|
||||
|
||||
cl_assert(git_win32__stack(buf, sizeof(buf), 0, NULL, NULL) == 0);
|
||||
|
||||
#if 0
|
||||
fprintf(stderr, "Stacktrace from [%s:%d]:\n%s\n", __FILE__, __LINE__, buf);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void b(void)
|
||||
{
|
||||
a();
|
||||
}
|
||||
|
||||
static void c(void)
|
||||
{
|
||||
b();
|
||||
}
|
||||
#endif
|
||||
|
||||
void test_trace_windows_stacktrace__basic(void)
|
||||
{
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
c();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void test_trace_windows_stacktrace__leaks(void)
|
||||
{
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
void * p1;
|
||||
void * p2;
|
||||
void * p3;
|
||||
void * p4;
|
||||
int before, after;
|
||||
int leaks;
|
||||
int error;
|
||||
|
||||
/* remember outstanding leaks due to set setup
|
||||
* and set mark/checkpoint.
|
||||
*/
|
||||
before = git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL |
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK,
|
||||
NULL);
|
||||
|
||||
p1 = git__malloc(5);
|
||||
leaks = git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
|
||||
"p1");
|
||||
cl_assert((leaks == 1));
|
||||
|
||||
p2 = git__malloc(5);
|
||||
leaks = git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
|
||||
"p1,p2");
|
||||
cl_assert((leaks == 2));
|
||||
|
||||
p3 = git__malloc(5);
|
||||
leaks = git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
|
||||
"p1,p2,p3");
|
||||
cl_assert((leaks == 3));
|
||||
|
||||
git__free(p2);
|
||||
leaks = git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
|
||||
"p1,p3");
|
||||
cl_assert((leaks == 2));
|
||||
|
||||
/* move the mark. only new leaks should appear afterwards */
|
||||
error = git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK,
|
||||
NULL);
|
||||
cl_assert((error == 0));
|
||||
|
||||
leaks = git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
|
||||
"not_p1,not_p3");
|
||||
cl_assert((leaks == 0));
|
||||
|
||||
p4 = git__malloc(5);
|
||||
leaks = git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
|
||||
"p4,not_p1,not_p3");
|
||||
cl_assert((leaks == 1));
|
||||
|
||||
git__free(p1);
|
||||
git__free(p3);
|
||||
leaks = git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
|
||||
"p4");
|
||||
cl_assert((leaks == 1));
|
||||
|
||||
git__free(p4);
|
||||
leaks = git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
|
||||
"end");
|
||||
cl_assert((leaks == 0));
|
||||
|
||||
/* confirm current absolute leaks count matches beginning value. */
|
||||
after = git_win32__crtdbg_stacktrace__dump(
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
|
||||
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL,
|
||||
"total");
|
||||
cl_assert((before == after));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
static void aux_cb_alloc__1(unsigned int *aux_id)
|
||||
{
|
||||
static unsigned int aux_counter = 0;
|
||||
|
||||
*aux_id = aux_counter++;
|
||||
}
|
||||
|
||||
static void aux_cb_lookup__1(unsigned int aux_id, char *aux_msg, unsigned int aux_msg_len)
|
||||
{
|
||||
p_snprintf(aux_msg, aux_msg_len, "\tQQ%08x\n", aux_id);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void test_trace_windows_stacktrace__aux1(void)
|
||||
{
|
||||
#if defined(GIT_MSVC_CRTDBG)
|
||||
git_win32__stack__set_aux_cb(aux_cb_alloc__1, aux_cb_lookup__1);
|
||||
c();
|
||||
c();
|
||||
c();
|
||||
c();
|
||||
git_win32__stack__set_aux_cb(NULL, NULL);
|
||||
#endif
|
||||
}
|
Loading…
Reference in New Issue
Block a user