1
1
forked from 0ad/0ad

debug: add alloca fallback in case heap alloc fails (no more "not enough mem to display stack trace")

bring debug_get_nth_caller in line with debug_dump_stack; clarify
parameter dox.
use debug_get_nth_caller instead of debug_dump_stack to get exception
locus (much less evil)
fix out() clamping if buffer is full - no more chicken scratching at end
of buffer

This was SVN commit r2474.
This commit is contained in:
janwas 2005-07-09 20:17:58 +00:00
parent f3bd639fcd
commit 37e7732b40
4 changed files with 156 additions and 89 deletions

View File

@ -351,19 +351,35 @@ ErrorReaction display_error(const wchar_t* description, int flags,
const char* filename = slash? slash+1 : file;
debug_wprintf(L"%hs(%d): %s\n", filename, line, description);
wchar_t* text;
const size_t MAX_CHARS = 512*1024;
void* mem = malloc(MAX_CHARS*sizeof(wchar_t));
if(mem)
// allocate memory for the stack trace. this needs to be quite large,
// so preallocating is undesirable. it must work even if the heap is
// corrupted (since that's an error we might want to display), so
// we cannot rely on the heap alloc alone. what we do is try malloc,
// fall back to alloca if it failed, and give up after that.
wchar_t* text = 0;
size_t max_chars = 256*KiB;
// .. try allocating from heap
void* heap_mem = malloc(max_chars*sizeof(wchar_t));
text = (wchar_t*)heap_mem;
// .. heap alloc failed; try allocating from stack
if(!text)
{
text = (wchar_t*)mem;
static const wchar_t fmt[] = L"%s\r\n\r\nCall stack:\r\n\r\n";
int len = swprintf(text, MAX_CHARS, fmt, description);
max_chars = 128*KiB; // (stack limit is usually 1 MiB)
text = (wchar_t*)alloca(max_chars*sizeof(wchar_t));
}
if(!context)
skip++; // skip this frame
debug_dump_stack(text+len, MAX_CHARS-len, skip, context);
// in-place
// alloc succeeded; proceed
if(text)
{
static const wchar_t fmt[] = L"%s\r\n\r\nCall stack:\r\n\r\n";
int len = swprintf(text, max_chars, fmt, description);
// paranoia - only dump stack if this string output succeeded.
if(len >= 0)
{
if(!context)
skip++; // skip this frame
debug_dump_stack(text+len, max_chars-len, skip, context);
}
}
else
text = L"(insufficient memory to display error message)";
@ -383,7 +399,7 @@ ErrorReaction display_error(const wchar_t* description, int flags,
er = ER_CONTINUE;
}
free(mem);
free(heap_mem); // no-op if not allocated from heap
// after debug_break to ease debugging, but before exit to avoid leak.
// exit requested. do so here to disburden callers.
@ -407,8 +423,13 @@ ErrorReaction display_error(const wchar_t* description, int flags,
// local variables.
ErrorReaction debug_assert_failed(const char* file, int line, const char* expr)
{
// __FILE__ evaluates to the full path (albeit without drive letter)
// which is rather long. we only display the base name for clarity.
const char* slash = strrchr(file, DIR_SEP);
const char* base_name = slash? slash+1 : file;
uint skip = 1; void* context = 0;
wchar_t buf[200];
swprintf(buf, ARRAY_SIZE(buf), L"Assertion failed in %hs, line %d: \"%hs\"", file, line, expr);
return display_error(buf, DE_ALLOW_SUPPRESS|DE_MANUAL_BREAK, skip, context, file, line);
swprintf(buf, ARRAY_SIZE(buf), L"Assertion failed in %hs, line %d: \"%hs\"", base_name, line, expr);
return display_error(buf, DE_ALLOW_SUPPRESS|DE_MANUAL_BREAK, skip, context, base_name, line);
}

View File

@ -210,7 +210,8 @@ const size_t DBG_FILE_LEN = 100;
// output parameters are optional; we pass back as much information as is
// available and desired. return 0 iff any information was successfully
// retrieved and stored.
// sym_name and file must hold at least the number of chars above.
// sym_name and file must hold at least the number of chars above;
// file is the base name only, not path (see rationale in wdbg_sym).
// the PDB implementation is rather slow (~500µs).
extern int debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int* line);
@ -219,9 +220,9 @@ extern int debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* fil
// platform-specific representation of execution state (e.g. Win32 CONTEXT)
// and tracing starts there; this is useful for exceptions.
// otherwise, tracing starts at the current stack position, and the given
// number of stack frames (i.e. functions) are skipped. this prevents
// functions like debug_assert_failed from cluttering up the trace.
// returns the buffer for convenience.
// number of stack frames (i.e. functions) above the caller are skipped.
// this prevents functions like debug_assert_failed from
// cluttering up the trace. returns the buffer for convenience.
extern const wchar_t* debug_dump_stack(wchar_t* buf, size_t max_chars, uint skip, void* context);
@ -232,12 +233,15 @@ extern const wchar_t* debug_dump_stack(wchar_t* buf, size_t max_chars, uint skip
// abstraction of all STL iterators used by debug_stl.
typedef const u8* (*DebugIterator)(void* internal, size_t el_size);
// examine call stack and return the address of the Nth parent/caller of
// the function from which this is called. n starts at one, which denotes
// the immediate caller.
// return address of the Nth function on the call stack.
// if <context> is nonzero, it is assumed to be a platform-specific
// representation of execution state (e.g. Win32 CONTEXT) and tracing
// starts there; this is useful for exceptions.
// otherwise, tracing starts at the current stack position, and the given
// number of stack frames (i.e. functions) above the caller are skipped.
// used by mmgr to determine what function requested each allocation;
// this is fast enough to allow that.
extern void* debug_get_nth_caller(uint n);
extern void* debug_get_nth_caller(uint skip, void* context);
// return 1 if the pointer appears to be totally bogus, otherwise 0.
// this check is not authoritative in that the pointer may in truth

View File

@ -932,19 +932,23 @@ static const wchar_t* get_exception_description(const EXCEPTION_POINTERS* ep)
static const wchar_t* get_exception_locus(const EXCEPTION_POINTERS* ep)
{
// HACK: <ep> provides no useful information - ExceptionAddress always
// points to kernel32!RaiseException. we use debug_dump_stack to determine the
// real location.
// points to kernel32!RaiseException. we use debug_get_nth_caller to
// determine the real location.
wchar_t buf[1000];
// we only want the beginning and this is guarded against overflow.
const wchar_t* stack_trace = debug_dump_stack(buf, ARRAY_SIZE(buf), 1, ep->ContextRecord);
void* func = debug_get_nth_caller(1, ep->ContextRecord);
// skip RaiseException
const size_t MAX_LOCUS_CHARS = 256;
static wchar_t locus[MAX_LOCUS_CHARS];
wcsncpy_s(locus, MAX_LOCUS_CHARS, stack_trace, MAX_LOCUS_CHARS-1);
wchar_t* end = wcschr(locus, '\r');
if(end)
*end = '\0';
char func_name[DBG_SYMBOL_LEN];
char file[DBG_FILE_LEN] = {0};
int line = 0;
(void)debug_resolve_symbol(func, func_name, file, &line);
// note: file is the base path only (no drive letter), so there are
// no problems with wdbg_exception_filter's "%[^:]" format string.
// note: keep formatting in sync with wdbg_exception_filter, which
// extracts file/line for use with display_error.
static wchar_t locus[256];
swprintf(locus, ARRAY_SIZE(locus), L"%hs (%hs:%d)", func_name, file, line);
return locus;
}
@ -1019,7 +1023,7 @@ LONG WINAPI wdbg_exception_filter(EXCEPTION_POINTERS* ep)
const wchar_t* locus = get_exception_locus (ep);
wchar_t func_name[DBG_SYMBOL_LEN]; char file[DBG_FILE_LEN] = {0}; int line = 0; wchar_t fmt[50];
swprintf(fmt, ARRAY_SIZE(fmt), L"%%%ds %%%dhs (%%d)", DBG_SYMBOL_LEN, DBG_FILE_LEN);
swprintf(fmt, ARRAY_SIZE(fmt), L"%%%ds (%%%dh[^:]:%%d)", DBG_SYMBOL_LEN, DBG_FILE_LEN);
// bake in the string limits (future-proof)
if(swscanf(locus, fmt, func_name, file, &line) != 3)
debug_warn("error extracting file/line from exception locus");

View File

@ -188,7 +188,8 @@ struct TI_FINDCHILDREN_PARAMS2
// output parameters are optional; we pass back as much information as is
// available and desired. return 0 iff any information was successfully
// retrieved and stored.
// sym_name and file must hold at least the number of chars above.
// sym_name and file must hold at least the number of chars above;
// file is the base name only, not path (see rationale in wdbg_sym).
// the PDB implementation is rather slow (~500µs).
int debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int* line)
{
@ -199,7 +200,7 @@ int debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int*
lock();
// get symbol name
// get symbol name (if requested)
if(sym_name)
{
sym_name[0] = '\0';
@ -212,18 +213,34 @@ int debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int*
}
}
// get source file + line number
// get source file and/or line number (if requested)
if(file || line)
{
IMAGEHLP_LINE64 line_info = { sizeof(IMAGEHLP_LINE64) };
DWORD displacement; // unused but required by SymGetLineFromAddr64!
if(SymGetLineFromAddr64(hProcess, addr, &displacement, &line_info))
successes++;
// note: were left zeroed if SymGetLineFromAddr64 failed
if(file)
snprintf(file, DBG_FILE_LEN, "%s", line_info.FileName);
if(line)
*line = line_info.LineNumber;
{
if(file)
{
// strip full path down to base name only.
// this loses information, but that isn't expected to be a
// problem and is balanced by not having to do this from every
// call site (full path is too long to display nicely).
const char* base_name = line_info.FileName;
const char* slash = strrchr(base_name, DIR_SEP);
if(slash)
base_name = slash+1;
snprintf(file, DBG_FILE_LEN, "%s", base_name);
successes++;
}
if(line)
{
*line = line_info.LineNumber;
successes++;
}
}
}
unlock();
@ -440,24 +457,30 @@ static int nth_caller_cb(const STACKFRAME64* sf, void* user_arg)
}
// examine call stack and return the address of the Nth parent/caller of
// the function from which this is called. n starts at one, which denotes
// the immediate caller.
// return address of the Nth function on the call stack.
// if <context> is nonzero, it is assumed to be a platform-specific
// representation of execution state (e.g. Win32 CONTEXT) and tracing
// starts there; this is useful for exceptions.
// otherwise, tracing starts at the current stack position, and the given
// number of stack frames (i.e. functions) above the caller are skipped.
// used by mmgr to determine what function requested each allocation;
// this is fast enough to allow that.
void* debug_get_nth_caller(uint n)
void* debug_get_nth_caller(uint skip, void* pcontext)
{
debug_assert(n >= 1);
if(!pcontext)
skip++; // skip this frame
void* func; // set by callback
const uint skip = n-1 + 3;
// make 0-based; skip walk_stack, debug_get_nth_caller and its caller.
if(walk_stack(nth_caller_cb, &func, skip) == 0)
return func;
return 0;
lock();
void* func;
int err = walk_stack(nth_caller_cb, &func, skip, (const CONTEXT*)pcontext);
unlock();
return (err == 0)? func : 0;
}
//////////////////////////////////////////////////////////////////////////////
//
// helper routines for symbol value dump
@ -482,8 +505,9 @@ struct DumpState
}
};
//----------------------------------------------------------------------------
static ssize_t out_chars_left;
static size_t out_chars_left;
static bool out_have_warned_of_overflow;
// only do so once until next out_init to avoid flood of messages.
static wchar_t* out_pos;
@ -508,47 +532,63 @@ static bool out_have_warned_of_limit;
static void out_init(wchar_t* buf, size_t max_chars)
{
out_pos = buf;
out_chars_left = (ssize_t)max_chars;
out_chars_left = max_chars;
out_have_warned_of_overflow = false;
out_have_warned_of_limit = false;
}
static void out(const wchar_t* fmt, ...)
{
const size_t MAX_OUT_CHARS = 1000;
// we assume each out() call will produce less than MAX_OUT_CHARS.
// if there's not that much left, overflow would be possible, so we bail.
// false alarms are possible but we never actually overflow.
if(out_chars_left < MAX_OUT_CHARS)
{
if(!out_have_warned_of_overflow)
{
debug_printf("out: out of room; disabled until next out_init\n");
out_have_warned_of_overflow = true;
}
return;
};
va_list args;
va_start(args, fmt);
int len = vswprintf(out_pos, MAX_OUT_CHARS, fmt, args);
int len = _vsnwprintf(out_pos, out_chars_left, fmt, args);
va_end(args);
// limit exceeded; shouldn't happen, but we're careful.
if(len < 0)
// success
if(len >= 0)
{
debug_warn("out: arbitrary limit of 1000 exceeded; why?");
len = MAX_OUT_CHARS;
out_pos += len;
// make sure out_chars_left remains nonnegative
if((size_t)len > out_chars_left)
{
debug_warn("out: apparently wrote more than out_chars_left");
len = (int)out_chars_left;
}
out_chars_left -= len;
}
// no more room left
else
{
// the buffer really is full yet out_chars_left may not be 0
// (since it isn't updated if _vsnwprintf returns -1).
// must be set so subsequent calls don't try to squeeze stuff in.
out_chars_left = 0;
out_pos += len;
out_chars_left -= len;
// write a warning into the output buffer (once) so it isn't
// abruptly cut off (which looks like an error)
if(!out_have_warned_of_overflow)
{
out_have_warned_of_overflow = true;
// with the current out_pos / out_chars_left variables, there's
// no way of knowing where the buffer actually ends. no matter;
// we'll just put the warning before out_pos and eat into the
// second newest text.
const wchar_t text[] = L"(no more room in buffer)";
wcscpy(out_pos-ARRAY_SIZE(text), text); // safe
}
}
}
static void out_erase(size_t num_chars)
{
// don't do anything if end of buffer was hit (prevents repeatedly
// scribbling over the last few bytes).
if(out_have_warned_of_overflow)
return;
out_chars_left += (ssize_t)num_chars;
out_pos -= num_chars;
*out_pos = '\0';
@ -579,6 +619,7 @@ static int out_check_limit()
return 0;
}
//----------------------------------------------------------------------------
#define INDENT STMT(for(uint i = 0; i <= state.level; i++) out(L" ");)
#define UNINDENT STMT(out_erase((state.level+1)*4);)
@ -1760,14 +1801,12 @@ struct IMAGEHLP_STACK_FRAME2 : public IMAGEHLP_STACK_FRAME
// called by walk_stack for each stack frame
static int dump_frame_cb(const STACKFRAME64* sf, void* user_arg)
{
// note: keep frame formatting in sync with get_exception_locus.
UNUSED(user_arg);
current_stackframe64 = sf;
void* func = (void*)sf->AddrPC.Offset;
char func_name[DBG_SYMBOL_LEN]; char path[DBG_FILE_LEN]; int line;
if(debug_resolve_symbol(func, func_name, path, &line) == 0)
char func_name[DBG_SYMBOL_LEN]; char file[DBG_FILE_LEN]; int line;
if(debug_resolve_symbol(func, func_name, file, &line) == 0)
{
// don't trace back further than the app's entry point
// (noone wants to see this frame). checking for the
@ -1777,9 +1816,7 @@ static int dump_frame_cb(const STACKFRAME64* sf, void* user_arg)
if(!strcmp(func_name, "_BaseProcessStart@4"))
return 0;
const char* slash = strrchr(path, DIR_SEP);
const char* file = slash? slash+1 : path;
out(L"%hs %hs (%lu)\r\n", func_name, file, line);
out(L"%hs (%hs:%d)\r\n", func_name, file, line);
}
else
out(L"%p\r\n", func);
@ -1806,9 +1843,9 @@ static int dump_frame_cb(const STACKFRAME64* sf, void* user_arg)
// platform-specific representation of execution state (e.g. Win32 CONTEXT)
// and tracing starts there; this is useful for exceptions.
// otherwise, tracing starts at the current stack position, and the given
// number of stack frames (i.e. functions) are skipped. this prevents
// functions like debug_assert_failed from cluttering up the trace.
// returns the buffer for convenience.
// number of stack frames (i.e. functions) above the caller are skipped.
// this prevents functions like debug_assert_failed from
// cluttering up the trace. returns the buffer for convenience.
const wchar_t* debug_dump_stack(wchar_t* buf, size_t max_chars, uint skip, void* pcontext)
{
static uintptr_t already_in_progress;
@ -1920,7 +1957,8 @@ static void test_array()
int ints[] = { 1,2,3,4,5 };
wchar_t chars[] = { 'w','c','h','a','r','s',0 };
DISPLAY_ERROR(L"wdbg_sym self test: check if stack trace below is ok.");
//DISPLAY_ERROR(L"wdbg_sym self test: check if stack trace below is ok.");
RaiseException(0xf001,0,0,0);
}
// also used by test_stl as an element type