root/trunk/src/shared/WheatyExceptionReport.cpp @ 273

Revision 103, 36.8 kB (checked in by yumileroy, 17 years ago)

[svn] Restore automatic crashlog generation on Windows. Make sure to have a Crashes folder in the working directory of the executables.

Original author: w12x
Date: 2008-10-23 05:57:15-05:00

Line 
1//==========================================
2// Matt Pietrek
3// MSDN Magazine, 2002
4// FILE: WheatyExceptionReport.CPP
5//==========================================
6#define WIN32_LEAN_AND_MEAN
7#pragma warning(disable:4996)
8#pragma warning(disable:4312)
9#pragma warning(disable:4311)
10#include <windows.h>
11#include <tlhelp32.h>
12#include <stdio.h>
13#include <tchar.h>
14#define _NO_CVCONST_H
15#include <dbghelp.h>
16#include "WheatyExceptionReport.h"
17#define CrashFolder _T("Crashes")
18//#pragma comment(linker, "/defaultlib:dbghelp.lib")
19
20inline LPTSTR ErrorMessage(DWORD dw)
21{
22    LPVOID lpMsgBuf;
23    FormatMessage(
24        FORMAT_MESSAGE_ALLOCATE_BUFFER |
25        FORMAT_MESSAGE_FROM_SYSTEM,
26        NULL,
27        dw,
28        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
29        (LPTSTR) &lpMsgBuf,
30        0, NULL );
31    return (LPTSTR)lpMsgBuf;
32}
33
34//============================== Global Variables =============================
35
36//
37// Declare the static variables of the WheatyExceptionReport class
38//
39TCHAR WheatyExceptionReport::m_szLogFileName[MAX_PATH];
40LPTOP_LEVEL_EXCEPTION_FILTER WheatyExceptionReport::m_previousFilter;
41HANDLE WheatyExceptionReport::m_hReportFile;
42HANDLE WheatyExceptionReport::m_hProcess;
43
44// Declare global instance of class
45WheatyExceptionReport g_WheatyExceptionReport;
46
47//============================== Class Methods =============================
48
49WheatyExceptionReport::WheatyExceptionReport( )             // Constructor
50{
51    // Install the unhandled exception filter function
52    m_previousFilter = SetUnhandledExceptionFilter(WheatyUnhandledExceptionFilter);
53    m_hProcess = GetCurrentProcess();
54}
55
56//============
57// Destructor
58//============
59WheatyExceptionReport::~WheatyExceptionReport( )
60{
61    if(m_previousFilter)
62        SetUnhandledExceptionFilter( m_previousFilter );
63}
64
65//===========================================================
66// Entry point where control comes on an unhandled exception
67//===========================================================
68LONG WINAPI WheatyExceptionReport::WheatyUnhandledExceptionFilter(
69PEXCEPTION_POINTERS pExceptionInfo )
70{
71    TCHAR module_folder_name[MAX_PATH];
72    GetModuleFileName( 0, module_folder_name, MAX_PATH );
73    TCHAR* pos = _tcsrchr(module_folder_name, '\\');
74    if(!pos)
75        return 0;
76    pos[0] = '\0';
77    ++pos;
78
79    TCHAR crash_folder_path[MAX_PATH];
80    sprintf(crash_folder_path, "%s\\%s", module_folder_name, CrashFolder);
81    if(!CreateDirectory(crash_folder_path, NULL))
82    {
83        if(GetLastError() != ERROR_ALREADY_EXISTS)
84            return 0;
85    }
86
87    SYSTEMTIME systime;
88    GetLocalTime(&systime);
89    sprintf(m_szLogFileName, "%s\\%s_[%u-%u_%u-%u-%u].txt",
90        crash_folder_path, pos, systime.wDay, systime.wMonth, systime.wHour, systime.wMinute, systime.wSecond
91        );
92
93    m_hReportFile = CreateFile( m_szLogFileName,
94        GENERIC_WRITE,
95        0,
96        0,
97        OPEN_ALWAYS,
98        FILE_FLAG_WRITE_THROUGH,
99        0 );
100
101    if ( m_hReportFile )
102    {
103        SetFilePointer( m_hReportFile, 0, 0, FILE_END );
104
105        GenerateExceptionReport( pExceptionInfo );
106
107        CloseHandle( m_hReportFile );
108        m_hReportFile = 0;
109    }
110
111    if ( m_previousFilter )
112        return m_previousFilter( pExceptionInfo );
113    else
114        return EXCEPTION_EXECUTE_HANDLER/*EXCEPTION_CONTINUE_SEARCH*/;
115}
116
117BOOL WheatyExceptionReport::_GetProcessorName(TCHAR* sProcessorName, DWORD maxcount)
118{
119    if(!sProcessorName)
120        return FALSE;
121
122    HKEY hKey;
123    LONG lRet;
124    lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"),
125        0, KEY_QUERY_VALUE, &hKey);
126    if (lRet != ERROR_SUCCESS)
127        return FALSE;
128    TCHAR szTmp[2048];
129    DWORD cntBytes = sizeof(szTmp);
130    lRet = ::RegQueryValueEx(hKey, _T("ProcessorNameString"), NULL, NULL,
131        (LPBYTE)szTmp, &cntBytes);
132    if (lRet != ERROR_SUCCESS)
133        return FALSE;
134    ::RegCloseKey(hKey);
135    sProcessorName[0] = '\0';
136    // Skip spaces
137    TCHAR* psz = szTmp;
138    while (iswspace(*psz))
139        ++psz;
140    _tcsncpy(sProcessorName, psz, maxcount);
141    return TRUE;
142}
143
144BOOL WheatyExceptionReport::_GetWindowsVersion(TCHAR* szVersion, DWORD cntMax)
145{
146    // Try calling GetVersionEx using the OSVERSIONINFOEX structure.
147    // If that fails, try using the OSVERSIONINFO structure.
148    OSVERSIONINFOEX osvi = { 0 };
149    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
150    BOOL bOsVersionInfoEx;
151    bOsVersionInfoEx = ::GetVersionEx((LPOSVERSIONINFO)(&osvi));
152    if (!bOsVersionInfoEx)
153    {
154        osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
155        if (!::GetVersionEx((OSVERSIONINFO*)&osvi))
156            return FALSE;
157    }
158    *szVersion = _T('\0');
159    TCHAR wszTmp[128];
160    switch (osvi.dwPlatformId)
161    {
162        // Windows NT product family.
163        case VER_PLATFORM_WIN32_NT:
164            // Test for the specific product family.
165            if (osvi.dwMajorVersion == 6)
166                _tcsncat(szVersion, _T("Windows Vista or Windows Server 2008 "), cntMax);
167            if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2)
168                _tcsncat(szVersion, _T("Microsoft Windows Server 2003 "), cntMax);
169            if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1)
170                _tcsncat(szVersion, _T("Microsoft Windows XP "), cntMax);
171            if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0)
172                _tcsncat(szVersion, _T("Microsoft Windows 2000 "), cntMax);
173            if (osvi.dwMajorVersion <= 4 )
174                _tcsncat(szVersion, _T("Microsoft Windows NT "), cntMax);
175
176            // Test for specific product on Windows NT 4.0 SP6 and later.
177            if (bOsVersionInfoEx)
178            {
179                // Test for the workstation type.
180            #if WINVER < 0x0500
181                if (osvi.wReserved[1] == VER_NT_WORKSTATION)
182            #else
183                    if (osvi.wProductType == VER_NT_WORKSTATION)
184            #endif                                          // WINVER < 0x0500
185            {
186                if (osvi.dwMajorVersion == 4)
187                    _tcsncat(szVersion, _T("Workstation 4.0 "), cntMax);
188                #if WINVER < 0x0500
189                else if (osvi.wReserved[0] & VER_SUITE_PERSONAL)
190                #else
191                    else if (osvi.wSuiteMask & VER_SUITE_PERSONAL)
192                #endif                                      // WINVER < 0x0500
193                        _tcsncat(szVersion, _T("Home Edition "), cntMax);
194                #if WINVER < 0x0500
195                else if (osvi.wReserved[0] & VER_SUITE_EMBEDDEDNT)
196                #else
197                    else if (osvi.wSuiteMask & VER_SUITE_EMBEDDEDNT)
198                #endif                                      // WINVER < 0x0500
199                        _tcsncat(szVersion, _T("Embedded "), cntMax);
200                else
201                    _tcsncat(szVersion, _T("Professional "), cntMax);
202            }
203            // Test for the server type.
204            #if WINVER < 0x0500
205            else if (osvi.wReserved[1] == VER_NT_SERVER)
206            #else
207                else if (osvi.wProductType == VER_NT_SERVER)
208            #endif                                          // WINVER < 0x0500
209            {
210                if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2)
211                {
212                    #if WINVER < 0x0500
213                    if (osvi.wReserved[0] & VER_SUITE_DATACENTER)
214                    #else
215                        if (osvi.wSuiteMask & VER_SUITE_DATACENTER)
216                    #endif                                  // WINVER < 0x0500
217                            _tcsncat(szVersion, _T("Datacenter Edition "), cntMax);
218                    #if WINVER < 0x0500
219                    else if (osvi.wReserved[0] & VER_SUITE_ENTERPRISE)
220                    #else
221                        else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
222                    #endif                                  // WINVER < 0x0500
223                            _tcsncat(szVersion, _T("Enterprise Edition "), cntMax);
224                    #if WINVER < 0x0500
225                    else if (osvi.wReserved[0] == VER_SUITE_BLADE)
226                    #else
227                        else if (osvi.wSuiteMask == VER_SUITE_BLADE)
228                    #endif                                  // WINVER < 0x0500
229                            _tcsncat(szVersion, _T("Web Edition "), cntMax);
230                    else
231                        _tcsncat(szVersion, _T("Standard Edition "), cntMax);
232                }
233                else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0)
234                {
235                    #if WINVER < 0x0500
236                    if (osvi.wReserved[0] & VER_SUITE_DATACENTER)
237                    #else
238                        if (osvi.wSuiteMask & VER_SUITE_DATACENTER)
239                    #endif                                  // WINVER < 0x0500
240                            _tcsncat(szVersion, _T("Datacenter Server "), cntMax);
241                    #if WINVER < 0x0500
242                    else if (osvi.wReserved[0] & VER_SUITE_ENTERPRISE )
243                    #else
244                        else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
245                    #endif                                  // WINVER < 0x0500
246                            _tcsncat(szVersion, _T("Advanced Server "), cntMax);
247                    else
248                        _tcsncat(szVersion, _T("Server "), cntMax);
249                }
250                else                                        // Windows NT 4.0
251                {
252                    #if WINVER < 0x0500
253                    if (osvi.wReserved[0] & VER_SUITE_ENTERPRISE)
254                    #else
255                        if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
256                    #endif                                  // WINVER < 0x0500
257                            _tcsncat(szVersion, _T("Server 4.0, Enterprise Edition "), cntMax);
258                    else
259                        _tcsncat(szVersion, _T("Server 4.0 "), cntMax);
260                }
261            }
262        }
263        // Display service pack (if any) and build number.
264        if (osvi.dwMajorVersion == 4 && _tcsicmp(osvi.szCSDVersion, _T("Service Pack 6")) == 0)
265        {
266            HKEY hKey;
267            LONG lRet;
268
269            // Test for SP6 versus SP6a.
270            lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Hotfix\\Q246009"), 0, KEY_QUERY_VALUE, &hKey);
271            if (lRet == ERROR_SUCCESS)
272            {
273                _stprintf(wszTmp, _T("Service Pack 6a (Version %d.%d, Build %d)"),
274                    osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
275                _tcsncat(szVersion, wszTmp, cntMax);
276            }
277            else                                            // Windows NT 4.0 prior to SP6a
278            {
279                _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"),
280                    osvi.szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
281                _tcsncat(szVersion, wszTmp, cntMax);
282            }
283            ::RegCloseKey(hKey);
284        }
285        else                                                // Windows NT 3.51 and earlier or Windows 2000 and later
286        {
287            if (!_tcslen(osvi.szCSDVersion))
288                _stprintf(wszTmp, _T("(Version %d.%d, Build %d)"),
289                    osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
290            else
291                _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"),
292                    osvi.szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
293            _tcsncat(szVersion, wszTmp, cntMax);
294        }
295        break;
296        default:
297            _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"),
298                osvi.szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
299            _tcsncat(szVersion, wszTmp, cntMax);
300            break;
301    }
302
303    return TRUE;
304}
305
306void WheatyExceptionReport::PrintSystemInfo()
307{
308    SYSTEM_INFO SystemInfo;
309    ::GetSystemInfo(&SystemInfo);
310
311    MEMORYSTATUS MemoryStatus;
312    MemoryStatus.dwLength = sizeof (MEMORYSTATUS);
313    ::GlobalMemoryStatus(&MemoryStatus);
314    TCHAR sString[1024];
315    _tprintf(_T("//=====================================================\r\n"));
316    if (_GetProcessorName(sString, countof(sString)))
317        _tprintf(_T("*** Hardware ***\r\nProcessor: %s\r\nNumber Of Processors: %d\r\nPhysical Memory: %d KB (Available: %d KB)\r\nCommit Charge Limit: %d KB\r\n"),
318            sString, SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400);
319    else
320        _tprintf(_T("*** Hardware ***\r\nProcessor: <unknown>\r\nNumber Of Processors: %d\r\nPhysical Memory: %d KB (Available: %d KB)\r\nCommit Charge Limit: %d KB\r\n"),
321            SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400);
322
323    if(_GetWindowsVersion(sString, countof(sString)))
324        _tprintf(_T("\r\n*** Operation System ***\r\n%s\r\n"), sString);
325    else
326        _tprintf(_T("\r\n*** Operation System:\r\n<unknown>\r\n"));
327}
328
329//===========================================================================
330void WheatyExceptionReport::printTracesForAllThreads()
331{
332  HANDLE hThreadSnap = INVALID_HANDLE_VALUE; 
333  THREADENTRY32 te32; 
334 
335  DWORD dwOwnerPID = GetCurrentProcessId();
336  m_hProcess = GetCurrentProcess();
337  // Take a snapshot of all running threads 
338  hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); 
339  if( hThreadSnap == INVALID_HANDLE_VALUE ) 
340    return; 
341 
342  // Fill in the size of the structure before using it.
343  te32.dwSize = sizeof(THREADENTRY32 ); 
344 
345  // Retrieve information about the first thread,
346  // and exit if unsuccessful
347  if( !Thread32First( hThreadSnap, &te32 ) ) 
348  {
349    CloseHandle( hThreadSnap );    // Must clean up the
350                                   //   snapshot object!
351    return;
352  }
353
354  // Now walk the thread list of the system,
355  // and display information about each thread
356  // associated with the specified process
357  do 
358  { 
359    if( te32.th32OwnerProcessID == dwOwnerPID )
360    {
361        CONTEXT context;
362        context.ContextFlags = 0xffffffff;
363        HANDLE threadHandle = OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION,false, te32.th32ThreadID);
364        if(threadHandle && GetThreadContext(threadHandle, &context))
365        {
366            WriteStackDetails( &context, false, threadHandle );
367        }
368        CloseHandle(threadHandle);
369    }
370  } while( Thread32Next(hThreadSnap, &te32 ) ); 
371
372//  Don't forget to clean up the snapshot object.
373  CloseHandle( hThreadSnap );
374}
375
376
377//===========================================================================
378// Open the report file, and write the desired information to it.  Called by
379// WheatyUnhandledExceptionFilter
380//===========================================================================
381void WheatyExceptionReport::GenerateExceptionReport(
382PEXCEPTION_POINTERS pExceptionInfo )
383{
384    SYSTEMTIME systime;
385    GetLocalTime(&systime);
386
387    // Start out with a banner
388    _tprintf(_T("Date %u:%u:%u. Time %u:%u \r\n"), systime.wDay, systime.wMonth, systime.wYear, systime.wHour, systime.wMinute);
389    PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;
390
391    PrintSystemInfo();
392    // First print information about the type of fault
393    _tprintf(_T("\r\n//=====================================================\r\n"));
394    _tprintf(   _T("Exception code: %08X %s\r\n"),
395        pExceptionRecord->ExceptionCode,
396        GetExceptionString(pExceptionRecord->ExceptionCode) );
397
398    // Now print information about where the fault occured
399    TCHAR szFaultingModule[MAX_PATH];
400    DWORD section;
401    DWORD_PTR offset;
402    GetLogicalAddress(  pExceptionRecord->ExceptionAddress,
403        szFaultingModule,
404        sizeof( szFaultingModule ),
405        section, offset );
406
407#ifdef _M_IX86
408    _tprintf( _T("Fault address:  %08X %02X:%08X %s\r\n"),
409        pExceptionRecord->ExceptionAddress,
410        section, offset, szFaultingModule );
411#endif
412#ifdef _M_X64
413    _tprintf( _T("Fault address:  %016I64X %02X:%016I64X %s\r\n"),
414        pExceptionRecord->ExceptionAddress,
415        section, offset, szFaultingModule );
416#endif
417
418    PCONTEXT pCtx = pExceptionInfo->ContextRecord;
419
420    // Show the registers
421    #ifdef _M_IX86                                          // X86 Only!
422    _tprintf( _T("\r\nRegisters:\r\n") );
423
424    _tprintf(_T("EAX:%08X\r\nEBX:%08X\r\nECX:%08X\r\nEDX:%08X\r\nESI:%08X\r\nEDI:%08X\r\n")
425        ,pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx,
426        pCtx->Esi, pCtx->Edi );
427
428    _tprintf( _T("CS:EIP:%04X:%08X\r\n"), pCtx->SegCs, pCtx->Eip );
429    _tprintf( _T("SS:ESP:%04X:%08X  EBP:%08X\r\n"),
430        pCtx->SegSs, pCtx->Esp, pCtx->Ebp );
431    _tprintf( _T("DS:%04X  ES:%04X  FS:%04X  GS:%04X\r\n"),
432        pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs );
433    _tprintf( _T("Flags:%08X\r\n"), pCtx->EFlags );
434    #endif
435
436    #ifdef _M_X64
437    _tprintf( _T("\r\nRegisters:\r\n") );
438    _tprintf(_T("RAX:%016I64X\r\nRBX:%016I64X\r\nRCX:%016I64X\r\nRDX:%016I64X\r\nRSI:%016I64X\r\nRDI:%016I64X\r\n")
439        _T("R8: %016I64X\r\nR9: %016I64X\r\nR10:%016I64X\r\nR11:%016I64X\r\nR12:%016I64X\r\nR13:%016I64X\r\nR14:%016I64X\r\nR15:%016I64X\r\n")
440        ,pCtx->Rax, pCtx->Rbx, pCtx->Rcx, pCtx->Rdx,
441        pCtx->Rsi, pCtx->Rdi ,pCtx->R9,pCtx->R10,pCtx->R11,pCtx->R12,pCtx->R13,pCtx->R14,pCtx->R15);
442    _tprintf( _T("CS:RIP:%04X:%016I64X\r\n"), pCtx->SegCs, pCtx->Rip );
443    _tprintf( _T("SS:RSP:%04X:%016X  RBP:%08X\r\n"),
444        pCtx->SegSs, pCtx->Rsp, pCtx->Rbp );
445    _tprintf( _T("DS:%04X  ES:%04X  FS:%04X  GS:%04X\r\n"),
446        pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs );
447    _tprintf( _T("Flags:%08X\r\n"), pCtx->EFlags );
448    #endif
449
450    SymSetOptions( SYMOPT_DEFERRED_LOADS );
451
452    // Initialize DbgHelp
453    if ( !SymInitialize( GetCurrentProcess(), 0, TRUE ) )
454    {
455        _tprintf(_T("\n\rCRITICAL ERROR.\n\r Couldn't initialize the symbol handler for process.\n\rError [%s].\n\r\n\r"),
456            ErrorMessage(GetLastError()));
457    }
458
459    CONTEXT trashableContext = *pCtx;
460
461    WriteStackDetails( &trashableContext, false, NULL );
462    printTracesForAllThreads();
463
464//    #ifdef _M_IX86                                          // X86 Only!
465
466    _tprintf( _T("========================\r\n") );
467    _tprintf( _T("Local Variables And Parameters\r\n") );
468
469    trashableContext = *pCtx;
470    WriteStackDetails( &trashableContext, true, NULL );
471
472    _tprintf( _T("========================\r\n") );
473    _tprintf( _T("Global Variables\r\n") );
474
475    SymEnumSymbols( GetCurrentProcess(),
476        (DWORD64)GetModuleHandle(szFaultingModule),
477        0, EnumerateSymbolsCallback, 0 );
478  //  #endif                                                  // X86 Only!
479
480    SymCleanup( GetCurrentProcess() );
481
482    _tprintf( _T("\r\n") );
483}
484
485//======================================================================
486// Given an exception code, returns a pointer to a static string with a
487// description of the exception
488//======================================================================
489LPTSTR WheatyExceptionReport::GetExceptionString( DWORD dwCode )
490{
491    #define EXCEPTION( x ) case EXCEPTION_##x: return _T(#x);
492
493    switch ( dwCode )
494    {
495        EXCEPTION( ACCESS_VIOLATION )
496            EXCEPTION( DATATYPE_MISALIGNMENT )
497            EXCEPTION( BREAKPOINT )
498            EXCEPTION( SINGLE_STEP )
499            EXCEPTION( ARRAY_BOUNDS_EXCEEDED )
500            EXCEPTION( FLT_DENORMAL_OPERAND )
501            EXCEPTION( FLT_DIVIDE_BY_ZERO )
502            EXCEPTION( FLT_INEXACT_RESULT )
503            EXCEPTION( FLT_INVALID_OPERATION )
504            EXCEPTION( FLT_OVERFLOW )
505            EXCEPTION( FLT_STACK_CHECK )
506            EXCEPTION( FLT_UNDERFLOW )
507            EXCEPTION( INT_DIVIDE_BY_ZERO )
508            EXCEPTION( INT_OVERFLOW )
509            EXCEPTION( PRIV_INSTRUCTION )
510            EXCEPTION( IN_PAGE_ERROR )
511            EXCEPTION( ILLEGAL_INSTRUCTION )
512            EXCEPTION( NONCONTINUABLE_EXCEPTION )
513            EXCEPTION( STACK_OVERFLOW )
514            EXCEPTION( INVALID_DISPOSITION )
515            EXCEPTION( GUARD_PAGE )
516            EXCEPTION( INVALID_HANDLE )
517    }
518
519    // If not one of the "known" exceptions, try to get the string
520    // from NTDLL.DLL's message table.
521
522    static TCHAR szBuffer[512] = { 0 };
523
524    FormatMessage( FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE,
525        GetModuleHandle( _T("NTDLL.DLL") ),
526        dwCode, 0, szBuffer, sizeof( szBuffer ), 0 );
527
528    return szBuffer;
529}
530
531//=============================================================================
532// Given a linear address, locates the module, section, and offset containing
533// that address.
534//
535// Note: the szModule paramater buffer is an output buffer of length specified
536// by the len parameter (in characters!)
537//=============================================================================
538BOOL WheatyExceptionReport::GetLogicalAddress(
539PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD_PTR& offset )
540{
541    MEMORY_BASIC_INFORMATION mbi;
542
543    if ( !VirtualQuery( addr, &mbi, sizeof(mbi) ) )
544        return FALSE;
545
546    DWORD_PTR hMod = (DWORD_PTR)mbi.AllocationBase;
547
548    if ( !GetModuleFileName( (HMODULE)hMod, szModule, len ) )
549        return FALSE;
550
551    // Point to the DOS header in memory
552    PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;
553
554    // From the DOS header, find the NT (PE) header
555    PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + DWORD_PTR(pDosHdr->e_lfanew));
556
557    PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr );
558
559    DWORD_PTR rva = (DWORD_PTR)addr - hMod;                         // RVA is offset from module load address
560
561    // Iterate through the section table, looking for the one that encompasses
562    // the linear address.
563    for (   unsigned i = 0;
564        i < pNtHdr->FileHeader.NumberOfSections;
565        i++, pSection++ )
566    {
567        DWORD_PTR sectionStart = pSection->VirtualAddress;
568        DWORD_PTR sectionEnd = sectionStart
569            + DWORD_PTR(max(pSection->SizeOfRawData, pSection->Misc.VirtualSize));
570
571        // Is the address in this section???
572        if ( (rva >= sectionStart) && (rva <= sectionEnd) )
573        {
574            // Yes, address is in the section.  Calculate section and offset,
575            // and store in the "section" & "offset" params, which were
576            // passed by reference.
577            section = i+1;
578            offset = rva - sectionStart;
579            return TRUE;
580        }
581    }
582
583    return FALSE;                                           // Should never get here!
584}
585
586// It contains SYMBOL_INFO structure plus additional
587// space for the name of the symbol
588struct CSymbolInfoPackage : public SYMBOL_INFO_PACKAGE
589{
590    CSymbolInfoPackage()
591    {
592        si.SizeOfStruct = sizeof(SYMBOL_INFO);
593        si.MaxNameLen   = sizeof(name);
594    }
595};
596
597//============================================================
598// Walks the stack, and writes the results to the report file
599//============================================================
600void WheatyExceptionReport::WriteStackDetails(
601PCONTEXT pContext,
602bool bWriteVariables, HANDLE pThreadHandle)                                      // true if local/params should be output
603{
604    _tprintf( _T("\r\nCall stack:\r\n") );
605
606    _tprintf( _T("Address   Frame     Function          SourceFile\r\n") );
607
608    DWORD dwMachineType = 0;
609    // Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag
610
611    STACKFRAME64 sf;
612    memset( &sf, 0, sizeof(sf) );
613
614    #ifdef _M_IX86
615    // Initialize the STACKFRAME structure for the first call.  This is only
616    // necessary for Intel CPUs, and isn't mentioned in the documentation.
617    sf.AddrPC.Offset       = pContext->Eip;
618    sf.AddrPC.Mode         = AddrModeFlat;
619    sf.AddrStack.Offset    = pContext->Esp;
620    sf.AddrStack.Mode      = AddrModeFlat;
621    sf.AddrFrame.Offset    = pContext->Ebp;
622    sf.AddrFrame.Mode      = AddrModeFlat;
623
624    dwMachineType = IMAGE_FILE_MACHINE_I386;
625    #endif
626
627#ifdef _M_X64
628    sf.AddrPC.Offset    = pContext->Rip;
629    sf.AddrPC.Mode = AddrModeFlat;
630    sf.AddrStack.Offset    = pContext->Rsp;
631    sf.AddrStack.Mode      = AddrModeFlat;
632    sf.AddrFrame.Offset    = pContext->Rbp;
633    sf.AddrFrame.Mode      = AddrModeFlat;
634    dwMachineType = IMAGE_FILE_MACHINE_AMD64;
635#endif
636
637    while ( 1 )
638    {
639        // Get the next stack frame
640        if ( ! StackWalk64(  dwMachineType,
641            m_hProcess,
642            pThreadHandle != NULL ? pThreadHandle : GetCurrentThread(),
643            &sf,
644            pContext,
645            0,
646            SymFunctionTableAccess64,
647            SymGetModuleBase64,
648            0 ) )
649            break;
650        if ( 0 == sf.AddrFrame.Offset )                     // Basic sanity check to make sure
651            break;                                          // the frame is OK.  Bail if not.
652#ifdef _M_IX86
653        _tprintf( _T("%08X  %08X  "), sf.AddrPC.Offset, sf.AddrFrame.Offset );
654#endif
655#ifdef _M_X64
656        _tprintf( _T("%016I64X  %016I64X  "), sf.AddrPC.Offset, sf.AddrFrame.Offset );
657#endif
658
659        DWORD64 symDisplacement = 0;                        // Displacement of the input address,
660        // relative to the start of the symbol
661
662        // Get the name of the function for this stack frame entry
663        CSymbolInfoPackage sip;
664        if ( SymFromAddr(
665            m_hProcess,                                     // Process handle of the current process
666            sf.AddrPC.Offset,                               // Symbol address
667            &symDisplacement,                               // Address of the variable that will receive the displacement
668            &sip.si                                         // Address of the SYMBOL_INFO structure (inside "sip" object)
669            ))
670        {
671            _tprintf( _T("%hs+%I64X"), sip.si.Name, symDisplacement );
672
673        }
674        else                                                // No symbol found.  Print out the logical address instead.
675        {
676            TCHAR szModule[MAX_PATH] = _T("");
677            DWORD section = 0;
678            DWORD_PTR offset = 0;
679
680            GetLogicalAddress(  (PVOID)sf.AddrPC.Offset,
681                szModule, sizeof(szModule), section, offset );
682#ifdef _M_IX86
683            _tprintf( _T("%04X:%08X %s"), section, offset, szModule );
684#endif
685#ifdef _M_X64
686            _tprintf( _T("%04X:%016I64X %s"), section, offset, szModule );
687#endif
688        }
689
690        // Get the source line for this stack frame entry
691        IMAGEHLP_LINE64 lineInfo = { sizeof(IMAGEHLP_LINE) };
692        DWORD dwLineDisplacement;
693        if ( SymGetLineFromAddr64( m_hProcess, sf.AddrPC.Offset,
694            &dwLineDisplacement, &lineInfo ) )
695        {
696            _tprintf(_T("  %s line %u"),lineInfo.FileName,lineInfo.LineNumber);
697        }
698
699        _tprintf( _T("\r\n") );
700
701        // Write out the variables, if desired
702        if ( bWriteVariables )
703        {
704            // Use SymSetContext to get just the locals/params for this frame
705            IMAGEHLP_STACK_FRAME imagehlpStackFrame;
706            imagehlpStackFrame.InstructionOffset = sf.AddrPC.Offset;
707            SymSetContext( m_hProcess, &imagehlpStackFrame, 0 );
708
709            // Enumerate the locals/parameters
710            SymEnumSymbols( m_hProcess, 0, 0, EnumerateSymbolsCallback, &sf );
711
712            _tprintf( _T("\r\n") );
713        }
714    }
715
716}
717
718//////////////////////////////////////////////////////////////////////////////
719// The function invoked by SymEnumSymbols
720//////////////////////////////////////////////////////////////////////////////
721
722BOOL CALLBACK
723WheatyExceptionReport::EnumerateSymbolsCallback(
724PSYMBOL_INFO  pSymInfo,
725ULONG         SymbolSize,
726PVOID         UserContext )
727{
728
729    char szBuffer[2048];
730
731    __try
732    {
733        if ( FormatSymbolValue( pSymInfo, (STACKFRAME*)UserContext,
734            szBuffer, sizeof(szBuffer) ) )
735            _tprintf( _T("\t%s\r\n"), szBuffer );
736    }
737    __except( 1 )
738    {
739        _tprintf( _T("punting on symbol %s\r\n"), pSymInfo->Name );
740    }
741
742    return TRUE;
743}
744
745//////////////////////////////////////////////////////////////////////////////
746// Given a SYMBOL_INFO representing a particular variable, displays its
747// contents.  If it's a user defined type, display the members and their
748// values.
749//////////////////////////////////////////////////////////////////////////////
750bool WheatyExceptionReport::FormatSymbolValue(
751PSYMBOL_INFO pSym,
752STACKFRAME * sf,
753char * pszBuffer,
754unsigned cbBuffer )
755{
756    char * pszCurrBuffer = pszBuffer;
757
758    // Indicate if the variable is a local or parameter
759    if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_PARAMETER )
760        pszCurrBuffer += sprintf( pszCurrBuffer, "Parameter " );
761    else if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_LOCAL )
762        pszCurrBuffer += sprintf( pszCurrBuffer, "Local " );
763
764    // If it's a function, don't do anything.
765    if ( pSym->Tag == 5 )                                   // SymTagFunction from CVCONST.H from the DIA SDK
766        return false;
767
768    DWORD_PTR pVariable = 0;                                // Will point to the variable's data in memory
769
770    if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGRELATIVE )
771    {
772        // if ( pSym->Register == 8 )   // EBP is the value 8 (in DBGHELP 5.1)
773        {                                                   //  This may change!!!
774            pVariable = sf->AddrFrame.Offset;
775            pVariable += (DWORD_PTR)pSym->Address;
776        }
777        // else
778        //  return false;
779    }
780    else if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGISTER )
781    {
782        return false;                                       // Don't try to report register variable
783    }
784    else
785    {
786        pVariable = (DWORD_PTR)pSym->Address;               // It must be a global variable
787    }
788
789    // Determine if the variable is a user defined type (UDT).  IF so, bHandled
790    // will return true.
791    bool bHandled;
792    pszCurrBuffer = DumpTypeIndex(pszCurrBuffer,pSym->ModBase, pSym->TypeIndex,
793        0, pVariable, bHandled, pSym->Name );
794
795    if ( !bHandled )
796    {
797        // The symbol wasn't a UDT, so do basic, stupid formatting of the
798        // variable.  Based on the size, we're assuming it's a char, WORD, or
799        // DWORD.
800        BasicType basicType = GetBasicType( pSym->TypeIndex, pSym->ModBase );
801        pszCurrBuffer += sprintf( pszCurrBuffer, rgBaseType[basicType]);
802
803        // Emit the variable name
804        pszCurrBuffer += sprintf( pszCurrBuffer, "\'%s\'", pSym->Name );
805
806        pszCurrBuffer = FormatOutputValue(pszCurrBuffer, basicType, pSym->Size,
807            (PVOID)pVariable );
808    }
809
810    return true;
811}
812
813//////////////////////////////////////////////////////////////////////////////
814// If it's a user defined type (UDT), recurse through its members until we're
815// at fundamental types.  When he hit fundamental types, return
816// bHandled = false, so that FormatSymbolValue() will format them.
817//////////////////////////////////////////////////////////////////////////////
818char * WheatyExceptionReport::DumpTypeIndex(
819char * pszCurrBuffer,
820DWORD64 modBase,
821DWORD dwTypeIndex,
822unsigned nestingLevel,
823DWORD_PTR offset,
824bool & bHandled,
825char* Name)
826{
827    bHandled = false;
828
829    // Get the name of the symbol.  This will either be a Type name (if a UDT),
830    // or the structure member name.
831    WCHAR * pwszTypeName;
832    if ( SymGetTypeInfo( m_hProcess, modBase, dwTypeIndex, TI_GET_SYMNAME,
833        &pwszTypeName ) )
834    {
835        pszCurrBuffer += sprintf( pszCurrBuffer, " %ls", pwszTypeName );
836        LocalFree( pwszTypeName );
837    }
838
839    // Determine how many children this type has.
840    DWORD dwChildrenCount = 0;
841    SymGetTypeInfo( m_hProcess, modBase, dwTypeIndex, TI_GET_CHILDRENCOUNT,
842        &dwChildrenCount );
843
844    if ( !dwChildrenCount )                                 // If no children, we're done
845        return pszCurrBuffer;
846
847    // Prepare to get an array of "TypeIds", representing each of the children.
848    // SymGetTypeInfo(TI_FINDCHILDREN) expects more memory than just a
849    // TI_FINDCHILDREN_PARAMS struct has.  Use derivation to accomplish this.
850    struct FINDCHILDREN : TI_FINDCHILDREN_PARAMS
851    {
852        ULONG   MoreChildIds[1024];
853        FINDCHILDREN(){Count = sizeof(MoreChildIds) / sizeof(MoreChildIds[0]);}
854    } children;
855
856    children.Count = dwChildrenCount;
857    children.Start= 0;
858
859    // Get the array of TypeIds, one for each child type
860    if ( !SymGetTypeInfo( m_hProcess, modBase, dwTypeIndex, TI_FINDCHILDREN,
861        &children ) )
862    {
863        return pszCurrBuffer;
864    }
865
866    // Append a line feed
867    pszCurrBuffer += sprintf( pszCurrBuffer, "\r\n" );
868
869    // Iterate through each of the children
870    for ( unsigned i = 0; i < dwChildrenCount; i++ )
871    {
872        // Add appropriate indentation level (since this routine is recursive)
873        for ( unsigned j = 0; j <= nestingLevel+1; j++ )
874            pszCurrBuffer += sprintf( pszCurrBuffer, "\t" );
875
876        // Recurse for each of the child types
877        bool bHandled2;
878        BasicType basicType = GetBasicType(children.ChildId[i], modBase );
879        pszCurrBuffer += sprintf( pszCurrBuffer, rgBaseType[basicType]);
880
881        pszCurrBuffer = DumpTypeIndex( pszCurrBuffer, modBase,
882            children.ChildId[i], nestingLevel+1,
883            offset, bHandled2, ""/*Name */);
884
885        // If the child wasn't a UDT, format it appropriately
886        if ( !bHandled2 )
887        {
888            // Get the offset of the child member, relative to its parent
889            DWORD dwMemberOffset;
890            SymGetTypeInfo( m_hProcess, modBase, children.ChildId[i],
891                TI_GET_OFFSET, &dwMemberOffset );
892
893            // Get the real "TypeId" of the child.  We need this for the
894            // SymGetTypeInfo( TI_GET_TYPEID ) call below.
895            DWORD typeId;
896            SymGetTypeInfo( m_hProcess, modBase, children.ChildId[i],
897                TI_GET_TYPEID, &typeId );
898
899            // Get the size of the child member
900            ULONG64 length;
901            SymGetTypeInfo(m_hProcess, modBase, typeId, TI_GET_LENGTH,&length);
902
903            // Calculate the address of the member
904            DWORD_PTR dwFinalOffset = offset + dwMemberOffset;
905
906            //             BasicType basicType = GetBasicType(children.ChildId[i], modBase );
907            //
908            //                  pszCurrBuffer += sprintf( pszCurrBuffer, rgBaseType[basicType]);
909            //
910            // Emit the variable name
911            //                  pszCurrBuffer += sprintf( pszCurrBuffer, "\'%s\'", Name );
912
913            pszCurrBuffer = FormatOutputValue( pszCurrBuffer, basicType,
914                length, (PVOID)dwFinalOffset );
915
916            pszCurrBuffer += sprintf( pszCurrBuffer, "\r\n" );
917        }
918    }
919
920    bHandled = true;
921    return pszCurrBuffer;
922}
923
924char * WheatyExceptionReport::FormatOutputValue(   char * pszCurrBuffer,
925BasicType basicType,
926DWORD64 length,
927PVOID pAddress )
928{
929    // Format appropriately (assuming it's a 1, 2, or 4 bytes (!!!)
930    if ( length == 1 )
931        pszCurrBuffer += sprintf( pszCurrBuffer, " = %X", *(PBYTE)pAddress );
932    else if ( length == 2 )
933        pszCurrBuffer += sprintf( pszCurrBuffer, " = %X", *(PWORD)pAddress );
934    else if ( length == 4 )
935    {
936        if ( basicType == btFloat )
937        {
938            pszCurrBuffer += sprintf(pszCurrBuffer," = %f", *(PFLOAT)pAddress);
939        }
940        else if ( basicType == btChar )
941        {
942            if ( !IsBadStringPtr( *(PSTR*)pAddress, 32) )
943            {
944                pszCurrBuffer += sprintf( pszCurrBuffer, " = \"%.31s\"",
945                    *(PDWORD)pAddress );
946            }
947            else
948                pszCurrBuffer += sprintf( pszCurrBuffer, " = %X",
949                    *(PDWORD)pAddress );
950        }
951        else
952            pszCurrBuffer += sprintf(pszCurrBuffer," = %X", *(PDWORD)pAddress);
953    }
954    else if ( length == 8 )
955    {
956        if ( basicType == btFloat )
957        {
958            pszCurrBuffer += sprintf( pszCurrBuffer, " = %lf",
959                *(double *)pAddress );
960        }
961        else
962            pszCurrBuffer += sprintf( pszCurrBuffer, " = %I64X",
963                *(DWORD64*)pAddress );
964    }
965
966    return pszCurrBuffer;
967}
968
969BasicType
970WheatyExceptionReport::GetBasicType( DWORD typeIndex, DWORD64 modBase )
971{
972    BasicType basicType;
973    if ( SymGetTypeInfo( m_hProcess, modBase, typeIndex,
974        TI_GET_BASETYPE, &basicType ) )
975    {
976        return basicType;
977    }
978
979    // Get the real "TypeId" of the child.  We need this for the
980    // SymGetTypeInfo( TI_GET_TYPEID ) call below.
981    DWORD typeId;
982    if (SymGetTypeInfo(m_hProcess,modBase, typeIndex, TI_GET_TYPEID, &typeId))
983    {
984        if ( SymGetTypeInfo( m_hProcess, modBase, typeId, TI_GET_BASETYPE,
985            &basicType ) )
986        {
987            return basicType;
988        }
989    }
990
991    return btNoType;
992}
993
994//============================================================================
995// Helper function that writes to the report file, and allows the user to use
996// printf style formating
997//============================================================================
998int __cdecl WheatyExceptionReport::_tprintf(const TCHAR * format, ...)
999{
1000    TCHAR szBuff[1024];
1001    int retValue;
1002    DWORD cbWritten;
1003    va_list argptr;
1004
1005    va_start( argptr, format );
1006    retValue = vsprintf( szBuff, format, argptr );
1007    va_end( argptr );
1008
1009    WriteFile(m_hReportFile, szBuff, retValue * sizeof(TCHAR), &cbWritten, 0 );
1010
1011    return retValue;
1012}
Note: See TracBrowser for help on using the browser.