Commit cb4b3bdf by Edward Thomson

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".
parent a6d151bf
......@@ -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;
......
......@@ -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;
}
......
......@@ -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>
......
/*
* 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_crtdbg_stacktrace.h"
#if defined(GIT_MSVC_CRTDBG)
#include "w32_stack.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 (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 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) {
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;
}
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
/*
* 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
/*
* 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_MSVC_CRTDBG)
#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
* 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);
}
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;
}
void git_win32_leakcheck_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_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;
}
#endif
......@@ -5,13 +5,21 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_win32_w32_stack_h__
#define INCLUDE_win32_w32_stack_h__
#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
......@@ -24,7 +32,7 @@
* (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);
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
......@@ -38,7 +46,7 @@ typedef void (*git_win32__stack__aux_cb_alloc)(unsigned int *aux_id);
* @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);
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.
......@@ -50,15 +58,15 @@ typedef void (*git_win32__stack__aux_cb_lookup)(unsigned int aux_id, char *aux_m
* 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);
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__STACK__MAX_FRAMES 30
#define GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES 30
/**
* Wrapper containing the raw unprocessed stackframe
......@@ -72,8 +80,8 @@ GIT_EXTERN(int) git_win32__stack__set_aux_cb(
typedef struct {
unsigned int aux_id;
unsigned int nr_frames;
void *frames[GIT_WIN32__STACK__MAX_FRAMES];
} git_win32__stack__raw_data;
void *frames[GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES];
} git_win32_leakcheck_stack_raw_data;
/**
......@@ -81,14 +89,14 @@ typedef struct {
* thread at startup (under a lock if there are other threads
* active).
*/
void git_win32__stack_init(void);
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__stack_cleanup(void);
void git_win32_leakcheck_stack_cleanup(void);
/**
......@@ -98,16 +106,16 @@ void git_win32__stack_cleanup(void);
* 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);
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__stack_compare(
git_win32__stack__raw_data *d1,
git_win32__stack__raw_data *d2);
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.
......@@ -115,9 +123,9 @@ int git_win32__stack_compare(
* @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(
int git_win32_leakcheck_stack_format(
char *pbuf, size_t buf_len,
const git_win32__stack__raw_data *pdata,
const git_win32_leakcheck_stack_raw_data *pdata,
const char *prefix, const char *suffix);
/**
......@@ -131,10 +139,118 @@ int git_win32__stack_format(
* @param prefix String written before each frame; defaults to "\t".
* @param suffix String written after each frame; defaults to "\n".
*/
int git_win32__stack(
int git_win32_leakcheck_stack(
char * pbuf, size_t buf_len,
int skip,
const char *prefix, const char *suffix);
#endif /* GIT_MSVC_CRTDBG */
/* 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
/*
* 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
#include "clar_libgit2.h"
#include "win32/w32_stack.h"
#include "win32/w32_crtdbg_stacktrace.h"
#include "win32/w32_leakcheck.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);
cl_assert(git_win32_leakcheck_stack(buf, sizeof(buf), 0, NULL, NULL) == 0);
#if 0
fprintf(stderr, "Stacktrace from [%s:%d]:\n%s\n", __FILE__, __LINE__, buf);
......@@ -47,79 +46,79 @@ void test_trace_windows_stacktrace__leaks(void)
/* 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,
before = git_win32_leakcheck_stacktrace_dump(
GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET |
GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL |
GIT_WIN32_LEAKCHECK_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,
leaks = git_win32_leakcheck_stacktrace_dump(
GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET |
GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK,
"p1");
cl_assert_equal_i(1, leaks);
p2 = git__malloc(5);
leaks = git_win32__crtdbg_stacktrace__dump(
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
leaks = git_win32_leakcheck_stacktrace_dump(
GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET |
GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK,
"p1,p2");
cl_assert_equal_i(2, leaks);
p3 = git__malloc(5);
leaks = git_win32__crtdbg_stacktrace__dump(
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
leaks = git_win32_leakcheck_stacktrace_dump(
GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET |
GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK,
"p1,p2,p3");
cl_assert_equal_i(3, leaks);
git__free(p2);
leaks = git_win32__crtdbg_stacktrace__dump(
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
leaks = git_win32_leakcheck_stacktrace_dump(
GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET |
GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK,
"p1,p3");
cl_assert_equal_i(2, leaks);
/* move the mark. only new leaks should appear afterwards */
error = git_win32__crtdbg_stacktrace__dump(
GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK,
error = git_win32_leakcheck_stacktrace_dump(
GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK,
NULL);
/* cannot use cl_git_pass() since that may allocate memory. */
cl_assert_equal_i(0, error);
leaks = git_win32__crtdbg_stacktrace__dump(
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
leaks = git_win32_leakcheck_stacktrace_dump(
GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET |
GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK,
"not_p1,not_p3");
cl_assert_equal_i(0, leaks);
p4 = git__malloc(5);
leaks = git_win32__crtdbg_stacktrace__dump(
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
leaks = git_win32_leakcheck_stacktrace_dump(
GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET |
GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK,
"p4,not_p1,not_p3");
cl_assert_equal_i(1, leaks);
git__free(p1);
git__free(p3);
leaks = git_win32__crtdbg_stacktrace__dump(
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
leaks = git_win32_leakcheck_stacktrace_dump(
GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET |
GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK,
"p4");
cl_assert_equal_i(1, leaks);
git__free(p4);
leaks = git_win32__crtdbg_stacktrace__dump(
GIT_WIN32__CRTDBG_STACKTRACE__QUIET |
GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK,
leaks = git_win32_leakcheck_stacktrace_dump(
GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET |
GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK,
"end");
cl_assert_equal_i(0, leaks);
/* 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,
after = git_win32_leakcheck_stacktrace_dump(
GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET |
GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL,
"total");
cl_assert_equal_i(before, after);
#endif
......@@ -143,11 +142,11 @@ static void aux_cb_lookup__1(unsigned int aux_id, char *aux_msg, size_t aux_msg_
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);
git_win32_leakcheck_stack_set_aux_cb(aux_cb_alloc__1, aux_cb_lookup__1);
c();
c();
c();
c();
git_win32__stack__set_aux_cb(NULL, NULL);
git_win32_leakcheck_stack_set_aux_cb(NULL, NULL);
#endif
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment