Descent3/win32/windebug.cpp
2024-04-17 00:25:04 +03:00

1276 lines
38 KiB
C++

/*
* $Logfile: /DescentIII/Main/win32/windebug.cpp $
* $Revision: 33 $
* $Date: 9/15/01 1:46p $
* $Author: Kevin $
*
* Win32 Debugging 'kernal'
*
* $Log: /DescentIII/Main/win32/windebug.cpp $
*
* 33 9/15/01 1:46p Kevin
* Added -nocrashbox to suppress crash dialog box
*
* 32 5/19/99 2:43a Jeff
* handle some possible crashes. The UCT->Localtime doesn't seem to work,
* although it is setup correctly.
*
* 31 5/18/99 9:48p Kevin
* adjust time to time zone for Jeff
*
* 30 5/07/99 5:46p Jason
* changes for gamegauge release
*
* 29 4/18/99 1:18p Kevin
* Demo2 build changes
*
* 28 4/12/99 7:14p Samir
* added multiple pages per mono window.
*
* 27 4/01/99 11:07a Kevin
* Added some more usefull debugging info
*
* 26 4/01/99 9:40a Matt
* Fixed dump_text_to_clipboard. It used to allocate a fixed number of
* bytes to allow space for adding CRs to strings, but now I actually
* count the number needed and allocate that much.
*
* 25 3/29/99 6:50p Kevin
* Added some parameters to the log file
*
* 24 2/16/99 11:25a Kevin
* Took out testing code.
*
* 23 2/16/99 12:36a Kevin
* Fixes for release builds of OEM V3 and KAtmai
*
* 22 2/15/99 1:22p Kevin
* Changes for GameGauge
*
* 21 2/13/99 1:56p Kevin
*
* 20 1/05/99 4:28p Kevin
* Moved exception handling code to windebug.cpp
*
* 19 1/05/99 3:55p Kevin
* Added structured exception handling
*
* 18 10/18/98 8:52p Matt
* Revamped debug/error system.
*
* 17 10/12/98 10:20a Samir
* added parameter to debug init.
*
* 16 10/08/98 4:24p Kevin
* Changed code to comply with memory library usage. Always use mem_malloc
* , mem_free and mem_strdup
*
* 15 6/23/98 2:40p Matt
* Added Yes/No/Cancel type to OutrageMessageBox() and
* Debug_MesssageBox(), and changed return value from a bool to an
* integer.
*
* 14 4/21/98 2:30p Matt
* dump_text_to_clipboard() wasn't putting terminating null on text string
*
* 13 4/08/98 7:19p Samir
* Added runtime debugging option.
*
* 12 4/07/98 9:21p Samir
* Added volition's call stack and memory dump code. This will only
* happen if you run the game without the -debug argument.
*
* 11 4/03/98 5:15p Samir
* Implemented simple debug message filtering.
*
* 10 3/20/98 2:43p Samir
* Some better Int3 support.
*
* 9 1/29/98 12:24p Samir
* Added logfile support.
*
* 8 1/28/98 4:45p Samir
* Mono check stuff.
*
* 7 1/28/98 11:56a Samir
* Code to check for monochrome debug.
*
* 6 1/28/98 11:17a Samir
* Added debugbreak stuff.
*
* 5 10/15/97 3:05p Samir
* NT mono output works.
*
* 4 10/13/97 2:41p Samir
* Debug breaks now are macros to work depending on the operating system.
*
* 3 9/04/97 12:00p Matt
* Changed Debug_MessageBox() to return bool instead of int, since it was
* already being used that way.
*
* 2 7/28/97 3:46p Samir
* NT mono stubbed out.
*
* 18 6/11/97 1:10p Samir
* Fixed up function names.
*
* 17 3/31/97 3:50p Samir
* MessageBox should be task modal.
*
* 16 3/18/97 2:14p Samir
* DebugBreak called instead of Int3
*
* 15 3/17/97 8:10p Matt
* Don't print to monochrome screen unless D3_MONO evironment var is set
*
* 14 3/14/97 4:15p Matt
* Changed title of OutrageMessageBox() dialogs
*
* 13 3/10/97 12:29p Samir
* Moved mono code and altered lowlevel console interface to machine
* library.
*
* 12 3/10/97 11:13a Samir
* Changes made to reflect deletion of OSDebug class from system
*
* 11 3/04/97 7:15p Samir
* Fixed bug in message_box, caused an Int3.
*
* 10 3/04/97 1:00p Samir
* Fixed return type prototype errors.
*
* 9 3/04/97 12:55p Samir
* Added types of messageboxes.
*
* 8 3/03/97 5:07p Samir
* stupid
*
* 7 3/03/97 4:36p Samir
* Removed instances of pserror.h code. this is a lower level lib than
* that!
*
* 6 2/27/97 7:00p Samir
* Removed windows.h from oewin_os.h and moved to library.
*
* 5 2/26/97 7:04p Samir
* Added Win32 DebugBreak
*
* 4 2/18/97 7:00p Samir
* Now print out last Win32 error too.
*
* 3 1/31/97 3:31p Samir
* forgot to use OSWINDBG_MAX_STRINGS constant in array init.
*
* 2 1/30/97 6:49p Samir
* Added some winDebug code for interface with error handler
*
* 1 1/28/97 2:32p Samir
* Standard debugging functions
*
* $NoKeywords: $
*/
#include "Debug.h"
#include "mono.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winioctl.h>
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
///////////////////////////////////////////////////////////////////////////////
bool Debug_break = false;
bool Debug_NT = false;
char *Debug_DumpInfo();
///////////////////////////////////////////////////////////////////////////////
bool Debug_Init(bool debugger, bool con_debug) {
// initialization of debugging consoles.
DWORD version = GetVersion();
if (version & 0x80000000) {
// Are we 95 or Win32s!
if ((version & 0xff) < 4) {
MessageBox(NULL, "This application will only run on Windows 95 or Windows NT.", "Error", MB_OK);
exit(1);
}
Debug_NT = false;
} else {
if ((version & 0xff) < 4) {
MessageBox(NULL, "This application needs Windows NT 4.0 or higher.", "Error", MB_OK);
exit(1);
}
Debug_NT = true;
}
#ifndef RELEASE
Debug_break = debugger;
if (con_debug) {
Debug_ConsoleInit();
Debug_ConsoleOpen(0, 9, 1, 78, 15, "Debug Spew");
Debug_ConsoleOpen(1, 1, 1, 58, 6, "Warnings");
if (!Debug_NT) {
Debug_ConsoleOpen(2, 1, 61, 18, 6, "Stats");
mprintf((0, "Win95 system.\n"));
} else {
mprintf((0, "WinNT system.\n"));
}
}
if (Debug_break) {
mprintf((0, "Debug Break enabled.\n"));
}
#endif // ifndef RELEASE
return true;
}
/* Message Box functions */
// Does a messagebox with a stack dump
// Messagebox shows topstring, then stack dump, then bottomstring
// Return types are as the same the Windows return values
int Debug_ErrorBox(int type, const char *title, const char *topstring, const char *bottomstring) {
int answer;
char *dumptext = Debug_DumpInfo();
HWND wnd = GetActiveWindow();
// ShowWindow(wnd, SW_SHOWMINNOACTIVE);
DWORD flags;
if (type == OSMBOX_OK)
flags = MB_OK;
else if (type == OSMBOX_YESNO)
flags = MB_YESNO;
else if (type == OSMBOX_YESNOCANCEL)
flags = MB_YESNOCANCEL;
else if (type == OSMBOX_ABORTRETRYIGNORE)
flags = MB_ABORTRETRYIGNORE;
else if (type == OSMBOX_OKCANCEL)
flags = MB_OKCANCEL;
else
debug_break();
char *tmpbuf = (char *)GlobalAlloc(
GMEM_FIXED, strlen(dumptext) + strlen(topstring) + strlen(bottomstring) +
10); // malloc(strlen(dumptext) + strlen(topstring) + strlen(bottomstring) + 10);
strcpy(tmpbuf, topstring);
if (dumptext[0]) {
strcat(tmpbuf, "\r\n\r\n");
strcat(tmpbuf, dumptext);
}
if (bottomstring[0]) {
strcat(tmpbuf, "\r\n\r\n");
strcat(tmpbuf, bottomstring);
}
wsprintf(tmpbuf, "%s\r\n\r\n%s\r\n\r\n%s", topstring, dumptext, bottomstring);
ShowCursor(TRUE);
answer = MessageBox(NULL, tmpbuf, title, flags | MB_TASKMODAL | MB_SETFOREGROUND); // what's MB_ICONHAND?
ShowCursor(FALSE);
// free(tmpbuf);
GlobalFree(tmpbuf);
// ShowWindow(wnd, SW_SHOWMAXIMIZED);
return answer;
}
// Return types are the same the Windows return values
int Debug_MessageBox(int type, const char *title, const char *str) {
DWORD flags;
int answer;
HWND wnd = GetActiveWindow();
if (type == OSMBOX_OK)
flags = MB_OK;
else if (type == OSMBOX_YESNO)
flags = MB_YESNO;
else if (type == OSMBOX_YESNOCANCEL)
flags = MB_YESNOCANCEL;
else if (type == OSMBOX_ABORTRETRYIGNORE)
flags = MB_ABORTRETRYIGNORE;
else
DebugBreak();
ShowCursor(TRUE);
// ShowWindow(wnd, SW_SHOWMINNOACTIVE);
answer = MessageBox(NULL, str, title, flags | MB_TASKMODAL | MB_SETFOREGROUND);
ShowCursor(FALSE);
// ShowWindow(wnd, SW_SHOWMAXIMIZED);
return answer;
}
///////////////////////////////////////////////////////////////////////////////
// This CODE SHOULD ONLY BE USED IN A DEBUGGABLE VERSION.
#define SHOW_CALL_STACK // Uncomment SHOW_CALL_STACK to show the call stack in terminal cases
// #define DUMPRAM
#ifdef SHOW_CALL_STACK
class DumpBuffer {
public:
enum { BUFFER_SIZE = 32000 };
DumpBuffer();
void Clear();
void Printf(const char *format, ...);
void SetWindowText(HWND hWnd) const;
char buffer[BUFFER_SIZE];
private:
char *current;
};
class PE_Debug {
public:
PE_Debug();
~PE_Debug();
void ClearReport();
int DumpDebugInfo(DumpBuffer &dumpBuffer, const BYTE *caller, HINSTANCE hInstance);
void Display();
private:
// Report data
enum { MAX_MODULENAME_LEN = 512, VA_MAX_FILENAME_LEN = 256 };
char latestModule[MAX_MODULENAME_LEN];
char latestFile[VA_MAX_FILENAME_LEN];
// File mapping data
HANDLE hFile;
HANDLE hFileMapping;
PIMAGE_DOS_HEADER fileBase;
// Pointers to debug information
PIMAGE_NT_HEADERS NT_Header;
PIMAGE_COFF_SYMBOLS_HEADER COFFDebugInfo;
PIMAGE_SYMBOL COFFSymbolTable;
int COFFSymbolCount;
const char *stringTable;
void ClearFileCache();
void ClearDebugPtrs();
void MapFileInMemory(const char *module);
void FindDebugInfo();
void DumpSymbolInfo(DumpBuffer &dumpBuffer, DWORD relativeAddress);
void DumpLineNumber(DumpBuffer &dumpBuffer, DWORD relativeAddress);
PIMAGE_COFF_SYMBOLS_HEADER GetDebugHeader();
PIMAGE_SECTION_HEADER SectionHeaderFromName(const char *name);
const char *GetSymbolName(PIMAGE_SYMBOL sym);
};
DumpBuffer ::DumpBuffer() { Clear(); }
void DumpBuffer ::Clear() { current = buffer; }
void DumpBuffer ::Printf(const char *format, ...) {
// protect against obvious buffer overflow
if (current - buffer < BUFFER_SIZE) {
va_list argPtr;
va_start(argPtr, format);
int count = wvsprintf(current, format, argPtr);
va_end(argPtr);
current += count;
}
}
void DumpBuffer ::SetWindowText(HWND hWnd) const { SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM)buffer); }
///////////////////////////////////////////////////////////////////////////////
// Add an offset to a pointer and cast to a given type; may be
// implemented as a template function but Visual C++ has some problems.
#define BasedPtr(type, ptr, ofs) (type)((DWORD)(ptr) + (DWORD)(ofs))
PE_Debug ::PE_Debug() {
// Init file mapping cache
hFileMapping = 0;
hFile = INVALID_HANDLE_VALUE;
fileBase = 0;
ClearDebugPtrs();
ClearReport();
}
PE_Debug ::~PE_Debug() { ClearFileCache(); }
void PE_Debug ::ClearReport() {
latestModule[0] = 0;
latestFile[0] = 0;
}
void PE_Debug ::ClearDebugPtrs() {
NT_Header = NULL;
COFFDebugInfo = NULL;
COFFSymbolTable = NULL;
COFFSymbolCount = 0;
stringTable = NULL;
}
void PE_Debug ::ClearFileCache() {
if (fileBase) {
UnmapViewOfFile(fileBase);
fileBase = 0;
}
if (hFileMapping != 0) {
CloseHandle(hFileMapping);
hFileMapping = 0;
}
if (hFile != INVALID_HANDLE_VALUE) {
CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
}
}
void PE_Debug ::DumpLineNumber(DumpBuffer &dumpBuffer, DWORD relativeAddress) {
PIMAGE_LINENUMBER line = BasedPtr(PIMAGE_LINENUMBER, COFFDebugInfo, COFFDebugInfo->LvaToFirstLinenumber);
DWORD lineCount = COFFDebugInfo->NumberOfLinenumbers;
const DWORD none = (DWORD)-1;
DWORD maxAddr = 0;
DWORD lineNum = none;
for (DWORD i = 0; i < lineCount; i++) {
if (line->Linenumber != 0) // A regular line number
{
// look for line with bigger address <= relativeAddress
if (line->Type.VirtualAddress <= relativeAddress && line->Type.VirtualAddress > maxAddr) {
maxAddr = line->Type.VirtualAddress;
lineNum = line->Linenumber;
}
}
line++;
}
if (lineNum != none)
dumpBuffer.Printf(" line %d\r\n", lineNum);
// else
// dumpBuffer.Printf( " line <unknown>\r\n" ) ;
}
const char *PE_Debug ::GetSymbolName(PIMAGE_SYMBOL sym) {
const int NAME_MAX_LEN = 64;
static char buf[NAME_MAX_LEN];
if (sym->N.Name.Short != 0) {
strncpy(buf, (const char *)sym->N.ShortName, 8);
buf[8] = 0;
} else {
strncpy(buf, stringTable + sym->N.Name.Long, NAME_MAX_LEN);
buf[NAME_MAX_LEN - 1] = 0;
}
return (buf);
}
void unmangle(char *dst, const char *src) {
// strcpy( dst, src );
// return;
src++;
while ((*src) && (*src != ' ') && (*src != '@')) {
*dst++ = *src++;
}
*dst++ = 0;
}
#ifdef DUMPRAM
typedef struct MemSymbol {
int section;
int offset;
int size;
char name[132];
} MemSymbol;
int Num_symbols = 0;
int Max_symbols = 0;
MemSymbol *Symbols;
void InitSymbols() {
Num_symbols = 0;
Max_symbols = 5000;
Symbols = (MemSymbol *)mem_malloc(Max_symbols * sizeof(MemSymbol));
if (!Symbols) {
Max_symbols = 0;
}
}
void Add_Symbol(int section, int offset, const char *name, char *module) {
if (Num_symbols >= Max_symbols) {
return;
}
MemSymbol *sym = &Symbols[Num_symbols++];
sym->section = section;
sym->offset = offset;
sym->size = -1;
strcpy(sym->name, name);
strcat(sym->name, "(");
strcat(sym->name, module);
strcat(sym->name, ")");
}
int Sym_compare(const void *arg1, const void *arg2) {
MemSymbol *sym1 = (MemSymbol *)arg1;
MemSymbol *sym2 = (MemSymbol *)arg2;
if (sym1->section < sym2->section) {
return -1;
} else if (sym1->section > sym2->section) {
return 1;
} else {
if (sym1->offset > sym2->offset) {
return 1;
} else {
return -1;
}
}
}
int Sym_compare1(const void *arg1, const void *arg2) {
MemSymbol *sym1 = (MemSymbol *)arg1;
MemSymbol *sym2 = (MemSymbol *)arg2;
if (sym1->size < sym2->size) {
return 1;
} else if (sym1->size > sym2->size) {
return -1;
} else {
return 0;
}
}
void DumpSymbols() {
int i;
qsort(Symbols, Num_symbols, sizeof(MemSymbol), Sym_compare);
for (i = 0; i < Num_symbols; i++) {
MemSymbol *sym1 = &Symbols[i];
MemSymbol *sym2 = &Symbols[i + 1];
if ((i < Num_symbols - 1) && (sym1->section == sym2->section)) {
sym1->size = sym2->offset - sym1->offset;
} else {
sym1->size = -1;
}
}
qsort(Symbols, Num_symbols, sizeof(MemSymbol), Sym_compare1);
FILE *fp = fopen("memory.out", "wt");
fprintf(fp, "%-60s %8s %8s\n", "Name", "Size", "Total");
int total_size = 0;
for (i = 0; i < Num_symbols; i++) {
MemSymbol *sym = &Symbols[i];
if (sym->size > 0)
total_size += sym->size;
fprintf(fp, "%-60s %8d %8d\n", sym->name, sym->size, total_size);
}
fclose(fp);
mem_free(Symbols);
Symbols = NULL;
_asm int 3
}
#endif
void PE_Debug::DumpSymbolInfo(DumpBuffer &dumpBuffer, DWORD relativeAddress) {
// Variables to keep track of function symbols
PIMAGE_SYMBOL currentSym = COFFSymbolTable;
PIMAGE_SYMBOL fnSymbol = NULL;
DWORD maxFnAddress = 0;
#ifdef DUMPRAM
InitSymbols();
#endif
// Variables to keep track of file symbols
PIMAGE_SYMBOL fileSymbol = NULL;
PIMAGE_SYMBOL latestFileSymbol = NULL;
for (int i = 0; i < COFFSymbolCount; i++) {
// Look for .text section where relativeAddress belongs to.
// Keep track of the filename the .text section belongs to.
if (currentSym->StorageClass == IMAGE_SYM_CLASS_FILE) {
latestFileSymbol = currentSym;
}
// Borland uses "CODE" instead of the standard ".text" entry
// Microsoft uses sections that only _begin_ with .text
const char *symName = GetSymbolName(currentSym);
if (strnicmp(symName, ".text", 5) == 0 || strcmpi(symName, "CODE") == 0) {
if (currentSym->Value <= relativeAddress) {
PIMAGE_AUX_SYMBOL auxSym = (PIMAGE_AUX_SYMBOL)(currentSym + 1);
if (currentSym->Value + auxSym->Section.Length >= relativeAddress) {
fileSymbol = latestFileSymbol;
}
}
}
// Look for the function with biggest address <= relativeAddress
BOOL isFunction = ISFCN(currentSym->Type); // Type == 0x20, See WINNT.H
if (isFunction &&
(currentSym->StorageClass == IMAGE_SYM_CLASS_EXTERNAL || currentSym->StorageClass == IMAGE_SYM_CLASS_STATIC)) {
if (currentSym->Value <= relativeAddress && currentSym->Value > maxFnAddress) {
maxFnAddress = currentSym->Value;
fnSymbol = currentSym;
}
}
#ifdef DUMPRAM
if (!isFunction && (currentSym->SectionNumber > -1)) {
if ((symName[0] == '_' && symName[1] != '$') || (symName[0] == '?')) {
char pretty_module[1024];
if (fileSymbol) {
const char *auxSym = (const char *)(latestFileSymbol + 1);
char tmpFile[VA_MAX_FILENAME_LEN];
strcpy(tmpFile, auxSym);
strcpy(pretty_module, tmpFile);
char *p = pretty_module + strlen(pretty_module) - 1;
// Move p to point to first letter of EXE filename
while ((*p != '\\') && (*p != '/') && (*p != ':'))
p--;
p++;
if (strlen(p) < 1) {
strcpy(pretty_module, "<unknown>");
} else {
memmove(pretty_module, p, strlen(p) + 1);
}
} else {
strcpy(pretty_module, "");
}
Add_Symbol(currentSym->SectionNumber, currentSym->Value, symName, pretty_module);
}
}
#endif
// Advance counters, skip aux symbols
i += currentSym->NumberOfAuxSymbols;
currentSym += currentSym->NumberOfAuxSymbols;
currentSym++;
}
#ifdef DUMPRAM
DumpSymbols();
#endif
// dump symbolic info if found
if (fileSymbol) {
const char *auxSym = (const char *)(fileSymbol + 1);
if (strcmpi(latestFile, auxSym)) {
strcpy(latestFile, auxSym);
// JAS dumpBuffer.Printf( " file: %s\r\n", auxSym ) ;
}
} else {
latestFile[0] = 0;
// JAS dumpBuffer.Printf( " file: unknown\r\n" ) ;
}
if (fnSymbol) {
char tmp_name[1024];
unmangle(tmp_name, GetSymbolName(fnSymbol));
dumpBuffer.Printf(" %s()", tmp_name);
} else {
dumpBuffer.Printf(" <unknown>");
}
}
PIMAGE_SECTION_HEADER PE_Debug ::SectionHeaderFromName(const char *name) {
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(NT_Header);
for (unsigned i = 0; i < NT_Header->FileHeader.NumberOfSections; i++) {
if (strnicmp((const char *)section->Name, name, IMAGE_SIZEOF_SHORT_NAME) == 0)
return (section);
else
section++;
}
return 0;
}
PIMAGE_COFF_SYMBOLS_HEADER PE_Debug ::GetDebugHeader() {
// Some files have a wrong entry in the COFF header, so
// first check if the debug info exists at all
if (NT_Header->FileHeader.PointerToSymbolTable == 0)
return (0);
DWORD debugDirRVA = NT_Header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
if (debugDirRVA == 0)
return (0);
// The following values must be calculated differently for MS/Borland files
PIMAGE_DEBUG_DIRECTORY debugDir;
DWORD size;
// Borland files have the debug directory at the beginning of a .debug section
PIMAGE_SECTION_HEADER debugHeader = SectionHeaderFromName(".debug");
if (debugHeader && debugHeader->VirtualAddress == debugDirRVA) {
debugDir = (PIMAGE_DEBUG_DIRECTORY)(debugHeader->PointerToRawData + (DWORD)fileBase);
size = NT_Header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size * sizeof(IMAGE_DEBUG_DIRECTORY);
} else
// Microsoft debug directory is in the .rdata section
{
debugHeader = SectionHeaderFromName(".rdata");
if (debugHeader == 0)
return (0);
size = NT_Header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size;
DWORD offsetInto_rdata = debugDirRVA - debugHeader->VirtualAddress;
debugDir = BasedPtr(PIMAGE_DEBUG_DIRECTORY, fileBase, debugHeader->PointerToRawData + offsetInto_rdata);
}
// look for COFF debug info
DWORD debugFormats = size / sizeof(IMAGE_DEBUG_DIRECTORY);
for (DWORD i = 0; i < debugFormats; i++) {
if (debugDir->Type == IMAGE_DEBUG_TYPE_COFF)
return ((PIMAGE_COFF_SYMBOLS_HEADER)((DWORD)fileBase + debugDir->PointerToRawData));
else
debugDir++;
}
return (NULL);
}
void PE_Debug ::FindDebugInfo() {
ClearDebugPtrs();
// Put everything into a try/catch in case the file has wrong fields
try {
// Verify that fileBase is a valid pointer to a DOS header
if (fileBase && fileBase->e_magic == IMAGE_DOS_SIGNATURE) {
// Get a pointer to the PE header
NT_Header = BasedPtr(PIMAGE_NT_HEADERS, fileBase, fileBase->e_lfanew);
// Verify that NT_Header is a valid pointer to a NT header
if (NT_Header && NT_Header->Signature == IMAGE_NT_SIGNATURE) {
// Get a pointer to the debug header if any
COFFDebugInfo = GetDebugHeader();
// Get a pointer to the symbol table and retrieve the number of symbols
if (NT_Header->FileHeader.PointerToSymbolTable)
COFFSymbolTable = BasedPtr(PIMAGE_SYMBOL, fileBase, NT_Header->FileHeader.PointerToSymbolTable);
COFFSymbolCount = NT_Header->FileHeader.NumberOfSymbols;
// The string table starts right after the symbol table
stringTable = (const char *)(COFFSymbolTable + COFFSymbolCount);
}
}
} catch (...) {
// Header wrong, do nothing
}
}
void PE_Debug ::MapFileInMemory(const char *module) {
ClearFileCache();
hFile = CreateFile(module, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile != INVALID_HANDLE_VALUE) {
hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hFileMapping != 0)
fileBase = (PIMAGE_DOS_HEADER)MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
}
// NB: open files/mapping are closed later in ClearFileCache
}
int PE_Debug::DumpDebugInfo(DumpBuffer &dumpBuffer, const BYTE *caller, HINSTANCE hInstance) {
// Avoid to open, map and looking for debug header/symbol table
// by caching the latest and comparing the actual module with
// the latest one.
static char module[MAX_MODULENAME_LEN];
GetModuleFileName(hInstance, module, MAX_MODULENAME_LEN);
// New module
if (strcmpi(latestModule, module)) {
strcpy(latestModule, module);
// JAS dumpBuffer.Printf( "Module: %s\r\n", module );
MapFileInMemory(module);
FindDebugInfo();
}
char pretty_module[1024];
strcpy(pretty_module, module);
char *p = pretty_module + strlen(pretty_module) - 1;
// Move p to point to first letter of EXE filename
while ((*p != '\\') && (*p != '/') && (*p != ':'))
p--;
p++;
if (strlen(p) < 1) {
strcpy(pretty_module, "<unknown>");
} else {
memmove(pretty_module, p, strlen(p) + 1);
}
if (fileBase) {
// Put everything into a try/catch in case the file has wrong fields
try {
DWORD relativeAddress = caller - (BYTE *)hInstance;
// Dump symbolic information and line number if available
if (COFFSymbolCount != 0 && COFFSymbolTable != NULL) {
DumpSymbolInfo(dumpBuffer, relativeAddress);
if (COFFDebugInfo)
DumpLineNumber(dumpBuffer, relativeAddress);
return 1;
} else {
// dumpBuffer.Printf( "Call stack is unavailable, because there is\r\nno COFF debugging info in this
// module.\r\n" ) ; JAS dumpBuffer.Printf( " no debug information\r\n" ) ;
dumpBuffer.Printf(" %s %08x()\r\n", pretty_module, caller);
return 0;
}
} catch (...) {
// Header wrong, do nothing
return 0;
}
} else {
dumpBuffer.Printf(" %s %08x()\r\n", pretty_module, caller);
// JAS dumpBuffer.Printf( " module not accessible\r\n" ) ;
// JAS dumpBuffer.Printf( " address: %8X\r\n", caller ) ;
return 0;
}
}
void DumpCallsStack(DumpBuffer &dumpBuffer) {
const char *separator = "------------------------------------------------------------------\r\n";
static PE_Debug PE_debug;
dumpBuffer.Printf("\r\nCall stack:\r\n");
dumpBuffer.Printf(separator);
// The structure of the stack frames is the following:
// EBP -> parent stack frame EBP
// return address for this call ( = caller )
// The chain can be navigated iteratively, after the
// initial value of EBP is loaded from the register
DWORD parentEBP, retval;
MEMORY_BASIC_INFORMATION mbi;
HINSTANCE hInstance;
int depth = 0;
__asm MOV parentEBP, EBP
do {
depth++;
if (depth > 16)
break;
if ((parentEBP & 3) || IsBadReadPtr((DWORD *)parentEBP, sizeof(DWORD))) {
break;
}
parentEBP = *(DWORD *)parentEBP;
BYTE **NextCaller = ((BYTE **)parentEBP + 1);
if (IsBadReadPtr(NextCaller, sizeof(BYTE *))) {
break;
}
BYTE *caller = *NextCaller; // Error sometimes!!!
// Skip the first EBP as it points to AssertionFailed, which is
// uninteresting for the user
if (depth > 1) {
// Get the instance handle of the module where caller belongs to
retval = VirtualQuery(caller, &mbi, sizeof(mbi));
// The instance handle is equal to the allocation base in Win32
hInstance = (HINSTANCE)mbi.AllocationBase;
if ((retval == sizeof(mbi)) && hInstance) {
if (!PE_debug.DumpDebugInfo(dumpBuffer, caller, hInstance)) {
// break;
}
} else {
break; // End of the call chain
}
}
}
while (TRUE)
;
dumpBuffer.Printf(separator);
PE_debug.ClearReport(); // Prepare for future calls
}
// This ought to be local to VerboseAssert, but it
// causes problems in Visual C++ (in the CRTL init phase)
static DumpBuffer dumpBuffer;
#endif // SHOW_CALL_STACK
///////////////////////////////////////////////////////////////////////////////
void dump_text_to_clipboard(char *text);
char *Debug_DumpInfo() {
#ifdef SHOW_CALL_STACK
dumpBuffer.Clear();
DumpCallsStack(dumpBuffer);
dump_text_to_clipboard(dumpBuffer.buffer);
dumpBuffer.Printf("\r\n[ This info has been copied to the clipboard for pasting to a bug report. ]\r\n");
return dumpBuffer.buffer;
#else
return ("");
#endif
}
///////////////////////////////////////////////////////////////////////////////
void DumpTextToClipboard(char *text) {
char *ptr;
// Count number of carriage returns to be added
char last_char = 0;
int extra = 0;
ptr = text;
while (*ptr) {
if ((*ptr == '\n') && (last_char != '\r')) {
extra++;
}
last_char = *ptr++;
}
// Length of string with CRs added
int len = strlen(text) + extra + 1;
HGLOBAL h_text = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, len);
if (!h_text)
return;
ptr = (char *)GlobalLock(h_text);
if (!ptr)
return;
// copy then, if you find any \n's without \r's, then add in the \r.
last_char = 0;
while (*text) {
if ((*text == '\n') && (last_char != '\r')) {
*ptr++ = '\r';
}
last_char = *text;
*ptr++ = last_char;
text++;
}
*ptr = 0; // terminate string
GlobalUnlock(h_text);
OpenClipboard(NULL);
EmptyClipboard();
SetClipboardData(CF_TEXT, h_text);
CloseClipboard();
}
void dump_text_to_clipboard(char *text) { DumpTextToClipboard(text); }
// Translate the exception code into something human readable.
static const char *GetExceptionDescription(DWORD ExceptionCode) {
struct ExceptionNames {
DWORD ExceptionCode;
char *ExceptionName;
};
ExceptionNames ExceptionMap[] = {
{0x40010005, "a Control-C"},
{0x40010008, "a Control-Break"},
{0x80000002, "a Datatype Misalignment"},
{0x80000003, "a Breakpoint"},
{0xc0000005, "an Access Violation"},
{0xc0000006, "an In Page Error"},
{0xc0000017, "a No Memory"},
{0xc000001d, "an Illegal Instruction"},
{0xc0000025, "a Noncontinuable Exception"},
{0xc0000026, "an Invalid Disposition"},
{0xc000008c, "a Array Bounds Exceeded"},
{0xc000008d, "a Float Denormal Operand"},
{0xc000008e, "a Float Divide by Zero"},
{0xc000008f, "a Float Inexact Result"},
{0xc0000090, "a Float Invalid Operation"},
{0xc0000091, "a Float Overflow"},
{0xc0000092, "a Float Stack Check"},
{0xc0000093, "a Float Underflow"},
{0xc0000094, "an Integer Divide by Zero"},
{0xc0000095, "an Integer Overflow"},
{0xc0000096, "a Privileged Instruction"},
{0xc00000fD, "a Stack Overflow"},
{0xc0000142, "a DLL Initialization Failed"},
{0xe06d7363, "a Microsoft C++ Exception"},
};
for (int i = 0; i < sizeof(ExceptionMap) / sizeof(ExceptionMap[0]); i++)
if (ExceptionCode == ExceptionMap[i].ExceptionCode)
return ExceptionMap[i].ExceptionName;
return "Unknown exception type";
}
static void PrintFileTime(char *sztime, FILETIME ftime) {
SYSTEMTIME systime, fixtime;
*sztime = NULL;
if (FileTimeToSystemTime(&ftime, &fixtime)) {
if (SystemTimeToTzSpecificLocalTime(NULL, &fixtime, &systime)) {
wsprintf(sztime, "%d/%d/%d %02d:%02d:%02d", systime.wMonth, systime.wDay, systime.wYear, systime.wHour,
systime.wMinute, systime.wSecond);
} else {
wsprintf(sztime, "Unknown Time");
}
}
}
// Print information about a code module (DLL or EXE) such as its size,
// location, time stamp, etc.
static void ShowModuleInfo(HANDLE LogFile, HINSTANCE ModuleHandle) {
char FmtString[2000];
unsigned long bytesout;
char ModName[MAX_PATH];
__try {
if (GetModuleFileName(ModuleHandle, ModName, sizeof(ModName)) > 0) {
// If GetModuleFileName returns greater than zero then this must
// be a valid code module address. Therefore we can try to walk
// our way through its structures to find the link time stamp.
IMAGE_DOS_HEADER *DosHeader = (IMAGE_DOS_HEADER *)ModuleHandle;
if (IMAGE_DOS_SIGNATURE != DosHeader->e_magic)
return;
IMAGE_NT_HEADERS *NTHeader = (IMAGE_NT_HEADERS *)((char *)DosHeader + DosHeader->e_lfanew);
if (IMAGE_NT_SIGNATURE != NTHeader->Signature)
return;
// Open the code module file so that we can get its file date
// and size.
HANDLE ModuleFile =
CreateFile(ModName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
char TimeBuffer[100] = "";
DWORD FileSize = 0;
if (ModuleFile != INVALID_HANDLE_VALUE) {
FileSize = GetFileSize(ModuleFile, 0);
FILETIME LastWriteTime;
if (GetFileTime(ModuleFile, 0, 0, &LastWriteTime)) {
PrintFileTime(TimeBuffer + lstrlen(TimeBuffer), LastWriteTime);
}
CloseHandle(ModuleFile);
}
wsprintf(FmtString, "%s,\taddr: 0x%08x\tsize:%d\t[%s]\r\n", ModName, ModuleHandle, FileSize,
TimeBuffer); // NTHeader->FileHeader.TimeDateStamp,
WriteFile(LogFile, FmtString, lstrlen(FmtString), &bytesout, 0);
}
}
// Handle any exceptions by continuing from this point.
__except (EXCEPTION_EXECUTE_HANDLER) {
}
}
// Scan memory looking for code modules (DLLs or EXEs). VirtualQuery is used
// to find all the blocks of address space that were reserved or committed,
// and ShowModuleInfo will display module information if they are code
// modules.
static void RecordModuleList(HANDLE LogFile) {
char FmtString[2000];
unsigned long bytesout;
wsprintf(FmtString, "\r\n"
"Modules:\r\n");
WriteFile(LogFile, FmtString, lstrlen(FmtString), &bytesout, 0);
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);
const size_t PageSize = SystemInfo.dwPageSize;
// Set NumPages to the number of pages in the 4GByte address space,
// while being careful to avoid overflowing ints.
const size_t NumPages = 4 * size_t((1024 * 1024 * 1024) / PageSize);
size_t pageNum = 0;
void *LastAllocationBase = 0;
while (pageNum < NumPages) {
MEMORY_BASIC_INFORMATION MemInfo;
if (VirtualQuery((void *)(pageNum * PageSize), &MemInfo, sizeof(MemInfo))) {
if (MemInfo.RegionSize > 0) {
// Adjust the page number to skip over this block of memory.
pageNum += MemInfo.RegionSize / PageSize;
if (MemInfo.State == MEM_COMMIT && MemInfo.AllocationBase > LastAllocationBase) {
// Look for new blocks of committed memory, and try
// recording their module names - this will fail
// gracefully if they aren't code modules.
LastAllocationBase = MemInfo.AllocationBase;
ShowModuleInfo(LogFile, (HINSTANCE)LastAllocationBase);
}
} else
pageNum += (64 * 1024) / PageSize;
} else
pageNum += (64 * 1024) / PageSize;
}
}
// Record information about the user's system, such as processor type, amount
// of memory, etc.
static void RecordSystemInformation(HANDLE LogFile) {
char FmtString[2000];
unsigned long bytesout;
FILETIME CurrentTime;
GetSystemTimeAsFileTime(&CurrentTime);
char TimeBuffer[100];
PrintFileTime(TimeBuffer, CurrentTime);
wsprintf(FmtString, "Error occurred at %s.\r\n", TimeBuffer);
WriteFile(LogFile, FmtString, lstrlen(FmtString), &bytesout, 0);
char ModuleName[MAX_PATH];
if (GetModuleFileName(0, ModuleName, sizeof(ModuleName)) <= 0)
lstrcpy(ModuleName, "Unknown");
wsprintf(FmtString, "Module: %s.\r\n", ModuleName);
WriteFile(LogFile, FmtString, lstrlen(FmtString), &bytesout, 0);
MEMORYSTATUS ms;
ms.dwLength = sizeof(ms);
GlobalMemoryStatus(&ms);
// Print out the amount of physical memory, rounded up.
wsprintf(FmtString, "System Memory Status:\r\n");
WriteFile(LogFile, FmtString, lstrlen(FmtString), &bytesout, 0);
wsprintf(FmtString, "Percent of memory in use: %d\r\n", ms.dwMemoryLoad);
WriteFile(LogFile, FmtString, lstrlen(FmtString), &bytesout, 0);
wsprintf(FmtString, "Bytes of physical memory : %d\r\n", ms.dwTotalPhys);
WriteFile(LogFile, FmtString, lstrlen(FmtString), &bytesout, 0);
wsprintf(FmtString, "Free physical memory bytes : %d\r\n", ms.dwAvailPhys);
WriteFile(LogFile, FmtString, lstrlen(FmtString), &bytesout, 0);
wsprintf(FmtString, "Available virtual memory : %d\r\n\r\n", ms.dwAvailPageFile);
WriteFile(LogFile, FmtString, lstrlen(FmtString), &bytesout, 0);
}
/*
extern bool Debug_break;
extern char *Debug_DumpInfo();
*/
#define FORMAT_REG \
"Registers:\r\nEAX=%08x CS=%04x EIP=%08x EFLGS=%08x\r\nEBX=%08x SS=%04x ESP=%08x EBP=%08x\r\nECX=%08x DS=%04x " \
"ESI=%08x FS=%04x\r\nEDX=%08x ES=%04x EDI=%08x GS=%04x\r\n"
#define FORMAT_SEP "\r\n--------------------------------------------------------------------------------\r\n"
#define FORMATCRLF "\r\n"
const int NumCodeBytes = 16; // Number of code bytes to record.
const int MaxStackDump = 2048; // Maximum number of DWORDS in stack dumps.
const int StackColumns = 8; // Number of columns in stack dump.
#if (defined(RELEASE) && (!defined(DEMO)) && (!defined(GAMEGAUGE)))
#include "buildno.h"
#endif
extern int no_debug_dialog;
int __cdecl RecordExceptionInfo(PEXCEPTION_POINTERS data, const char *Message) {
static int BeenHere;
if (BeenHere) // Going recursive! That must mean this routine crashed!
return EXCEPTION_CONTINUE_SEARCH;
BeenHere = true;
PEXCEPTION_RECORD Exception = data->ExceptionRecord;
PCONTEXT Context = data->ContextRecord;
const char *desc = GetExceptionDescription(Exception->ExceptionCode);
// An Int3()
if (0x80000003 == Exception->ExceptionCode) {
BeenHere = false;
return EXCEPTION_CONTINUE_SEARCH;
}
char topmsg[500];
char tmpmsg[1000];
char bottommsg[1000] = "";
char callstack[1000] = "";
wsprintf(topmsg, "Excecution in %s was stopped by %s", Message, desc);
wsprintf(bottommsg, FORMAT_REG, Context->Eax, Context->SegCs, Context->Eip, Context->EFlags, Context->Ebx,
Context->SegSs, Context->Esp, Context->Ebp, Context->Ecx, Context->SegDs, Context->Esi, Context->SegFs,
Context->Edx, Context->SegEs, Context->Edi, Context->SegGs);
HANDLE LogFile =
CreateFile("error.log", GENERIC_WRITE, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 0);
if (LogFile != INVALID_HANDLE_VALUE) {
char *p = Debug_DumpInfo();
lstrcpy(callstack, p);
unsigned long NumBytes;
SetFilePointer(LogFile, 0, 0, FILE_END);
WriteFile(LogFile, topmsg, lstrlen(topmsg), &NumBytes, 0);
WriteFile(LogFile, FORMATCRLF, lstrlen(FORMATCRLF), &NumBytes, 0);
char Username[100];
unsigned long unamelen = 99;
char Machinename[200];
unsigned long cnamelen = 199;
GetUserName(Username, &unamelen);
GetComputerName(Machinename, &cnamelen);
wsprintf(callstack, "Username: %s\r\nMachineName: %s\r\n", Username, Machinename);
WriteFile(LogFile, callstack, lstrlen(callstack), &NumBytes, 0);
#if (defined(RELEASE) && (!defined(DEMO)) && (!defined(GAMEGAUGE)))
wsprintf(callstack, "Descent 3 Release build %d\r\n", D3_RELEASE_BUILD_NO);
WriteFile(LogFile, callstack, lstrlen(callstack), &NumBytes, 0);
#endif
RecordSystemInformation(LogFile);
RecordModuleList(LogFile);
WriteFile(LogFile, bottommsg, lstrlen(bottommsg), &NumBytes, 0);
WriteFile(LogFile, FORMAT_SEP, lstrlen(FORMAT_SEP), &NumBytes, 0);
DWORD *pStack = (DWORD *)Context->Esp;
DWORD *pStackTop;
__asm {
mov eax, fs:[4]
mov pStackTop, eax
}
if (pStackTop > pStack + MaxStackDump) pStackTop = pStack + MaxStackDump;
int Count = 0;
char buffer[1000] = "";
const int safetyzone = 50;
char *nearend = buffer + sizeof(buffer) - safetyzone;
char *output = buffer;
while (pStack + 1 <= pStackTop) {
if ((Count % StackColumns) == 0)
output += wsprintf(output, "%08x: ", pStack);
char *Suffix = " ";
if ((++Count % StackColumns) == 0 || pStack + 2 > pStackTop)
Suffix = "\r\n";
output += wsprintf(output, "%08x%s", *pStack, Suffix);
pStack++;
// Check for when the buffer is almost full, and flush it to disk.
if (output > nearend) {
wsprintf(tmpmsg, "%s", buffer);
WriteFile(LogFile, tmpmsg, lstrlen(tmpmsg), &NumBytes, 0);
buffer[0] = 0;
output = buffer;
}
}
CloseHandle(LogFile);
}
if ((!Debug_break) && (!no_debug_dialog))
Debug_ErrorBox(OSMBOX_OK, "Error", topmsg, bottommsg);
if (no_debug_dialog)
exit(0);
BeenHere = false;
if (Debug_break)
return EXCEPTION_CONTINUE_SEARCH;
else
return EXCEPTION_EXECUTE_HANDLER;
}