Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
wdbg_heap.cpp
Go to the documentation of this file.
1 /* Copyright (c) 2010 Wildfire Games
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include "precompiled.h"
25 
26 #include "lib/sysdep/os/win/win.h"
27 #include <crtdbg.h>
28 #include <excpt.h>
29 
31 #include "lib/sysdep/cpu.h" // cpu_AtomicAdd
33 #include "lib/sysdep/os/win/wdbg.h" // wdbg_printf
34 #include "lib/sysdep/os/win/wdbg_sym.h" // wdbg_sym_WalkStack
35 
36 
37 WINIT_REGISTER_EARLY_INIT2(wdbg_heap_Init); // wutil -> wdbg_heap
38 WINIT_REGISTER_LATE_SHUTDOWN2(wdbg_heap_Shutdown); // last - no leaks are detected after this
39 
40 
41 void wdbg_heap_Enable(bool enable)
42 {
43 #ifdef _DEBUG // (avoid "expression has no effect" warning in release builds)
44  int flags = 0;
45  if(enable)
46  {
47  flags |= _CRTDBG_ALLOC_MEM_DF; // enable checks at deallocation time
48  flags |= _CRTDBG_LEAK_CHECK_DF; // report leaks at exit
49 #if 0
50  flags |= _CRTDBG_CHECK_ALWAYS_DF; // check during every heap operation (too slow to be practical)
51  flags |= _CRTDBG_DELAY_FREE_MEM_DF; // memory is never actually freed
52 #endif
53  }
54  _CrtSetDbgFlag(flags);
55 
56  // Send output to stdout as well as the debug window, so it works during
57  // the normal build process as well as when debugging the test .exe
58  _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
59  _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);
60 #else
61  UNUSED2(enable);
62 #endif
63 }
64 
65 
67 {
68  int ok = TRUE;
69  __try
70  {
71  // NB: this is a no-op if !_CRTDBG_ALLOC_MEM_DF.
72  // we could call _heapchk but that would catch fewer errors.
73  ok = _CrtCheckMemory();
74  }
75  __except(EXCEPTION_EXECUTE_HANDLER)
76  {
77  ok = FALSE;
78  }
79 
80  wdbg_assert(ok == TRUE); // else: heap is corrupt!
81 }
82 
83 
84 //-----------------------------------------------------------------------------
85 // improved leak detection
86 //-----------------------------------------------------------------------------
87 
88 // (this relies on the debug CRT; not compiling it at all in release builds
89 // avoids unreferenced local function warnings)
90 // (this has only been tested on IA32 and seems to have trouble with larger
91 // pointers and is horribly expensive, so it's disabled for now.)
92 #if !defined(NDEBUG) && ARCH_IA32 && 0
93 # define ENABLE_LEAK_INSTRUMENTATION 1
94 #else
95 # define ENABLE_LEAK_INSTRUMENTATION 0
96 #endif
97 
98 #if ENABLE_LEAK_INSTRUMENTATION
99 
100 // leak detectors often rely on macro redirection to determine the file and
101 // line of allocation owners (see _CRTDBG_MAP_ALLOC). unfortunately this
102 // breaks code that uses placement new or functions called free() etc.
103 //
104 // we avoid this problem by using stack traces. this implementation differs
105 // from other approaches, e.g. Visual Leak Detector (the safer variant
106 // before DLL hooking was used) in that no auxiliary storage is needed.
107 // instead, the trace is stashed within the memory block header.
108 //
109 // to avoid duplication of effort, the CRT's leak detection code is not
110 // modified; we only need an allocation and report hook. the latter
111 // mixes the improved file/line information into the normal report.
112 
113 
114 //-----------------------------------------------------------------------------
115 // memory block header
116 
117 // the one disadvantage of our approach is that it requires knowledge of
118 // the internal memory block header structure. it is hoped that IsValid will
119 // uncover any changes. the following definition was adapted from dbgint.h:
120 struct _CrtMemBlockHeader
121 {
122  struct _CrtMemBlockHeader* next;
123  struct _CrtMemBlockHeader* prev;
124  char* file;
125  int line;
126  // fields reversed on Win64 to ensure size % 16 == 0
127 #if OS_WIN64
128  int blockType;
129  size_t userDataSize;
130 #else
131  size_t userDataSize;
132  int blockType;
133 #endif
134  long allocationNumber;
135  u8 gap[4];
136 
137  bool IsValid() const
138  {
139  __try
140  {
141  if(prev && prev->next != this)
142  return false;
143  if(next && next->prev != this)
144  return false;
145  if((unsigned)blockType > 4)
146  return false;
147  if(userDataSize > 1*GiB)
148  return false;
149  if(allocationNumber == 0)
150  return false;
151  for(int i = 0; i < 4; i++)
152  {
153  if(gap[i] != 0xFD)
154  return false;
155  }
156 
157  // this is a false alarm if there is exactly one extant allocation,
158  // but also a valuable indication of a block that has been removed
159  // from the list (i.e. freed).
160  if(prev == next)
161  return false;
162  }
163  __except(EXCEPTION_EXECUTE_HANDLER)
164  {
165  return false;
166  }
167 
168  return true;
169  }
170 };
171 
172 static _CrtMemBlockHeader* HeaderFromData(void* userData)
173 {
174  _CrtMemBlockHeader* const header = ((_CrtMemBlockHeader*)userData)-1;
175  wdbg_assert(header->IsValid());
176  return header;
177 }
178 
179 
180 /**
181  * update our idea of the head of the linked list of heap blocks.
182  * called from the allocation hook (see explanation there)
183  *
184  * @return the current head (most recent allocation).
185  * @param operation the current heap operation
186  * @param userData allocation address (if reallocating or deallocating)
187  * @param hasChanged a convenient indication of whether the return value is
188  * different than that of the last call.
189  **/
190 static _CrtMemBlockHeader* GetHeapListHead(int operation, void* userData, bool& hasChanged)
191 {
192  static _CrtMemBlockHeader* s_heapListHead;
193 
194  // first call: get the heap block list head
195  // notes:
196  // - there is no O(1) accessor for this, so we maintain a copy.
197  // - must be done here instead of in an initializer to guarantee
198  // consistency, since we are now under the _HEAP_LOCK.
199  if(!s_heapListHead)
200  {
201  _CrtMemState state = {0};
202  _CrtMemCheckpoint(&state); // O(N)
203  s_heapListHead = state.pBlockHeader;
204  wdbg_assert(s_heapListHead->IsValid());
205  }
206 
207  // the last operation was an allocation or expanding reallocation;
208  // exactly one block has been prepended to the list.
209  if(s_heapListHead->prev)
210  {
211  s_heapListHead = s_heapListHead->prev; // set to new head of list
212  wdbg_assert(s_heapListHead->IsValid());
213  wdbg_assert(s_heapListHead->prev == 0);
214  hasChanged = true;
215  }
216  // the list head remained unchanged, so the last operation was a
217  // non-expanding reallocation or free.
218  else
219  hasChanged = false;
220 
221  // special case: handle invalidation of the list head
222  // note: even shrinking reallocations cause deallocation.
223  if(operation != _HOOK_ALLOC && userData == s_heapListHead+1)
224  {
225  s_heapListHead = s_heapListHead->next;
226  wdbg_assert(s_heapListHead->IsValid());
227 
228  hasChanged = false; // (head is now the same as last time)
229  }
230 
231  return s_heapListHead;
232 }
233 
234 
235 //-----------------------------------------------------------------------------
236 // call stack filter
237 
238 // we need to make the most out of the limited amount of frames. to that end,
239 // only user functions are stored; we skip known library and helper functions.
240 // these are determined by recording frames encountered in a backtrace.
241 
242 /**
243  * extents of a module in memory; used to ignore callers that lie within
244  * the C runtime library.
245  **/
246 class ModuleExtents
247 {
248 public:
249  ModuleExtents()
250  : m_address(0), m_length(0)
251  {
252  }
253 
254  ModuleExtents(const wchar_t* dllName)
255  {
256  HMODULE hModule = GetModuleHandleW(dllName);
257  PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((u8*)hModule + ((PIMAGE_DOS_HEADER)hModule)->e_lfanew);
258  m_address = (uintptr_t)hModule + ntHeaders->OptionalHeader.BaseOfCode;
259  MEMORY_BASIC_INFORMATION mbi = {0};
260  VirtualQuery((void*)m_address, &mbi, sizeof(mbi));
261  m_length = mbi.RegionSize;
262  }
263 
264  uintptr_t Address() const
265  {
266  return m_address;
267  }
268 
269  uintptr_t Length() const
270  {
271  return m_length;
272  }
273 
274  bool Contains(uintptr_t address) const
275  {
276  return (address - m_address) < m_length;
277  }
278 
279 private:
280  uintptr_t m_address;
281  size_t m_length;
282 };
283 
284 
285 /**
286  * set data structure that avoids dynamic allocations because they would
287  * cause the allocation hook to be reentered (bad).
288  **/
289 template<typename T, size_t maxItems>
290 class ArraySet
291 {
292 public:
293  ArraySet()
294  {
295  m_arrayEnd = m_array;
296  }
297 
298  void Add(T t)
299  {
300  if(m_arrayEnd == m_array+maxItems)
301  {
302  RemoveDuplicates();
303  wdbg_assert(m_arrayEnd < m_array+maxItems);
304  }
305  *m_arrayEnd++ = t;
306  }
307 
308  bool Find(T t) const
309  {
310  return std::find(m_array, const_cast<const T*>(m_arrayEnd), t) != m_arrayEnd;
311  }
312 
313  void RemoveDuplicates()
314  {
315  std::sort(m_array, m_arrayEnd);
316  m_arrayEnd = std::unique(m_array, m_arrayEnd);
317  }
318 
319 private:
320  T m_array[maxItems];
321  T* m_arrayEnd;
322 };
323 
324 
325 class CallerFilter
326 {
327 public:
328  CallerFilter()
329  {
330  AddRuntimeLibraryToIgnoreList();
331 
332  m_isRecordingKnownCallers = true;
333  CallHeapFunctions();
334  m_isRecordingKnownCallers = false;
335  m_knownCallers.RemoveDuplicates();
336  }
337 
338  Status NotifyOfCaller(uintptr_t pc)
339  {
340  if(!m_isRecordingKnownCallers)
341  return INFO::SKIPPED;
342 
343  // last 'known' function has been reached
344  if(pc == (uintptr_t)&CallerFilter::CallHeapFunctions)
345  return INFO::ALL_COMPLETE;
346 
347  // pc is a 'known' function on the allocation hook's back-trace
348  // (e.g. _malloc_dbg and other helper functions)
349  m_knownCallers.Add(pc);
350  return INFO::OK;
351  }
352 
353  bool IsKnownCaller(uintptr_t pc) const
354  {
355  for(size_t i = 0; i < numModules; i++)
356  {
357  if(m_moduleIgnoreList[i].Contains(pc))
358  return true;
359  }
360 
361  return m_knownCallers.Find(pc);
362  }
363 
364 private:
365  static const size_t numModules = 2;
366 
367  void AddRuntimeLibraryToIgnoreList()
368  {
369 #if MSC_VERSION && _DLL // DLL runtime library
370 #ifdef NDEBUG
371  static const wchar_t* dllNameFormat = L"msvc%c%d" L".dll";
372 #else
373  static const wchar_t* dllNameFormat = L"msvc%c%d" L"d" L".dll";
374 #endif
375  const int dllVersion = (MSC_VERSION-600)/10; // VC2005: 1400 => 80
376  wdbg_assert(0 < dllVersion && dllVersion <= 999);
377  for(int i = 0; i < numModules; i++)
378  {
379  static const char modules[numModules] = { 'r', 'p' }; // C and C++ runtime libraries
380  wchar_t dllName[20];
381  swprintf_s(dllName, ARRAY_SIZE(dllName), dllNameFormat, modules[i], dllVersion);
382  m_moduleIgnoreList[i] = ModuleExtents(dllName);
383  }
384 #endif
385  }
386 
387  static void CallHeapFunctions()
388  {
389  {
390  void* p1 = malloc(1);
391  void* p2 = realloc(p1, 111);
392  if(p2)
393  free(p2);
394  else
395  free(p1);
396  }
397  {
398  u8* p = new u8;
399  delete p;
400  }
401  {
402  u8* p = new u8[2];
403  delete[] p;
404  }
405  }
406 
407  ModuleExtents m_moduleIgnoreList[numModules];
408 
409  // note: this mechanism cannot hope to exclude every single STL helper
410  // function, which is why we need the module ignore list.
411  // however, it is still useful when compiling against the static CRT.
412  bool m_isRecordingKnownCallers;
413  ArraySet<uintptr_t, 500> m_knownCallers;
414 };
415 
416 
417 //-----------------------------------------------------------------------------
418 // stash (part of) a stack trace within _CrtMemBlockHeader
419 
420 // this avoids the need for a mapping between allocation number and the
421 // caller information, which is slow, requires locking and consumes memory.
422 //
423 // callers := array of addresses inside functions that constitute the
424 // stack back-trace.
425 
426 static const size_t numQuantizedPcBits = sizeof(uintptr_t)*CHAR_BIT - 2;
427 
428 static uintptr_t Quantize(uintptr_t pc)
429 {
430  // postcondition: the return value lies within the same function as
431  // pc but can be stored in fewer bits. this is possible because:
432  // - linkers typically align functions to at least four bytes
433  // - pc is a return address and thus preceded by a call instruction and
434  // function prolog, which requires at least four bytes.
435  return pc/4;
436 }
437 
438 static uintptr_t Expand(uintptr_t pc)
439 {
440  return pc*4;
441 }
442 
443 
444 static const size_t numEncodedLengthBits = 2;
445 static const size_t maxCallers = (sizeof(char*)+sizeof(int))*CHAR_BIT / (2+14);
446 
447 static size_t NumBitsForEncodedLength(size_t encodedLength)
448 {
449  static const size_t numBitsForEncodedLength[1u << numEncodedLengthBits] =
450  {
451  8, // 1K
452  14, // 64K
453  20, // 4M
454  numQuantizedPcBits // a full pointer
455  };
456  return numBitsForEncodedLength[encodedLength];
457 }
458 
459 static size_t EncodedLength(uintptr_t quantizedOffset)
460 {
461  for(size_t encodedLength = 0; encodedLength < 1u << numEncodedLengthBits; encodedLength++)
462  {
463  const size_t numBits = NumBitsForEncodedLength(encodedLength);
464  const uintptr_t maxValue = (1u << numBits)-1;
465  if(quantizedOffset <= maxValue)
466  return encodedLength;
467  }
468 
469  wdbg_assert(0); // unreachable
470  return 0;
471 }
472 
473 
474 static uintptr_t codeSegmentAddress;
475 static uintptr_t quantizedCodeSegmentAddress;
476 static uintptr_t quantizedCodeSegmentLength;
477 
478 static void FindCodeSegment()
479 {
480  const wchar_t* dllName = 0; // current module
481  ModuleExtents extents(dllName);
482  codeSegmentAddress = extents.Address();
483  quantizedCodeSegmentAddress = Quantize(codeSegmentAddress);
484  quantizedCodeSegmentLength = Quantize(extents.Length());
485 }
486 
487 
488 class BitStream
489 {
490 public:
491  BitStream(u8* storage, size_t storageSize)
492  : m_remainderBits(0), m_numRemainderBits(0)
493  , m_pos(storage), m_bitsLeft((size_t)storageSize*8)
494  {
495  }
496 
497  size_t BitsLeft() const
498  {
499  return m_bitsLeft;
500  }
501 
502  void Write(const size_t numOutputBits, uintptr_t outputValue)
503  {
504  wdbg_assert(numOutputBits <= m_bitsLeft);
505  wdbg_assert(outputValue < ((uintptr_t)1u << numOutputBits));
506 
507  size_t outputBitsLeft = numOutputBits;
508  while(outputBitsLeft > 0)
509  {
510  const size_t numBits = std::min(outputBitsLeft, size_t(8));
511  m_bitsLeft -= numBits;
512 
513  // (NB: there is no need to extract exactly numBits because
514  // outputValue's MSBs were verified to be zero)
515  const uintptr_t outputByte = outputValue & 0xFF;
516  outputValue >>= 8;
517  outputBitsLeft -= numBits;
518 
519  m_remainderBits |= outputByte << m_numRemainderBits;
520  m_numRemainderBits += numBits;
521  if(m_numRemainderBits >= 8)
522  {
523  const u8 remainderByte = (m_remainderBits & 0xFF);
524  m_remainderBits >>= 8;
525  m_numRemainderBits -= 8;
526 
527  *m_pos++ = remainderByte;
528  }
529  }
530  }
531 
532  void Finish()
533  {
534  const size_t partialBits = m_numRemainderBits % 8;
535  if(partialBits)
536  {
537  m_bitsLeft -= 8-partialBits;
538  m_numRemainderBits += 8-partialBits;
539  }
540  while(m_numRemainderBits)
541  {
542  const u8 remainderByte = (m_remainderBits & 0xFF);
543  *m_pos++ = remainderByte;
544  m_remainderBits >>= 8;
545  m_numRemainderBits -= 8;
546  }
547 
548  wdbg_assert(m_bitsLeft % 8 == 0);
549  while(m_bitsLeft)
550  {
551  *m_pos++ = 0;
552  m_bitsLeft -= 8;
553  }
554  }
555 
556  uintptr_t Read(const size_t numInputBits)
557  {
558  wdbg_assert(numInputBits <= m_bitsLeft);
559 
560  uintptr_t inputValue = 0;
561  size_t inputBitsLeft = numInputBits;
562  while(inputBitsLeft > 0)
563  {
564  const size_t numBits = std::min(inputBitsLeft, size_t(8));
565  m_bitsLeft -= numBits;
566 
567  if(m_numRemainderBits < numBits)
568  {
569  const size_t inputByte = *m_pos++;
570  m_remainderBits |= inputByte << m_numRemainderBits;
571  m_numRemainderBits += 8;
572  }
573 
574  const uintptr_t remainderByte = (m_remainderBits & ((1u << numBits)-1));
575  m_remainderBits >>= numBits;
576  m_numRemainderBits -= numBits;
577  inputValue |= remainderByte << (numInputBits-inputBitsLeft);
578 
579  inputBitsLeft -= numBits;
580  }
581 
582  return inputValue;
583  }
584 
585 private:
586  uintptr_t m_remainderBits;
587  size_t m_numRemainderBits;
588  u8* m_pos;
589  size_t m_bitsLeft;
590 };
591 
592 
593 static void StashCallers(_CrtMemBlockHeader* header, const uintptr_t* callers, size_t numCallers)
594 {
595  // transform an array of callers into a (sorted and unique) set.
596  uintptr_t quantizedPcSet[maxCallers];
597  std::transform(callers, callers+numCallers, quantizedPcSet, Quantize);
598  std::sort(quantizedPcSet, quantizedPcSet+numCallers);
599  uintptr_t* const end = std::unique(quantizedPcSet, quantizedPcSet+numCallers);
600  const size_t quantizedPcSetSize = end-quantizedPcSet;
601 
602  // transform the set into a sequence of quantized offsets.
603  uintptr_t quantizedOffsets[maxCallers];
604  if(quantizedPcSet[0] >= quantizedCodeSegmentAddress)
605  quantizedOffsets[0] = quantizedPcSet[0] - quantizedCodeSegmentAddress;
606  else
607  {
608  quantizedOffsets[0] = quantizedPcSet[0];
609 
610  // make sure RetrieveCallers can differentiate between pointers and code-segment-offsets
611  wdbg_assert(quantizedOffsets[0] >= quantizedCodeSegmentLength);
612  }
613  for(size_t i = 1; i < numCallers; i++)
614  quantizedOffsets[i] = quantizedPcSet[i] - quantizedPcSet[i-1];
615 
616  // write quantized offsets to stream
617  BitStream bitStream((u8*)&header->file, sizeof(header->file)+sizeof(header->line));
618  for(size_t i = 0; i < quantizedPcSetSize; i++)
619  {
620  const uintptr_t quantizedOffset = quantizedOffsets[i];
621  const size_t encodedLength = EncodedLength(quantizedOffset);
622  const size_t numBits = NumBitsForEncodedLength(encodedLength);
623  if(bitStream.BitsLeft() < numEncodedLengthBits+numBits)
624  break;
625  bitStream.Write(numEncodedLengthBits, encodedLength);
626  bitStream.Write(numBits, quantizedOffset);
627  }
628 
629  bitStream.Finish();
630 }
631 
632 
633 static void RetrieveCallers(_CrtMemBlockHeader* header, uintptr_t* callers, size_t& numCallers)
634 {
635  // read quantized offsets from stream
636  uintptr_t quantizedOffsets[maxCallers];
637  numCallers = 0;
638  BitStream bitStream((u8*)&header->file, sizeof(header->file)+sizeof(header->line));
639  for(;;)
640  {
641  if(bitStream.BitsLeft() < numEncodedLengthBits)
642  break;
643  const size_t encodedLength = bitStream.Read(numEncodedLengthBits);
644  const size_t numBits = NumBitsForEncodedLength(encodedLength);
645  if(bitStream.BitsLeft() < numBits)
646  break;
647  const uintptr_t quantizedOffset = bitStream.Read(numBits);
648  if(!quantizedOffset)
649  break;
650  quantizedOffsets[numCallers++] = quantizedOffset;
651  }
652 
653  if(!numCallers)
654  return;
655 
656  // expand offsets into a set of callers
657  if(quantizedOffsets[0] <= quantizedCodeSegmentLength)
658  callers[0] = Expand(quantizedOffsets[0] + quantizedCodeSegmentAddress);
659  else
660  callers[0] = Expand(quantizedOffsets[0]);
661  for(size_t i = 1; i < numCallers; i++)
662  callers[i] = callers[i-1] + Expand(quantizedOffsets[i]);
663 }
664 
665 
666 //-----------------------------------------------------------------------------
667 // find out who called an allocation function
668 
669 /**
670  * gather and store a (filtered) list of callers.
671  **/
672 class CallStack
673 {
674 public:
675  void Gather()
676  {
677  m_numCallers = 0;
678  CONTEXT context;
679  (void)debug_CaptureContext(&context);
680  (void)wdbg_sym_WalkStack(OnFrame_Trampoline, (uintptr_t)this, context);
681  std::fill(m_callers+m_numCallers, m_callers+maxCallers, 0);
682  }
683 
684  const uintptr_t* Callers() const
685  {
686  return m_callers;
687  }
688 
689  size_t NumCallers() const
690  {
691  return m_numCallers;
692  }
693 
694 private:
695  Status OnFrame(const STACKFRAME64* frame)
696  {
697  const uintptr_t pc = frame->AddrPC.Offset;
698 
699  // skip invalid frames
700  if(pc == 0)
701  return INFO::OK;
702 
703  Status ret = m_filter.NotifyOfCaller(pc);
704  // (CallerFilter provokes stack traces of heap functions; if that is
705  // what happened, then we must not continue)
706  if(ret != INFO::SKIPPED)
707  return ret;
708 
709  // stop the stack walk if frame storage is full
710  if(m_numCallers >= maxCallers)
711  return INFO::ALL_COMPLETE;
712 
713  if(!m_filter.IsKnownCaller(pc))
714  m_callers[m_numCallers++] = pc;
715  return INFO::OK;
716  }
717 
718  static Status OnFrame_Trampoline(const STACKFRAME64* frame, uintptr_t cbData)
719  {
720  CallStack* this_ = (CallStack*)cbData;
721  return this_->OnFrame(frame);
722  }
723 
724  CallerFilter m_filter;
725 
726  uintptr_t m_callers[maxCallers];
727  size_t m_numCallers;
728 };
729 
730 
731 //-----------------------------------------------------------------------------
732 // RAII wrapper for installing a CRT allocation hook
733 
734 class AllocationHook
735 {
736 public:
737  AllocationHook()
738  {
739  wdbg_assert(s_instance == 0 && s_previousHook == 0);
740  s_instance = this;
741  s_previousHook = _CrtSetAllocHook(Hook);
742  }
743 
744  ~AllocationHook()
745  {
746  _CRT_ALLOC_HOOK removedHook = _CrtSetAllocHook(s_previousHook);
747  wdbg_assert(removedHook == Hook); // warn if we removed someone else's hook
748  s_instance = 0;
749  s_previousHook = 0;
750  }
751 
752  /**
753  * @param operation either _HOOK_ALLOC, _HOOK_REALLOC or _HOOK_FREE
754  * @param userData is only valid (nonzero) for realloc and free because
755  * we are called BEFORE the actual heap operation.
756  **/
757  virtual void OnHeapOperation(int operation, void* userData, size_t size, long allocationNumber) = 0;
758 
759 private:
760  static int __cdecl Hook(int operation, void* userData, size_t size, int blockType, long allocationNumber, const unsigned char* file, int line)
761  {
762  static bool busy = false;
763  wdbg_assert(!busy);
764  busy = true;
765  s_instance->OnHeapOperation(operation, userData, size, allocationNumber);
766  busy = false;
767 
768  if(s_previousHook)
769  return s_previousHook(operation, userData, size, blockType, allocationNumber, file, line);
770  return 1; // continue as if the hook had never been called
771  }
772 
773  // unfortunately static because we can't pass our `this' pointer through
774  // the allocation hook.
775  static AllocationHook* s_instance;
776  static _CRT_ALLOC_HOOK s_previousHook;
777 };
778 
779 AllocationHook* AllocationHook::s_instance;
780 _CRT_ALLOC_HOOK AllocationHook::s_previousHook;
781 
782 
783 //-----------------------------------------------------------------------------
784 // our allocation hook
785 
786 // ideally we would just stash the callers in the newly created header.
787 // unfortunately we are called BEFORE it (and the allocation) are actually
788 // created, so we need to keep the information around until the next call to
789 // AllocHook; only then can it be stored.
790 //
791 // unfortunately the CRT does not provide an O(1) means of getting at the
792 // most recent block header. instead, we do so once and then keep it
793 // up-to-date in the allocation hook. this is safe because we run under
794 // the _HEAP_LOCK and ensure the allocation numbers match.
795 
796 static intptr_t s_numAllocations;
797 
799 {
800  return s_numAllocations;
801 }
802 
803 class AllocationTracker : public AllocationHook
804 {
805 public:
806  AllocationTracker()
807  : m_pendingAllocationNumber(0)
808  {
809  }
810 
811  virtual void OnHeapOperation(int operation, void* userData, size_t size, long allocationNumber)
812  {
813  UNUSED2(size);
814 
815  if(operation == _HOOK_ALLOC || operation == _HOOK_REALLOC)
816  cpu_AtomicAdd(&s_numAllocations, 1);
817 
818  bool hasChanged;
819  _CrtMemBlockHeader* head = GetHeapListHead(operation, userData, hasChanged);
820  // if the head changed, the last operation was a (re)allocation and
821  // we now have its header; stash the pending call stack there.
822  if(hasChanged)
823  {
824  wdbg_assert(head->allocationNumber == m_pendingAllocationNumber);
825 
826  // note: overwrite existing file/line info (even if valid) to avoid
827  // special cases in the report hook.
828  StashCallers(head, m_pendingCallStack.Callers(), m_pendingCallStack.NumCallers());
829  }
830 
831  // remember the current caller for next time
832  m_pendingCallStack.Gather(); // NB: called for each operation, as required by the filter recording step
833  m_pendingAllocationNumber = allocationNumber;
834  }
835 
836 private:
837  long m_pendingAllocationNumber;
838  CallStack m_pendingCallStack;
839 };
840 
841 
842 //-----------------------------------------------------------------------------
843 
844 static void PrintCallStack(const uintptr_t* callers, size_t numCallers)
845 {
846  if(!numCallers || callers[0] == 0)
847  {
848  wdbg_printf(L"\n call stack not available.\n");
849  return;
850  }
851 
852  wdbg_printf(L"\n partial, unordered call stack:\n");
853  for(size_t i = 0; i < numCallers; i++)
854  {
855  wchar_t name[DEBUG_SYMBOL_CHARS] = {'\0'}; wchar_t file[DEBUG_FILE_CHARS] = {'\0'}; int line = -1;
856  Status err = debug_ResolveSymbol((void*)callers[i], name, file, &line);
857  wdbg_printf(L" ");
858  if(err != INFO::OK)
859  wdbg_printf(L"(error %d resolving PC=%p) ", err, callers[i]);
860  if(file[0] != '\0')
861  wdbg_printf(L"%ls(%d) : ", file, line);
862  wdbg_printf(L"%ls\n", name);
863  }
864 }
865 
866 static int __cdecl ReportHook(int reportType, wchar_t* message, int* out)
867 {
868  UNUSED2(reportType);
869 
870  // set up return values to reduce the chance of mistakes below
871  *out = 0; // alternatives are failure (-1) and breakIntoDebugger (1)
872  const int ret = 0; // not "handled", continue calling other hooks
873 
874  // note: this hook is transparent in that it never affects the CRT.
875  // we can't suppress parts of a leak report because that causes the
876  // rest of it to be skipped.
877 
878  static enum
879  {
880  WaitingForDump,
881  WaitingForBlock,
882  IsBlock
883  }
884  state = WaitingForDump;
885  switch(state)
886  {
887  case WaitingForDump:
888  if(!wcscmp(message, L"Dumping objects ->\n"))
889  state = WaitingForBlock;
890  return ret;
891 
892  case IsBlock:
893  {
894  // common case: "normal block at 0xPPPPPPPP, N bytes long".
895  const wchar_t* addressString = wcsstr(message, L"0x");
896  if(addressString)
897  {
898  const uintptr_t address = wcstoul(addressString, 0, 0);
899  _CrtMemBlockHeader* header = HeaderFromData((void*)address);
900  uintptr_t callers[maxCallers]; size_t numCallers;
901  RetrieveCallers(header, callers, numCallers);
902  PrintCallStack(callers, numCallers);
903 
904  state = WaitingForBlock;
905  return ret;
906  }
907  // else: for reasons unknown, there's apparently no information
908  // about the block; fall through to the previous state.
909  }
910 
911  case WaitingForBlock:
912  if(message[0] == '{')
913  state = IsBlock;
914  // suppress messages containing "file" and "line" since the normal
915  // interpretation of those header fields is invalid.
916  else if(wcschr(message, '('))
917  message[0] = '\0';
918  return ret;
919 
920  default:
921  wdbg_assert(0); // unreachable
922  }
923 
924  wdbg_assert(0); // unreachable
925  return 0;
926 }
927 
928 #else
929 
931 {
932  return 0;
933 }
934 
935 #endif
936 
937 //-----------------------------------------------------------------------------
938 
939 #if ENABLE_LEAK_INSTRUMENTATION
940 static AllocationTracker* s_tracker;
941 #endif
942 
944 {
945 #if ENABLE_LEAK_INSTRUMENTATION
946  FindCodeSegment();
947 
948  // load symbol information now (fails if it happens during shutdown)
949  wchar_t name[DEBUG_SYMBOL_CHARS]; wchar_t file[DEBUG_FILE_CHARS]; int line;
950  (void)debug_ResolveSymbol(wdbg_heap_Init, name, file, &line);
951 
952  int ret = _CrtSetReportHookW2(_CRT_RPTHOOK_INSTALL, ReportHook);
953  if(ret == -1)
954  abort();
955 
956  s_tracker = new AllocationTracker;
957 #endif
958 
959  wdbg_heap_Enable(true);
960 
961  return INFO::OK;
962 }
963 
965 {
966 #if ENABLE_LEAK_INSTRUMENTATION
967  SAFE_DELETE(s_tracker);
968 #endif
969 
970  return INFO::OK;
971 }
#define u8
Definition: types.h:39
Status wdbg_sym_WalkStack(StackFrameCallback cb, uintptr_t cbData, CONTEXT &context, const wchar_t *lastFuncToSkip)
Iterate over a call stack, invoking a callback for each frame encountered.
Definition: wdbg_sym.cpp:311
static const size_t DEBUG_SYMBOL_CHARS
Maximum number of characters (including null terminator) written to user&#39;s buffers by debug_ResolveSy...
Definition: debug.h:428
const Status OK
Definition: status.h:386
static void out(const wchar_t *fmt,...)
Definition: wdbg_sym.cpp:419
intptr_t cpu_AtomicAdd(volatile intptr_t *location, intptr_t increment)
add a signed value to a variable without the possibility of interference from other threads/CPUs...
Definition: arm.cpp:31
int swprintf_s(wchar_t *buf, size_t max_chars, const wchar_t *fmt,...) WPRINTF_ARGS(3)
const Status ALL_COMPLETE
Definition: status.h:400
#define MSC_VERSION
Definition: compiler.h:37
#define ARRAY_SIZE(name)
static const size_t GiB
Definition: alignment.h:73
LIB_API Status debug_CaptureContext(void *context)
Definition: udbg.cpp:42
#define UNUSED2(param)
mark a function local variable or parameter as unused and avoid the corresponding compiler warning...
void wdbg_heap_Enable(bool enable)
enable or disable manual and automatic heap validity checking.
Definition: wdbg_heap.cpp:41
#define WINIT_REGISTER_LATE_SHUTDOWN2(func)
Definition: winit.h:158
static Status wdbg_heap_Shutdown()
Definition: wdbg_heap.cpp:964
static const size_t DEBUG_FILE_CHARS
Definition: debug.h:429
#define SAFE_DELETE(p)
delete memory ensuing from new and set the pointer to zero (thus making double-frees safe / a no-op) ...
i64 Status
Error handling system.
Definition: status.h:171
#define T(string_literal)
Definition: secure_crt.cpp:70
void wdbg_printf(const wchar_t *fmt,...)
same as debug_printf except that some type conversions aren&#39;t supported (in particular, no floating point) and output is limited to 1024+1 characters.
Definition: wdbg.cpp:101
intptr_t wdbg_heap_NumberOfAllocations()
Definition: wdbg_heap.cpp:930
void wdbg_heap_Validate()
check heap integrity.
Definition: wdbg_heap.cpp:66
const Status SKIPPED
Definition: status.h:392
static Status wdbg_heap_Init()
Definition: wdbg_heap.cpp:943
u64 Read(u64 reg)
Definition: msr.cpp:130
LIB_API Status debug_ResolveSymbol(void *ptr_of_interest, wchar_t *sym_name, wchar_t *file, int *line)
read and return symbol information for the given address.
Definition: bdbg.cpp:99
#define wdbg_assert(expr)
similar to ENSURE but safe to use during critical init or while under the heap or dbghelp locks...
Definition: wdbg.h:43
static float Length(const SVec3 v)
Definition: mikktspace.cpp:112
void Write(u64 reg, u64 value)
Definition: msr.cpp:136
static enum @41 state
#define WINIT_REGISTER_EARLY_INIT2(func)
Definition: winit.h:144