1
1
forked from 0ad/0ad

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:
janwas 2008-02-25 21:19:52 +00:00
parent 3f23e37fac
commit 74b4ac19c1
24 changed files with 999 additions and 200 deletions

View File

@ -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

View File

@ -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;

View File

@ -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.

View File

@ -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))\

View File

@ -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();

View File

@ -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)

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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)

View File

@ -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);
}
} }
} }

View File

@ -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.
// //

View 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();)

View File

@ -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;
} }

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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);
} }
} }

View File

@ -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.

View File

@ -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;

View File

@ -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 =

View File

@ -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;

View File

@ -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