]>
Commit | Line | Data |
---|---|---|
93b42728 JH |
1 | /* |
2 | * Copyright (C) the libgit2 contributors. All rights reserved. | |
3 | * | |
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. | |
6 | */ | |
7 | ||
c25aa7cd | 8 | #include "w32_leakcheck.h" |
eae0bfdc | 9 | |
c25aa7cd | 10 | #if defined(GIT_WIN32_LEAKCHECK) |
93b42728 | 11 | |
c25aa7cd PP |
12 | #include "Windows.h" |
13 | #include "Dbghelp.h" | |
14 | #include "win32/posix.h" | |
15 | #include "hash.h" | |
16 | #include "runtime.h" | |
17 | ||
18 | /* Stack frames (for stack tracing, below) */ | |
19 | ||
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; | |
24 | ||
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) | |
28 | { | |
29 | g_aux_cb_alloc = cb_alloc; | |
30 | g_aux_cb_lookup = cb_lookup; | |
31 | ||
32 | return 0; | |
33 | } | |
34 | ||
35 | /** | |
36 | * Load symbol table data. This should be done in the primary | |
37 | * thread at startup (under a lock if there are other threads | |
38 | * active). | |
39 | */ | |
40 | void git_win32_leakcheck_stack_init(void) | |
41 | { | |
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; | |
47 | } | |
48 | } | |
49 | ||
50 | /** | |
51 | * Cleanup symbol table data. This should be done in the | |
52 | * primary thead at shutdown (under a lock if there are other | |
53 | * threads active). | |
54 | */ | |
55 | void git_win32_leakcheck_stack_cleanup(void) | |
56 | { | |
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; | |
61 | } | |
62 | } | |
63 | ||
64 | int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip) | |
65 | { | |
66 | if (!g_win32_stack_initialized) { | |
67 | git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); | |
68 | return GIT_ERROR; | |
69 | } | |
70 | ||
71 | memset(pdata, 0, sizeof(*pdata)); | |
72 | pdata->nr_frames = RtlCaptureStackBackTrace( | |
73 | skip+1, GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES, pdata->frames, NULL); | |
74 | ||
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. | |
78 | */ | |
79 | if (g_aux_cb_alloc) | |
80 | (g_aux_cb_alloc)(&pdata->aux_id); | |
81 | ||
82 | return 0; | |
83 | } | |
84 | ||
85 | int git_win32_leakcheck_stack_compare( | |
86 | git_win32_leakcheck_stack_raw_data *d1, | |
87 | git_win32_leakcheck_stack_raw_data *d2) | |
88 | { | |
89 | return memcmp(d1, d2, sizeof(*d1)); | |
90 | } | |
91 | ||
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) | |
96 | { | |
97 | #define MY_MAX_FILENAME 255 | |
98 | ||
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. | |
101 | */ | |
102 | struct { | |
103 | SYMBOL_INFO symbol; | |
104 | char extra[MY_MAX_FILENAME + 1]; | |
105 | } s; | |
106 | ||
107 | IMAGEHLP_LINE64 line; | |
108 | size_t buf_used = 0; | |
109 | unsigned int k; | |
110 | char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */ | |
111 | size_t detail_len; | |
112 | ||
113 | if (!g_win32_stack_initialized) { | |
114 | git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); | |
115 | return GIT_ERROR; | |
116 | } | |
117 | ||
118 | if (!prefix) | |
119 | prefix = "\t"; | |
120 | if (!suffix) | |
121 | suffix = "\n"; | |
122 | ||
123 | memset(pbuf, 0, buf_len); | |
124 | ||
125 | memset(&s, 0, sizeof(s)); | |
126 | s.symbol.MaxNameLen = MY_MAX_FILENAME; | |
127 | s.symbol.SizeOfStruct = sizeof(SYMBOL_INFO); | |
128 | ||
129 | memset(&line, 0, sizeof(line)); | |
130 | line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); | |
131 | ||
132 | for (k=0; k < pdata->nr_frames; k++) { | |
133 | DWORD64 frame_k = (DWORD64)pdata->frames[k]; | |
134 | DWORD dwUnused; | |
135 | ||
136 | if (SymFromAddr(g_win32_stack_process, frame_k, 0, &s.symbol) && | |
137 | SymGetLineFromAddr64(g_win32_stack_process, frame_k, &dwUnused, &line)) { | |
138 | const char *pslash; | |
139 | const char *pfile; | |
140 | ||
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); | |
145 | } else { | |
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. | |
150 | */ | |
151 | p_snprintf(detail, sizeof(detail), "%s??%s", prefix, suffix); | |
152 | } | |
153 | detail_len = strlen(detail); | |
154 | ||
155 | if (buf_len < (buf_used + detail_len + 1)) { | |
156 | /* we don't have room for this frame in the buffer, so just stop. */ | |
157 | break; | |
158 | } | |
159 | ||
160 | memcpy(&pbuf[buf_used], detail, detail_len); | |
161 | buf_used += detail_len; | |
162 | } | |
163 | ||
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. | |
166 | */ | |
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; | |
174 | } | |
175 | ||
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 | |
178 | * item was created. | |
179 | */ | |
180 | if (g_aux_cb_lookup) | |
181 | (g_aux_cb_lookup)(pdata->aux_id, &pbuf[buf_used], (buf_len - buf_used - 1)); | |
182 | } | |
183 | ||
184 | return GIT_OK; | |
185 | } | |
186 | ||
187 | int git_win32_leakcheck_stack( | |
188 | char * pbuf, size_t buf_len, | |
189 | int skip, | |
190 | const char *prefix, const char *suffix) | |
191 | { | |
192 | git_win32_leakcheck_stack_raw_data data; | |
193 | int error; | |
194 | ||
195 | if ((error = git_win32_leakcheck_stack_capture(&data, skip)) < 0) | |
196 | return error; | |
197 | if ((error = git_win32_leakcheck_stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0) | |
198 | return error; | |
199 | return 0; | |
200 | } | |
201 | ||
e579e0f7 | 202 | /* Stack tracing */ |
c25aa7cd PP |
203 | |
204 | #define STACKTRACE_UID_LEN (15) | |
93b42728 JH |
205 | |
206 | /** | |
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. | |
212 | */ | |
213 | typedef struct { | |
c25aa7cd PP |
214 | char uid[STACKTRACE_UID_LEN + 1]; |
215 | } git_win32_leakcheck_stacktrace_uid; | |
93b42728 JH |
216 | |
217 | /** | |
218 | * All mallocs with the same stacktrace will be de-duped | |
219 | * and aggregated into this row. | |
220 | */ | |
221 | typedef struct { | |
c25aa7cd PP |
222 | git_win32_leakcheck_stacktrace_uid uid; /* must be first */ |
223 | git_win32_leakcheck_stack_raw_data raw_data; | |
93b42728 JH |
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 */ | |
c25aa7cd | 227 | } git_win32_leakcheck_stacktrace_row; |
93b42728 JH |
228 | |
229 | static CRITICAL_SECTION g_crtdbg_stacktrace_cs; | |
230 | ||
231 | /** | |
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 | |
240 | * UID pointers). | |
241 | * | |
242 | * And to efficiently look for duplicates we need an index on the rows | |
243 | * so we can bsearch it. Again, without mallocing. | |
244 | * | |
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 | |
248 | * it and try again. | |
249 | */ | |
250 | ||
c25aa7cd PP |
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]; | |
93b42728 JH |
254 | |
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 */ | |
261 | ||
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 */ | |
264 | ||
265 | /** | |
266 | * Compare function for bsearch on g_cs_index table. | |
267 | */ | |
268 | static int row_cmp(const void *v1, const void *v2) | |
269 | { | |
c25aa7cd PP |
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; | |
93b42728 | 272 | |
c25aa7cd | 273 | return (git_win32_leakcheck_stack_compare(d1, &r2->raw_data)); |
93b42728 JH |
274 | } |
275 | ||
276 | /** | |
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. | |
279 | */ | |
c25aa7cd PP |
280 | static git_win32_leakcheck_stacktrace_row * insert_unique( |
281 | const git_win32_leakcheck_stack_raw_data *pdata) | |
93b42728 JH |
282 | { |
283 | size_t pos; | |
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); | |
288 | ||
289 | /* Insert pointer to it into the proper place in the index table. */ | |
290 | if (pos < g_cs_ins) | |
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]; | |
293 | ||
294 | g_cs_ins++; | |
295 | } | |
296 | ||
297 | g_cs_index[pos]->count_allocs++; | |
298 | ||
299 | return g_cs_index[pos]; | |
300 | } | |
301 | ||
302 | /** | |
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. | |
306 | * | |
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. | |
310 | * | |
311 | * See https://msdn.microsoft.com/en-us/library/74kabxyx.aspx | |
312 | * | |
313 | * We suppress the actual crtdbg output. | |
314 | */ | |
315 | static int __cdecl report_hook(int nRptType, char *szMsg, int *retVal) | |
316 | { | |
317 | static int hook_result = TRUE; /* FALSE to get stock dump; TRUE to suppress. */ | |
318 | unsigned int pos; | |
319 | ||
320 | *retVal = 0; /* do not invoke debugger */ | |
321 | ||
322 | if ((szMsg[0] != '#') || (szMsg[1] != '#')) | |
323 | return hook_result; | |
324 | ||
325 | if (sscanf(&szMsg[2], "%08lx", &pos) < 1) | |
326 | return hook_result; | |
327 | if (pos >= g_cs_ins) | |
328 | return hook_result; | |
329 | ||
330 | if (g_transient_leaks_since_mark) { | |
331 | if (g_cs_rows[pos].count_allocs == g_cs_rows[pos].count_allocs_at_last_checkpoint) | |
332 | return hook_result; | |
333 | } | |
334 | ||
335 | g_cs_rows[pos].transient_count_leaks++; | |
336 | ||
337 | if (g_cs_rows[pos].transient_count_leaks == 1) | |
338 | g_transient_count_dedup_leaks++; | |
339 | ||
340 | g_transient_count_total_leaks++; | |
341 | ||
342 | return hook_result; | |
343 | } | |
344 | ||
345 | /** | |
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. | |
349 | */ | |
350 | static void my_output(const char *buf) | |
351 | { | |
352 | fwrite(buf, strlen(buf), 1, stderr); | |
353 | OutputDebugString(buf); | |
354 | } | |
355 | ||
356 | /** | |
357 | * For each row with leaks, dump a stacktrace for it. | |
358 | */ | |
359 | static void dump_summary(const char *label) | |
360 | { | |
361 | unsigned int k; | |
362 | char buf[10 * 1024]; | |
363 | ||
364 | if (g_transient_count_total_leaks == 0) | |
365 | return; | |
366 | ||
367 | fflush(stdout); | |
368 | fflush(stderr); | |
369 | my_output("\n"); | |
370 | ||
371 | if (g_limit_reached) { | |
372 | sprintf(buf, | |
373 | "LEAK SUMMARY: de-dup row table[%d] filled. Increase MY_ROW_LIMIT.\n", | |
374 | MY_ROW_LIMIT); | |
375 | my_output(buf); | |
376 | } | |
377 | ||
378 | if (!label) | |
379 | label = ""; | |
380 | ||
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); | |
384 | my_output(buf); | |
385 | } else { | |
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); | |
388 | my_output(buf); | |
389 | } | |
390 | my_output("\n"); | |
391 | ||
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); | |
398 | my_output(buf); | |
399 | ||
c25aa7cd | 400 | if (git_win32_leakcheck_stack_format( |
93b42728 JH |
401 | buf, sizeof(buf), &g_cs_rows[k].raw_data, |
402 | NULL, NULL) >= 0) { | |
403 | my_output(buf); | |
404 | } | |
405 | ||
406 | my_output("\n"); | |
407 | } | |
408 | } | |
409 | ||
410 | fflush(stderr); | |
411 | } | |
412 | ||
c25aa7cd PP |
413 | /** |
414 | * Initialize our memory leak tracking and de-dup data structures. | |
415 | * This should ONLY be called by git_libgit2_init(). | |
416 | */ | |
417 | void git_win32_leakcheck_stacktrace_init(void) | |
93b42728 JH |
418 | { |
419 | InitializeCriticalSection(&g_crtdbg_stacktrace_cs); | |
420 | ||
421 | EnterCriticalSection(&g_crtdbg_stacktrace_cs); | |
422 | ||
423 | _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); | |
424 | ||
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); | |
428 | ||
429 | _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); | |
430 | _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); | |
431 | _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); | |
432 | ||
433 | LeaveCriticalSection(&g_crtdbg_stacktrace_cs); | |
434 | } | |
435 | ||
c25aa7cd PP |
436 | int git_win32_leakcheck_stacktrace_dump( |
437 | git_win32_leakcheck_stacktrace_options opt, | |
93b42728 JH |
438 | const char *label) |
439 | { | |
440 | _CRT_REPORT_HOOK old; | |
441 | unsigned int k; | |
442 | int r = 0; | |
443 | ||
444 | #define IS_BIT_SET(o,b) (((o) & (b)) != 0) | |
445 | ||
c25aa7cd PP |
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); | |
93b42728 JH |
450 | |
451 | if (b_leaks_since_mark && b_leaks_total) { | |
ac3d33df | 452 | git_error_set(GIT_ERROR_INVALID, "cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL."); |
93b42728 JH |
453 | return GIT_ERROR; |
454 | } | |
455 | if (!b_set_mark && !b_leaks_since_mark && !b_leaks_total) { | |
ac3d33df | 456 | git_error_set(GIT_ERROR_INVALID, "nothing to do."); |
93b42728 JH |
457 | return GIT_ERROR; |
458 | } | |
459 | ||
460 | EnterCriticalSection(&g_crtdbg_stacktrace_cs); | |
461 | ||
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. | |
465 | */ | |
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; | |
470 | } | |
471 | } | |
472 | ||
473 | g_transient_leaks_since_mark = b_leaks_since_mark; | |
474 | ||
475 | old = _CrtSetReportHook(report_hook); | |
476 | _CrtDumpMemoryLeaks(); | |
477 | _CrtSetReportHook(old); | |
478 | ||
479 | if (b_leaks_since_mark || b_leaks_total) { | |
480 | r = g_transient_count_dedup_leaks; | |
481 | ||
482 | if (!b_quiet) | |
483 | dump_summary(label); | |
484 | } | |
485 | ||
486 | if (b_set_mark) { | |
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; | |
489 | } | |
490 | ||
491 | g_checkpoint_id++; | |
492 | } | |
493 | ||
494 | LeaveCriticalSection(&g_crtdbg_stacktrace_cs); | |
495 | ||
496 | return r; | |
497 | } | |
498 | ||
c25aa7cd PP |
499 | /** |
500 | * Shutdown our memory leak tracking and dump summary data. | |
501 | * This should ONLY be called by git_libgit2_shutdown(). | |
502 | * | |
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. | |
506 | * | |
507 | * This cleanup does not happen if the app calls exit() | |
508 | * without calling the libgit2 shutdown code. | |
509 | * | |
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. | |
513 | */ | |
514 | void git_win32_leakcheck_stacktrace_cleanup(void) | |
93b42728 | 515 | { |
c25aa7cd | 516 | /* At shutdown/cleanup, dump cumulative leak info |
93b42728 JH |
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 | |
521 | * checkpoints. | |
522 | */ | |
c25aa7cd PP |
523 | git_win32_leakcheck_stacktrace_dump( |
524 | GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL, | |
93b42728 JH |
525 | "CLEANUP"); |
526 | ||
527 | DeleteCriticalSection(&g_crtdbg_stacktrace_cs); | |
528 | } | |
529 | ||
c25aa7cd | 530 | const char *git_win32_leakcheck_stacktrace(int skip, const char *file) |
93b42728 | 531 | { |
c25aa7cd PP |
532 | git_win32_leakcheck_stack_raw_data new_data; |
533 | git_win32_leakcheck_stacktrace_row *row; | |
93b42728 JH |
534 | const char * result = file; |
535 | ||
c25aa7cd | 536 | if (git_win32_leakcheck_stack_capture(&new_data, skip+1) < 0) |
93b42728 JH |
537 | return result; |
538 | ||
539 | EnterCriticalSection(&g_crtdbg_stacktrace_cs); | |
540 | ||
541 | if (g_cs_ins < g_cs_end) { | |
542 | row = insert_unique(&new_data); | |
543 | result = row->uid.uid; | |
544 | } else { | |
545 | g_limit_reached = true; | |
546 | } | |
547 | ||
548 | g_count_total_allocs++; | |
549 | ||
550 | LeaveCriticalSection(&g_crtdbg_stacktrace_cs); | |
551 | ||
552 | return result; | |
553 | } | |
ac3d33df | 554 | |
c25aa7cd PP |
555 | static void git_win32_leakcheck_global_shutdown(void) |
556 | { | |
557 | git_win32_leakcheck_stacktrace_cleanup(); | |
558 | git_win32_leakcheck_stack_cleanup(); | |
559 | } | |
560 | ||
561 | bool git_win32_leakcheck_has_leaks(void) | |
562 | { | |
563 | return (g_transient_count_total_leaks > 0); | |
564 | } | |
565 | ||
566 | int git_win32_leakcheck_global_init(void) | |
567 | { | |
568 | git_win32_leakcheck_stacktrace_init(); | |
569 | git_win32_leakcheck_stack_init(); | |
570 | ||
571 | return git_runtime_shutdown_register(git_win32_leakcheck_global_shutdown); | |
572 | } | |
573 | ||
574 | #else | |
575 | ||
576 | int git_win32_leakcheck_global_init(void) | |
577 | { | |
578 | return 0; | |
579 | } | |
580 | ||
93b42728 | 581 | #endif |