allocators: add include guard and posix.h (needed for PROT_*)
debug: add debug_is_code_ptr and debug_is_stack_ptr config: add CONFIG_OMIT_FP (needed for ia32_stackwalk). h_mgr: hyperparanoid overrun detection straightjacket for key2idx - now safe to use at any time and any unexpected changes cause access violation. ia32: add ia32_get_call_target, which disassembles backwards and checks for a valid CALL instruction + its target wdbg_sym: add win-specific walk stack function. it's quite simple and requires EBP frames (hence CONFIG_OMIT_FP). rationale: dbghelp requires ~30 seconds (!) to start up. recent changes (tracking malloc callers via debug_get_nth_caller) made this happen on every run, so added a fast-path stack walker to avoid this. This was SVN commit r3043.
This commit is contained in:
parent
e8540b29bd
commit
8c437850e6
@ -1,4 +1,8 @@
|
||||
#ifndef ALLOCATORS_H__
|
||||
#define ALLOCATORS_H__
|
||||
|
||||
#include "lib/types.h"
|
||||
#include "lib/posix.h" // PROT_* constants for da_set_prot
|
||||
|
||||
struct DynArray
|
||||
{
|
||||
@ -119,3 +123,5 @@ extern void** matrix_alloc(uint cols, uint rows, size_t el_size);
|
||||
// callers will likely want to pass variables of a different type
|
||||
// (e.g. int**); they must be cast to void**.
|
||||
extern void matrix_free(void** matrix);
|
||||
|
||||
#endif // #ifndef ALLOCATORS_H__
|
||||
|
@ -15,6 +15,17 @@
|
||||
|
||||
// allow override via compiler settings by checking #ifndef.
|
||||
|
||||
// omit frame pointers - stack frames will not be generated, which
|
||||
// improves performance but makes walking the stack harder.
|
||||
// this is acted upon by #pragmas in sysdep.h.
|
||||
#ifndef CONFIG_OMIT_FP
|
||||
# ifdef NDEBUG
|
||||
# define CONFIG_OMIT_FP 1
|
||||
# else
|
||||
# define CONFIG_OMIT_FP 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// enable memory tracking (slow). see mmgr.cpp.
|
||||
#ifndef CONFIG_USE_MMGR
|
||||
# define CONFIG_USE_MMGR 0
|
||||
|
@ -325,6 +325,9 @@ extern void* debug_get_nth_caller(uint skip, void* context);
|
||||
// but can be used to filter out obviously wrong values in a portable manner.
|
||||
extern int debug_is_pointer_bogus(const void* p);
|
||||
|
||||
extern bool debug_is_code_ptr(void* p);
|
||||
extern bool debug_is_stack_ptr(void* p);
|
||||
|
||||
|
||||
// set the current thread's name; it will be returned by subsequent calls to
|
||||
// debug_get_thread_name.
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <new> // std::bad_alloc
|
||||
#include "lib/allocators.h"
|
||||
|
||||
static const uint MAX_EXTANT_HANDLES = 10000;
|
||||
|
||||
@ -121,8 +122,6 @@ static const uint TYPE_BITS = 8;
|
||||
static const size_t HDATA_USER_SIZE = 44+64;
|
||||
|
||||
|
||||
// 64 bytes
|
||||
// TODO: not anymore, fix later
|
||||
struct HDATA
|
||||
{
|
||||
uintptr_t key;
|
||||
@ -340,13 +339,68 @@ static int free_idx(i32 idx)
|
||||
|
||||
typedef STL_HASH_MULTIMAP<uintptr_t, i32> Key2Idx;
|
||||
typedef Key2Idx::iterator It;
|
||||
static Key2Idx key2idx;
|
||||
|
||||
static DynArray key2idx_da;
|
||||
static Key2Idx* key2idx_;
|
||||
|
||||
|
||||
static Handle find_key(uintptr_t key, H_Type type, bool remove = false)
|
||||
static void key2idx_lock()
|
||||
{
|
||||
std::pair<It, It> range = key2idx.equal_range(key);
|
||||
da_set_prot(&key2idx_da, PROT_NONE);
|
||||
}
|
||||
|
||||
static void key2idx_unlock()
|
||||
{
|
||||
da_set_prot(&key2idx_da, PROT_READ|PROT_WRITE);
|
||||
}
|
||||
|
||||
static void key2idx_init(void)
|
||||
{
|
||||
const size_t size = 4096;
|
||||
cassert(sizeof(Key2Idx) <= size);
|
||||
if(da_alloc(&key2idx_da, size) < 0)
|
||||
goto fail;
|
||||
if(da_set_size(&key2idx_da, size) < 0)
|
||||
goto fail;
|
||||
|
||||
key2idx_ = new(key2idx_da.base) Key2Idx;
|
||||
key2idx_lock();
|
||||
return; // success
|
||||
|
||||
fail:
|
||||
debug_warn("key2idx mem alloc failed");
|
||||
}
|
||||
|
||||
static void key2idx_shutdown()
|
||||
{
|
||||
key2idx_ = 0;
|
||||
(void)da_free(&key2idx_da);
|
||||
}
|
||||
|
||||
|
||||
static Key2Idx* key2idx_get()
|
||||
{
|
||||
static pthread_once_t key2idx_once = PTHREAD_ONCE_INIT;
|
||||
pthread_once(&key2idx_once, key2idx_init);
|
||||
key2idx_unlock();
|
||||
return key2idx_;
|
||||
}
|
||||
|
||||
|
||||
enum KeyRemoveFlag { KEY_NOREMOVE, KEY_REMOVE };
|
||||
|
||||
static Handle key_find(uintptr_t key, H_Type type, KeyRemoveFlag remove_option = KEY_NOREMOVE)
|
||||
{
|
||||
Key2Idx* key2idx = key2idx_get();
|
||||
if(!key2idx)
|
||||
return ERR_NO_MEM;
|
||||
|
||||
// initial return value: "not found at all, or it's of the
|
||||
// wrong type". the latter happens when called by h_alloc to
|
||||
// check if e.g. a Tex object already exists; at that time,
|
||||
// only the corresponding VFile exists.
|
||||
Handle ret = -1;
|
||||
|
||||
std::pair<It, It> range = key2idx->equal_range(key);
|
||||
for(It it = range.first; it != range.second; ++it)
|
||||
{
|
||||
i32 idx = it->second;
|
||||
@ -354,33 +408,42 @@ static Handle find_key(uintptr_t key, H_Type type, bool remove = false)
|
||||
// found match
|
||||
if(hd && hd->type == type && hd->key == key)
|
||||
{
|
||||
if(remove)
|
||||
key2idx.erase(it);
|
||||
return handle(idx, hd->tag);
|
||||
if(remove_option == KEY_REMOVE)
|
||||
key2idx->erase(it);
|
||||
ret = handle(idx, hd->tag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// not found at all, or it's of the wrong type.
|
||||
// the latter happens when called by h_alloc to check
|
||||
// if e.g. a Tex object already exists; at that time,
|
||||
// only the corresponding VFile exists.
|
||||
return -1;
|
||||
key2idx_lock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static void key_add(uintptr_t key, Handle h)
|
||||
{
|
||||
Key2Idx* key2idx = key2idx_get();
|
||||
if(!key2idx)
|
||||
return;
|
||||
|
||||
const i32 idx = h_idx(h);
|
||||
key2idx.insert(std::make_pair(key, idx));
|
||||
// note: MSDN documentation of stdext::hash_multimap is incorrect;
|
||||
// there is no overload of insert() that returns pair<iterator, bool>.
|
||||
(void)key2idx->insert(std::make_pair(key, idx));
|
||||
|
||||
key2idx_lock();
|
||||
}
|
||||
|
||||
|
||||
static void key_remove(uintptr_t key, H_Type type)
|
||||
{
|
||||
Handle ret = find_key(key, type, true);
|
||||
Handle ret = key_find(key, type, KEY_REMOVE);
|
||||
debug_assert(ret > 0);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//
|
||||
// path string suballocator (they're stored in HDATA)
|
||||
//
|
||||
@ -718,10 +781,12 @@ static int h_free_idx(i32 idx, HDATA* hd)
|
||||
fn = (slash && slash[1] != '\0')? slash+1 : hd->fn;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
char buf[H_STRING_LEN];
|
||||
if(vtbl->to_string(hd->user, buf) < 0)
|
||||
strcpy(buf, "(error)"); // safe
|
||||
debug_printf("H_MGR| free %s %s accesses=%d %s\n", hd->type->name, fn, hd->num_derefs, buf);
|
||||
#endif
|
||||
|
||||
fn_free(hd);
|
||||
|
||||
@ -847,7 +912,7 @@ int h_reload(const char* fn)
|
||||
|
||||
Handle h_find(H_Type type, uintptr_t key)
|
||||
{
|
||||
return find_key(key, type);
|
||||
return key_find(key, type);
|
||||
}
|
||||
|
||||
|
||||
@ -953,4 +1018,6 @@ void h_mgr_shutdown()
|
||||
}
|
||||
|
||||
fn_shutdown();
|
||||
|
||||
key2idx_shutdown();
|
||||
}
|
||||
|
@ -613,6 +613,67 @@ void ia32_get_cpu_info()
|
||||
#endif
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
int ia32_get_call_target(void* ret_addr, void** target)
|
||||
{
|
||||
*target = 0;
|
||||
|
||||
// points to end of the CALL instruction (which is of unknown length)
|
||||
const u8* c = (const u8*)ret_addr;
|
||||
// this would allow for avoiding exceptions when accessing ret_addr
|
||||
// close to the beginning of the code segment. it's not currently set
|
||||
// because this is really unlikely and not worth the trouble.
|
||||
const size_t len = ~0u;
|
||||
|
||||
// CALL rel32 (E8 cd)
|
||||
if(len >= 5 && c[-5] == 0xE8)
|
||||
{
|
||||
*target = (u8*)ret_addr + *(i32*)(c-4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// CALL r/m32 (FF /2)
|
||||
// .. CALL [r32 + r32*s] => FF 14 SIB
|
||||
if(len >= 3 && c[-3] == 0xFF && c[-2] == 0x14)
|
||||
return 1;
|
||||
// .. CALL [disp32] => FF 15 disp32
|
||||
if(len >= 6 && c[6] == 0xFF && c[-5] == 0x15)
|
||||
{
|
||||
void* addr_of_target = *(void**)(c-4);
|
||||
if(!debug_is_pointer_bogus(addr_of_target))
|
||||
{
|
||||
*target = *(void**)addr_of_target;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// .. CALL [r32] => FF 00-3F(!14/15)
|
||||
if(len >= 2 && c[-2] == 0xFF && c[-1] < 0x40 && c[-1] != 0x14 && c[-1] != 0x15)
|
||||
return 1;
|
||||
// .. CALL [r32 + r32*s + disp8] => FF 54 SIB disp8
|
||||
if(len >= 4 && c[-4] == 0xFF && c[-3] == 0x54)
|
||||
return 1;
|
||||
// .. CALL [r32 + disp8] => FF 50-57(!54) disp8
|
||||
if(len >= 3 && c[-3] == 0xFF && (c[-2] & 0xF8) == 0x50 && c[-2] != 0x54)
|
||||
return 1;
|
||||
// .. CALL [r32 + r32*s + disp32] => FF 94 SIB disp32
|
||||
if(len >= 7 && c[-7] == 0xFF && c[-6] == 0x94)
|
||||
return 1;
|
||||
// .. CALL [r32 + disp32] => FF 90-97(!94) disp32
|
||||
if(len >= 6 && c[-6] == 0xFF && (c[-5] & 0xF8) == 0x90 && c[-5] != 0x94)
|
||||
return 1;
|
||||
// .. CALL r32 => FF D0-D7
|
||||
if(len >= 2 && c[-2] == 0xFF && (c[-1] & 0xF8) == 0xD0)
|
||||
return 1;
|
||||
|
||||
return -3;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
// Assembler-optimized function for color conversion
|
||||
extern "C" {
|
||||
|
@ -113,6 +113,8 @@ extern void ia32_hook_capabilities(void);
|
||||
|
||||
// internal use only
|
||||
|
||||
extern int ia32_get_call_target(void* ret_addr, void** target);
|
||||
|
||||
// order in which registers are stored in regs array
|
||||
// (do not change! brand string relies on this ordering)
|
||||
enum IA32Regs
|
||||
|
@ -13,6 +13,17 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// pass "omit frame pointer" setting on to the compiler
|
||||
#if MSC_VERSION
|
||||
# if CONFIG_OMIT_FP
|
||||
# pragma optimize("y", on)
|
||||
# else
|
||||
# pragma optimize("y", off)
|
||||
# endif
|
||||
#elif GCC_VERSION
|
||||
// TODO
|
||||
#endif
|
||||
|
||||
// compiling without exceptions (usually for performance reasons);
|
||||
// tell STL not to generate any.
|
||||
#if CONFIG_DISABLE_EXCEPTIONS
|
||||
|
@ -154,30 +154,6 @@ void debug_heap_enable(DebugHeapChecks what)
|
||||
}
|
||||
|
||||
|
||||
// return 1 if the pointer appears to be totally bogus, otherwise 0.
|
||||
// this check is not authoritative (the pointer may be "valid" but incorrect)
|
||||
// but can be used to filter out obviously wrong values in a portable manner.
|
||||
int debug_is_pointer_bogus(const void* p)
|
||||
{
|
||||
#if CPU_IA32
|
||||
if(p < (void*)0x10000)
|
||||
return true;
|
||||
if(p >= (void*)(uintptr_t)0x80000000)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
// notes:
|
||||
// - we don't check alignment because nothing can be assumed about a
|
||||
// string pointer and we mustn't reject any actually valid pointers.
|
||||
// - nor do we bother checking the address against known stack/heap areas
|
||||
// because that doesn't cover everything (e.g. DLLs, VirtualAlloc).
|
||||
// - cannot use IsBadReadPtr because it accesses the mem
|
||||
// (false alarm for reserved address space).
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
@ -779,3 +755,74 @@ static int wdbg_init(void)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// return 1 if the pointer appears to be totally bogus, otherwise 0.
|
||||
// this check is not authoritative (the pointer may be "valid" but incorrect)
|
||||
// but can be used to filter out obviously wrong values in a portable manner.
|
||||
int debug_is_pointer_bogus(const void* p)
|
||||
{
|
||||
#if CPU_IA32
|
||||
if(p < (void*)0x10000)
|
||||
return true;
|
||||
if(p >= (void*)(uintptr_t)0x80000000)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
// notes:
|
||||
// - we don't check alignment because nothing can be assumed about a
|
||||
// string pointer and we mustn't reject any actually valid pointers.
|
||||
// - nor do we bother checking the address against known stack/heap areas
|
||||
// because that doesn't cover everything (e.g. DLLs, VirtualAlloc).
|
||||
// - cannot use IsBadReadPtr because it accesses the mem
|
||||
// (false alarm for reserved address space).
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool debug_is_code_ptr(void* p)
|
||||
{
|
||||
uintptr_t addr = (uintptr_t)p;
|
||||
// totally invalid pointer
|
||||
if(debug_is_pointer_bogus(p))
|
||||
return false;
|
||||
// comes before load address
|
||||
static const HMODULE base = GetModuleHandle(0);
|
||||
if(addr < (uintptr_t)base)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static NT_TIB* get_tib()
|
||||
{
|
||||
NT_TIB* tib;
|
||||
__asm
|
||||
{
|
||||
mov eax, fs:[NT_TIB.Self]
|
||||
mov [tib], eax
|
||||
}
|
||||
return tib;
|
||||
}
|
||||
|
||||
bool debug_is_stack_ptr(void* p)
|
||||
{
|
||||
uintptr_t addr = (uintptr_t)p;
|
||||
// totally invalid pointer
|
||||
if(debug_is_pointer_bogus(p))
|
||||
return false;
|
||||
// not aligned
|
||||
if(addr % sizeof(void*))
|
||||
return false;
|
||||
// out of bounds (note: IA32 stack grows downwards)
|
||||
NT_TIB* tib = get_tib();
|
||||
if(!(tib->StackLimit < p && p < tib->StackBase))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -259,9 +259,87 @@ int debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int*
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// stack walk via dbghelp
|
||||
// stack walk
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
|
||||
/*
|
||||
Subroutine linkage example code:
|
||||
|
||||
push param2
|
||||
push param1
|
||||
call func
|
||||
ret_addr:
|
||||
[..]
|
||||
|
||||
func:
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
sub esp, local_size
|
||||
[..]
|
||||
|
||||
Stack contents (down = decreasing address)
|
||||
[param2]
|
||||
[param1]
|
||||
ret_addr
|
||||
prev_ebp (<- current ebp points at this value)
|
||||
[local_variables]
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
call func1
|
||||
ret1:
|
||||
|
||||
func1:
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
call func2
|
||||
ret2:
|
||||
|
||||
func2:
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
STARTHERE
|
||||
|
||||
*/
|
||||
|
||||
static int ia32_walk_stack(STACKFRAME64* sf)
|
||||
{
|
||||
// read previous values from STACKFRAME64
|
||||
void* prev_fp = (void*)sf->AddrFrame .Offset;
|
||||
void* prev_ip = (void*)sf->AddrPC .Offset;
|
||||
void* prev_ret = (void*)sf->AddrReturn.Offset;
|
||||
if(!debug_is_stack_ptr(prev_fp))
|
||||
return -100;
|
||||
if(prev_ip && !debug_is_code_ptr(prev_ip))
|
||||
return -101;
|
||||
if(prev_ret && !debug_is_code_ptr(prev_ret))
|
||||
return -102;
|
||||
|
||||
// read stack frame
|
||||
void* fp = ((void**)prev_fp)[0];
|
||||
void* ret_addr = ((void**)prev_fp)[1];
|
||||
if(!debug_is_stack_ptr(fp))
|
||||
return -103;
|
||||
if(!debug_is_code_ptr(ret_addr))
|
||||
return -104;
|
||||
|
||||
void* target;
|
||||
int err = ia32_get_call_target(ret_addr, &target);
|
||||
RETURN_ERR(err);
|
||||
if(err == 0)
|
||||
debug_assert(debug_is_code_ptr(target));
|
||||
|
||||
sf->AddrFrame .Offset = (DWORD64)fp;
|
||||
sf->AddrPC .Offset = (DWORD64)target;
|
||||
sf->AddrReturn.Offset = (DWORD64)ret_addr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// called for each stack frame found by walk_stack, passing information
|
||||
// about the frame and <user_arg>.
|
||||
// return <= 0 to stop immediately and have walk_stack return that;
|
||||
@ -274,13 +352,10 @@ typedef int (*StackFrameCallback)(const STACKFRAME64*, void*);
|
||||
// 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 int walk_stack(StackFrameCallback cb, void* user_arg = 0, uint skip = 0, const CONTEXT* pcontext = 0)
|
||||
{
|
||||
sym_init();
|
||||
|
||||
const HANDLE hThread = GetCurrentThread();
|
||||
|
||||
// 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;
|
||||
@ -342,18 +417,52 @@ static int walk_stack(StackFrameCallback cb, void* user_arg = 0, uint skip = 0,
|
||||
sf.AddrStack.Offset = pcontext->SP_;
|
||||
sf.AddrStack.Mode = AddrModeFlat;
|
||||
|
||||
const HANDLE hThread = GetCurrentThread();
|
||||
|
||||
// for each stack frame found:
|
||||
int ret = WDBG_NO_STACK_FRAMES_FOUND;
|
||||
for(;;)
|
||||
{
|
||||
// rationale:
|
||||
// - provide a separate ia32 implementation so that simple
|
||||
// stack walks (e.g. to determine callers of malloc) do not
|
||||
// require firing up dbghelp. that takes tens of seconds when
|
||||
// OS symbols are installed (because symserv is wanting to access
|
||||
// inet), which is entirely unacceptable.
|
||||
// - VC7.1 sometimes generates stack frames despite /Oy ;
|
||||
// ia32_walk_stack may appear to work, but it isn't reliable in
|
||||
// this case and therefore must not be used!
|
||||
// - don't switch between ia32_stack_walk and StackWalk64 when one
|
||||
// of them fails: this needlessly complicates things. the ia32
|
||||
// code is authoritative provided its prerequisite (FP not omitted)
|
||||
// is met, otherwise totally unusable.
|
||||
int err;
|
||||
#if CPU_IA32 && !CONFIG_OMIT_FP
|
||||
err = ia32_walk_stack(&sf);
|
||||
#else
|
||||
sym_init();
|
||||
// note: unfortunately StackWalk64 doesn't always SetLastError,
|
||||
// so we have to reset it and check for 0. *sigh*
|
||||
SetLastError(0);
|
||||
BOOL ok = StackWalk64(machine, hProcess, hThread, &sf, (PVOID)pcontext,
|
||||
0, SymFunctionTableAccess64, SymGetModuleBase64, 0);
|
||||
if(ok)
|
||||
err = 0;
|
||||
else
|
||||
{
|
||||
err = GetLastError();
|
||||
if(err == 0)
|
||||
err = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// no more frames found - abort.
|
||||
// note: also test FP because StackWalk64 sometimes erroneously
|
||||
// reports success. unfortunately it doesn't SetLastError either,
|
||||
// so we can't indicate the cause of failure. *sigh*
|
||||
if(!ok || !sf.AddrFrame.Offset)
|
||||
void* ret_addr = (void*)(uintptr_t)sf.AddrReturn.Offset;
|
||||
void* fp = (void*)(uintptr_t)sf.AddrFrame .Offset;
|
||||
void* pc = (void*)(uintptr_t)sf.AddrPC .Offset;
|
||||
|
||||
// no more frames found - abort. note: also test FP because
|
||||
// StackWalk64 sometimes erroneously reports success.
|
||||
if(err < 0 || !fp)
|
||||
return ret;
|
||||
|
||||
if(skip)
|
||||
|
Loading…
Reference in New Issue
Block a user