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)]
|
||||
|
||||
|
||||
// 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
|
||||
|
@ -65,14 +65,10 @@ void debug_wprintf_mem(const wchar_t* fmt, ...)
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int len = vswprintf(debug_log_pos, chars_left-2, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
if(len < 0)
|
||||
{
|
||||
debug_assert(0); // vswprintf failed
|
||||
return;
|
||||
}
|
||||
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,
|
||||
// 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.
|
||||
// we store FNV hash of tag strings for fast comparison; collisions are
|
||||
// extremely unlikely and can only result in displaying more/less text.
|
||||
@ -161,56 +153,24 @@ bool debug_filter_allows(const char* text)
|
||||
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
|
||||
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_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);
|
||||
|
||||
if(debug_filter_allows(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);
|
||||
|
||||
// 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;
|
||||
|
@ -46,9 +46,6 @@ extern void debug_break();
|
||||
**/
|
||||
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.
|
||||
|
@ -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,
|
||||
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;
|
||||
#define CONTAINER(name, type_name_pattern)\
|
||||
else if(match_wildcard(type_name, type_name_pattern))\
|
||||
|
@ -244,7 +244,7 @@ public:
|
||||
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();
|
||||
|
||||
|
@ -198,7 +198,7 @@ BlockCache BlockIo::s_blockCache;
|
||||
// IoSplitter
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class IoSplitter : boost::noncopyable
|
||||
class IoSplitter : noncopyable
|
||||
{
|
||||
public:
|
||||
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)
|
||||
{
|
||||
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);
|
||||
cpu_memcpy(newData.get(), m_data.get(), m_size);
|
||||
m_data = newData;
|
||||
|
@ -29,7 +29,7 @@ private:
|
||||
};
|
||||
|
||||
|
||||
class UnalignedWriter : public boost::noncopyable
|
||||
class UnalignedWriter : public noncopyable
|
||||
{
|
||||
public:
|
||||
UnalignedWriter(PIFile file, off_t ofs);
|
||||
|
@ -23,13 +23,13 @@
|
||||
struct PathTraits;
|
||||
typedef fs::basic_path<std::string, PathTraits> Path;
|
||||
|
||||
struct PathTraits
|
||||
struct LIB_API PathTraits
|
||||
{
|
||||
typedef std::string internal_string_type;
|
||||
typedef std::string external_string_type;
|
||||
|
||||
LIB_API 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 external_string_type to_external(const Path&, const internal_string_type& src);
|
||||
static internal_string_type to_internal(const external_string_type& src);
|
||||
};
|
||||
|
||||
namespace boost
|
||||
|
@ -29,7 +29,7 @@ static size_t s_numArchivedFiles;
|
||||
|
||||
// helper class that allows breaking up the logic into sub-functions without
|
||||
// always having to pass directory/realDirectory as parameters.
|
||||
class PopulateHelper : boost::noncopyable
|
||||
class PopulateHelper : noncopyable
|
||||
{
|
||||
public:
|
||||
PopulateHelper(VfsDirectory* directory, PRealDirectory realDirectory)
|
||||
|
@ -240,9 +240,12 @@ static void StartDriver(const char* driverPathname)
|
||||
BOOL ok = StartService(hService, numArgs, 0);
|
||||
if(!ok)
|
||||
{
|
||||
// if it wasn't already running, starting failed
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
// 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 "wdbg_heap.h"
|
||||
|
||||
#include "lib/sysdep/win/win.h"
|
||||
#include <crtdbg.h>
|
||||
#include <excpt.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
#include "lib/sysdep/cpu.h" // cpu_AtomicAdd
|
||||
#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)
|
||||
{
|
||||
uint flags = 0;
|
||||
@ -31,7 +38,7 @@ void wdbg_heap_Enable(bool enable)
|
||||
|
||||
void wdbg_heap_Validate()
|
||||
{
|
||||
int ret;
|
||||
int ret = _HEAPOK;
|
||||
__try
|
||||
{
|
||||
// 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] == '{')
|
||||
{
|
||||
debug_printf("leak\n");
|
||||
}
|
||||
struct _CrtMemBlockHeader* next;
|
||||
struct _CrtMemBlockHeader* prev;
|
||||
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;
|
||||
return 1; // CRT is to display the message as normal
|
||||
bool IsValid() const
|
||||
{
|
||||
__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
|
||||
* we are called BEFORE the actual heap operation.
|
||||
* update our idea of the head of the linked list of heap blocks.
|
||||
* 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)
|
||||
return previousAllocHook(allocType, userData, size, blockType, requestNumber, file, line);
|
||||
return 1; // continue as if the hook had never been called
|
||||
static _CrtMemBlockHeader* s_heapListHead;
|
||||
|
||||
// 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
|
||||
static void InstallHooks()
|
||||
// we need to make the most out of the limited amount of frames. to that end,
|
||||
// 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);
|
||||
if(ret == -1)
|
||||
abort();
|
||||
|
||||
previousAllocHook = _CrtSetAllocHook(AllocHook);
|
||||
}
|
||||
s_tracker = new AllocationTracker;
|
||||
#endif
|
||||
|
||||
static LibError wdbg_heap_Init()
|
||||
{
|
||||
InstallHooks();
|
||||
wdbg_heap_Enable(true);
|
||||
|
||||
return INFO::OK;
|
||||
}
|
||||
|
@ -30,4 +30,10 @@ LIB_API void wdbg_heap_Enable(bool);
|
||||
**/
|
||||
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
|
||||
|
@ -26,11 +26,10 @@
|
||||
# include "lib/sysdep/ia32/ia32.h"
|
||||
#endif
|
||||
#include "win.h"
|
||||
#include "winit.h"
|
||||
#include "wutil.h"
|
||||
|
||||
#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
|
||||
|
||||
#if MSC_VERSION
|
||||
@ -38,14 +37,13 @@
|
||||
#pragma comment(lib, "oleaut32.lib") // VariantChangeType
|
||||
#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
|
||||
// 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.
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// dbghelp
|
||||
//----------------------------------------------------------------------------
|
||||
@ -60,7 +58,7 @@ static ULONG64 mod_base;
|
||||
// for StackWalk64; taken from PE header by wdbg_init.
|
||||
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.
|
||||
static LibError sym_init()
|
||||
{
|
||||
@ -80,6 +78,7 @@ static LibError sym_init()
|
||||
DWORD opts = SymGetOptions();
|
||||
opts |= SYMOPT_DEFERRED_LOADS; // the "fastest, most efficient way"
|
||||
//opts |= SYMOPT_DEBUG; // lots of debug spew in output window
|
||||
opts |= SYMOPT_UNDNAME;
|
||||
SymSetOptions(opts);
|
||||
|
||||
// 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
|
||||
{
|
||||
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;
|
||||
if(SymFromAddrW(hProcess, addr, 0, sym))
|
||||
{
|
||||
sprintf_s(sym_name, DBG_SYMBOL_LEN, "%ws", sym->Name);
|
||||
wsprintfA(sym_name, "%ws", sym->Name);
|
||||
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
|
||||
// call site (full path is too long to display nicely).
|
||||
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++;
|
||||
}
|
||||
|
||||
@ -205,8 +196,6 @@ LibError debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file,
|
||||
// stack walk
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
static VOID (*pRtlCaptureContext)(PCONTEXT);
|
||||
|
||||
/*
|
||||
Subroutine linkage example code:
|
||||
|
||||
@ -250,9 +239,9 @@ func2:
|
||||
|
||||
#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_ip = (void*)sf->AddrPC .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);
|
||||
RETURN_ERR(err);
|
||||
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->AddrPC .Offset = (DWORD64)target;
|
||||
@ -289,31 +281,17 @@ static LibError ia32_walk_stack(STACKFRAME64* sf)
|
||||
#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)
|
||||
{
|
||||
if(!context)
|
||||
skip++;
|
||||
}
|
||||
|
||||
// iterate over a call stack, calling back for each frame encountered.
|
||||
// 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)
|
||||
LibError wdbg_sym_WalkStack(StackFrameCallback cb, uintptr_t cbData, uint skip, const CONTEXT* pcontext)
|
||||
{
|
||||
// to function properly, StackWalk64 requires a CONTEXT on
|
||||
// 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.
|
||||
CONTEXT context;
|
||||
// .. 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
|
||||
ia32_asm_GetCurrentContext(&context);
|
||||
#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)
|
||||
pRtlCaptureContext(&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;
|
||||
|
||||
STACKFRAME64 sf;
|
||||
_tagSTACKFRAME64 sf;
|
||||
memset(&sf, 0, sizeof(sf));
|
||||
sf.AddrPC.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
|
||||
err = ia32_walk_stack(&sf);
|
||||
#else
|
||||
WinScopedLock lock(WDBG_SYM_CS);
|
||||
sym_init();
|
||||
// note: unfortunately StackWalk64 doesn't always SetLastError,
|
||||
// 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;
|
||||
}
|
||||
|
||||
ret = cb(&sf, user_arg);
|
||||
ret = cb(&sf, cbData);
|
||||
// callback is allowing us to 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.
|
||||
// (can be either success or failure)
|
||||
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
|
||||
static LibError nth_caller_cb(const STACKFRAME64* sf, void* user_arg)
|
||||
// called by wdbg_sym_WalkStack for each stack frame
|
||||
static LibError nth_caller_cb(const _tagSTACKFRAME64* sf, uintptr_t cbData)
|
||||
{
|
||||
void** pfunc = (void**)user_arg;
|
||||
void** pfunc = (void**)cbData;
|
||||
|
||||
// return its address
|
||||
*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)
|
||||
{
|
||||
WinScopedLock lock(WDBG_SYM_CS);
|
||||
void* func;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -649,7 +633,7 @@ static LibError dump_sym(DWORD id, const u8* p, DumpState state);
|
||||
// from cvconst.h
|
||||
//
|
||||
// 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;
|
||||
// all we can do is display FP-relative variables.
|
||||
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)
|
||||
{
|
||||
const STACKFRAME64* sf = current_stackframe64;
|
||||
const _tagSTACKFRAME64* sf = current_stackframe64;
|
||||
|
||||
DWORD 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
|
||||
// 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()
|
||||
{
|
||||
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
|
||||
{
|
||||
IMAGEHLP_STACK_FRAME2(const STACKFRAME64* sf)
|
||||
IMAGEHLP_STACK_FRAME2(const _tagSTACKFRAME64* sf)
|
||||
{
|
||||
// apparently only PC, FP and SP are necessary, but
|
||||
// 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
|
||||
}
|
||||
|
||||
// called by walk_stack for each stack frame
|
||||
static LibError dump_frame_cb(const STACKFRAME64* sf, void* UNUSED(user_arg))
|
||||
// called by wdbg_sym_WalkStack for each stack frame
|
||||
static LibError dump_frame_cb(const _tagSTACKFRAME64* sf, uintptr_t UNUSED(cbData))
|
||||
{
|
||||
current_stackframe64 = sf;
|
||||
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);
|
||||
SymSetContext(hProcess, &imghlp_frame, 0); // last param is ignored
|
||||
|
||||
SymEnumSymbols(hProcess, 0, 0, dump_sym_cb, 0);
|
||||
// 2nd and 3rd params indicate scope set by SymSetContext
|
||||
// should be used.
|
||||
const ULONG64 base = 0; const char* const mask = 0; // use scope set by SymSetContext
|
||||
SymEnumSymbols(hProcess, base, mask, dump_sym_cb, 0);
|
||||
|
||||
out(L"\r\n");
|
||||
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)
|
||||
{
|
||||
buf='\0';
|
||||
return INFO::OK;
|
||||
|
||||
static uintptr_t already_in_progress;
|
||||
if(!cpu_CAS(&already_in_progress, 0, 1))
|
||||
return ERR::REENTERED; // NOWARN
|
||||
@ -1839,9 +1820,8 @@ return INFO::OK;
|
||||
out_init(buf, max_chars);
|
||||
ptr_reset_visited();
|
||||
|
||||
WinScopedLock lock(WDBG_SYM_CS);
|
||||
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;
|
||||
|
||||
@ -1855,7 +1835,7 @@ return INFO::OK;
|
||||
// examining the crash in a debugger. called by wdbg_exception_filter.
|
||||
// heavily modified from http://www.codeproject.com/debug/XCrashReportPt3.asp
|
||||
// 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);
|
||||
|
||||
@ -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);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1879,31 +1859,7 @@ void wdbg_sym_write_minidump(EXCEPTION_POINTERS* exception_pointers)
|
||||
|
||||
HANDLE hProcess = GetCurrentProcess(); DWORD pid = GetCurrentProcessId();
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
#define INCLUDED_WDBG_SYM
|
||||
|
||||
struct _tagSTACKFRAME64;
|
||||
struct _CONTEXT;
|
||||
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
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
// derived implementations must be called CounterIMPL,
|
||||
// where IMPL matches the WHRT_IMPL identifier. (see CREATE)
|
||||
class ICounter : boost::noncopyable
|
||||
class ICounter : noncopyable
|
||||
{
|
||||
public:
|
||||
// (compiled-generated) ctor only sets up the vptr
|
||||
|
@ -62,13 +62,13 @@ static ICounter* GetNextBestSafeCounter()
|
||||
LibError err = ActivateCounter(counter);
|
||||
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
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,13 @@
|
||||
// to insulate against changes there. another advantage is that callbacks
|
||||
// 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);
|
||||
|
||||
// pointers to start and end of function tables.
|
||||
|
@ -24,6 +24,7 @@ static DWORD pageSize;
|
||||
static DWORD numProcessors;
|
||||
static BOOL (WINAPI *pGlobalMemoryStatusEx)(MEMORYSTATUSEX*);
|
||||
|
||||
// NB: called from critical init
|
||||
static void InitSysconf()
|
||||
{
|
||||
SYSTEM_INFO si;
|
||||
|
@ -15,7 +15,7 @@
|
||||
#include "lib/sysdep/cpu.h"
|
||||
#include "win.h"
|
||||
#include "wutil.h"
|
||||
#include "wdbg_sym.h" // wdbg_sym_write_minidump
|
||||
#include "wdbg_sym.h" // wdbg_sym_WriteMinidump
|
||||
|
||||
#if MSC_VERSION >= 1400
|
||||
# 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
|
||||
// exit immediately there.
|
||||
wdbg_sym_write_minidump(ep);
|
||||
wdbg_sym_WriteMinidump(ep);
|
||||
|
||||
wchar_t message[500];
|
||||
const wchar_t* messageFormat =
|
||||
|
@ -31,11 +31,11 @@ LIB_API double timer_Resolution(void);
|
||||
// scope timing
|
||||
|
||||
/// used by TIMER
|
||||
class ScopeTimer : boost::noncopyable
|
||||
class LIB_API ScopeTimer : noncopyable
|
||||
{
|
||||
public:
|
||||
LIB_API ScopeTimer(const char* description);
|
||||
LIB_API ~ScopeTimer();
|
||||
ScopeTimer(const char* description);
|
||||
~ScopeTimer();
|
||||
|
||||
private:
|
||||
double m_t0;
|
||||
@ -91,7 +91,7 @@ private:
|
||||
// this supplements in-game profiling by providing low-overhead,
|
||||
// high resolution time accounting of specific areas.
|
||||
|
||||
union LIB_API TimerUnit
|
||||
LIB_API union TimerUnit
|
||||
{
|
||||
public:
|
||||
void SetToZero();
|
||||
@ -158,11 +158,11 @@ LIB_API void timer_BillClient(TimerClient* tc, TimerUnit t0, TimerUnit t1);
|
||||
LIB_API void timer_DisplayClientTotals();
|
||||
|
||||
/// used by TIMER_ACCRUE
|
||||
class ScopeTimerAccrue
|
||||
class LIB_API ScopeTimerAccrue
|
||||
{
|
||||
public:
|
||||
LIB_API ScopeTimerAccrue(TimerClient* tc);
|
||||
LIB_API ~ScopeTimerAccrue();
|
||||
ScopeTimerAccrue(TimerClient* tc);
|
||||
~ScopeTimerAccrue();
|
||||
|
||||
private:
|
||||
TimerUnit m_t0;
|
||||
|
@ -12,6 +12,9 @@
|
||||
#include "ProfileViewer.h"
|
||||
#include "lib/timer.h"
|
||||
|
||||
#if OS_WIN
|
||||
#include "lib/sysdep/win/wdbg_heap.h"
|
||||
#endif
|
||||
#if defined(__GLIBC__) && !defined(NDEBUG)
|
||||
# define GLIBC_MALLOC_HOOK
|
||||
# include <malloc.h>
|
||||
@ -373,26 +376,13 @@ void CProfileNode::Frame()
|
||||
}
|
||||
|
||||
// TODO: these should probably only count allocations that occur in the thread being profiled
|
||||
#if MSC_VERSION
|
||||
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;
|
||||
}
|
||||
#if OS_WIN
|
||||
static void alloc_hook_initialize()
|
||||
{
|
||||
old_alloc_hook = _CrtSetAllocHook(&alloc_hook);
|
||||
}
|
||||
static long get_memory_alloc_count()
|
||||
{
|
||||
return memory_alloc_count;
|
||||
return (long)wdbg_heap_NumberOfAllocations();
|
||||
}
|
||||
#elif defined(GLIBC_MALLOC_HOOK)
|
||||
// Set up malloc hooks to count allocations - see
|
||||
|
Loading…
Reference in New Issue
Block a user