2005-01-26 00:55:49 +01:00
// stack trace, improved assert and exception handler
// Copyright (c) 2002-2005 Jan Wassenberg
2005-01-23 19:07:34 +01:00
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
2004-06-19 16:54:25 +02:00
# include "precompiled.h"
# include <stdlib.h>
# include <stdio.h>
# include "lib.h"
# include "win_internal.h"
2005-01-25 19:42:42 +01:00
# define _NO_CVCONST_H // request SymTagEnum be defined
2005-01-25 19:54:48 +01:00
# include "dbghelp.h"
2005-01-25 19:42:42 +01:00
# include <OAIdl.h> // VARIANT
2004-06-19 16:54:25 +02:00
2004-06-19 16:57:29 +02:00
# include "wdbg.h"
# include "assert_dlg.h"
2005-01-23 19:07:34 +01:00
# ifdef _MSC_VER
# pragma comment(lib, "dbghelp.lib")
# endif
2005-01-26 00:55:49 +01:00
2005-01-23 19:07:34 +01:00
// automatic module init (before main) and shutdown (before termination)
# pragma data_seg(".LIB$WCC")
WIN_REGISTER_FUNC ( wdbg_init ) ;
# pragma data_seg(".LIB$WTB")
WIN_REGISTER_FUNC ( wdbg_shutdown ) ;
# pragma data_seg()
2004-06-19 16:54:25 +02:00
2004-08-05 21:21:16 +02:00
2005-01-23 19:07:34 +01:00
static void set_exception_handler ( ) ;
2004-06-19 16:54:25 +02:00
2005-01-26 00:55:49 +01:00
//
// globals, set by wdbg_init
//
2004-06-19 16:54:25 +02:00
2005-01-26 00:55:49 +01:00
// our instance (= load address = 0x400000 on Win32). used by
// CreateDialogParam to locate the "program error" dialog resource.
2005-01-25 19:42:42 +01:00
static HINSTANCE hInstance ;
2005-01-26 00:55:49 +01:00
// passed to all dbghelp symbol query functions. we're not interested in
// resolving symbols in other processes; the purpose here is only to
// generate a stack trace. if that changes, we need to init a local copy
// of these in dump_sym_cb and pass them to all subsequent dump_*.
2004-06-19 16:54:25 +02:00
static HANDLE hProcess ;
2005-01-26 00:55:49 +01:00
static ULONG64 mod_base ;
2004-06-19 16:54:25 +02:00
2005-01-26 00:55:49 +01:00
// for StackWalk64; taken from PE header by wdbg_init
2005-01-23 19:07:34 +01:00
static WORD machine ;
static int wdbg_init ( )
{
2005-01-26 00:55:49 +01:00
// we don't want wrap the contents of main() in a __try block
// (platform-specific), and regular C++ exceptions don't catch
// everything. therefore, install an unhandled exception filter here.
// it won't be called if the program is being debugged, so to test it,
// wrap the call to main() in win.cpp!WinMain in a __try block.
2005-01-23 19:07:34 +01:00
set_exception_handler ( ) ;
hProcess = GetCurrentProcess ( ) ;
2005-01-25 19:42:42 +01:00
hInstance = GetModuleHandle ( 0 ) ;
2005-01-23 19:07:34 +01:00
SymSetOptions ( SYMOPT_DEFERRED_LOADS ) ;
SymInitialize ( hProcess , 0 , TRUE ) ;
2005-01-26 00:55:49 +01:00
mod_base = SymGetModuleBase64 ( hProcess , ( u64 ) & wdbg_init ) ;
IMAGE_NT_HEADERS * header = ImageNtHeader ( ( void * ) mod_base ) ;
2005-01-23 19:07:34 +01:00
machine = header - > FileHeader . Machine ;
return 0 ;
}
2005-01-26 00:55:49 +01:00
2005-01-23 19:07:34 +01:00
static int wdbg_shutdown ( void )
{
SymCleanup ( hProcess ) ;
return 0 ;
}
//////////////////////////////////////////////////////////////////////////////
2004-07-17 19:09:33 +02:00
2005-01-25 19:42:42 +01:00
// need to shoehorn printf-style variable params into
// the OutputDebugString call.
// - don't want to split into multiple calls - would add newlines to output.
// - fixing Win32 _vsnprintf to return # characters that would be written,
// as required by C99, looks difficult and unnecessary. if any other code
// needs that, implement GNU vasprintf.
// - fixed size buffers aren't nice, but much simpler than vasprintf-style
// allocate+expand_until_it_fits. these calls are for quick debug output,
// not loads of data, anyway.
// max output size of 1 call of (w)debug_out (including \0)
2005-01-26 00:55:49 +01:00
static const int MAX_CNT = 512 ;
2004-06-19 16:54:25 +02:00
2005-01-23 19:07:34 +01:00
void debug_out ( const char * fmt , . . . )
2004-06-19 16:54:25 +02:00
{
2005-01-23 19:07:34 +01:00
char buf [ MAX_CNT ] ;
buf [ MAX_CNT - 1 ] = ' \0 ' ;
va_list ap ;
va_start ( ap , fmt ) ;
vsnprintf ( buf , MAX_CNT - 1 , fmt , ap ) ;
va_end ( ap ) ;
OutputDebugString ( buf ) ;
2004-06-19 16:54:25 +02:00
}
2005-01-23 19:07:34 +01:00
void wdebug_out ( const wchar_t * fmt , . . . )
2004-06-19 16:54:25 +02:00
{
2005-01-23 19:07:34 +01:00
wchar_t buf [ MAX_CNT ] ;
buf [ MAX_CNT - 1 ] = L ' \0 ' ;
va_list ap ;
va_start ( ap , fmt ) ;
vsnwprintf ( buf , MAX_CNT - 1 , fmt , ap ) ;
va_end ( ap ) ;
2004-06-19 16:54:25 +02:00
2005-01-23 19:07:34 +01:00
OutputDebugStringW ( buf ) ;
}
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
//////////////////////////////////////////////////////////////////////////////
//
// dbghelp support routines for walking the stack
//
2005-01-23 19:07:34 +01:00
//////////////////////////////////////////////////////////////////////////////
2004-06-19 16:54:25 +02:00
2005-01-26 00:55:49 +01:00
// dbghelp isn't thread-safe. lock is taken by walk_stack,
// debug_resolve_symbol, and while dumping a stack frame (dump_frame_cb).
static void lock ( )
{
win_lock ( DBGHELP_CS ) ;
}
static void unlock ( )
{
win_unlock ( DBGHELP_CS ) ;
}
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// iterate over a call stack.
// if <thread_context> != 0, we start there; otherwise, at the current
// stack frame. call <cb> for each stack frame found, also passing the
// user-specified <ctx>. if it returns <= 0, we stop immediately and
// pass on that value; otherwise, the eventual return value is -1
// ("callback never succeeded").
//
// note: can't just pass function's address to the callback -
// dump_frame_cb needs the frame pointer for reg-relative variables.
static int walk_stack ( int ( * cb ) ( STACKFRAME64 * , void * ) , void * ctx , CONTEXT * thread_context = 0 )
{
HANDLE hThread = GetCurrentThread ( ) ;
// we need to set STACKFRAME64.AddrPC and AddrFrame for the initial
// StackWalk call in our loop. there is no portable way to do this,
// since CONTEXT is platform-specific. if the caller passed in a thread
// context (e.g. if calling from an exception handler), we use that;
// otherwise, determine the current PC / frame pointer ourselves.
// GetThreadContext is documented not to work if the current thread
// is running, but that seems to be current practice. Regardless, we
// avoid using it, since simple asm code is safer.
STACKFRAME64 frame ;
memset ( & frame , 0 , sizeof ( frame ) ) ;
# if defined(_M_AMD64)
DWORD64 rip_ , rbp_ ;
if ( thread_context )
{
rip_ = thread_context - > Rip ;
rbp_ = thread_context - > Rbp ;
}
else
{
__asm
{
cur_rip :
mov rax , offset cur_rip
mov [ rip_ ] , rax
mov [ rbp_ ] , rbp
}
}
frame . AddrPC . Offset = rip_ ;
frame . AddrPC . Mode = AddrModeFlat ;
frame . AddrFrame . Offset = rbp_ ;
frame . AddrFrame . Mode = AddrModeFlat ;
# elif defined(_M_IX86)
DWORD eip_ , ebp_ ;
if ( thread_context )
{
eip_ = thread_context - > Eip ;
ebp_ = thread_context - > Ebp ;
}
else
{
__asm
{
cur_eip :
mov eax , offset cur_eip
mov [ eip_ ] , eax
mov [ ebp_ ] , ebp
}
}
frame . AddrPC . Offset = eip_ ;
frame . AddrPC . Mode = AddrModeFlat ;
frame . AddrFrame . Offset = ebp_ ;
frame . AddrFrame . Mode = AddrModeFlat ;
# else
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
# error "TODO: set STACKFRAME64.AddrPC, AddrFrame for this platform"
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
# endif
// for each stack frame found:
for ( ; ; )
{
lock ( ) ;
BOOL ok = StackWalk64 ( machine , hProcess , hThread , & frame , thread_context , 0 , SymFunctionTableAccess64 , SymGetModuleBase64 , 0 ) ;
unlock ( ) ;
// no more frames found, and callback never succeeded; abort.
if ( ! ok )
return - 1 ;
2005-01-23 19:07:34 +01:00
2005-01-25 19:42:42 +01:00
void * func = ( void * ) frame . AddrPC . Offset ;
2005-01-23 19:07:34 +01:00
2005-01-25 19:42:42 +01:00
int ret = cb ( & frame , ctx ) ;
// callback reports it's done; stop calling it and return that value.
// (can be 0 for success, or a negative error code)
if ( ret < = 0 )
return ret ;
}
}
2005-01-23 19:07:34 +01:00
2005-01-25 19:42:42 +01:00
// ~500�s
int debug_resolve_symbol ( void * ptr_of_interest , char * sym_name , char * file , int * line )
2004-06-19 16:54:25 +02:00
{
2005-01-25 19:42:42 +01:00
int ret = - 1 ;
lock ( ) ;
{
const DWORD64 addr = ( DWORD64 ) ptr_of_interest ;
// get symbol name
SYMBOL_INFO_PACKAGE sp ;
SYMBOL_INFO * sym = & sp . si ;
sym - > SizeOfStruct = sizeof ( sp . si ) ;
sym - > MaxNameLen = MAX_SYM_NAME ;
if ( ! SymFromAddr ( hProcess , addr , 0 , sym ) )
goto fail ;
sprintf ( sym_name , " %s " , sym - > Name ) ;
// get source file + line number
IMAGEHLP_LINE64 line_info = { sizeof ( IMAGEHLP_LINE64 ) } ;
DWORD displacement ; // unused but required by SymGetLineFromAddr64!
if ( ! SymGetLineFromAddr64 ( hProcess , addr , & displacement , & line_info ) )
goto fail ;
sprintf ( file , " %s " , line_info . FileName ) ;
* line = line_info . LineNumber ;
ret = 0 ;
}
fail :
unlock ( ) ;
return ret ;
}
//////////////////////////////////////////////////////////////////////////////
//
// get address of Nth function above us on the call stack (uses walk_stack)
//
//////////////////////////////////////////////////////////////////////////////
struct NthCallerParams
{
int left_to_skip ;
void * func ;
} ;
// called by walk_stack for each stack frame
static int nth_caller_cb ( STACKFRAME64 * frame , void * ctx )
{
NthCallerParams * p = ( NthCallerParams * ) ctx ;
// not the one we want yet
if ( p - > left_to_skip > 0 )
{
p - > left_to_skip - - ;
return 1 ; // keep calling
}
// return its address
p - > func = ( void * ) frame - > AddrPC . Offset ;
return 0 ;
}
// n starts at 1
void * debug_get_nth_caller ( uint n )
{
NthCallerParams params = { ( int ) n - 1 + 3 , 0 } ;
// skip walk_stack, debug_get_nth_caller and its caller
if ( walk_stack ( nth_caller_cb , & params ) = = 0 )
return params . func ;
return 0 ;
}
//////////////////////////////////////////////////////////////////////////////
//
// helper routines for symbol value dump
//
//////////////////////////////////////////////////////////////////////////////
static const size_t DUMP_BUF_SIZE = 64000 ;
static wchar_t buf [ DUMP_BUF_SIZE ] ; // buffer for stack trace
static wchar_t * pos ; // current pos in buf
static void out ( const wchar_t * fmt , . . . )
{
// Don't overflow the buffer (and abort if we're about to)
if ( pos - buf + 1000 > DUMP_BUF_SIZE )
{
debug_warn ( " " ) ;
return ;
} ;
va_list args ;
va_start ( args , fmt ) ;
pos + = vswprintf ( pos , 1000 , fmt , args ) ;
va_end ( args ) ;
}
2005-01-26 00:55:49 +01:00
// does it look like an ASCII string is located at <addr>?
2005-01-25 19:42:42 +01:00
// set <stride> to 2 to search for WCS-2 strings (of western characters!).
// called by dump_ptr and dump_array for their string special-cases.
//
// algorithm: scan the "string" and count # text chars vs. garbage.
static bool is_string_ptr ( u64 addr , size_t stride = 1 )
{
// completely bogus on IA32; save ourselves the segfault (slow).
2004-06-19 16:54:25 +02:00
# ifdef _WIN32
2005-01-25 19:42:42 +01:00
if ( addr < 0x10000 | | addr > 0xc0000000 )
2004-06-19 16:54:25 +02:00
return false ;
# endif
const char * str = ( const char * ) addr ;
__try
{
int score = 0 ;
2005-01-25 19:42:42 +01:00
2004-06-19 16:54:25 +02:00
for ( ; ; )
{
2005-01-25 19:42:42 +01:00
// current character is:
const int c = * str & 0xff ; // prevent sign extension
// .. text
2004-06-19 16:54:25 +02:00
if ( isalnum ( c ) )
2005-01-25 19:42:42 +01:00
score + = 2 ;
// .. end of string
2004-06-19 16:54:25 +02:00
else if ( ! c )
2005-01-25 19:42:42 +01:00
break ;
// .. garbage
2004-06-19 16:54:25 +02:00
else if ( ! isprint ( c ) )
score - = 5 ;
2005-01-25 19:42:42 +01:00
// too much garbage found, probably not a string.
// abort fairly early so we don't segfault unnecessarily (slow).
2004-06-19 16:54:25 +02:00
if ( score < = - 10 )
2005-01-25 19:42:42 +01:00
break ;
str + = stride ;
2004-06-19 16:54:25 +02:00
}
2005-01-25 19:42:42 +01:00
return ( score > 0 ) ;
2004-06-19 16:54:25 +02:00
}
__except ( 1 )
{
return false ;
}
}
2005-01-25 19:42:42 +01:00
// zero-extend <size> (truncated to 8) bytes of little-endian data to u64,
// starting at address <p> (need not be aligned).
static u64 movzx_64le ( const u8 * p , size_t size )
2004-06-19 16:54:25 +02:00
{
2005-01-25 19:42:42 +01:00
if ( size > 8 )
size = 8 ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
u64 data = 0 ;
for ( u64 i = 0 ; i < MIN ( size , 8 ) ; i + + )
data | = ( ( u64 ) p [ i ] ) < < ( i * 8 ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
return data ;
}
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// sign-extend <size> (truncated to 8) bytes of little-endian data to i64,
// starting at address <p> (need not be aligned).
static i64 movsx_64le ( const u8 * p , size_t size )
{
if ( size > 8 )
size = 8 ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
u64 data = movzx_64le ( p , size ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// no point in sign-extending if >= 8 bytes were requested
if ( size < 8 )
2004-06-19 16:54:25 +02:00
{
2005-01-25 19:42:42 +01:00
u64 sign_bit = 1 ;
sign_bit < < = ( size * 8 ) - 1 ;
// be sure that we don't shift more than variable's bit width
// number would be negative in the smaller type,
// so sign-extend, i.e. set all more significant bits.
if ( data & sign_bit )
{
const u64 size_mask = ( sign_bit + sign_bit ) - 1 ;
data | = ~ size_mask ;
}
2004-06-19 16:54:25 +02:00
}
2005-01-25 19:42:42 +01:00
return ( i64 ) data ;
}
2004-07-17 19:09:33 +02:00
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
//////////////////////////////////////////////////////////////////////////////
//
// output values of specific types of local variables
//
//////////////////////////////////////////////////////////////////////////////
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// forward decl; called by dump_udt.
static int dump_data_sym ( DWORD data_idx , const u8 * p , uint level ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// forward decl; called by dump_array.
static int dump_type_sym ( DWORD type_idx , const u8 * p , uint level ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// these functions return -1 if they're not able to produce any reasonable
// output; dump_type_sym will display value as "?"
2004-06-19 16:54:25 +02:00
2004-07-17 19:09:33 +02:00
2005-01-25 19:42:42 +01:00
// <type_id> is a SymTagPointerType; output its value.
// called by dump_type_sym; lock is held.
static int dump_ptr ( const u8 * p , size_t size )
{
const u64 data = movzx_64le ( p , size ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
const wchar_t * fmt ;
// char*
if ( is_string_ptr ( data , sizeof ( char ) ) )
fmt = L " \" %hs \" " ;
// WCHAR*
else if ( is_string_ptr ( data , sizeof ( WCHAR ) ) )
fmt = L " \" %s \" " ;
// generic 32-bit pointer
else if ( size = = 4 )
fmt = L " 0x%08X " ;
// generic 64-bit pointer
else
fmt = L " 0x%I64016X " ;
out ( fmt , data ) ;
return 0 ;
2004-06-19 16:54:25 +02:00
}
2005-01-25 19:42:42 +01:00
//////////////////////////////////////////////////////////////////////////////
// <type_id> is a SymTagBaseType; output its value.
// called by dump_type_sym; lock is held.
static int dump_base_type ( DWORD type_idx , const u8 * p , size_t size , uint level )
2004-06-19 16:54:25 +02:00
{
2005-01-25 19:42:42 +01:00
UNUSED ( level ) ;
DWORD base_type ;
if ( ! SymGetTypeInfo ( hProcess , mod_base , type_idx , TI_GET_BASETYPE , & base_type ) )
return - 1 ;
u64 data = movzx_64le ( p , size ) ;
// single out() call
// (note: passing pointers in u64 assumes little-endian)
const wchar_t * fmt ;
switch ( base_type )
2004-06-19 16:54:25 +02:00
{
2005-01-25 19:42:42 +01:00
case btBool :
assert ( size = = sizeof ( bool ) ) ;
fmt = L " %hs " ;
data = ( u64 ) ( data ? " true " : " false " ) ;
break ;
2004-07-15 21:12:54 +02:00
2005-01-25 19:42:42 +01:00
case btFloat :
if ( size = = sizeof ( float ) )
fmt = L " %f " ;
else if ( size = = sizeof ( double ) )
fmt = L " %lf " ;
else
assert ( 0 ) ;
break ;
2004-07-15 21:12:54 +02:00
2005-01-25 19:42:42 +01:00
// signed integers
case btInt :
case btLong :
data = movsx_64le ( p , size ) ;
2005-01-26 17:25:56 +01:00
if ( size = = 1 | | size = = 2 | | size = = 4 )
2005-01-25 19:42:42 +01:00
fmt = L " %d " ;
else if ( size = = 8 )
fmt = L " %I64d " ;
else
assert ( 0 ) ;
break ;
2004-07-17 19:09:33 +02:00
2005-01-25 19:42:42 +01:00
// unsigned integers (displayed as hex)
case btUInt :
case btULong :
2005-01-26 17:25:56 +01:00
if ( size = = 1 )
fmt = L " 0x%02X " ;
else if ( size = = 2 )
2005-01-25 19:42:42 +01:00
fmt = L " 0x%04X " ;
else if ( size = = 4 )
fmt = L " 0x%08X " ;
else if ( size = = 8 )
fmt = L " 0x%016I64X " ;
else
assert ( 0 ) ;
break ;
2004-07-17 19:09:33 +02:00
2005-01-25 19:42:42 +01:00
// either 8-bit integer or character (character value appended below)
case btChar :
assert ( size = = sizeof ( char ) ) ;
fmt = L " %I64d " ;
break ;
case btWChar :
assert ( size = = sizeof ( wchar_t ) ) ;
fmt = L " %c " ;
break ;
// shouldn't happen
case btNoType :
case btVoid :
default :
//-fallthrough
// unsupported complex types
case btBCD :
case btCurrency :
case btDate :
case btVariant :
case btComplex :
case btBit :
case btBSTR :
case btHresult :
return - 1 ;
2004-06-19 16:54:25 +02:00
}
2005-01-25 19:42:42 +01:00
out ( fmt , data ) ;
2004-07-15 21:12:54 +02:00
2005-01-26 17:25:56 +01:00
// if the current value is a printable character, display in that form.
// this isn't only done in btChar because sometimes ints store characters.
2005-01-25 19:42:42 +01:00
if ( data < 0x100 )
2004-06-19 16:54:25 +02:00
{
2005-01-25 19:42:42 +01:00
int c = ( int ) data ;
if ( isprint ( c ) )
out ( L " ('%hc') " , c ) ;
2004-06-19 16:54:25 +02:00
}
2005-01-25 19:42:42 +01:00
return 0 ;
}
2004-07-13 23:22:53 +02:00
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
//////////////////////////////////////////////////////////////////////////////
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// <type_id> is a SymTagEnum; output its value.
// called by dump_type_sym; lock is held.
static int dump_enum ( DWORD type_idx , const u8 * p , size_t size , uint level )
{
UNUSED ( level ) ;
const i64 current_value = movsx_64le ( p , size ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
DWORD num_children ;
if ( ! SymGetTypeInfo ( hProcess , mod_base , type_idx , TI_GET_CHILDRENCOUNT , & num_children ) )
goto name_unavailable ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// alloc an array to hold child IDs
const size_t MAX_CHILDREN = 1000 ;
char child_buf [ sizeof ( TI_FINDCHILDREN_PARAMS ) + MAX_CHILDREN * sizeof ( DWORD ) ] ;
TI_FINDCHILDREN_PARAMS * fcp = ( TI_FINDCHILDREN_PARAMS * ) child_buf ;
fcp - > Start = 0 ;
fcp - > Count = MIN ( num_children , MAX_CHILDREN ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
if ( ! SymGetTypeInfo ( hProcess , mod_base , type_idx , TI_FINDCHILDREN , fcp ) )
goto name_unavailable ;
for ( uint i = 0 ; i < fcp - > Count ; i + + )
{
DWORD child_data_idx = fcp - > ChildId [ i ] ;
// get enum value. don't make any assumptions about the
// variant's type (i.e. size) - no restriction is documented.
// also don't do this manually - it's tedious and we might not
// cover everything. OLE DLL is already pulled in anyway.
VARIANT v ;
SymGetTypeInfo ( hProcess , mod_base , child_data_idx , TI_GET_VALUE , & v ) ;
if ( VariantChangeType ( & v , & v , 0 , VT_I8 ) ! = S_OK )
continue ;
if ( current_value = = v . llVal )
{
WCHAR * name ;
if ( ! SymGetTypeInfo ( hProcess , mod_base , child_data_idx , TI_GET_SYMNAME , & name ) )
goto name_unavailable ;
out ( L " %s " , name ) ;
LocalFree ( name ) ;
return 0 ;
}
}
name_unavailable :
// we can produce reasonable output (the numeric value),
// but weren't able to retrieve the matching enum name.
out ( L " %I64d " , current_value ) ;
return 1 ;
2004-06-19 16:54:25 +02:00
}
2005-01-25 19:42:42 +01:00
//////////////////////////////////////////////////////////////////////////////
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// <type_id> is a SymTagArrayType; output its value.
// called by dump_type_sym; lock is held.
static int dump_array ( DWORD type_idx , const u8 * p , size_t size , uint level )
2004-06-19 16:54:25 +02:00
{
2005-01-25 19:42:42 +01:00
DWORD elements ;
if ( ! SymGetTypeInfo ( hProcess , mod_base , type_idx , TI_GET_COUNT , & elements ) )
return - 1 ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
DWORD el_type_idx = 0 ;
if ( ! SymGetTypeInfo ( hProcess , mod_base , type_idx , TI_GET_TYPEID , & el_type_idx ) )
return - 1 ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
size_t stride = ( size_t ) ( size / elements ) ;
//
// special case for character arrays, i.e. strings
//
u64 addr = ( u64 ) p ;
// .. char[]
if ( stride = = sizeof ( char ) & & is_string_ptr ( addr , stride ) )
2004-06-19 16:54:25 +02:00
{
2005-01-25 19:42:42 +01:00
out ( L " \" %hs \" " , p ) ;
return 0 ;
}
// .. WCHAR[] (don't use wchar_t, since that might be 4 bytes)
if ( stride = = sizeof ( WCHAR ) & & is_string_ptr ( addr , stride ) )
{
out ( L " \" %s \" " , p ) ;
return 0 ;
}
//
// regular array output
//
int err = 0 ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
out ( L " { " ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
const uint elements_to_show = MIN ( 32 , elements ) ;
for ( uint i = 0 ; i < elements_to_show ; i + + )
{
int ret = dump_type_sym ( el_type_idx , p + i * stride , level + 1 ) ;
// skip trailing comma
if ( i ! = elements_to_show - 1 )
out ( L " , " ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// remember first error
if ( err = = 0 )
err = ret ;
2004-06-19 16:54:25 +02:00
}
2005-01-25 19:42:42 +01:00
// we truncated some
if ( elements ! = elements_to_show )
out ( L " ... " ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
out ( L " } " ) ;
return err ;
2004-06-19 16:54:25 +02:00
}
2005-01-25 19:42:42 +01:00
//////////////////////////////////////////////////////////////////////////////
// <type_id> is a SymTagUDT; output its value.
// called by dump_type_sym; lock is held.
static int dump_udt ( DWORD type_idx , const u8 * p , size_t size , uint level )
{
UNUSED ( size ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
out ( L " \r \n " ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
DWORD num_children ;
if ( ! SymGetTypeInfo ( hProcess , mod_base , type_idx , TI_GET_CHILDRENCOUNT , & num_children ) )
return - 1 ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// alloc an array to hold child IDs
const size_t MAX_CHILDREN = 1000 ;
char child_buf [ sizeof ( TI_FINDCHILDREN_PARAMS ) + MAX_CHILDREN * sizeof ( DWORD ) ] ;
TI_FINDCHILDREN_PARAMS * fcp = ( TI_FINDCHILDREN_PARAMS * ) child_buf ;
fcp - > Start = 0 ;
fcp - > Count = MIN ( num_children , MAX_CHILDREN ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
if ( ! SymGetTypeInfo ( hProcess , mod_base , type_idx , TI_FINDCHILDREN , fcp ) )
return - 1 ;
2005-01-23 19:07:34 +01:00
2005-01-25 19:42:42 +01:00
int err = 0 ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// recursively display each child (call back to dump_data)
for ( uint i = 0 ; i < fcp - > Count ; i + + )
2004-07-15 21:12:54 +02:00
{
2005-01-25 19:42:42 +01:00
DWORD child_data_idx = fcp - > ChildId [ i ] ;
DWORD ofs = 0 ;
if ( ! SymGetTypeInfo ( hProcess , mod_base , child_data_idx , TI_GET_OFFSET , & ofs ) )
debug_warn ( " dump_udt: warning: TI_GET_OFFSET query failed " ) ;
int ret = dump_data_sym ( child_data_idx , p + ofs , level + 1 ) ;
// remember first error
if ( err = = 0 )
err = ret ;
2004-07-15 21:12:54 +02:00
}
2005-01-25 19:42:42 +01:00
return err ;
}
//////////////////////////////////////////////////////////////////////////////
//
//
//
//////////////////////////////////////////////////////////////////////////////
// given a data symbol's type identifier, output its type name (if
// applicable), determine what kind of variable it describes, and
// call the appropriate dump_* routine.
// lock is held.
static int dump_type_sym ( DWORD type_idx , const u8 * p , uint level )
{
DWORD type_tag ;
if ( ! SymGetTypeInfo ( hProcess , mod_base , type_idx , TI_GET_SYMTAG , & type_tag ) )
return - 1 ;
// get "type name" (only available for SymTagUDT, SymTagEnum, and
// SymTagTypedef types).
// note: can't use SymFromIndex to get tag as well as name, because it
// fails when name isn't available (e.g. if this is a SymTagBaseType).
WCHAR * type_name ;
if ( SymGetTypeInfo ( hProcess , mod_base , type_idx , TI_GET_SYMNAME , & type_name ) )
2005-01-23 19:07:34 +01:00
{
2005-01-25 19:42:42 +01:00
out ( L " (%s) " , type_name ) ;
LocalFree ( type_name ) ;
2005-01-23 19:07:34 +01:00
}
2005-01-25 19:42:42 +01:00
ULONG64 size ;
if ( ! SymGetTypeInfo ( hProcess , mod_base , type_idx , TI_GET_LENGTH , & size ) )
return - 1 ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
switch ( type_tag )
2005-01-23 19:07:34 +01:00
{
2005-01-25 19:42:42 +01:00
case SymTagPointerType :
return dump_ptr ( p , ( size_t ) size ) ;
2005-01-23 19:07:34 +01:00
2005-01-25 19:42:42 +01:00
case SymTagEnum :
return dump_enum ( type_idx , p , ( size_t ) size , level ) ;
2005-01-23 19:07:34 +01:00
2005-01-25 19:42:42 +01:00
case SymTagBaseType :
return dump_base_type ( type_idx , p , ( size_t ) size , level ) ;
2005-01-23 19:07:34 +01:00
2005-01-25 19:42:42 +01:00
case SymTagUDT :
return dump_udt ( type_idx , p , ( size_t ) size , level ) ;
case SymTagArrayType :
return dump_array ( type_idx , p , ( size_t ) size , level ) ;
case SymTagTypedef :
if ( ! SymGetTypeInfo ( hProcess , mod_base , type_idx , TI_GET_TYPEID , & type_idx ) )
return - 1 ;
return dump_type_sym ( type_idx , p , level ) ;
default :
debug_out ( " Unknown tag: %d \n " , type_tag ) ;
return - 1 ;
2005-01-23 19:07:34 +01:00
}
}
2005-01-25 19:42:42 +01:00
//////////////////////////////////////////////////////////////////////////////
2005-01-23 19:07:34 +01:00
2005-01-25 19:42:42 +01:00
// indent to current nesting level, display name, and output value via
// dump_type_sym.
//
// split out of dump_sym_cb so dump_udt can call back here for its members.
// lock is held.
static int dump_data_sym ( DWORD data_idx , const u8 * p , uint level )
2005-01-23 19:07:34 +01:00
{
2005-01-25 19:42:42 +01:00
SYMBOL_INFO_PACKAGEW sp ;
SYMBOL_INFOW * sym = & sp . si ;
sym - > SizeOfStruct = sizeof ( sp . si ) ;
sym - > MaxNameLen = MAX_SYM_NAME ;
if ( ! SymFromIndexW ( hProcess , mod_base , data_idx , sym ) )
return - 1 ;
2005-01-23 19:07:34 +01:00
2005-01-26 17:25:56 +01:00
if ( sym - > Tag ! = SymTagData )
{
assert ( sym - > Tag = = SymTagData ) ;
return - 1 ;
}
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// indent
for ( uint i = 0 ; i < = level + 1 ; i + + )
out ( L " " ) ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
out ( L " %s = " , sym - > Name ) ;
2004-06-19 16:54:25 +02:00
2005-01-26 00:55:49 +01:00
int ret ;
__try
{
ret = dump_type_sym ( sym - > TypeIndex , p , level ) ;
// couldn't produce any reasonable output; value = "?"
if ( ret < 0 )
out ( L " ? " ) ;
}
__except ( 1 )
{
ret = - 1 ;
out ( L " (internal error) " ) ;
}
2005-01-25 19:42:42 +01:00
out ( L " \r \n " ) ;
return ret ;
2005-01-23 19:07:34 +01:00
}
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
//////////////////////////////////////////////////////////////////////////////
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
struct DumpSymParams
2005-01-23 19:07:34 +01:00
{
2005-01-25 19:42:42 +01:00
STACKFRAME64 * frame ;
bool locals_active ;
2005-01-23 19:07:34 +01:00
} ;
2005-01-25 19:42:42 +01:00
// get actual address of what the symbol represents (may be relative
// to frame pointer); demarcate local/param sections; output name+value via
// dump_data_sym.
//
// called from dump_frame_cb for each local symbol; lock is held.
static BOOL CALLBACK dump_sym_cb ( SYMBOL_INFO * sym , ULONG sym_size , void * ctx )
2005-01-23 19:07:34 +01:00
{
2005-01-25 19:42:42 +01:00
UNUSED ( sym_size ) ;
DumpSymParams * p = ( DumpSymParams * ) ctx ;
2005-01-23 19:07:34 +01:00
2005-01-26 00:55:49 +01:00
assert ( mod_base = = sym - > ModBase ) ;
2005-01-25 19:42:42 +01:00
// get address
ULONG64 addr = sym - > Address ;
// .. relative to a register; we assume it's the frame pointer,
// since sym->Register is undocumented.
if ( sym - > Flags & SYMF_REGREL | | sym - > Flags & SYMF_FRAMEREL )
addr + = p - > frame - > AddrFrame . Offset ;
// .. in register; we can't reliably retrieve it (since undocumented)
else if ( sym - > Flags & SYMF_REGISTER )
2005-01-23 19:07:34 +01:00
return 1 ;
2005-01-25 19:42:42 +01:00
// .. global variable (address already set)
// demarcate local / parameter report sections - this is nicer than
// printing e.g. "local" in front of each variable. we assume that
// symbols are sorted by group (local/param); if not, we waste space
// with redundant "locals:"/"params:" tags.
// note that both flags can be set, so we can't combine the if pairs.
if ( sym - > Flags & SYMF_PARAMETER )
{
if ( p - > locals_active )
{
out ( L " params: \r \n " ) ;
p - > locals_active = false ;
}
}
else if ( sym - > Flags & SYMF_LOCAL )
{
if ( ! p - > locals_active )
{
out ( L " locals: \r \n " ) ;
p - > locals_active = true ;
}
}
2005-01-23 19:07:34 +01:00
2005-01-25 19:42:42 +01:00
dump_data_sym ( sym - > Index , ( const u8 * ) addr , 0 ) ;
return TRUE ;
2005-01-23 19:07:34 +01:00
}
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
//////////////////////////////////////////////////////////////////////////////
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
struct DumpFrameParams
{
int left_to_skip ;
} ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// called by walk_stack for each stack frame
static int dump_frame_cb ( STACKFRAME64 * frame , void * ctx )
2005-01-23 19:07:34 +01:00
{
2005-01-25 19:42:42 +01:00
DumpFrameParams * p = ( DumpFrameParams * ) ctx ;
// not the one we want yet
if ( p - > left_to_skip > 0 )
{
p - > left_to_skip - - ;
2005-01-23 19:07:34 +01:00
return 1 ; // keep calling
2005-01-25 19:42:42 +01:00
}
lock ( ) ;
2005-01-23 19:07:34 +01:00
2005-01-25 19:42:42 +01:00
void * func = ( void * ) frame - > AddrPC . Offset ;
char func_name [ 1000 ] ; char file [ 100 ] ; int line ;
if ( debug_resolve_symbol ( func , func_name , file , & line ) = = 0 )
2005-01-23 19:07:34 +01:00
out ( L " %hs (%hs:%lu) " , func_name , file , line ) ;
else
out ( L " %p " , func ) ;
out ( L " \r \n " ) ;
2005-01-25 19:42:42 +01:00
// only enumerate symbols for this stack frame
// (i.e. its locals and parameters)
// problem: debug info is scope-aware, so we won't see any variables
// declared in sub-blocks. we'd have to pass an address in that block,
// which isn't worth the trouble. since
2005-01-23 19:07:34 +01:00
IMAGEHLP_STACK_FRAME imghlp_frame ;
imghlp_frame . InstructionOffset = ( DWORD64 ) func ;
2005-01-25 19:42:42 +01:00
SymSetContext ( hProcess , & imghlp_frame , 0 ) ; // last param is ignored
DumpSymParams params = { frame , true } ;
SymEnumSymbols ( hProcess , 0 , 0 , dump_sym_cb , & params ) ;
// 2nd and 3rd params indicate scope set by SymSetContext
// should be used.
2005-01-23 19:07:34 +01:00
out ( L " \r \n " ) ;
2005-01-25 19:42:42 +01:00
unlock ( ) ;
2005-01-23 19:07:34 +01:00
return 1 ; // keep calling
}
2005-01-25 19:42:42 +01:00
//////////////////////////////////////////////////////////////////////////////
2005-01-23 19:07:34 +01:00
// most recent <skip> stack frames will be skipped
// (we don't want to show e.g. GetThreadContext / this call)
2005-01-25 19:42:42 +01:00
static void dump_stack ( uint skip , CONTEXT * thread_context = NULL )
2005-01-23 19:07:34 +01:00
{
out ( L " \r \n Call stack: \r \n \r \n " ) ;
2005-01-25 19:42:42 +01:00
DumpFrameParams params = { ( int ) skip + 2 } ;
// skip dump_stack and walk_stack
walk_stack ( dump_frame_cb , & params , thread_context ) ;
2004-06-19 16:54:25 +02:00
}
2005-01-25 19:42:42 +01:00
//////////////////////////////////////////////////////////////////////////////
//
// "program error" dialog with stack trace (triggered by assert and exception)
//
//////////////////////////////////////////////////////////////////////////////
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
enum DialogType
2004-06-19 16:54:25 +02:00
{
ASSERT ,
EXCEPTION
2005-01-25 19:42:42 +01:00
} ;
2004-06-19 16:54:25 +02:00
2005-01-26 17:25:56 +01:00
//
// support for resizing the dialog / its controls
// (have to do this manually - grr)
//
static POINTS dlg_client_origin ;
static POINTS prev_dlg_client_size ;
const int ANCHOR_LEFT = 0x01 ;
const int ANCHOR_RIGHT = 0x02 ;
const int ANCHOR_TOP = 0x04 ;
const int ANCHOR_BOTTOM = 0x08 ;
const int ANCHOR_ALL = 0x0f ;
static void resize_control ( HWND hDlg , int dlg_item , int dx , int dy , int anchors )
{
HWND hControl = GetDlgItem ( hDlg , dlg_item ) ;
RECT r ;
GetWindowRect ( hControl , & r ) ;
int w = r . right - r . left , h = r . bottom - r . top ;
int x = r . left - dlg_client_origin . x , y = r . top - dlg_client_origin . y ;
if ( anchors & ANCHOR_RIGHT )
{
// right only
if ( ! ( anchors & ANCHOR_LEFT ) )
x + = dx ;
// horizontal (stretch width)
else
w + = dx ;
}
if ( anchors & ANCHOR_BOTTOM )
{
// bottom only
if ( ! ( anchors & ANCHOR_TOP ) )
y + = dy ;
// vertical (stretch height)
else
h + = dy ;
}
SetWindowPos ( hControl , 0 , x , y , w , h , SWP_NOZORDER ) ;
}
static void onSize ( HWND hDlg , WPARAM wParam , LPARAM lParam )
{
// 'minimize' was clicked. we need to ignore this, otherwise
// dx/dy would reduce some control positions to less than 0.
// since Windows clips them, we wouldn't later be able to
// reconstruct the previous values when 'restoring'.
if ( wParam = = SIZE_MINIMIZED )
return ;
// first call for this dialog instance. WM_MOVE hasn't been sent yet,
// so dlg_client_origin are invalid => must not call resize_control().
// we need to set prev_dlg_client_size for the next call before exiting.
bool first_call = ( prev_dlg_client_size . y = = 0 ) ;
POINTS dlg_client_size = MAKEPOINTS ( lParam ) ;
int dx = dlg_client_size . x - prev_dlg_client_size . x ;
int dy = dlg_client_size . y - prev_dlg_client_size . y ;
prev_dlg_client_size = dlg_client_size ;
if ( first_call )
return ;
resize_control ( hDlg , IDC_CONTINUE , dx , dy , ANCHOR_LEFT | ANCHOR_BOTTOM ) ;
resize_control ( hDlg , IDC_SUPPRESS , dx , dy , ANCHOR_LEFT | ANCHOR_BOTTOM ) ;
resize_control ( hDlg , IDC_BREAK , dx , dy , ANCHOR_LEFT | ANCHOR_BOTTOM ) ;
resize_control ( hDlg , IDC_EXIT , dx , dy , ANCHOR_LEFT | ANCHOR_BOTTOM ) ;
resize_control ( hDlg , IDC_COPY , dx , dy , ANCHOR_RIGHT | ANCHOR_BOTTOM ) ;
resize_control ( hDlg , IDC_EDIT1 , dx , dy , ANCHOR_ALL ) ;
}
2004-06-19 16:54:25 +02:00
static int CALLBACK dlgproc ( HWND hDlg , unsigned int msg , WPARAM wParam , LPARAM lParam )
{
switch ( msg )
{
case WM_INITDIALOG :
2005-01-26 17:25:56 +01:00
{
// need to reset for new instance of dialog
dlg_client_origin . x = dlg_client_origin . y = 0 ;
prev_dlg_client_size . x = prev_dlg_client_size . y = 0 ;
2005-01-23 19:07:34 +01:00
// disable inappropriate buttons
2005-01-26 17:25:56 +01:00
DialogType type = ( DialogType ) lParam ;
2004-06-19 16:54:25 +02:00
if ( type ! = ASSERT )
{
HWND h ;
h = GetDlgItem ( hDlg , IDC_CONTINUE ) ;
2005-01-25 19:42:42 +01:00
EnableWindow ( h , FALSE ) ;
2004-06-19 16:54:25 +02:00
h = GetDlgItem ( hDlg , IDC_SUPPRESS ) ;
2005-01-25 19:42:42 +01:00
EnableWindow ( h , FALSE ) ;
2004-06-19 16:54:25 +02:00
h = GetDlgItem ( hDlg , IDC_BREAK ) ;
2005-01-25 19:42:42 +01:00
EnableWindow ( h , FALSE ) ;
2004-06-19 16:54:25 +02:00
}
2004-07-13 23:22:53 +02:00
SetDlgItemTextW ( hDlg , IDC_EDIT1 , buf ) ;
2005-01-26 17:25:56 +01:00
return TRUE ; // set default keyboard focus
}
2004-06-19 16:54:25 +02:00
case WM_SYSCOMMAND :
2005-01-25 19:42:42 +01:00
// close dialog if [X] is clicked (doesn't happen automatically)
// note: lower 4 bits are reserved
if ( ( wParam & 0xFFF0 ) = = SC_CLOSE )
{
2004-06-19 16:54:25 +02:00
EndDialog ( hDlg , 0 ) ;
2005-01-26 17:25:56 +01:00
return 0 ; // processed
2005-01-25 19:42:42 +01:00
}
break ;
2004-06-19 16:54:25 +02:00
2005-01-25 19:42:42 +01:00
// return 0 if processed, otherwise break
2004-06-19 16:54:25 +02:00
case WM_COMMAND :
switch ( wParam )
{
case IDC_COPY :
2004-07-13 23:22:53 +02:00
clipboard_set ( buf ) ;
2005-01-25 19:42:42 +01:00
return 0 ;
2004-06-19 16:54:25 +02:00
2005-01-23 19:07:34 +01:00
case IDC_CONTINUE : // 2000
2004-06-19 16:54:25 +02:00
case IDC_SUPPRESS :
2005-01-23 19:07:34 +01:00
case IDC_BREAK : // 2002
2004-06-19 16:54:25 +02:00
EndDialog ( hDlg , wParam - 2000 ) ;
2005-01-25 19:42:42 +01:00
return 0 ;
2004-06-19 16:54:25 +02:00
case IDC_EXIT :
exit ( 0 ) ;
2005-01-25 19:42:42 +01:00
return 0 ;
default :
break ;
2004-06-19 16:54:25 +02:00
}
2005-01-25 19:42:42 +01:00
break ;
2005-01-26 17:25:56 +01:00
case WM_MOVE :
dlg_client_origin = MAKEPOINTS ( lParam ) ;
break ;
case WM_GETMINMAXINFO :
{
// we must make sure resize_control will never set negative coords -
// Windows would clip them, and its real position would be lost.
// restrict to a reasonable and good looking minimum size [pixels].
MINMAXINFO * mmi = ( MINMAXINFO * ) lParam ;
mmi - > ptMinTrackSize . x = 407 ;
mmi - > ptMinTrackSize . y = 159 ; // determined experimentally
return 0 ;
}
case WM_SIZE :
onSize ( hDlg , wParam , lParam ) ;
break ;
2005-01-25 19:42:42 +01:00
default :
break ;
2004-06-19 16:54:25 +02:00
}
2005-01-25 19:42:42 +01:00
// we didn't process the message; caller will perform default action.
return FALSE ;
2004-06-19 16:54:25 +02:00
}
2005-01-23 19:07:34 +01:00
// show error dialog, with stack trace (must be stored in buf[])
// exits directly if 'exit' is clicked.
//
// return values:
// 0 - continue
// 1 - suppress
// 2 - break
2005-01-25 19:42:42 +01:00
static int dialog ( DialogType type )
2004-06-19 16:54:25 +02:00
{
2005-01-25 19:42:42 +01:00
// we don't know if the enclosing app even has a Window.
const HWND hParent = GetDesktopWindow ( ) ;
return ( int ) DialogBoxParam ( hInstance , MAKEINTRESOURCE ( IDD_DIALOG1 ) , hParent , dlgproc , ( LPARAM ) type ) ;
2004-06-19 16:54:25 +02:00
}
2005-01-26 00:55:49 +01:00
int debug_assert_failed ( const char * file , int line , const char * expr )
{
pos = buf ;
out ( L " Assertion failed in %hs, line %d: \" %hs \" \r \n " , file , line , expr ) ;
dump_stack ( 1 ) ; // skip this function's frame
return dialog ( ASSERT ) ;
}
//////////////////////////////////////////////////////////////////////////////
//
// exception handler
//
//////////////////////////////////////////////////////////////////////////////
2004-06-19 16:54:25 +02:00
static PTOP_LEVEL_EXCEPTION_FILTER prev_except_filter ;
static long CALLBACK except_filter ( EXCEPTION_POINTERS * except )
{
PEXCEPTION_RECORD except_record = except - > ExceptionRecord ;
uintptr_t addr = ( uintptr_t ) except_record - > ExceptionAddress ;
DWORD code = except_record - > ExceptionCode ;
2005-01-23 19:07:34 +01:00
const char * except_str ;
2004-08-05 03:56:55 +02:00
# define X(e) case EXCEPTION_##e: except_str = #e; break;
2004-06-19 16:54:25 +02:00
switch ( code )
{
X ( ACCESS_VIOLATION )
X ( DATATYPE_MISALIGNMENT )
X ( BREAKPOINT )
X ( SINGLE_STEP )
X ( ARRAY_BOUNDS_EXCEEDED )
X ( FLT_DENORMAL_OPERAND )
X ( FLT_DIVIDE_BY_ZERO )
X ( FLT_INEXACT_RESULT )
X ( FLT_INVALID_OPERATION )
X ( FLT_OVERFLOW )
X ( FLT_STACK_CHECK )
X ( FLT_UNDERFLOW )
X ( INT_DIVIDE_BY_ZERO )
X ( INT_OVERFLOW )
X ( PRIV_INSTRUCTION )
X ( IN_PAGE_ERROR )
X ( ILLEGAL_INSTRUCTION )
X ( NONCONTINUABLE_EXCEPTION )
X ( STACK_OVERFLOW )
X ( INVALID_DISPOSITION )
X ( GUARD_PAGE )
X ( INVALID_HANDLE )
2005-01-23 19:07:34 +01:00
default :
except_str = " (unknown) " ;
2004-06-19 16:54:25 +02:00
}
# undef X
MEMORY_BASIC_INFORMATION mbi ;
char module_buf [ 100 ] ;
2005-01-23 19:07:34 +01:00
const char * module = " ??? " ;
2004-06-19 16:54:25 +02:00
uintptr_t base = 0 ;
if ( VirtualQuery ( ( void * ) addr , & mbi , sizeof ( mbi ) ) )
{
base = ( uintptr_t ) mbi . AllocationBase ;
if ( GetModuleFileName ( ( HMODULE ) base , module_buf , sizeof ( module_buf ) ) )
2004-08-05 04:30:27 +02:00
module = strrchr ( module_buf , ' \\ ' ) + 1 ;
// GetModuleFileName returns fully qualified path =>
// trailing '\\' exists
2004-06-19 16:54:25 +02:00
}
2005-01-25 19:42:42 +01:00
pos = buf ;
out ( L " Exception %hs at %hs!%08lX \r \n " , except_str , module , addr - base ) ;
dump_stack ( 0 , except - > ContextRecord ) ;
2004-06-19 16:54:25 +02:00
dialog ( EXCEPTION ) ;
if ( prev_except_filter )
return prev_except_filter ( except ) ;
return EXCEPTION_CONTINUE_EXECUTION ;
}
2004-07-12 18:38:48 +02:00
static void set_exception_handler ( )
{
prev_except_filter = SetUnhandledExceptionFilter ( except_filter ) ;
}
2004-09-02 21:24:29 +02:00
2005-01-27 16:22:47 +01:00
# ifndef NO_0AD_EXCEPTION
2004-09-02 21:24:29 +02:00
# ifdef LOCALISED_TEXT
// Split this into a separate function because destructors and __try don't mix
void i18n_display_fatal_msg ( const wchar_t * errortext ) {
2004-12-05 22:56:09 +01:00
CStrW title = translate ( L " Pyrogenesis Failure " ) ;
2004-09-02 21:24:29 +02:00
CStrW message = translate ( L " A fatal error has occurred: $msg. Please report this to http://bugs.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files from your 'data' folder. " ) < < I18n : : Raw ( errortext ) ;
wdisplay_msg ( title . c_str ( ) , message . c_str ( ) ) ;
}
# endif // LOCALISED_TEXT
2005-01-26 00:55:49 +01:00
// from http://www.codeproject.com/debug/XCrashReportPt3.asp
static void DumpMiniDump ( HANDLE hFile , PEXCEPTION_POINTERS excpInfo )
{
/*
if ( excpInfo = = NULL )
{
// Generate exception to get proper context in dump
__try
{
OutputDebugString ( " RaiseException for MinidumpWriteDump \n " ) ;
RaiseException ( EXCEPTION_BREAKPOINT , 0 , 0 , NULL ) ;
}
__except ( DumpMiniDump ( hFile , GetExceptionInformation ( ) ) , EXCEPTION_CONTINUE_EXECUTION )
{
}
}
else
{ */
MINIDUMP_EXCEPTION_INFORMATION eInfo ;
eInfo . ThreadId = GetCurrentThreadId ( ) ;
eInfo . ExceptionPointers = excpInfo ;
eInfo . ClientPointers = FALSE ;
// TODO: Store the crashlog.txt inside the UserStreamParam
// so that people only have to send us one file?
// note: MiniDumpWithIndirectlyReferencedMemory does not work on Win98
if ( ! MiniDumpWriteDump (
GetCurrentProcess ( ) ,
GetCurrentProcessId ( ) ,
hFile ,
MiniDumpNormal ,
excpInfo ? & eInfo : NULL ,
NULL ,
NULL ) )
{
throw ; // fail noisily
}
// }
}
static void cat_atow ( FILE * in , FILE * out )
{
const int bufsize = 1024 ;
char buffer [ bufsize + 1 ] ; // bufsize+1 so there's space for a \0 at the end
while ( ! feof ( in ) )
{
int r = ( int ) fread ( buffer , 1 , bufsize , in ) ;
if ( ! r ) break ;
buffer [ r ] = 0 ; // ensure proper NULLness
fwprintf ( out , L " %hs " , buffer ) ;
}
}
// Imported from sysdep.cpp
extern wchar_t MicroBuffer [ ] ;
extern size_t MicroBuffer_off ;
static int write_crashlog ( const char * file , const wchar_t * header , CONTEXT * context )
{
pos = buf ;
out ( L " Unhandled exception. \r \n " ) ;
dump_stack ( 0 , context ) ;
FILE * f = fopen ( file , " wb " ) ;
u16 BOM = 0xFEFF ;
fwrite ( & BOM , 2 , 1 , f ) ;
if ( header )
{
fwrite ( header , wcslen ( header ) , sizeof ( wchar_t ) , f ) ;
fwrite ( L " \r \n \r \n " , 4 , sizeof ( wchar_t ) , f ) ;
}
fwrite ( buf , pos - buf , 2 , f ) ;
const wchar_t * divider = L " \r \n \r \n ==================================== \r \n \r \n " ;
fwrite ( divider , wcslen ( divider ) , sizeof ( wchar_t ) , f ) ;
// HACK: Insert the contents from a couple of other log files
// and one memory log. These really ought to be integrated better.
// Copy the contents of ../logs/systeminfo.txt
FILE * in ;
in = fopen ( " ../logs/system_info.txt " , " rb " ) ;
if ( in )
{
fwprintf ( f , L " System info: \r \n \r \n " ) ;
cat_atow ( in , f ) ;
fclose ( in ) ;
}
fwrite ( divider , wcslen ( divider ) , sizeof ( wchar_t ) , f ) ;
// Copy the contents of ../logs/mainlog.html
in = fopen ( " ../logs/mainlog.html " , " rb " ) ;
if ( in )
{
fwprintf ( f , L " Main log: \r \n \r \n " ) ;
cat_atow ( in , f ) ;
fclose ( in ) ;
}
fwrite ( divider , wcslen ( divider ) , sizeof ( wchar_t ) , f ) ;
fwprintf ( f , L " Last known activity: \r \n \r \n " ) ;
fwrite ( MicroBuffer , MicroBuffer_off , sizeof ( wchar_t ) , f ) ;
fclose ( f ) ;
return 0 ;
}
2004-07-15 21:12:54 +02:00
// PT: Alternate version of the exception handler, which makes
// the crash log more useful, and takes the responsibility of
// suiciding away from main.cpp.
2004-07-24 16:04:40 +02:00
int debug_main_exception_filter ( unsigned int UNUSEDPARAM ( code ) , PEXCEPTION_POINTERS ep )
2004-07-15 21:12:54 +02:00
{
2004-07-17 19:09:33 +02:00
// If something crashes after we've already crashed (i.e. when shutting
// down everything), don't bother logging it, because the first crash
// is the most important one to fix.
static bool already_crashed = false ;
if ( already_crashed )
{
return EXCEPTION_EXECUTE_HANDLER ;
}
already_crashed = true ;
2005-01-27 16:22:47 +01:00
/*
2004-07-17 19:09:33 +02:00
// The timer thread sometimes dies from EXCEPTION_PRIV_INSTRUCTION
// when debugging this exception handler code (which gets quite
// annoying), so kill it before it gets a chance.
__try
{
abort_timer ( ) ;
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
}
2005-01-27 16:22:47 +01:00
*/
2004-07-17 19:09:33 +02:00
2004-07-15 21:12:54 +02:00
const wchar_t * error = NULL ;
// C++ exceptions put a pointer to the exception object
// into ExceptionInformation[1] -- so see if it looks like
// a PSERROR*, and use the relevant message if it is.
2004-07-17 19:09:33 +02:00
__try
2004-07-15 21:12:54 +02:00
{
if ( ep - > ExceptionRecord - > NumberParameters = = 3 )
{
2005-01-26 00:55:49 +01:00
/*/*
2004-07-15 21:12:54 +02:00
PSERROR * err = ( PSERROR * ) ep - > ExceptionRecord - > ExceptionInformation [ 1 ] ;
2004-07-17 19:09:33 +02:00
if ( err - > magic = = 0x45725221 )
2004-07-15 21:12:54 +02:00
{
2005-01-26 00:55:49 +01:00
int code = err - > code ;
error = GetErrorString ( code ) ;
2004-07-15 21:12:54 +02:00
}
2005-01-26 00:55:49 +01:00
*/
2004-07-15 21:12:54 +02:00
}
}
2004-07-17 19:09:33 +02:00
__except ( EXCEPTION_EXECUTE_HANDLER )
{
2004-07-15 21:12:54 +02:00
// Presumably it wasn't a PSERROR and resulted in
2004-07-17 19:09:33 +02:00
// accessing invalid memory locations.
error = NULL ;
2004-07-15 21:12:54 +02:00
}
// Try getting nice names for other types of error:
if ( ! error )
{
switch ( ep - > ExceptionRecord - > ExceptionCode )
{
2004-07-19 21:05:34 +02:00
case EXCEPTION_ACCESS_VIOLATION : error = L " Access violation " ; break ;
case EXCEPTION_DATATYPE_MISALIGNMENT : error = L " Datatype misalignment " ; break ;
2004-07-15 21:12:54 +02:00
case EXCEPTION_BREAKPOINT : error = L " Breakpoint " ; break ;
2004-07-19 21:05:34 +02:00
case EXCEPTION_SINGLE_STEP : error = L " Single step " ; break ;
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED : error = L " Array bounds exceeded " ; break ;
case EXCEPTION_FLT_DENORMAL_OPERAND : error = L " Float denormal operand " ; break ;
case EXCEPTION_FLT_DIVIDE_BY_ZERO : error = L " Float divide by zero " ; break ;
case EXCEPTION_FLT_INEXACT_RESULT : error = L " Float inexact result " ; break ;
case EXCEPTION_FLT_INVALID_OPERATION : error = L " Float invalid operation " ; break ;
case EXCEPTION_FLT_OVERFLOW : error = L " Float overflow " ; break ;
case EXCEPTION_FLT_STACK_CHECK : error = L " Float stack check " ; break ;
case EXCEPTION_FLT_UNDERFLOW : error = L " Float underflow " ; break ;
case EXCEPTION_INT_DIVIDE_BY_ZERO : error = L " Integer divide by zero " ; break ;
case EXCEPTION_INT_OVERFLOW : error = L " Integer overflow " ; break ;
case EXCEPTION_PRIV_INSTRUCTION : error = L " Privileged instruction " ; break ;
case EXCEPTION_IN_PAGE_ERROR : error = L " In page error " ; break ;
case EXCEPTION_ILLEGAL_INSTRUCTION : error = L " Illegal instruction " ; break ;
case EXCEPTION_NONCONTINUABLE_EXCEPTION : error = L " Noncontinuable exception " ; break ;
case EXCEPTION_STACK_OVERFLOW : error = L " Stack overflow " ; break ;
case EXCEPTION_INVALID_DISPOSITION : error = L " Invalid disposition " ; break ;
case EXCEPTION_GUARD_PAGE : error = L " Guard page " ; break ;
case EXCEPTION_INVALID_HANDLE : error = L " Invalid handle " ; break ;
2004-07-15 21:12:54 +02:00
default : error = L " [unknown exception] " ;
}
}
wchar_t * errortext = ( wchar_t * ) error ;
// Handle access violations specially, because we can see
// whether and where they were reading/writing.
if ( ep - > ExceptionRecord - > ExceptionCode = = EXCEPTION_ACCESS_VIOLATION )
{
errortext = new wchar_t [ 256 ] ;
if ( ep - > ExceptionRecord - > ExceptionInformation [ 0 ] )
swprintf ( errortext , 256 , L " Access violation writing 0x%08X " , ep - > ExceptionRecord - > ExceptionInformation [ 1 ] ) ;
else
swprintf ( errortext , 256 , L " Access violation reading 0x%08X " , ep - > ExceptionRecord - > ExceptionInformation [ 1 ] ) ;
}
2004-09-06 13:39:27 +02:00
bool localised_successfully = false ;
2004-09-02 21:24:29 +02:00
# ifdef LOCALISED_TEXT
// In case this is called before/after the i18n system is
// alive, make sure it's actually a valid pointer
if ( g_CurrentLocale )
{
2004-09-06 13:39:27 +02:00
__try
{
i18n_display_fatal_msg ( errortext ) ;
localised_successfully = true ;
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
}
2004-09-02 21:24:29 +02:00
}
# endif // LOCALISED_TEXT
2004-09-06 13:39:27 +02:00
if ( ! localised_successfully )
2004-09-02 21:24:29 +02:00
{
wchar_t message [ 1024 ] ;
swprintf ( message , 1024 , L " A fatal error has occurred: %ls. \n Please report this to http://bugs.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files from your 'data' folder. " , errortext ) ;
message [ 1023 ] = 0 ;
2004-12-05 22:56:09 +01:00
wdisplay_msg ( L " Pyrogenesis failure " , message ) ;
2004-09-02 21:24:29 +02:00
}
2004-07-15 21:12:54 +02:00
2004-07-17 19:09:33 +02:00
__try
{
2005-01-26 00:55:49 +01:00
write_crashlog ( " crashlog.txt " , errortext , ep - > ContextRecord ) ;
2004-07-17 19:09:33 +02:00
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
2004-12-05 22:56:09 +01:00
wdisplay_msg ( L " Pyrogenesis failure " , L " Error generating crash log. " ) ;
2004-07-17 19:09:33 +02:00
}
2004-07-15 21:12:54 +02:00
2004-07-17 19:09:33 +02:00
__try
{
HANDLE hFile = CreateFile ( " crashlog.dmp " , GENERIC_WRITE , FILE_SHARE_WRITE , 0 , CREATE_ALWAYS , 0 , 0 ) ;
2004-07-19 21:05:34 +02:00
if ( hFile = = INVALID_HANDLE_VALUE ) throw ;
2004-07-17 19:09:33 +02:00
DumpMiniDump ( hFile , ep ) ;
CloseHandle ( hFile ) ;
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
2004-12-05 22:56:09 +01:00
wdisplay_msg ( L " Pyrogenesis failure " , L " Error generating crash dump. " ) ;
2004-07-17 19:09:33 +02:00
}
2004-07-15 21:12:54 +02:00
// Disable memory-leak reporting, because it's going to
2004-07-19 21:05:34 +02:00
// leak like a bucket with a missing bottom when it crashes.
2004-07-15 21:12:54 +02:00
# ifdef HAVE_DEBUGALLOC
2004-09-03 22:21:09 +02:00
uint flags = _CrtSetDbgFlag ( _CRTDBG_REPORT_FLAG ) ;
2004-12-28 21:53:54 +01:00
_CrtSetDbgFlag ( flags & ~ _CRTDBG_LEAK_CHECK_DF ) ;
2004-07-15 21:12:54 +02:00
# endif
exit ( EXIT_FAILURE ) ;
}
2005-01-27 16:22:47 +01:00
# endif // #ifndef NO_0AD_EXCEPTION