cleanup+fixes; wdbg_heap is now feature complete.
should be safe to distribute this build code_annotation.h: add our own noncopyable to avoid DLL base class warning debug: remove some unicode functions (unnecessary); minor cleanup debug_stl: disabled mahaf: do not warn when running as non admin wdbg: add wdbg_printf and wdbg_assert for use in allocation hook. add header wdbg_heap: now feature complete. . allows storing 3..4 frames from the call stack with 0 space overhead. . prevents garbage file/line output. . takes care of allocation counting (previously done by profiler) wdbg_sym.cpp: cleanup+fixes (locking, wdbg_*) fix: properly specify __declspec(dllimport) for certain classes This was SVN commit r5679.
This commit is contained in:
parent
3f23e37fac
commit
74b4ac19c1
@ -151,4 +151,23 @@ namespace detail
|
|||||||
**/
|
**/
|
||||||
#define cassert2(expr) extern char CASSERT_FAILURE[1][(expr)]
|
#define cassert2(expr) extern char CASSERT_FAILURE[1][(expr)]
|
||||||
|
|
||||||
|
|
||||||
|
// copied from boost::noncopyable; this definition avoids warnings when
|
||||||
|
// an exported class derives from noncopyable.
|
||||||
|
|
||||||
|
namespace noncopyable_ // protection from unintended ADL
|
||||||
|
{
|
||||||
|
class LIB_API noncopyable
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
noncopyable() {}
|
||||||
|
~noncopyable() {}
|
||||||
|
private: // emphasize the following members are private
|
||||||
|
noncopyable(const noncopyable&);
|
||||||
|
const noncopyable& operator=(const noncopyable&);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef noncopyable_::noncopyable noncopyable;
|
||||||
|
|
||||||
#endif // #ifndef INCLUDED_CODE_ANNOTATION
|
#endif // #ifndef INCLUDED_CODE_ANNOTATION
|
||||||
|
@ -65,14 +65,10 @@ void debug_wprintf_mem(const wchar_t* fmt, ...)
|
|||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
int len = vswprintf(debug_log_pos, chars_left-2, fmt, args);
|
int len = vswprintf(debug_log_pos, chars_left-2, fmt, args);
|
||||||
|
|
||||||
va_end(args);
|
va_end(args);
|
||||||
if(len < 0)
|
|
||||||
{
|
|
||||||
debug_assert(0); // vswprintf failed
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
debug_log_pos += len+2;
|
debug_log_pos += len+2;
|
||||||
wcscpy(debug_log_pos-2, L"\r\n"); // safe
|
wcscpy_s(debug_log_pos-2, 3, L"\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -87,10 +83,6 @@ void debug_wprintf_mem(const wchar_t* fmt, ...)
|
|||||||
// allocate+expand_until_it_fits. these calls are for quick debug output,
|
// allocate+expand_until_it_fits. these calls are for quick debug output,
|
||||||
// not loads of data, anyway.
|
// not loads of data, anyway.
|
||||||
|
|
||||||
// max # characters (including \0) output by debug_(w)printf in one call.
|
|
||||||
static const int MAX_CHARS = 512;
|
|
||||||
|
|
||||||
|
|
||||||
// rationale: static data instead of std::set to allow setting at any time.
|
// rationale: static data instead of std::set to allow setting at any time.
|
||||||
// we store FNV hash of tag strings for fast comparison; collisions are
|
// we store FNV hash of tag strings for fast comparison; collisions are
|
||||||
// extremely unlikely and can only result in displaying more/less text.
|
// extremely unlikely and can only result in displaying more/less text.
|
||||||
@ -161,56 +153,24 @@ bool debug_filter_allows(const char* text)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// max # characters (including \0) output by debug_printf in one call.
|
||||||
|
const size_t DEBUG_PRINTF_MAX_CHARS = 1024; // refer to wdbg.cpp!debug_vsprintf before changing this
|
||||||
|
|
||||||
#undef debug_printf // allowing #defining it out
|
#undef debug_printf // allowing #defining it out
|
||||||
void debug_printf(const char* fmt, ...)
|
void debug_printf(const char* fmt, ...)
|
||||||
{
|
{
|
||||||
char buf[MAX_CHARS]; buf[ARRAY_SIZE(buf)-1] = '\0';
|
char buf[DEBUG_PRINTF_MAX_CHARS]; buf[ARRAY_SIZE(buf)-1] = '\0';
|
||||||
|
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
vsnprintf(buf, MAX_CHARS-1, fmt, ap);
|
const int len = vsnprintf(buf, DEBUG_PRINTF_MAX_CHARS, fmt, ap);
|
||||||
|
debug_assert(len >= 0);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
if(debug_filter_allows(buf))
|
if(debug_filter_allows(buf))
|
||||||
debug_puts(buf);
|
debug_puts(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef debug_wprintf // allowing #defining it out
|
|
||||||
void debug_wprintf(const wchar_t* fmt, ...)
|
|
||||||
{
|
|
||||||
wchar_t wcs_buf[MAX_CHARS]; wcs_buf[ARRAY_SIZE(wcs_buf)-1] = '\0';
|
|
||||||
|
|
||||||
va_list ap;
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vswprintf(wcs_buf, MAX_CHARS-1, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
|
|
||||||
// convert wchar_t to UTF-8.
|
|
||||||
//
|
|
||||||
// rationale: according to fwide(3) and assorted manpage, FILEs are in
|
|
||||||
// single character or in wide character mode. When a FILE is in
|
|
||||||
// single character mode, wide character writes will fail, and no
|
|
||||||
// conversion is done automatically. Thus the manual conversion.
|
|
||||||
//
|
|
||||||
// it's done here (instead of in OS-specific debug_putws) because
|
|
||||||
// filter_allow requires the conversion also.
|
|
||||||
//
|
|
||||||
// jw: MSDN wcstombs dox say 2 bytes per wchar is enough.
|
|
||||||
// not sure about this; to be on the safe side, we check for overflow.
|
|
||||||
const size_t MAX_BYTES = MAX_CHARS*2;
|
|
||||||
char mbs_buf[MAX_BYTES]; mbs_buf[MAX_BYTES-1] = '\0';
|
|
||||||
size_t bytes_written = wcstombs(mbs_buf, wcs_buf, MAX_BYTES);
|
|
||||||
debug_assert(bytes_written != (size_t)-1); // else: invalid wcs character encountered
|
|
||||||
debug_assert(bytes_written <= MAX_BYTES); // else: overflow (should be impossible)
|
|
||||||
// exact fit, ensure it's 0-terminated
|
|
||||||
if(bytes_written == MAX_BYTES)
|
|
||||||
mbs_buf[MAX_BYTES-1] = '\0';
|
|
||||||
|
|
||||||
if(debug_filter_allows(mbs_buf))
|
|
||||||
debug_puts(mbs_buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -440,7 +400,7 @@ ErrorReaction debug_display_error(const wchar_t* description,
|
|||||||
const char* fn_only = path_name_only(file);
|
const char* fn_only = path_name_only(file);
|
||||||
|
|
||||||
// display in output window; double-click will navigate to error location.
|
// display in output window; double-click will navigate to error location.
|
||||||
debug_wprintf(L"%hs(%d): %ls\n", fn_only, line, description);
|
debug_printf("%s(%d): %ls\n", fn_only, line, description);
|
||||||
|
|
||||||
|
|
||||||
ErrorMessageMem emm;
|
ErrorMessageMem emm;
|
||||||
|
@ -46,9 +46,6 @@ extern void debug_break();
|
|||||||
**/
|
**/
|
||||||
LIB_API void debug_printf(const char* fmt, ...);
|
LIB_API void debug_printf(const char* fmt, ...);
|
||||||
|
|
||||||
/// note: this merely converts to a MBS and calls debug_printf.
|
|
||||||
LIB_API void debug_wprintf(const wchar_t* fmt, ...);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* translates and displays the given strings in a dialog.
|
* translates and displays the given strings in a dialog.
|
||||||
|
@ -585,6 +585,10 @@ template<class T> bool get_container_info(const T& t, size_t size, size_t el_siz
|
|||||||
LibError debug_stl_get_container_info(const char* type_name, const u8* p, size_t size,
|
LibError debug_stl_get_container_info(const char* type_name, const u8* p, size_t size,
|
||||||
size_t el_size, size_t* el_count, DebugStlIterator* el_iterator, void* it_mem)
|
size_t el_size, size_t* el_count, DebugStlIterator* el_iterator, void* it_mem)
|
||||||
{
|
{
|
||||||
|
#if MSC_VERSION >= 1400
|
||||||
|
return ERR::STL_CNT_UNKNOWN; // NOWARN
|
||||||
|
#endif
|
||||||
|
|
||||||
bool handled = false, IsValid = false;
|
bool handled = false, IsValid = false;
|
||||||
#define CONTAINER(name, type_name_pattern)\
|
#define CONTAINER(name, type_name_pattern)\
|
||||||
else if(match_wildcard(type_name, type_name_pattern))\
|
else if(match_wildcard(type_name, type_name_pattern))\
|
||||||
|
@ -244,7 +244,7 @@ public:
|
|||||||
return 'A';
|
return 'A';
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual LibError Load(const std::string& name, shared_ptr<u8> buf, size_t size) const
|
virtual LibError Load(const std::string& UNUSED(name), shared_ptr<u8> buf, size_t size) const
|
||||||
{
|
{
|
||||||
AdjustOffset();
|
AdjustOffset();
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ BlockCache BlockIo::s_blockCache;
|
|||||||
// IoSplitter
|
// IoSplitter
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
class IoSplitter : boost::noncopyable
|
class IoSplitter : noncopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
IoSplitter(off_t ofs, u8* alignedBuf, off_t size)
|
IoSplitter(off_t ofs, u8* alignedBuf, off_t size)
|
||||||
|
@ -17,7 +17,7 @@ void WriteBuffer::Append(const void* data, size_t size)
|
|||||||
{
|
{
|
||||||
if(m_size + size > m_capacity)
|
if(m_size + size > m_capacity)
|
||||||
{
|
{
|
||||||
m_capacity = round_up_to_pow2(m_size + size);
|
m_capacity = round_up_to_pow2((uint)(m_size + size));
|
||||||
shared_ptr<u8> newData = io_Allocate(m_capacity);
|
shared_ptr<u8> newData = io_Allocate(m_capacity);
|
||||||
cpu_memcpy(newData.get(), m_data.get(), m_size);
|
cpu_memcpy(newData.get(), m_data.get(), m_size);
|
||||||
m_data = newData;
|
m_data = newData;
|
||||||
|
@ -29,7 +29,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class UnalignedWriter : public boost::noncopyable
|
class UnalignedWriter : public noncopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
UnalignedWriter(PIFile file, off_t ofs);
|
UnalignedWriter(PIFile file, off_t ofs);
|
||||||
|
@ -23,13 +23,13 @@
|
|||||||
struct PathTraits;
|
struct PathTraits;
|
||||||
typedef fs::basic_path<std::string, PathTraits> Path;
|
typedef fs::basic_path<std::string, PathTraits> Path;
|
||||||
|
|
||||||
struct PathTraits
|
struct LIB_API PathTraits
|
||||||
{
|
{
|
||||||
typedef std::string internal_string_type;
|
typedef std::string internal_string_type;
|
||||||
typedef std::string external_string_type;
|
typedef std::string external_string_type;
|
||||||
|
|
||||||
LIB_API static external_string_type to_external(const Path&, const internal_string_type& src);
|
static external_string_type to_external(const Path&, const internal_string_type& src);
|
||||||
LIB_API static internal_string_type to_internal(const external_string_type& src);
|
static internal_string_type to_internal(const external_string_type& src);
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace boost
|
namespace boost
|
||||||
|
@ -29,7 +29,7 @@ static size_t s_numArchivedFiles;
|
|||||||
|
|
||||||
// helper class that allows breaking up the logic into sub-functions without
|
// helper class that allows breaking up the logic into sub-functions without
|
||||||
// always having to pass directory/realDirectory as parameters.
|
// always having to pass directory/realDirectory as parameters.
|
||||||
class PopulateHelper : boost::noncopyable
|
class PopulateHelper : noncopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PopulateHelper(VfsDirectory* directory, PRealDirectory realDirectory)
|
PopulateHelper(VfsDirectory* directory, PRealDirectory realDirectory)
|
||||||
|
@ -240,9 +240,12 @@ static void StartDriver(const char* driverPathname)
|
|||||||
BOOL ok = StartService(hService, numArgs, 0);
|
BOOL ok = StartService(hService, numArgs, 0);
|
||||||
if(!ok)
|
if(!ok)
|
||||||
{
|
{
|
||||||
// if it wasn't already running, starting failed
|
|
||||||
if(GetLastError() != ERROR_SERVICE_ALREADY_RUNNING)
|
if(GetLastError() != ERROR_SERVICE_ALREADY_RUNNING)
|
||||||
|
{
|
||||||
|
// starting failed. don't raise a warning because this
|
||||||
|
// always happens on least-permission user accounts.
|
||||||
WARN_IF_FALSE(0);
|
WARN_IF_FALSE(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +94,18 @@ void debug_puts(const char* text)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void wdbg_printf(const char* fmt, ...)
|
||||||
|
{
|
||||||
|
char buf[1024]; // as required by wvsprintf
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
wvsprintf(buf, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
debug_puts(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// inform the debugger of the current thread's description, which it then
|
// inform the debugger of the current thread's description, which it then
|
||||||
// displays instead of just the thread handle.
|
// displays instead of just the thread handle.
|
||||||
//
|
//
|
||||||
|
14
source/lib/sysdep/win/wdbg.h
Normal file
14
source/lib/sysdep/win/wdbg.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* same as debug_printf except that some type conversions aren't supported
|
||||||
|
* (in particular, no floating point).
|
||||||
|
*
|
||||||
|
* this function does not allocate memory from the CRT heap, which makes it
|
||||||
|
* safe to use from an allocation hook.
|
||||||
|
**/
|
||||||
|
LIB_API void wdbg_printf(const char* fmt, ...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* similar to debug_assert but safe to use during critical init or
|
||||||
|
* while under the heap or dbghelp locks.
|
||||||
|
**/
|
||||||
|
#define wdbg_assert(expr) STMT(if(!(expr)) debug_break();)
|
@ -1,13 +1,20 @@
|
|||||||
#include "precompiled.h"
|
#include "precompiled.h"
|
||||||
#include "wdbg_heap.h"
|
#include "wdbg_heap.h"
|
||||||
|
|
||||||
|
#include "lib/sysdep/win/win.h"
|
||||||
#include <crtdbg.h>
|
#include <crtdbg.h>
|
||||||
#include <excpt.h>
|
#include <excpt.h>
|
||||||
|
#include <dbghelp.h>
|
||||||
|
|
||||||
|
#include "lib/sysdep/cpu.h" // cpu_AtomicAdd
|
||||||
#include "winit.h"
|
#include "winit.h"
|
||||||
|
#include "wdbg.h" // wdbg_printf
|
||||||
|
#include "wdbg_sym.h" // wdbg_sym_WalkStack
|
||||||
|
|
||||||
|
|
||||||
|
WINIT_REGISTER_EARLY_INIT2(wdbg_heap_Init); // wutil -> wdbg_heap
|
||||||
|
|
||||||
WINIT_REGISTER_CRITICAL_INIT(wdbg_heap_Init);
|
|
||||||
|
|
||||||
// CAUTION: called from critical init
|
|
||||||
void wdbg_heap_Enable(bool enable)
|
void wdbg_heap_Enable(bool enable)
|
||||||
{
|
{
|
||||||
uint flags = 0;
|
uint flags = 0;
|
||||||
@ -31,7 +38,7 @@ void wdbg_heap_Enable(bool enable)
|
|||||||
|
|
||||||
void wdbg_heap_Validate()
|
void wdbg_heap_Validate()
|
||||||
{
|
{
|
||||||
int ret;
|
int ret = _HEAPOK;
|
||||||
__try
|
__try
|
||||||
{
|
{
|
||||||
// NB: this is a no-op if !_CRTDBG_ALLOC_MEM_DF.
|
// NB: this is a no-op if !_CRTDBG_ALLOC_MEM_DF.
|
||||||
@ -48,47 +55,847 @@ void wdbg_heap_Validate()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// improved leak detection
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
static int __cdecl ReportHook(int reportType, char* message, int* shouldTriggerBreakpoint)
|
// leak detectors often rely on macro redirection to determine the file and
|
||||||
|
// line of allocation owners (see _CRTDBG_MAP_ALLOC). unfortunately this
|
||||||
|
// breaks code that uses placement new or functions called free() etc.
|
||||||
|
//
|
||||||
|
// we avoid this problem by using stack traces. this implementation differs
|
||||||
|
// from other approaches, e.g. Visual Leak Detector (the safer variant
|
||||||
|
// before DLL hooking was used) in that no auxiliary storage is needed.
|
||||||
|
// instead, the trace is stashed within the memory block header.
|
||||||
|
//
|
||||||
|
// to avoid duplication of effort, the CRT's leak detection code is not
|
||||||
|
// modified; we only need an allocation and report hook. the latter
|
||||||
|
// mixes the improved file/line information into the normal report.
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// memory block header
|
||||||
|
|
||||||
|
// the one disadvantage of our approach is that it requires knowledge of
|
||||||
|
// the internal memory block header structure. it is hoped that IsValid will
|
||||||
|
// uncover any changes. the following definition was adapted from dbgint.h:
|
||||||
|
struct _CrtMemBlockHeader
|
||||||
{
|
{
|
||||||
if(message[0] == '{')
|
struct _CrtMemBlockHeader* next;
|
||||||
{
|
struct _CrtMemBlockHeader* prev;
|
||||||
debug_printf("leak\n");
|
char* file;
|
||||||
}
|
int line;
|
||||||
|
// fields reversed on Win64 to ensure size % 16 == 0
|
||||||
|
#if OS_WIN64
|
||||||
|
int blockType;
|
||||||
|
size_t userDataSize;
|
||||||
|
#else
|
||||||
|
size_t userDataSize;
|
||||||
|
int blockType;
|
||||||
|
#endif
|
||||||
|
long allocationNumber;
|
||||||
|
unsigned char gap[4];
|
||||||
|
|
||||||
*shouldTriggerBreakpoint = 0;
|
bool IsValid() const
|
||||||
return 1; // CRT is to display the message as normal
|
{
|
||||||
|
__try
|
||||||
|
{
|
||||||
|
if(prev && prev->next != this)
|
||||||
|
return false;
|
||||||
|
if(next && next->prev != this)
|
||||||
|
return false;
|
||||||
|
if((unsigned)blockType > 4)
|
||||||
|
return false;
|
||||||
|
if(userDataSize > 1*GiB)
|
||||||
|
return false;
|
||||||
|
if(allocationNumber == 0)
|
||||||
|
return false;
|
||||||
|
for(int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
if(gap[i] != 0xFD)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is a false alarm if there is exactly one extant allocation,
|
||||||
|
// but also a valuable indication of a block that has been removed
|
||||||
|
// from the list (i.e. freed).
|
||||||
|
if(prev == next)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static _CrtMemBlockHeader* HeaderFromData(void* userData)
|
||||||
|
{
|
||||||
|
_CrtMemBlockHeader* const header = ((_CrtMemBlockHeader*)userData)-1;
|
||||||
|
wdbg_assert(header->IsValid());
|
||||||
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
static _CRT_ALLOC_HOOK previousAllocHook;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param userData is only valid (nonzero) for allocType == _HOOK_FREE because
|
* update our idea of the head of the linked list of heap blocks.
|
||||||
* we are called BEFORE the actual heap operation.
|
* called from the allocation hook (see explanation there)
|
||||||
|
*
|
||||||
|
* @return the current head (most recent allocation).
|
||||||
|
* @param operation the current heap operation
|
||||||
|
* @param userData allocation address (if reallocating or deallocating)
|
||||||
|
* @param hasChanged a convenient indication of whether the return value is
|
||||||
|
* different than that of the last call.
|
||||||
**/
|
**/
|
||||||
static int __cdecl AllocHook(int allocType, void* userData, size_t size, int blockType, long requestNumber, const unsigned char* file, int line)
|
static _CrtMemBlockHeader* GetHeapListHead(int operation, void* userData, bool& hasChanged)
|
||||||
{
|
{
|
||||||
if(previousAllocHook)
|
static _CrtMemBlockHeader* s_heapListHead;
|
||||||
return previousAllocHook(allocType, userData, size, blockType, requestNumber, file, line);
|
|
||||||
return 1; // continue as if the hook had never been called
|
// first call: get the heap block list head
|
||||||
|
// notes:
|
||||||
|
// - there is no O(1) accessor for this, so we maintain a copy.
|
||||||
|
// - must be done here instead of in an initializer to guarantee
|
||||||
|
// consistency, since we are now under the _HEAP_LOCK.
|
||||||
|
if(!s_heapListHead)
|
||||||
|
{
|
||||||
|
_CrtMemState state = {0};
|
||||||
|
_CrtMemCheckpoint(&state); // O(N)
|
||||||
|
s_heapListHead = state.pBlockHeader;
|
||||||
|
wdbg_assert(s_heapListHead->IsValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// the last operation was an allocation or expanding reallocation;
|
||||||
|
// exactly one block has been prepended to the list.
|
||||||
|
if(s_heapListHead->prev)
|
||||||
|
{
|
||||||
|
s_heapListHead = s_heapListHead->prev; // set to new head of list
|
||||||
|
wdbg_assert(s_heapListHead->IsValid());
|
||||||
|
wdbg_assert(s_heapListHead->prev == 0);
|
||||||
|
hasChanged = true;
|
||||||
|
}
|
||||||
|
// the list head remained unchanged, so the last operation was a
|
||||||
|
// non-expanding reallocation or free.
|
||||||
|
else
|
||||||
|
hasChanged = false;
|
||||||
|
|
||||||
|
// special case: handle invalidation of the list head
|
||||||
|
// note: even shrinking reallocations cause deallocation.
|
||||||
|
if(operation != _HOOK_ALLOC && userData == s_heapListHead+1)
|
||||||
|
{
|
||||||
|
s_heapListHead = s_heapListHead->next;
|
||||||
|
wdbg_assert(s_heapListHead->IsValid());
|
||||||
|
|
||||||
|
hasChanged = false; // (head is now the same as last time)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s_heapListHead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// call stack filter
|
||||||
|
|
||||||
// NB: called from critical init => can't use debug.h services
|
// we need to make the most out of the limited amount of frames. to that end,
|
||||||
static void InstallHooks()
|
// only user functions are stored; we skip known library and helper functions.
|
||||||
|
// these are determined by recording frames encountered in a backtrace.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* extents of a module in memory; used to ignore callers that lie within
|
||||||
|
* the C runtime library.
|
||||||
|
**/
|
||||||
|
class ModuleExtents
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
ModuleExtents()
|
||||||
|
: m_address(0), m_length(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleExtents(const char* dllName)
|
||||||
|
{
|
||||||
|
HMODULE hModule = GetModuleHandle(dllName);
|
||||||
|
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((u8*)hModule + ((PIMAGE_DOS_HEADER)hModule)->e_lfanew);
|
||||||
|
m_address = (uintptr_t)hModule + ntHeaders->OptionalHeader.BaseOfCode;
|
||||||
|
MEMORY_BASIC_INFORMATION mbi = {0};
|
||||||
|
VirtualQuery((void*)m_address, &mbi, sizeof(mbi));
|
||||||
|
m_length = mbi.RegionSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t Address() const
|
||||||
|
{
|
||||||
|
return m_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t Length() const
|
||||||
|
{
|
||||||
|
return m_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Contains(uintptr_t address) const
|
||||||
|
{
|
||||||
|
return (address - m_address) < m_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uintptr_t m_address;
|
||||||
|
size_t m_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set data structure that avoids dynamic allocations because they would
|
||||||
|
* cause the allocation hook to be reentered (bad).
|
||||||
|
**/
|
||||||
|
template<typename T, size_t maxItems>
|
||||||
|
class ArraySet
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ArraySet()
|
||||||
|
{
|
||||||
|
m_arrayEnd = m_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Add(T t)
|
||||||
|
{
|
||||||
|
if(m_arrayEnd == m_array+maxItems)
|
||||||
|
{
|
||||||
|
RemoveDuplicates();
|
||||||
|
wdbg_assert(m_arrayEnd < m_array+maxItems);
|
||||||
|
}
|
||||||
|
*m_arrayEnd++ = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Find(T t) const
|
||||||
|
{
|
||||||
|
return std::find(m_array, const_cast<const T*>(m_arrayEnd), t) != m_arrayEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoveDuplicates()
|
||||||
|
{
|
||||||
|
std::sort(m_array, m_arrayEnd);
|
||||||
|
m_arrayEnd = std::unique(m_array, m_arrayEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T m_array[maxItems];
|
||||||
|
T* m_arrayEnd;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class CallerFilter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CallerFilter()
|
||||||
|
{
|
||||||
|
AddRuntimeLibraryToIgnoreList();
|
||||||
|
|
||||||
|
m_isRecordingKnownCallers = true;
|
||||||
|
CallHeapFunctions();
|
||||||
|
m_isRecordingKnownCallers = false;
|
||||||
|
m_knownCallers.RemoveDuplicates();
|
||||||
|
}
|
||||||
|
|
||||||
|
LibError NotifyOfCaller(uintptr_t pc)
|
||||||
|
{
|
||||||
|
if(!m_isRecordingKnownCallers)
|
||||||
|
return INFO::SKIPPED; // do not affect the stack walk
|
||||||
|
|
||||||
|
// last 'known' function has been reached
|
||||||
|
if(pc == (uintptr_t)&CallerFilter::CallHeapFunctions)
|
||||||
|
return INFO::OK; // stop stack walk
|
||||||
|
|
||||||
|
// pc is a 'known' function on the allocation hook's back-trace
|
||||||
|
// (e.g. _malloc_dbg and other helper functions)
|
||||||
|
m_knownCallers.Add(pc);
|
||||||
|
return INFO::CB_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsKnownCaller(uintptr_t pc) const
|
||||||
|
{
|
||||||
|
for(size_t i = 0; i < numModules; i++)
|
||||||
|
{
|
||||||
|
if(m_moduleIgnoreList[i].Contains(pc))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_knownCallers.Find(pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const size_t numModules = 2;
|
||||||
|
|
||||||
|
void AddRuntimeLibraryToIgnoreList()
|
||||||
|
{
|
||||||
|
#if MSC_VERSION && _DLL // DLL runtime library
|
||||||
|
#ifdef NDEBUG
|
||||||
|
static const char* dllNameFormat = "msvc%c%d" ".dll";
|
||||||
|
#else
|
||||||
|
static const char* dllNameFormat = "msvc%c%d" "d" ".dll";
|
||||||
|
#endif
|
||||||
|
const int dllVersion = (MSC_VERSION-600)/10; // VC2005: 1400 => 80
|
||||||
|
wdbg_assert(0 < dllVersion && dllVersion <= 999);
|
||||||
|
for(int i = 0; i < numModules; i++)
|
||||||
|
{
|
||||||
|
static const char modules[numModules] = { 'r', 'p' }; // C and C++ runtime libraries
|
||||||
|
char dllName[20];
|
||||||
|
sprintf_s(dllName, ARRAY_SIZE(dllName), dllNameFormat, modules[i], dllVersion);
|
||||||
|
m_moduleIgnoreList[i] = ModuleExtents(dllName);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CallHeapFunctions()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
void* p1 = malloc(1);
|
||||||
|
void* p2 = realloc(p1, 111);
|
||||||
|
if(p2)
|
||||||
|
free(p2);
|
||||||
|
else
|
||||||
|
free(p1);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
char* p = new char;
|
||||||
|
delete p;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
char* p = new char[2];
|
||||||
|
delete[] p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleExtents m_moduleIgnoreList[numModules];
|
||||||
|
|
||||||
|
// note: this mechanism cannot hope to exclude every single STL helper
|
||||||
|
// function, which is why we need the module ignore list.
|
||||||
|
// however, it is still useful when compiling against the static CRT.
|
||||||
|
bool m_isRecordingKnownCallers;
|
||||||
|
ArraySet<uintptr_t, 500> m_knownCallers;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// stash (part of) a stack trace within _CrtMemBlockHeader
|
||||||
|
|
||||||
|
// this avoids the need for a mapping between allocation number and the
|
||||||
|
// caller information, which is slow, requires locking and consumes memory.
|
||||||
|
//
|
||||||
|
// callers := array of addresses inside functions that constitute the
|
||||||
|
// stack back-trace.
|
||||||
|
|
||||||
|
static const size_t numQuantizedPcBits = sizeof(uintptr_t)*CHAR_BIT - 2;
|
||||||
|
|
||||||
|
static uintptr_t Quantize(uintptr_t pc)
|
||||||
|
{
|
||||||
|
// postcondition: the return value lies within the same function as
|
||||||
|
// pc but can be stored in fewer bits. this is possible because:
|
||||||
|
// - linkers typically align functions to at least four bytes
|
||||||
|
// - pc is a return address and thus preceded by a call instruction and
|
||||||
|
// function prolog, which requires at least four bytes.
|
||||||
|
return pc/4;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uintptr_t Expand(uintptr_t pc)
|
||||||
|
{
|
||||||
|
return pc*4;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const uint numEncodedLengthBits = 2;
|
||||||
|
static const size_t maxCallers = (sizeof(char*)+sizeof(int))*CHAR_BIT / (2+14);
|
||||||
|
|
||||||
|
static uint NumBitsForEncodedLength(uint encodedLength)
|
||||||
|
{
|
||||||
|
static const uint numBitsForEncodedLength[1u << numEncodedLengthBits] =
|
||||||
|
{
|
||||||
|
8, // 1K
|
||||||
|
14, // 64K
|
||||||
|
20, // 4M
|
||||||
|
numQuantizedPcBits // a full pointer
|
||||||
|
};
|
||||||
|
return numBitsForEncodedLength[encodedLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint EncodedLength(uintptr_t quantizedOffset)
|
||||||
|
{
|
||||||
|
for(uint encodedLength = 0; encodedLength < 1u << numEncodedLengthBits; encodedLength++)
|
||||||
|
{
|
||||||
|
const uint numBits = NumBitsForEncodedLength(encodedLength);
|
||||||
|
const uintptr_t maxValue = (1u << numBits)-1;
|
||||||
|
if(quantizedOffset <= maxValue)
|
||||||
|
return encodedLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
wdbg_assert(0); // unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uintptr_t codeSegmentAddress;
|
||||||
|
static uintptr_t quantizedCodeSegmentAddress;
|
||||||
|
static uintptr_t quantizedCodeSegmentLength;
|
||||||
|
|
||||||
|
static void FindCodeSegment()
|
||||||
|
{
|
||||||
|
const char* dllName = 0; // current module
|
||||||
|
ModuleExtents extents(dllName);
|
||||||
|
codeSegmentAddress = extents.Address();
|
||||||
|
quantizedCodeSegmentAddress = Quantize(codeSegmentAddress);
|
||||||
|
quantizedCodeSegmentLength = Quantize(extents.Length());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BitStream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BitStream(u8* storage, uint storageSize)
|
||||||
|
: m_remainderBits(0), m_numRemainderBits(0)
|
||||||
|
, m_pos(storage), m_bitsLeft((uint)storageSize*8)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint BitsLeft() const
|
||||||
|
{
|
||||||
|
return m_bitsLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Write(const uint numOutputBits, uintptr_t outputValue)
|
||||||
|
{
|
||||||
|
wdbg_assert(numOutputBits <= m_bitsLeft);
|
||||||
|
wdbg_assert(outputValue < (1u << numOutputBits));
|
||||||
|
|
||||||
|
uint outputBitsLeft = numOutputBits;
|
||||||
|
while(outputBitsLeft > 0)
|
||||||
|
{
|
||||||
|
const uint numBits = std::min(outputBitsLeft, 8u);
|
||||||
|
m_bitsLeft -= numBits;
|
||||||
|
|
||||||
|
// (NB: there is no need to extract exactly numBits because
|
||||||
|
// outputValue's MSBs were verified to be zero)
|
||||||
|
const uintptr_t outputByte = outputValue & 0xFF;
|
||||||
|
outputValue >>= 8;
|
||||||
|
outputBitsLeft -= numBits;
|
||||||
|
|
||||||
|
m_remainderBits |= outputByte << m_numRemainderBits;
|
||||||
|
m_numRemainderBits += numBits;
|
||||||
|
if(m_numRemainderBits >= 8)
|
||||||
|
{
|
||||||
|
const u8 remainderByte = (m_remainderBits & 0xFF);
|
||||||
|
m_remainderBits >>= 8;
|
||||||
|
m_numRemainderBits -= 8;
|
||||||
|
|
||||||
|
*m_pos++ = remainderByte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Finish()
|
||||||
|
{
|
||||||
|
const uint partialBits = m_numRemainderBits % 8;
|
||||||
|
if(partialBits)
|
||||||
|
{
|
||||||
|
m_bitsLeft -= 8-partialBits;
|
||||||
|
m_numRemainderBits += 8-partialBits;
|
||||||
|
}
|
||||||
|
while(m_numRemainderBits)
|
||||||
|
{
|
||||||
|
const u8 remainderByte = (m_remainderBits & 0xFF);
|
||||||
|
*m_pos++ = remainderByte;
|
||||||
|
m_remainderBits >>= 8;
|
||||||
|
m_numRemainderBits -= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
wdbg_assert(m_bitsLeft % 8 == 0);
|
||||||
|
while(m_bitsLeft)
|
||||||
|
{
|
||||||
|
*m_pos++ = 0;
|
||||||
|
m_bitsLeft -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t Read(const uint numInputBits)
|
||||||
|
{
|
||||||
|
wdbg_assert(numInputBits <= m_bitsLeft);
|
||||||
|
|
||||||
|
uintptr_t inputValue = 0;
|
||||||
|
uint inputBitsLeft = numInputBits;
|
||||||
|
while(inputBitsLeft > 0)
|
||||||
|
{
|
||||||
|
const uint numBits = std::min(inputBitsLeft, 8u);
|
||||||
|
m_bitsLeft -= numBits;
|
||||||
|
|
||||||
|
if(m_numRemainderBits < numBits)
|
||||||
|
{
|
||||||
|
const uint inputByte = *m_pos++;
|
||||||
|
m_remainderBits |= inputByte << m_numRemainderBits;
|
||||||
|
m_numRemainderBits += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uintptr_t remainderByte = (m_remainderBits & ((1u << numBits)-1));
|
||||||
|
m_remainderBits >>= numBits;
|
||||||
|
m_numRemainderBits -= numBits;
|
||||||
|
inputValue |= remainderByte << (numInputBits-inputBitsLeft);
|
||||||
|
|
||||||
|
inputBitsLeft -= numBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint m_remainderBits;
|
||||||
|
uint m_numRemainderBits;
|
||||||
|
u8* m_pos;
|
||||||
|
uint m_bitsLeft;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static void StashCallers(_CrtMemBlockHeader* header, const uintptr_t* callers, size_t numCallers)
|
||||||
|
{
|
||||||
|
// transform an array of callers into a (sorted and unique) set.
|
||||||
|
uintptr_t quantizedPcSet[maxCallers];
|
||||||
|
std::transform(callers, callers+numCallers, quantizedPcSet, Quantize);
|
||||||
|
std::sort(quantizedPcSet, quantizedPcSet+numCallers);
|
||||||
|
uintptr_t* const end = std::unique(quantizedPcSet, quantizedPcSet+numCallers);
|
||||||
|
const size_t quantizedPcSetSize = end-quantizedPcSet;
|
||||||
|
|
||||||
|
// transform the set into a sequence of quantized offsets.
|
||||||
|
uintptr_t quantizedOffsets[maxCallers];
|
||||||
|
if(quantizedPcSet[0] >= quantizedCodeSegmentAddress)
|
||||||
|
quantizedOffsets[0] = quantizedPcSet[0] - quantizedCodeSegmentAddress;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
quantizedOffsets[0] = quantizedPcSet[0];
|
||||||
|
|
||||||
|
// make sure RetrieveCallers can differentiate between pointers and code-segment-offsets
|
||||||
|
wdbg_assert(quantizedOffsets[0] >= quantizedCodeSegmentLength);
|
||||||
|
}
|
||||||
|
for(size_t i = 1; i < numCallers; i++)
|
||||||
|
quantizedOffsets[i] = quantizedPcSet[i] - quantizedPcSet[i-1];
|
||||||
|
|
||||||
|
// write quantized offsets to stream
|
||||||
|
BitStream bitStream((u8*)&header->file, sizeof(header->file)+sizeof(header->line));
|
||||||
|
for(size_t i = 0; i < quantizedPcSetSize; i++)
|
||||||
|
{
|
||||||
|
const uintptr_t quantizedOffset = quantizedOffsets[i];
|
||||||
|
const uint encodedLength = EncodedLength(quantizedOffset);
|
||||||
|
const uint numBits = NumBitsForEncodedLength(encodedLength);
|
||||||
|
if(bitStream.BitsLeft() < numEncodedLengthBits+numBits)
|
||||||
|
break;
|
||||||
|
bitStream.Write(numEncodedLengthBits, encodedLength);
|
||||||
|
bitStream.Write(numBits, quantizedOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
bitStream.Finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void RetrieveCallers(_CrtMemBlockHeader* header, uintptr_t* callers, size_t& numCallers)
|
||||||
|
{
|
||||||
|
// read quantized offsets from stream
|
||||||
|
uintptr_t quantizedOffsets[maxCallers];
|
||||||
|
numCallers = 0;
|
||||||
|
BitStream bitStream((u8*)&header->file, sizeof(header->file)+sizeof(header->line));
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
if(bitStream.BitsLeft() < numEncodedLengthBits)
|
||||||
|
break;
|
||||||
|
const uint encodedLength = bitStream.Read(numEncodedLengthBits);
|
||||||
|
const uint numBits = NumBitsForEncodedLength(encodedLength);
|
||||||
|
if(bitStream.BitsLeft() < numBits)
|
||||||
|
break;
|
||||||
|
const uintptr_t quantizedOffset = bitStream.Read(numBits);
|
||||||
|
if(!quantizedOffset)
|
||||||
|
break;
|
||||||
|
quantizedOffsets[numCallers++] = quantizedOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!numCallers)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// expand offsets into a set of callers
|
||||||
|
if(quantizedOffsets[0] <= quantizedCodeSegmentLength)
|
||||||
|
callers[0] = Expand(quantizedOffsets[0] + quantizedCodeSegmentAddress);
|
||||||
|
else
|
||||||
|
callers[0] = Expand(quantizedOffsets[0]);
|
||||||
|
for(size_t i = 1; i < numCallers; i++)
|
||||||
|
callers[i] = callers[i-1] + Expand(quantizedOffsets[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// find out who called an allocation function
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gather and store a (filtered) list of callers.
|
||||||
|
**/
|
||||||
|
class CallStack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Gather()
|
||||||
|
{
|
||||||
|
m_numCallers = 0;
|
||||||
|
(void)wdbg_sym_WalkStack(OnFrame_Trampoline, (uintptr_t)this);
|
||||||
|
std::fill(m_callers+m_numCallers, m_callers+maxCallers, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uintptr_t* Callers() const
|
||||||
|
{
|
||||||
|
return m_callers;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t NumCallers() const
|
||||||
|
{
|
||||||
|
return m_numCallers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
LibError OnFrame(const STACKFRAME64* frame)
|
||||||
|
{
|
||||||
|
const uintptr_t pc = frame->AddrPC.Offset;
|
||||||
|
|
||||||
|
// skip invalid frames
|
||||||
|
if(pc == 0)
|
||||||
|
return INFO::CB_CONTINUE;
|
||||||
|
|
||||||
|
LibError ret = m_filter.NotifyOfCaller(pc);
|
||||||
|
// (CallerFilter provokes stack traces of heap functions; if that is
|
||||||
|
// what happened, then we must not continue)
|
||||||
|
if(ret != INFO::SKIPPED)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
// stop the stack walk if frame storage is full
|
||||||
|
if(m_numCallers >= maxCallers)
|
||||||
|
return INFO::OK;
|
||||||
|
|
||||||
|
if(!m_filter.IsKnownCaller(pc))
|
||||||
|
m_callers[m_numCallers++] = pc;
|
||||||
|
return INFO::CB_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LibError OnFrame_Trampoline(const STACKFRAME64* frame, uintptr_t cbData)
|
||||||
|
{
|
||||||
|
CallStack* this_ = (CallStack*)cbData;
|
||||||
|
return this_->OnFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
CallerFilter m_filter;
|
||||||
|
|
||||||
|
uintptr_t m_callers[maxCallers];
|
||||||
|
size_t m_numCallers;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// RAII wrapper for installing a CRT allocation hook
|
||||||
|
|
||||||
|
class AllocationHook
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AllocationHook()
|
||||||
|
{
|
||||||
|
wdbg_assert(s_instance == 0 && s_previousHook == 0);
|
||||||
|
s_instance = this;
|
||||||
|
s_previousHook = _CrtSetAllocHook(Hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
~AllocationHook()
|
||||||
|
{
|
||||||
|
_CRT_ALLOC_HOOK removedHook = _CrtSetAllocHook(s_previousHook);
|
||||||
|
wdbg_assert(removedHook == Hook); // warn if we removed someone else's hook
|
||||||
|
s_instance = 0;
|
||||||
|
s_previousHook = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param operation either _HOOK_ALLOC, _HOOK_REALLOC or _HOOK_FREE
|
||||||
|
* @param userData is only valid (nonzero) for realloc and free because
|
||||||
|
* we are called BEFORE the actual heap operation.
|
||||||
|
**/
|
||||||
|
virtual void OnHeapOperation(int operation, void* userData, size_t size, long allocationNumber) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static int __cdecl Hook(int operation, void* userData, size_t size, int blockType, long allocationNumber, const unsigned char* file, int line)
|
||||||
|
{
|
||||||
|
static bool busy = false;
|
||||||
|
wdbg_assert(!busy);
|
||||||
|
busy = true;
|
||||||
|
s_instance->OnHeapOperation(operation, userData, size, allocationNumber);
|
||||||
|
busy = false;
|
||||||
|
|
||||||
|
if(s_previousHook)
|
||||||
|
return s_previousHook(operation, userData, size, blockType, allocationNumber, file, line);
|
||||||
|
return 1; // continue as if the hook had never been called
|
||||||
|
}
|
||||||
|
|
||||||
|
// unfortunately static because we can't pass our `this' pointer through
|
||||||
|
// the allocation hook.
|
||||||
|
static AllocationHook* s_instance;
|
||||||
|
static _CRT_ALLOC_HOOK s_previousHook;
|
||||||
|
};
|
||||||
|
|
||||||
|
AllocationHook* AllocationHook::s_instance;
|
||||||
|
_CRT_ALLOC_HOOK AllocationHook::s_previousHook;
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// our allocation hook
|
||||||
|
|
||||||
|
// ideally we would just stash the callers in the newly created header.
|
||||||
|
// unfortunately we are called BEFORE it (and the allocation) are actually
|
||||||
|
// created, so we need to keep the information around until the next call to
|
||||||
|
// AllocHook; only then can it be stored.
|
||||||
|
//
|
||||||
|
// unfortunately the CRT does not provide an O(1) means of getting at the
|
||||||
|
// most recent block header. instead, we do so once and then keep it
|
||||||
|
// up-to-date in the allocation hook. this is safe because we run under
|
||||||
|
// the _HEAP_LOCK and ensure the allocation numbers match.
|
||||||
|
|
||||||
|
static intptr_t s_numAllocations;
|
||||||
|
|
||||||
|
intptr_t wdbg_heap_NumberOfAllocations()
|
||||||
|
{
|
||||||
|
return s_numAllocations;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AllocationTracker : public AllocationHook
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AllocationTracker()
|
||||||
|
: m_pendingAllocationNumber(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void OnHeapOperation(int operation, void* userData, size_t size, long allocationNumber)
|
||||||
|
{
|
||||||
|
UNUSED2(size);
|
||||||
|
|
||||||
|
if(operation == _HOOK_ALLOC || operation == _HOOK_REALLOC)
|
||||||
|
cpu_AtomicAdd(&s_numAllocations, 1);
|
||||||
|
|
||||||
|
bool hasChanged;
|
||||||
|
_CrtMemBlockHeader* head = GetHeapListHead(operation, userData, hasChanged);
|
||||||
|
// if the head changed, the last operation was a (re)allocation and
|
||||||
|
// we now have its header; stash the pending call stack there.
|
||||||
|
if(hasChanged)
|
||||||
|
{
|
||||||
|
wdbg_assert(head->allocationNumber == m_pendingAllocationNumber);
|
||||||
|
|
||||||
|
// note: overwrite existing file/line info (even if valid) to avoid
|
||||||
|
// special cases in the report hook.
|
||||||
|
StashCallers(head, m_pendingCallStack.Callers(), m_pendingCallStack.NumCallers());
|
||||||
|
}
|
||||||
|
|
||||||
|
// remember the current caller for next time
|
||||||
|
m_pendingCallStack.Gather(); // NB: called for each operation, as required by the filter recording step
|
||||||
|
m_pendingAllocationNumber = allocationNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
long m_pendingAllocationNumber;
|
||||||
|
CallStack m_pendingCallStack;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static void PrintCallStack(const uintptr_t* callers, size_t numCallers)
|
||||||
|
{
|
||||||
|
if(!numCallers || callers[0] == 0)
|
||||||
|
{
|
||||||
|
wdbg_printf("\n call stack not available.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wdbg_printf("\n partial call stack:\n");
|
||||||
|
for(size_t i = 0; i < numCallers; i++)
|
||||||
|
{
|
||||||
|
char name[DBG_SYMBOL_LEN] = {'\0'}; char file[DBG_FILE_LEN] = {'\0'}; int line = -1;
|
||||||
|
LibError err = debug_resolve_symbol((void*)callers[i], name, file, &line);
|
||||||
|
wdbg_printf(" ");
|
||||||
|
if(err != INFO::OK)
|
||||||
|
wdbg_printf("(error %d resolving PC=%p) ", err, callers[i]);
|
||||||
|
if(file[0] != '\0')
|
||||||
|
wdbg_printf("%s(%d) : ", file, line);
|
||||||
|
wdbg_printf("%s\n", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int __cdecl ReportHook(int reportType, char* message, int* out)
|
||||||
|
{
|
||||||
|
UNUSED2(reportType);
|
||||||
|
|
||||||
|
// set up return values to reduce the chance of mistakes below
|
||||||
|
*out = 0; // alternatives are failure (-1) and breakIntoDebugger (1)
|
||||||
|
const int ret = 0; // not "handled", continue calling other hooks
|
||||||
|
|
||||||
|
// note: this hook is transparent in that it never affects the CRT.
|
||||||
|
// we can't suppress parts of a leak report because that causes the
|
||||||
|
// rest of it to be skipped.
|
||||||
|
|
||||||
|
static enum
|
||||||
|
{
|
||||||
|
WaitingForDump,
|
||||||
|
WaitingForLeakAddress,
|
||||||
|
IsLeakAddress
|
||||||
|
}
|
||||||
|
state = WaitingForDump;
|
||||||
|
switch(state)
|
||||||
|
{
|
||||||
|
case WaitingForDump:
|
||||||
|
if(!strcmp(message, "Dumping objects ->\n"))
|
||||||
|
state = WaitingForLeakAddress;
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
case WaitingForLeakAddress:
|
||||||
|
if(message[0] == '{')
|
||||||
|
state = IsLeakAddress;
|
||||||
|
else if(strchr(message, '('))
|
||||||
|
message[0] = '\0';
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
case IsLeakAddress:
|
||||||
|
{
|
||||||
|
const char* addressString = strstr(message, "0x");
|
||||||
|
const uintptr_t address = strtoul(addressString, 0, 0);
|
||||||
|
_CrtMemBlockHeader* header = HeaderFromData((void*)address);
|
||||||
|
uintptr_t callers[maxCallers]; size_t numCallers;
|
||||||
|
RetrieveCallers(header, callers, numCallers);
|
||||||
|
PrintCallStack(callers, numCallers);
|
||||||
|
}
|
||||||
|
state = WaitingForLeakAddress;
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
default:
|
||||||
|
wdbg_assert(0); // unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
wdbg_assert(0); // unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static AllocationTracker* s_tracker;
|
||||||
|
|
||||||
|
static LibError wdbg_heap_Init()
|
||||||
|
{
|
||||||
|
FindCodeSegment();
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// load symbol information now (fails if it happens during shutdown)
|
||||||
|
char name[DBG_SYMBOL_LEN]; char file[DBG_FILE_LEN]; int line;
|
||||||
|
(void)debug_resolve_symbol(wdbg_heap_Init, name, file, &line);
|
||||||
|
|
||||||
int ret = _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, ReportHook);
|
int ret = _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, ReportHook);
|
||||||
if(ret == -1)
|
if(ret == -1)
|
||||||
abort();
|
abort();
|
||||||
|
|
||||||
previousAllocHook = _CrtSetAllocHook(AllocHook);
|
s_tracker = new AllocationTracker;
|
||||||
}
|
#endif
|
||||||
|
|
||||||
static LibError wdbg_heap_Init()
|
|
||||||
{
|
|
||||||
InstallHooks();
|
|
||||||
wdbg_heap_Enable(true);
|
wdbg_heap_Enable(true);
|
||||||
|
|
||||||
return INFO::OK;
|
return INFO::OK;
|
||||||
}
|
}
|
||||||
|
@ -30,4 +30,10 @@ LIB_API void wdbg_heap_Enable(bool);
|
|||||||
**/
|
**/
|
||||||
LIB_API void wdbg_heap_Validate(void);
|
LIB_API void wdbg_heap_Validate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the total number of alloc and realloc operations thus far.
|
||||||
|
* used by the in-game profiler.
|
||||||
|
**/
|
||||||
|
LIB_API intptr_t wdbg_heap_NumberOfAllocations();
|
||||||
|
|
||||||
#endif // #ifndef INCLUDED_WDBG_HEAP
|
#endif // #ifndef INCLUDED_WDBG_HEAP
|
||||||
|
@ -26,11 +26,10 @@
|
|||||||
# include "lib/sysdep/ia32/ia32.h"
|
# include "lib/sysdep/ia32/ia32.h"
|
||||||
#endif
|
#endif
|
||||||
#include "win.h"
|
#include "win.h"
|
||||||
#include "winit.h"
|
|
||||||
#include "wutil.h"
|
#include "wutil.h"
|
||||||
|
|
||||||
#define _NO_CVCONST_H // request SymTagEnum be defined
|
#define _NO_CVCONST_H // request SymTagEnum be defined
|
||||||
#include <dbghelp.h> // must come after win_internal
|
#include <dbghelp.h> // must come after win.h
|
||||||
#include <OAIdl.h> // VARIANT
|
#include <OAIdl.h> // VARIANT
|
||||||
|
|
||||||
#if MSC_VERSION
|
#if MSC_VERSION
|
||||||
@ -38,14 +37,13 @@
|
|||||||
#pragma comment(lib, "oleaut32.lib") // VariantChangeType
|
#pragma comment(lib, "oleaut32.lib") // VariantChangeType
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
WINIT_REGISTER_MAIN_INIT(wdbg_sym_Init);
|
|
||||||
WINIT_REGISTER_MAIN_SHUTDOWN(wdbg_sym_Shutdown);
|
|
||||||
|
|
||||||
// note: it is safe to use debug_assert/debug_warn/CHECK_ERR even during a
|
// note: it is safe to use debug_assert/debug_warn/CHECK_ERR even during a
|
||||||
// stack trace (which is triggered by debug_assert et al. in app code) because
|
// stack trace (which is triggered by debug_assert et al. in app code) because
|
||||||
// nested stack traces are ignored and only the error is displayed.
|
// nested stack traces are ignored and only the error is displayed.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
// dbghelp
|
// dbghelp
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
@ -60,7 +58,7 @@ static ULONG64 mod_base;
|
|||||||
// for StackWalk64; taken from PE header by wdbg_init.
|
// for StackWalk64; taken from PE header by wdbg_init.
|
||||||
static WORD machine;
|
static WORD machine;
|
||||||
|
|
||||||
// call on-demand (allows handling exceptions raised before win.cpp
|
// call on-demand (allows handling exceptions raised before winit.cpp
|
||||||
// init functions are called); no effect if already initialized.
|
// init functions are called); no effect if already initialized.
|
||||||
static LibError sym_init()
|
static LibError sym_init()
|
||||||
{
|
{
|
||||||
@ -80,6 +78,7 @@ static LibError sym_init()
|
|||||||
DWORD opts = SymGetOptions();
|
DWORD opts = SymGetOptions();
|
||||||
opts |= SYMOPT_DEFERRED_LOADS; // the "fastest, most efficient way"
|
opts |= SYMOPT_DEFERRED_LOADS; // the "fastest, most efficient way"
|
||||||
//opts |= SYMOPT_DEBUG; // lots of debug spew in output window
|
//opts |= SYMOPT_DEBUG; // lots of debug spew in output window
|
||||||
|
opts |= SYMOPT_UNDNAME;
|
||||||
SymSetOptions(opts);
|
SymSetOptions(opts);
|
||||||
|
|
||||||
// initialize dbghelp.
|
// initialize dbghelp.
|
||||||
@ -99,14 +98,6 @@ static LibError sym_init()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// called from wdbg_sym_Shutdown.
|
|
||||||
static LibError sym_shutdown()
|
|
||||||
{
|
|
||||||
SymCleanup(hProcess);
|
|
||||||
return INFO::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct SYMBOL_INFO_PACKAGEW2 : public SYMBOL_INFO_PACKAGEW
|
struct SYMBOL_INFO_PACKAGEW2 : public SYMBOL_INFO_PACKAGEW
|
||||||
{
|
{
|
||||||
SYMBOL_INFO_PACKAGEW2()
|
SYMBOL_INFO_PACKAGEW2()
|
||||||
@ -150,7 +141,7 @@ static LibError debug_resolve_symbol_lk(void* ptr_of_interest, char* sym_name, c
|
|||||||
SYMBOL_INFOW* sym = &sp.si;
|
SYMBOL_INFOW* sym = &sp.si;
|
||||||
if(SymFromAddrW(hProcess, addr, 0, sym))
|
if(SymFromAddrW(hProcess, addr, 0, sym))
|
||||||
{
|
{
|
||||||
sprintf_s(sym_name, DBG_SYMBOL_LEN, "%ws", sym->Name);
|
wsprintfA(sym_name, "%ws", sym->Name);
|
||||||
successes++;
|
successes++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,7 +163,7 @@ static LibError debug_resolve_symbol_lk(void* ptr_of_interest, char* sym_name, c
|
|||||||
// problem and is balanced by not having to do this from every
|
// problem and is balanced by not having to do this from every
|
||||||
// call site (full path is too long to display nicely).
|
// call site (full path is too long to display nicely).
|
||||||
const char* base_name = path_name_only(line_info.FileName);
|
const char* base_name = path_name_only(line_info.FileName);
|
||||||
sprintf_s(file, DBG_FILE_LEN, "%s", base_name);
|
wsprintf(file, "%s", base_name);
|
||||||
successes++;
|
successes++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,8 +196,6 @@ LibError debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file,
|
|||||||
// stack walk
|
// stack walk
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
|
|
||||||
static VOID (*pRtlCaptureContext)(PCONTEXT);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Subroutine linkage example code:
|
Subroutine linkage example code:
|
||||||
|
|
||||||
@ -250,9 +239,9 @@ func2:
|
|||||||
|
|
||||||
#if ARCH_IA32 && !CONFIG_OMIT_FP
|
#if ARCH_IA32 && !CONFIG_OMIT_FP
|
||||||
|
|
||||||
static LibError ia32_walk_stack(STACKFRAME64* sf)
|
static LibError ia32_walk_stack(_tagSTACKFRAME64* sf)
|
||||||
{
|
{
|
||||||
// read previous values from STACKFRAME64
|
// read previous values from _tagSTACKFRAME64
|
||||||
void* prev_fp = (void*)sf->AddrFrame .Offset;
|
void* prev_fp = (void*)sf->AddrFrame .Offset;
|
||||||
void* prev_ip = (void*)sf->AddrPC .Offset;
|
void* prev_ip = (void*)sf->AddrPC .Offset;
|
||||||
void* prev_ret = (void*)sf->AddrReturn.Offset;
|
void* prev_ret = (void*)sf->AddrReturn.Offset;
|
||||||
@ -277,7 +266,10 @@ static LibError ia32_walk_stack(STACKFRAME64* sf)
|
|||||||
LibError err = ia32_GetCallTarget(ret_addr, &target);
|
LibError err = ia32_GetCallTarget(ret_addr, &target);
|
||||||
RETURN_ERR(err);
|
RETURN_ERR(err);
|
||||||
if(target) // were able to determine it from the call instruction
|
if(target) // were able to determine it from the call instruction
|
||||||
debug_assert(debug_is_code_ptr(target));
|
{
|
||||||
|
if(!debug_is_code_ptr(target))
|
||||||
|
return ERR::FAIL; // NOWARN (invalid address)
|
||||||
|
}
|
||||||
|
|
||||||
sf->AddrFrame .Offset = (DWORD64)fp;
|
sf->AddrFrame .Offset = (DWORD64)fp;
|
||||||
sf->AddrPC .Offset = (DWORD64)target;
|
sf->AddrPC .Offset = (DWORD64)target;
|
||||||
@ -289,31 +281,17 @@ static LibError ia32_walk_stack(STACKFRAME64* sf)
|
|||||||
#endif // #if ARCH_IA32 && !CONFIG_OMIT_FP
|
#endif // #if ARCH_IA32 && !CONFIG_OMIT_FP
|
||||||
|
|
||||||
|
|
||||||
// called for each stack frame found by walk_stack, passing information
|
|
||||||
// about the frame and <user_arg>.
|
|
||||||
// return INFO::CB_CONTINUE to continue, anything else to stop immediately
|
|
||||||
// and return that value to walk_stack's caller.
|
|
||||||
//
|
|
||||||
// rationale: we can't just pass function's address to the callback -
|
|
||||||
// dump_frame_cb needs the frame pointer for reg-relative variables.
|
|
||||||
typedef LibError (*StackFrameCallback)(const STACKFRAME64*, void*);
|
|
||||||
|
|
||||||
static void skip_this_frame(uint& skip, void* context)
|
static void skip_this_frame(uint& skip, void* context)
|
||||||
{
|
{
|
||||||
if(!context)
|
if(!context)
|
||||||
skip++;
|
skip++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterate over a call stack, calling back for each frame encountered.
|
LibError wdbg_sym_WalkStack(StackFrameCallback cb, uintptr_t cbData, uint skip, const CONTEXT* pcontext)
|
||||||
// if <pcontext> != 0, we start there; otherwise, at the current context.
|
|
||||||
// return an error if callback never succeeded (returned 0).
|
|
||||||
//
|
|
||||||
// lock must be held.
|
|
||||||
static LibError walk_stack(StackFrameCallback cb, void* user_arg = 0, uint skip = 0, const CONTEXT* pcontext = 0)
|
|
||||||
{
|
{
|
||||||
// to function properly, StackWalk64 requires a CONTEXT on
|
// to function properly, StackWalk64 requires a CONTEXT on
|
||||||
// non-x86 systems (documented) or when in release mode (observed).
|
// non-x86 systems (documented) or when in release mode (observed).
|
||||||
// exception handlers can call walk_stack with their context record;
|
// exception handlers can call wdbg_sym_WalkStack with their context record;
|
||||||
// otherwise (e.g. dump_stack from debug_assert), we need to query it.
|
// otherwise (e.g. dump_stack from debug_assert), we need to query it.
|
||||||
CONTEXT context;
|
CONTEXT context;
|
||||||
// .. caller knows the context (most likely from an exception);
|
// .. caller knows the context (most likely from an exception);
|
||||||
@ -341,7 +319,13 @@ static LibError walk_stack(StackFrameCallback cb, void* user_arg = 0, uint skip
|
|||||||
#if ARCH_IA32
|
#if ARCH_IA32
|
||||||
ia32_asm_GetCurrentContext(&context);
|
ia32_asm_GetCurrentContext(&context);
|
||||||
#else
|
#else
|
||||||
// preferred implementation (was imported during module init)
|
// RtlCaptureContext is preferable if available (on WinXP and later)
|
||||||
|
typedef VOID (*PRtlCaptureContext)(PCONTEXT);
|
||||||
|
static PRtlCaptureContext pRtlCaptureContext;
|
||||||
|
ONCE(\
|
||||||
|
HMODULE hKernel32Dll = GetModuleHandle("kernel32.dll");\
|
||||||
|
pRtlCaptureContext = (PRtlCaptureContext)GetProcAddress(hKernel32Dll, "RtlCaptureContext");\
|
||||||
|
);
|
||||||
if(pRtlCaptureContext)
|
if(pRtlCaptureContext)
|
||||||
pRtlCaptureContext(&context);
|
pRtlCaptureContext(&context);
|
||||||
// not available: raise+handle an exception; grab the reported context.
|
// not available: raise+handle an exception; grab the reported context.
|
||||||
@ -366,7 +350,7 @@ static LibError walk_stack(StackFrameCallback cb, void* user_arg = 0, uint skip
|
|||||||
}
|
}
|
||||||
pcontext = &context;
|
pcontext = &context;
|
||||||
|
|
||||||
STACKFRAME64 sf;
|
_tagSTACKFRAME64 sf;
|
||||||
memset(&sf, 0, sizeof(sf));
|
memset(&sf, 0, sizeof(sf));
|
||||||
sf.AddrPC.Mode = AddrModeFlat;
|
sf.AddrPC.Mode = AddrModeFlat;
|
||||||
sf.AddrFrame.Mode = AddrModeFlat;
|
sf.AddrFrame.Mode = AddrModeFlat;
|
||||||
@ -402,6 +386,7 @@ static LibError walk_stack(StackFrameCallback cb, void* user_arg = 0, uint skip
|
|||||||
#if ARCH_IA32 && !CONFIG_OMIT_FP
|
#if ARCH_IA32 && !CONFIG_OMIT_FP
|
||||||
err = ia32_walk_stack(&sf);
|
err = ia32_walk_stack(&sf);
|
||||||
#else
|
#else
|
||||||
|
WinScopedLock lock(WDBG_SYM_CS);
|
||||||
sym_init();
|
sym_init();
|
||||||
// note: unfortunately StackWalk64 doesn't always SetLastError,
|
// note: unfortunately StackWalk64 doesn't always SetLastError,
|
||||||
// so we have to reset it and check for 0. *sigh*
|
// so we have to reset it and check for 0. *sigh*
|
||||||
@ -425,10 +410,10 @@ static LibError walk_stack(StackFrameCallback cb, void* user_arg = 0, uint skip
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = cb(&sf, user_arg);
|
ret = cb(&sf, cbData);
|
||||||
// callback is allowing us to continue
|
// callback is allowing us to continue
|
||||||
if(ret == INFO::CB_CONTINUE)
|
if(ret == INFO::CB_CONTINUE)
|
||||||
ret = INFO::OK; // make sure this is never returned
|
ret = INFO::OK;
|
||||||
// callback reports it's done; stop calling it and return that value.
|
// callback reports it's done; stop calling it and return that value.
|
||||||
// (can be either success or failure)
|
// (can be either success or failure)
|
||||||
else
|
else
|
||||||
@ -441,13 +426,13 @@ static LibError walk_stack(StackFrameCallback cb, void* user_arg = 0, uint skip
|
|||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// get address of Nth function above us on the call stack (uses walk_stack)
|
// get address of Nth function above us on the call stack (uses wdbg_sym_WalkStack)
|
||||||
//
|
//
|
||||||
|
|
||||||
// called by walk_stack for each stack frame
|
// called by wdbg_sym_WalkStack for each stack frame
|
||||||
static LibError nth_caller_cb(const STACKFRAME64* sf, void* user_arg)
|
static LibError nth_caller_cb(const _tagSTACKFRAME64* sf, uintptr_t cbData)
|
||||||
{
|
{
|
||||||
void** pfunc = (void**)user_arg;
|
void** pfunc = (void**)cbData;
|
||||||
|
|
||||||
// return its address
|
// return its address
|
||||||
*pfunc = (void*)sf->AddrPC.Offset;
|
*pfunc = (void*)sf->AddrPC.Offset;
|
||||||
@ -456,10 +441,9 @@ static LibError nth_caller_cb(const STACKFRAME64* sf, void* user_arg)
|
|||||||
|
|
||||||
void* debug_get_nth_caller(uint skip, void* pcontext)
|
void* debug_get_nth_caller(uint skip, void* pcontext)
|
||||||
{
|
{
|
||||||
WinScopedLock lock(WDBG_SYM_CS);
|
|
||||||
void* func;
|
void* func;
|
||||||
skip_this_frame(skip, pcontext);
|
skip_this_frame(skip, pcontext);
|
||||||
LibError ret = walk_stack(nth_caller_cb, &func, skip, (const CONTEXT*)pcontext);
|
LibError ret = wdbg_sym_WalkStack(nth_caller_cb, (uintptr_t)&func, skip, (const CONTEXT*)pcontext);
|
||||||
return (ret == INFO::OK)? func : 0;
|
return (ret == INFO::OK)? func : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,7 +633,7 @@ static LibError dump_sym(DWORD id, const u8* p, DumpState state);
|
|||||||
// from cvconst.h
|
// from cvconst.h
|
||||||
//
|
//
|
||||||
// rationale: we don't provide a get_register routine, since only the
|
// rationale: we don't provide a get_register routine, since only the
|
||||||
// value of FP is known to dump_frame_cb (via STACKFRAME64).
|
// value of FP is known to dump_frame_cb (via _tagSTACKFRAME64).
|
||||||
// displaying variables stored in registers is out of the question;
|
// displaying variables stored in registers is out of the question;
|
||||||
// all we can do is display FP-relative variables.
|
// all we can do is display FP-relative variables.
|
||||||
enum CV_HREG_e
|
enum CV_HREG_e
|
||||||
@ -867,11 +851,11 @@ static LibError dump_array(const u8* p,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static const STACKFRAME64* current_stackframe64;
|
static const _tagSTACKFRAME64* current_stackframe64;
|
||||||
|
|
||||||
static LibError determine_symbol_address(DWORD id, DWORD UNUSED(type_id), const u8** pp)
|
static LibError determine_symbol_address(DWORD id, DWORD UNUSED(type_id), const u8** pp)
|
||||||
{
|
{
|
||||||
const STACKFRAME64* sf = current_stackframe64;
|
const _tagSTACKFRAME64* sf = current_stackframe64;
|
||||||
|
|
||||||
DWORD data_kind;
|
DWORD data_kind;
|
||||||
if(!SymGetTypeInfo(hProcess, mod_base, id, TI_GET_DATAKIND, &data_kind))
|
if(!SymGetTypeInfo(hProcess, mod_base, id, TI_GET_DATAKIND, &data_kind))
|
||||||
@ -1286,7 +1270,8 @@ static PtrSet* already_visited_ptrs;
|
|||||||
// if we put it in a function, construction still fails on VC7 because
|
// if we put it in a function, construction still fails on VC7 because
|
||||||
// the atexit table will not have been initialized yet.
|
// the atexit table will not have been initialized yet.
|
||||||
|
|
||||||
// called by debug_dump_stack and wdbg_sym_Shutdown
|
// called by debug_dump_stack but not during shutdown (this must remain valid
|
||||||
|
// until the very end to allow crash reports)
|
||||||
static void ptr_reset_visited()
|
static void ptr_reset_visited()
|
||||||
{
|
{
|
||||||
delete already_visited_ptrs;
|
delete already_visited_ptrs;
|
||||||
@ -1749,7 +1734,7 @@ static LibError dump_sym(DWORD type_id, const u8* p, DumpState state)
|
|||||||
|
|
||||||
struct IMAGEHLP_STACK_FRAME2 : public IMAGEHLP_STACK_FRAME
|
struct IMAGEHLP_STACK_FRAME2 : public IMAGEHLP_STACK_FRAME
|
||||||
{
|
{
|
||||||
IMAGEHLP_STACK_FRAME2(const STACKFRAME64* sf)
|
IMAGEHLP_STACK_FRAME2(const _tagSTACKFRAME64* sf)
|
||||||
{
|
{
|
||||||
// apparently only PC, FP and SP are necessary, but
|
// apparently only PC, FP and SP are necessary, but
|
||||||
// we go whole-hog to be safe.
|
// we go whole-hog to be safe.
|
||||||
@ -1787,8 +1772,8 @@ static BOOL CALLBACK dump_sym_cb(SYMBOL_INFO* sym, ULONG UNUSED(size), void* UNU
|
|||||||
return TRUE; // continue
|
return TRUE; // continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// called by walk_stack for each stack frame
|
// called by wdbg_sym_WalkStack for each stack frame
|
||||||
static LibError dump_frame_cb(const STACKFRAME64* sf, void* UNUSED(user_arg))
|
static LibError dump_frame_cb(const _tagSTACKFRAME64* sf, uintptr_t UNUSED(cbData))
|
||||||
{
|
{
|
||||||
current_stackframe64 = sf;
|
current_stackframe64 = sf;
|
||||||
void* func = (void*)sf->AddrPC.Offset;
|
void* func = (void*)sf->AddrPC.Offset;
|
||||||
@ -1818,9 +1803,8 @@ static LibError dump_frame_cb(const STACKFRAME64* sf, void* UNUSED(user_arg))
|
|||||||
IMAGEHLP_STACK_FRAME2 imghlp_frame(sf);
|
IMAGEHLP_STACK_FRAME2 imghlp_frame(sf);
|
||||||
SymSetContext(hProcess, &imghlp_frame, 0); // last param is ignored
|
SymSetContext(hProcess, &imghlp_frame, 0); // last param is ignored
|
||||||
|
|
||||||
SymEnumSymbols(hProcess, 0, 0, dump_sym_cb, 0);
|
const ULONG64 base = 0; const char* const mask = 0; // use scope set by SymSetContext
|
||||||
// 2nd and 3rd params indicate scope set by SymSetContext
|
SymEnumSymbols(hProcess, base, mask, dump_sym_cb, 0);
|
||||||
// should be used.
|
|
||||||
|
|
||||||
out(L"\r\n");
|
out(L"\r\n");
|
||||||
return INFO::CB_CONTINUE;
|
return INFO::CB_CONTINUE;
|
||||||
@ -1829,9 +1813,6 @@ static LibError dump_frame_cb(const STACKFRAME64* sf, void* UNUSED(user_arg))
|
|||||||
|
|
||||||
LibError debug_dump_stack(wchar_t* buf, size_t max_chars, uint skip, void* pcontext)
|
LibError debug_dump_stack(wchar_t* buf, size_t max_chars, uint skip, void* pcontext)
|
||||||
{
|
{
|
||||||
buf='\0';
|
|
||||||
return INFO::OK;
|
|
||||||
|
|
||||||
static uintptr_t already_in_progress;
|
static uintptr_t already_in_progress;
|
||||||
if(!cpu_CAS(&already_in_progress, 0, 1))
|
if(!cpu_CAS(&already_in_progress, 0, 1))
|
||||||
return ERR::REENTERED; // NOWARN
|
return ERR::REENTERED; // NOWARN
|
||||||
@ -1839,9 +1820,8 @@ return INFO::OK;
|
|||||||
out_init(buf, max_chars);
|
out_init(buf, max_chars);
|
||||||
ptr_reset_visited();
|
ptr_reset_visited();
|
||||||
|
|
||||||
WinScopedLock lock(WDBG_SYM_CS);
|
|
||||||
skip_this_frame(skip, pcontext);
|
skip_this_frame(skip, pcontext);
|
||||||
LibError ret = walk_stack(dump_frame_cb, 0, skip, (const CONTEXT*)pcontext);
|
LibError ret = wdbg_sym_WalkStack(dump_frame_cb, 0, skip, (const CONTEXT*)pcontext);
|
||||||
|
|
||||||
already_in_progress = 0;
|
already_in_progress = 0;
|
||||||
|
|
||||||
@ -1855,7 +1835,7 @@ return INFO::OK;
|
|||||||
// examining the crash in a debugger. called by wdbg_exception_filter.
|
// examining the crash in a debugger. called by wdbg_exception_filter.
|
||||||
// heavily modified from http://www.codeproject.com/debug/XCrashReportPt3.asp
|
// heavily modified from http://www.codeproject.com/debug/XCrashReportPt3.asp
|
||||||
// lock must be held.
|
// lock must be held.
|
||||||
void wdbg_sym_write_minidump(EXCEPTION_POINTERS* exception_pointers)
|
void wdbg_sym_WriteMinidump(EXCEPTION_POINTERS* exception_pointers)
|
||||||
{
|
{
|
||||||
WinScopedLock lock(WDBG_SYM_CS);
|
WinScopedLock lock(WDBG_SYM_CS);
|
||||||
|
|
||||||
@ -1863,7 +1843,7 @@ void wdbg_sym_write_minidump(EXCEPTION_POINTERS* exception_pointers)
|
|||||||
HANDLE hFile = CreateFile(path.string().c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0);
|
HANDLE hFile = CreateFile(path.string().c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0);
|
||||||
if(hFile == INVALID_HANDLE_VALUE)
|
if(hFile == INVALID_HANDLE_VALUE)
|
||||||
{
|
{
|
||||||
DEBUG_DISPLAY_ERROR(L"wdbg_sym_write_minidump: unable to create crashlog.dmp.");
|
DEBUG_DISPLAY_ERROR(L"wdbg_sym_WriteMinidump: unable to create crashlog.dmp.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1879,31 +1859,7 @@ void wdbg_sym_write_minidump(EXCEPTION_POINTERS* exception_pointers)
|
|||||||
|
|
||||||
HANDLE hProcess = GetCurrentProcess(); DWORD pid = GetCurrentProcessId();
|
HANDLE hProcess = GetCurrentProcess(); DWORD pid = GetCurrentProcessId();
|
||||||
if(!MiniDumpWriteDump(hProcess, pid, hFile, MiniDumpNormal, &mei, 0, 0))
|
if(!MiniDumpWriteDump(hProcess, pid, hFile, MiniDumpNormal, &mei, 0, 0))
|
||||||
DEBUG_DISPLAY_ERROR(L"wdbg_sym_write_minidump: unable to generate minidump.");
|
DEBUG_DISPLAY_ERROR(L"wdbg_sym_WriteMinidump: unable to generate minidump.");
|
||||||
|
|
||||||
CloseHandle(hFile);
|
CloseHandle(hFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
static LibError wdbg_sym_Init()
|
|
||||||
{
|
|
||||||
// try to import RtlCaptureContext (available on WinXP and later).
|
|
||||||
// it's used in walk_stack; import here to avoid overhead of doing so
|
|
||||||
// on every call. if not available, walk_stack emulates it.
|
|
||||||
//
|
|
||||||
// note: kernel32 is always loaded into every process, so we
|
|
||||||
// don't need LoadLibrary/FreeLibrary.
|
|
||||||
HMODULE hKernel32Dll = GetModuleHandle("kernel32.dll");
|
|
||||||
*(void**)&pRtlCaptureContext = GetProcAddress(hKernel32Dll, "RtlCaptureContext");
|
|
||||||
|
|
||||||
return INFO::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static LibError wdbg_sym_Shutdown()
|
|
||||||
{
|
|
||||||
ptr_reset_visited();
|
|
||||||
return sym_shutdown();
|
|
||||||
}
|
|
||||||
|
@ -11,7 +11,30 @@
|
|||||||
#ifndef INCLUDED_WDBG_SYM
|
#ifndef INCLUDED_WDBG_SYM
|
||||||
#define INCLUDED_WDBG_SYM
|
#define INCLUDED_WDBG_SYM
|
||||||
|
|
||||||
|
struct _tagSTACKFRAME64;
|
||||||
|
struct _CONTEXT;
|
||||||
struct _EXCEPTION_POINTERS;
|
struct _EXCEPTION_POINTERS;
|
||||||
extern void wdbg_sym_write_minidump(_EXCEPTION_POINTERS* ep);
|
|
||||||
|
/**
|
||||||
|
* called for each stack frame found by wdbg_sym_WalkStack.
|
||||||
|
*
|
||||||
|
* @param frame the dbghelp stack frame (we can't just pass the
|
||||||
|
* instruction-pointer because dump_frame_cb needs the frame pointer to
|
||||||
|
* locate frame-relative variables)
|
||||||
|
* @param cbData the user-specified value that was passed to wdbg_sym_WalkStack
|
||||||
|
* @return INFO::CB_CONTINUE to continue, anything else to stop immediately
|
||||||
|
* and return that value to wdbg_sym_WalkStack's caller.
|
||||||
|
**/
|
||||||
|
typedef LibError (*StackFrameCallback)(const _tagSTACKFRAME64* frame, uintptr_t cbData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iterate over a call stack, invoking a callback for each frame encountered.
|
||||||
|
*
|
||||||
|
* @param pcontext processor context from which to start (usually taken from
|
||||||
|
* an exception record), or 0 to walk the current stack.
|
||||||
|
**/
|
||||||
|
extern LibError wdbg_sym_WalkStack(StackFrameCallback cb, uintptr_t cbData = 0, uint skip = 0, const _CONTEXT* pcontext = 0);
|
||||||
|
|
||||||
|
extern void wdbg_sym_WriteMinidump(_EXCEPTION_POINTERS* ep);
|
||||||
|
|
||||||
#endif // #ifndef INCLUDED_WDBG_SYM
|
#endif // #ifndef INCLUDED_WDBG_SYM
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
// derived implementations must be called CounterIMPL,
|
// derived implementations must be called CounterIMPL,
|
||||||
// where IMPL matches the WHRT_IMPL identifier. (see CREATE)
|
// where IMPL matches the WHRT_IMPL identifier. (see CREATE)
|
||||||
class ICounter : boost::noncopyable
|
class ICounter : noncopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// (compiled-generated) ctor only sets up the vptr
|
// (compiled-generated) ctor only sets up the vptr
|
||||||
|
@ -62,13 +62,13 @@ static ICounter* GetNextBestSafeCounter()
|
|||||||
LibError err = ActivateCounter(counter);
|
LibError err = ActivateCounter(counter);
|
||||||
if(err == INFO::OK)
|
if(err == INFO::OK)
|
||||||
{
|
{
|
||||||
debug_printf("HRT/ using name=%s freq=%f\n", counter->Name(), counter->NominalFrequency());
|
debug_printf("HRT| using name=%s freq=%f\n", counter->Name(), counter->NominalFrequency());
|
||||||
return counter; // found a safe counter
|
return counter; // found a safe counter
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
char buf[100];
|
char buf[100];
|
||||||
debug_printf("HRT/ activating %s failed: %s\n", counter->Name(), error_description_r(err, buf, ARRAY_SIZE(buf)));
|
debug_printf("HRT| activating %s failed: %s\n", counter->Name(), error_description_r(err, buf, ARRAY_SIZE(buf)));
|
||||||
DestroyCounter(counter);
|
DestroyCounter(counter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,13 @@
|
|||||||
// to insulate against changes there. another advantage is that callbacks
|
// to insulate against changes there. another advantage is that callbacks
|
||||||
// can return LibError instead of int.
|
// can return LibError instead of int.
|
||||||
|
|
||||||
|
// currently (2008-02-17) the init groups are populated as follows:
|
||||||
|
// critical : wposix
|
||||||
|
// early : wutil
|
||||||
|
// early2 : whrt, wdbg_heap
|
||||||
|
// main : waio, wsock, wtime, wdir_watch
|
||||||
|
// late : wsdl
|
||||||
|
|
||||||
typedef LibError (*PfnLibErrorVoid)(void);
|
typedef LibError (*PfnLibErrorVoid)(void);
|
||||||
|
|
||||||
// pointers to start and end of function tables.
|
// pointers to start and end of function tables.
|
||||||
|
@ -24,6 +24,7 @@ static DWORD pageSize;
|
|||||||
static DWORD numProcessors;
|
static DWORD numProcessors;
|
||||||
static BOOL (WINAPI *pGlobalMemoryStatusEx)(MEMORYSTATUSEX*);
|
static BOOL (WINAPI *pGlobalMemoryStatusEx)(MEMORYSTATUSEX*);
|
||||||
|
|
||||||
|
// NB: called from critical init
|
||||||
static void InitSysconf()
|
static void InitSysconf()
|
||||||
{
|
{
|
||||||
SYSTEM_INFO si;
|
SYSTEM_INFO si;
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
#include "lib/sysdep/cpu.h"
|
#include "lib/sysdep/cpu.h"
|
||||||
#include "win.h"
|
#include "win.h"
|
||||||
#include "wutil.h"
|
#include "wutil.h"
|
||||||
#include "wdbg_sym.h" // wdbg_sym_write_minidump
|
#include "wdbg_sym.h" // wdbg_sym_WriteMinidump
|
||||||
|
|
||||||
#if MSC_VERSION >= 1400
|
#if MSC_VERSION >= 1400
|
||||||
# include <process.h> // __security_init_cookie
|
# include <process.h> // __security_init_cookie
|
||||||
@ -253,7 +253,7 @@ long __stdcall wseh_ExceptionFilter(struct _EXCEPTION_POINTERS* ep)
|
|||||||
|
|
||||||
// this must happen before the error dialog because user could choose to
|
// this must happen before the error dialog because user could choose to
|
||||||
// exit immediately there.
|
// exit immediately there.
|
||||||
wdbg_sym_write_minidump(ep);
|
wdbg_sym_WriteMinidump(ep);
|
||||||
|
|
||||||
wchar_t message[500];
|
wchar_t message[500];
|
||||||
const wchar_t* messageFormat =
|
const wchar_t* messageFormat =
|
||||||
|
@ -31,11 +31,11 @@ LIB_API double timer_Resolution(void);
|
|||||||
// scope timing
|
// scope timing
|
||||||
|
|
||||||
/// used by TIMER
|
/// used by TIMER
|
||||||
class ScopeTimer : boost::noncopyable
|
class LIB_API ScopeTimer : noncopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LIB_API ScopeTimer(const char* description);
|
ScopeTimer(const char* description);
|
||||||
LIB_API ~ScopeTimer();
|
~ScopeTimer();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
double m_t0;
|
double m_t0;
|
||||||
@ -91,7 +91,7 @@ private:
|
|||||||
// this supplements in-game profiling by providing low-overhead,
|
// this supplements in-game profiling by providing low-overhead,
|
||||||
// high resolution time accounting of specific areas.
|
// high resolution time accounting of specific areas.
|
||||||
|
|
||||||
union LIB_API TimerUnit
|
LIB_API union TimerUnit
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void SetToZero();
|
void SetToZero();
|
||||||
@ -158,11 +158,11 @@ LIB_API void timer_BillClient(TimerClient* tc, TimerUnit t0, TimerUnit t1);
|
|||||||
LIB_API void timer_DisplayClientTotals();
|
LIB_API void timer_DisplayClientTotals();
|
||||||
|
|
||||||
/// used by TIMER_ACCRUE
|
/// used by TIMER_ACCRUE
|
||||||
class ScopeTimerAccrue
|
class LIB_API ScopeTimerAccrue
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LIB_API ScopeTimerAccrue(TimerClient* tc);
|
ScopeTimerAccrue(TimerClient* tc);
|
||||||
LIB_API ~ScopeTimerAccrue();
|
~ScopeTimerAccrue();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TimerUnit m_t0;
|
TimerUnit m_t0;
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
#include "ProfileViewer.h"
|
#include "ProfileViewer.h"
|
||||||
#include "lib/timer.h"
|
#include "lib/timer.h"
|
||||||
|
|
||||||
|
#if OS_WIN
|
||||||
|
#include "lib/sysdep/win/wdbg_heap.h"
|
||||||
|
#endif
|
||||||
#if defined(__GLIBC__) && !defined(NDEBUG)
|
#if defined(__GLIBC__) && !defined(NDEBUG)
|
||||||
# define GLIBC_MALLOC_HOOK
|
# define GLIBC_MALLOC_HOOK
|
||||||
# include <malloc.h>
|
# include <malloc.h>
|
||||||
@ -373,26 +376,13 @@ void CProfileNode::Frame()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: these should probably only count allocations that occur in the thread being profiled
|
// TODO: these should probably only count allocations that occur in the thread being profiled
|
||||||
#if MSC_VERSION
|
#if OS_WIN
|
||||||
static intptr_t memory_alloc_count = 0;
|
|
||||||
static int (*old_alloc_hook) (int, void*, size_t, int, long, const unsigned char*, int);
|
|
||||||
static int alloc_hook(int allocType, void* userData, size_t size, int blockType,
|
|
||||||
long requestNumber, const unsigned char* filename, int lineNumber)
|
|
||||||
{
|
|
||||||
if (allocType == _HOOK_ALLOC || allocType == _HOOK_REALLOC)
|
|
||||||
cpu_AtomicAdd(&memory_alloc_count, 1);
|
|
||||||
if (old_alloc_hook)
|
|
||||||
return old_alloc_hook(allocType, userData, size, blockType, requestNumber, filename, lineNumber);
|
|
||||||
else
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
static void alloc_hook_initialize()
|
static void alloc_hook_initialize()
|
||||||
{
|
{
|
||||||
old_alloc_hook = _CrtSetAllocHook(&alloc_hook);
|
|
||||||
}
|
}
|
||||||
static long get_memory_alloc_count()
|
static long get_memory_alloc_count()
|
||||||
{
|
{
|
||||||
return memory_alloc_count;
|
return (long)wdbg_heap_NumberOfAllocations();
|
||||||
}
|
}
|
||||||
#elif defined(GLIBC_MALLOC_HOOK)
|
#elif defined(GLIBC_MALLOC_HOOK)
|
||||||
// Set up malloc hooks to count allocations - see
|
// Set up malloc hooks to count allocations - see
|
||||||
|
Loading…
Reference in New Issue
Block a user