2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
8 #include "w32_leakcheck.h"
10 #if defined(GIT_WIN32_LEAKCHECK)
14 #include "win32/posix.h"
18 /* Stack frames (for stack tracing, below) */
20 static bool g_win32_stack_initialized
= false;
21 static HANDLE g_win32_stack_process
= INVALID_HANDLE_VALUE
;
22 static git_win32_leakcheck_stack_aux_cb_alloc g_aux_cb_alloc
= NULL
;
23 static git_win32_leakcheck_stack_aux_cb_lookup g_aux_cb_lookup
= NULL
;
25 int git_win32_leakcheck_stack_set_aux_cb(
26 git_win32_leakcheck_stack_aux_cb_alloc cb_alloc
,
27 git_win32_leakcheck_stack_aux_cb_lookup cb_lookup
)
29 g_aux_cb_alloc
= cb_alloc
;
30 g_aux_cb_lookup
= cb_lookup
;
36 * Load symbol table data. This should be done in the primary
37 * thread at startup (under a lock if there are other threads
40 void git_win32_leakcheck_stack_init(void)
42 if (!g_win32_stack_initialized
) {
43 g_win32_stack_process
= GetCurrentProcess();
44 SymSetOptions(SYMOPT_LOAD_LINES
);
45 SymInitialize(g_win32_stack_process
, NULL
, TRUE
);
46 g_win32_stack_initialized
= true;
51 * Cleanup symbol table data. This should be done in the
52 * primary thead at shutdown (under a lock if there are other
55 void git_win32_leakcheck_stack_cleanup(void)
57 if (g_win32_stack_initialized
) {
58 SymCleanup(g_win32_stack_process
);
59 g_win32_stack_process
= INVALID_HANDLE_VALUE
;
60 g_win32_stack_initialized
= false;
64 int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data
*pdata
, int skip
)
66 if (!g_win32_stack_initialized
) {
67 git_error_set(GIT_ERROR_INVALID
, "git_win32_stack not initialized.");
71 memset(pdata
, 0, sizeof(*pdata
));
72 pdata
->nr_frames
= RtlCaptureStackBackTrace(
73 skip
+1, GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES
, pdata
->frames
, NULL
);
75 /* If an "aux" data provider was registered, ask it to capture
76 * whatever data it needs and give us an "aux_id" to it so that
77 * we can refer to it later when reporting.
80 (g_aux_cb_alloc
)(&pdata
->aux_id
);
85 int git_win32_leakcheck_stack_compare(
86 git_win32_leakcheck_stack_raw_data
*d1
,
87 git_win32_leakcheck_stack_raw_data
*d2
)
89 return memcmp(d1
, d2
, sizeof(*d1
));
92 int git_win32_leakcheck_stack_format(
93 char *pbuf
, size_t buf_len
,
94 const git_win32_leakcheck_stack_raw_data
*pdata
,
95 const char *prefix
, const char *suffix
)
97 #define MY_MAX_FILENAME 255
99 /* SYMBOL_INFO has char FileName[1] at the end. The docs say to
100 * to malloc it with extra space for your desired max filename.
104 char extra
[MY_MAX_FILENAME
+ 1];
107 IMAGEHLP_LINE64 line
;
110 char detail
[MY_MAX_FILENAME
* 2]; /* filename plus space for function name and formatting */
113 if (!g_win32_stack_initialized
) {
114 git_error_set(GIT_ERROR_INVALID
, "git_win32_stack not initialized.");
123 memset(pbuf
, 0, buf_len
);
125 memset(&s
, 0, sizeof(s
));
126 s
.symbol
.MaxNameLen
= MY_MAX_FILENAME
;
127 s
.symbol
.SizeOfStruct
= sizeof(SYMBOL_INFO
);
129 memset(&line
, 0, sizeof(line
));
130 line
.SizeOfStruct
= sizeof(IMAGEHLP_LINE64
);
132 for (k
=0; k
< pdata
->nr_frames
; k
++) {
133 DWORD64 frame_k
= (DWORD64
)pdata
->frames
[k
];
136 if (SymFromAddr(g_win32_stack_process
, frame_k
, 0, &s
.symbol
) &&
137 SymGetLineFromAddr64(g_win32_stack_process
, frame_k
, &dwUnused
, &line
)) {
141 pslash
= strrchr(line
.FileName
, '\\');
142 pfile
= ((pslash
) ? (pslash
+1) : line
.FileName
);
143 p_snprintf(detail
, sizeof(detail
), "%s%s:%d> %s%s",
144 prefix
, pfile
, line
.LineNumber
, s
.symbol
.Name
, suffix
);
146 /* This happens when we cross into another module.
147 * For example, in CLAR tests, this is typically
148 * the CRT startup code. Just print an unknown
149 * frame and continue.
151 p_snprintf(detail
, sizeof(detail
), "%s??%s", prefix
, suffix
);
153 detail_len
= strlen(detail
);
155 if (buf_len
< (buf_used
+ detail_len
+ 1)) {
156 /* we don't have room for this frame in the buffer, so just stop. */
160 memcpy(&pbuf
[buf_used
], detail
, detail_len
);
161 buf_used
+= detail_len
;
164 /* "aux_id" 0 is reserved to mean no aux data. This is needed to handle
165 * allocs that occur before the aux callbacks were registered.
167 if (pdata
->aux_id
> 0) {
168 p_snprintf(detail
, sizeof(detail
), "%saux_id: %d%s",
169 prefix
, pdata
->aux_id
, suffix
);
170 detail_len
= strlen(detail
);
171 if ((buf_used
+ detail_len
+ 1) < buf_len
) {
172 memcpy(&pbuf
[buf_used
], detail
, detail_len
);
173 buf_used
+= detail_len
;
176 /* If an "aux" data provider is still registered, ask it to append its detailed
177 * data to the end of ours using the "aux_id" it gave us when this de-duped
181 (g_aux_cb_lookup
)(pdata
->aux_id
, &pbuf
[buf_used
], (buf_len
- buf_used
- 1));
187 int git_win32_leakcheck_stack(
188 char * pbuf
, size_t buf_len
,
190 const char *prefix
, const char *suffix
)
192 git_win32_leakcheck_stack_raw_data data
;
195 if ((error
= git_win32_leakcheck_stack_capture(&data
, skip
)) < 0)
197 if ((error
= git_win32_leakcheck_stack_format(pbuf
, buf_len
, &data
, prefix
, suffix
)) < 0)
204 #define STACKTRACE_UID_LEN (15)
207 * The stacktrace of an allocation can be distilled
208 * to a unique id based upon the stackframe pointers
209 * and ignoring any size arguments. We will use these
210 * UIDs as the (char const*) __FILE__ argument we
211 * give to the CRT malloc routines.
214 char uid
[STACKTRACE_UID_LEN
+ 1];
215 } git_win32_leakcheck_stacktrace_uid
;
218 * All mallocs with the same stacktrace will be de-duped
219 * and aggregated into this row.
222 git_win32_leakcheck_stacktrace_uid uid
; /* must be first */
223 git_win32_leakcheck_stack_raw_data raw_data
;
224 unsigned int count_allocs
; /* times this alloc signature seen since init */
225 unsigned int count_allocs_at_last_checkpoint
; /* times since last mark */
226 unsigned int transient_count_leaks
; /* sum of leaks */
227 } git_win32_leakcheck_stacktrace_row
;
229 static CRITICAL_SECTION g_crtdbg_stacktrace_cs
;
232 * CRTDBG memory leak tracking takes a "char const * const file_name"
233 * and stores the pointer in the heap data (instead of allocing a copy
234 * for itself). Normally, this is not a problem, since we usually pass
235 * in __FILE__. But I'm going to lie to it and pass in the address of
236 * the UID in place of the file_name. Also, I do not want to alloc the
237 * stacktrace data (because we are called from inside our alloc routines).
238 * Therefore, I'm creating a very large static pool array to store row
239 * data. This also eliminates the temptation to realloc it (and move the
242 * And to efficiently look for duplicates we need an index on the rows
243 * so we can bsearch it. Again, without mallocing.
245 * If we observe more than MY_ROW_LIMIT unique malloc signatures, we
246 * fall through and use the traditional __FILE__ processing and don't
247 * try to de-dup them. If your testing hits this limit, just increase
251 #define MY_ROW_LIMIT (2 * 1024 * 1024)
252 static git_win32_leakcheck_stacktrace_row g_cs_rows
[MY_ROW_LIMIT
];
253 static git_win32_leakcheck_stacktrace_row
*g_cs_index
[MY_ROW_LIMIT
];
255 static unsigned int g_cs_end
= MY_ROW_LIMIT
;
256 static unsigned int g_cs_ins
= 0; /* insertion point == unique allocs seen */
257 static unsigned int g_count_total_allocs
= 0; /* number of allocs seen */
258 static unsigned int g_transient_count_total_leaks
= 0; /* number of total leaks */
259 static unsigned int g_transient_count_dedup_leaks
= 0; /* number of unique leaks */
260 static bool g_limit_reached
= false; /* had allocs after we filled row table */
262 static unsigned int g_checkpoint_id
= 0; /* to better label leak checkpoints */
263 static bool g_transient_leaks_since_mark
= false; /* payload for hook */
266 * Compare function for bsearch on g_cs_index table.
268 static int row_cmp(const void *v1
, const void *v2
)
270 git_win32_leakcheck_stack_raw_data
*d1
= (git_win32_leakcheck_stack_raw_data
*)v1
;
271 git_win32_leakcheck_stacktrace_row
*r2
= (git_win32_leakcheck_stacktrace_row
*)v2
;
273 return (git_win32_leakcheck_stack_compare(d1
, &r2
->raw_data
));
277 * Unique insert the new data into the row and index tables.
278 * We have to sort by the stackframe data itself, not the uid.
280 static git_win32_leakcheck_stacktrace_row
* insert_unique(
281 const git_win32_leakcheck_stack_raw_data
*pdata
)
284 if (git__bsearch(g_cs_index
, g_cs_ins
, pdata
, row_cmp
, &pos
) < 0) {
285 /* Append new unique item to row table. */
286 memcpy(&g_cs_rows
[g_cs_ins
].raw_data
, pdata
, sizeof(*pdata
));
287 sprintf(g_cs_rows
[g_cs_ins
].uid
.uid
, "##%08lx", g_cs_ins
);
289 /* Insert pointer to it into the proper place in the index table. */
291 memmove(&g_cs_index
[pos
+1], &g_cs_index
[pos
], (g_cs_ins
- pos
)*sizeof(g_cs_index
[0]));
292 g_cs_index
[pos
] = &g_cs_rows
[g_cs_ins
];
297 g_cs_index
[pos
]->count_allocs
++;
299 return g_cs_index
[pos
];
303 * Hook function to receive leak data from the CRT. (This includes
304 * both "<file_name>:(<line_number>)" data, but also each of the
305 * various headers and fields.
307 * Scan this for the special "##<pos>" UID forms that we substituted
308 * for the "<file_name>". Map <pos> back to the row data and
309 * increment its leak count.
311 * See https://msdn.microsoft.com/en-us/library/74kabxyx.aspx
313 * We suppress the actual crtdbg output.
315 static int __cdecl
report_hook(int nRptType
, char *szMsg
, int *retVal
)
317 static int hook_result
= TRUE
; /* FALSE to get stock dump; TRUE to suppress. */
320 *retVal
= 0; /* do not invoke debugger */
322 if ((szMsg
[0] != '#') || (szMsg
[1] != '#'))
325 if (sscanf(&szMsg
[2], "%08lx", &pos
) < 1)
330 if (g_transient_leaks_since_mark
) {
331 if (g_cs_rows
[pos
].count_allocs
== g_cs_rows
[pos
].count_allocs_at_last_checkpoint
)
335 g_cs_rows
[pos
].transient_count_leaks
++;
337 if (g_cs_rows
[pos
].transient_count_leaks
== 1)
338 g_transient_count_dedup_leaks
++;
340 g_transient_count_total_leaks
++;
346 * Write leak data to all of the various places we need.
347 * We force the caller to sprintf() the message first
348 * because we want to avoid fprintf() because it allocs.
350 static void my_output(const char *buf
)
352 fwrite(buf
, strlen(buf
), 1, stderr
);
353 OutputDebugString(buf
);
357 * For each row with leaks, dump a stacktrace for it.
359 static void dump_summary(const char *label
)
364 if (g_transient_count_total_leaks
== 0)
371 if (g_limit_reached
) {
373 "LEAK SUMMARY: de-dup row table[%d] filled. Increase MY_ROW_LIMIT.\n",
381 if (g_transient_leaks_since_mark
) {
382 sprintf(buf
, "LEAK CHECKPOINT %d: leaks %d unique %d: %s\n",
383 g_checkpoint_id
, g_transient_count_total_leaks
, g_transient_count_dedup_leaks
, label
);
386 sprintf(buf
, "LEAK SUMMARY: TOTAL leaks %d de-duped %d: %s\n",
387 g_transient_count_total_leaks
, g_transient_count_dedup_leaks
, label
);
392 for (k
= 0; k
< g_cs_ins
; k
++) {
393 if (g_cs_rows
[k
].transient_count_leaks
> 0) {
394 sprintf(buf
, "LEAK: %s leaked %d of %d times:\n",
395 g_cs_rows
[k
].uid
.uid
,
396 g_cs_rows
[k
].transient_count_leaks
,
397 g_cs_rows
[k
].count_allocs
);
400 if (git_win32_leakcheck_stack_format(
401 buf
, sizeof(buf
), &g_cs_rows
[k
].raw_data
,
414 * Initialize our memory leak tracking and de-dup data structures.
415 * This should ONLY be called by git_libgit2_init().
417 void git_win32_leakcheck_stacktrace_init(void)
419 InitializeCriticalSection(&g_crtdbg_stacktrace_cs
);
421 EnterCriticalSection(&g_crtdbg_stacktrace_cs
);
423 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF
| _CRTDBG_LEAK_CHECK_DF
);
425 _CrtSetReportMode(_CRT_ASSERT
, _CRTDBG_MODE_DEBUG
| _CRTDBG_MODE_FILE
);
426 _CrtSetReportMode(_CRT_ERROR
, _CRTDBG_MODE_DEBUG
| _CRTDBG_MODE_FILE
);
427 _CrtSetReportMode(_CRT_WARN
, _CRTDBG_MODE_DEBUG
| _CRTDBG_MODE_FILE
);
429 _CrtSetReportFile(_CRT_ASSERT
, _CRTDBG_FILE_STDERR
);
430 _CrtSetReportFile(_CRT_ERROR
, _CRTDBG_FILE_STDERR
);
431 _CrtSetReportFile(_CRT_WARN
, _CRTDBG_FILE_STDERR
);
433 LeaveCriticalSection(&g_crtdbg_stacktrace_cs
);
436 int git_win32_leakcheck_stacktrace_dump(
437 git_win32_leakcheck_stacktrace_options opt
,
440 _CRT_REPORT_HOOK old
;
444 #define IS_BIT_SET(o,b) (((o) & (b)) != 0)
446 bool b_set_mark
= IS_BIT_SET(opt
, GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK
);
447 bool b_leaks_since_mark
= IS_BIT_SET(opt
, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK
);
448 bool b_leaks_total
= IS_BIT_SET(opt
, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL
);
449 bool b_quiet
= IS_BIT_SET(opt
, GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET
);
451 if (b_leaks_since_mark
&& b_leaks_total
) {
452 git_error_set(GIT_ERROR_INVALID
, "cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL.");
455 if (!b_set_mark
&& !b_leaks_since_mark
&& !b_leaks_total
) {
456 git_error_set(GIT_ERROR_INVALID
, "nothing to do.");
460 EnterCriticalSection(&g_crtdbg_stacktrace_cs
);
462 if (b_leaks_since_mark
|| b_leaks_total
) {
463 /* All variables with "transient" in the name are per-dump counters
464 * and reset before each dump. This lets us handle checkpoints.
466 g_transient_count_total_leaks
= 0;
467 g_transient_count_dedup_leaks
= 0;
468 for (k
= 0; k
< g_cs_ins
; k
++) {
469 g_cs_rows
[k
].transient_count_leaks
= 0;
473 g_transient_leaks_since_mark
= b_leaks_since_mark
;
475 old
= _CrtSetReportHook(report_hook
);
476 _CrtDumpMemoryLeaks();
477 _CrtSetReportHook(old
);
479 if (b_leaks_since_mark
|| b_leaks_total
) {
480 r
= g_transient_count_dedup_leaks
;
487 for (k
= 0; k
< g_cs_ins
; k
++) {
488 g_cs_rows
[k
].count_allocs_at_last_checkpoint
= g_cs_rows
[k
].count_allocs
;
494 LeaveCriticalSection(&g_crtdbg_stacktrace_cs
);
500 * Shutdown our memory leak tracking and dump summary data.
501 * This should ONLY be called by git_libgit2_shutdown().
503 * We explicitly call _CrtDumpMemoryLeaks() during here so
504 * that we can compute summary data for the leaks. We print
505 * the stacktrace of each unique leak.
507 * This cleanup does not happen if the app calls exit()
508 * without calling the libgit2 shutdown code.
510 * This info we print here is independent of any automatic
511 * reporting during exit() caused by _CRTDBG_LEAK_CHECK_DF.
512 * Set it in your app if you also want traditional reporting.
514 void git_win32_leakcheck_stacktrace_cleanup(void)
516 /* At shutdown/cleanup, dump cumulative leak info
517 * with everything since startup. This might generate
518 * extra noise if the caller has been doing checkpoint
519 * dumps, but it might also eliminate some false
520 * positives for resources previously reported during
523 git_win32_leakcheck_stacktrace_dump(
524 GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL
,
527 DeleteCriticalSection(&g_crtdbg_stacktrace_cs
);
530 const char *git_win32_leakcheck_stacktrace(int skip
, const char *file
)
532 git_win32_leakcheck_stack_raw_data new_data
;
533 git_win32_leakcheck_stacktrace_row
*row
;
534 const char * result
= file
;
536 if (git_win32_leakcheck_stack_capture(&new_data
, skip
+1) < 0)
539 EnterCriticalSection(&g_crtdbg_stacktrace_cs
);
541 if (g_cs_ins
< g_cs_end
) {
542 row
= insert_unique(&new_data
);
543 result
= row
->uid
.uid
;
545 g_limit_reached
= true;
548 g_count_total_allocs
++;
550 LeaveCriticalSection(&g_crtdbg_stacktrace_cs
);
555 static void git_win32_leakcheck_global_shutdown(void)
557 git_win32_leakcheck_stacktrace_cleanup();
558 git_win32_leakcheck_stack_cleanup();
561 bool git_win32_leakcheck_has_leaks(void)
563 return (g_transient_count_total_leaks
> 0);
566 int git_win32_leakcheck_global_init(void)
568 git_win32_leakcheck_stacktrace_init();
569 git_win32_leakcheck_stack_init();
571 return git_runtime_shutdown_register(git_win32_leakcheck_global_shutdown
);
576 int git_win32_leakcheck_global_init(void)