1
0
forked from 0ad/0ad

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:
janwas 2005-10-29 02:32:36 +00:00
parent e8540b29bd
commit 8c437850e6
9 changed files with 368 additions and 51 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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