1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 
11 
12 // Copyright � 1998 Bruce Dawson.
13 /*
14 This source file contains the exception handler for recording error
15 information after crashes. See exceptionhandler.h for information
16 on how to hook it in.
17 */
18 
19 #ifdef GAME_ERRORLOG_TXT
20 
21 #ifdef _WIN32
22 
23 #include "globalincs/pstypes.h"
24 
25 #include <windows.h>
26 
27 
28 // --------------------
29 //
30 // Defines
31 //
32 // --------------------
33 #define	ONEK			1024
34 #define	SIXTYFOURK		(64*ONEK)
35 #define	ONEM			(ONEK*ONEK)
36 #define	ONEG			(ONEK*ONEK*ONEK)
37 
38 // --------------------
39 //
40 // Enumerated Types
41 //
42 // --------------------
43 
44 
45 // --------------------
46 //
47 // Structures
48 //
49 // --------------------
50 
51 
52 // --------------------
53 //
54 // Classes
55 //
56 // --------------------
57 
58 
59 // --------------------
60 //
61 // Global Variables
62 //
63 // --------------------
64 
65 
66 // --------------------
67 //
68 // Local Variables
69 //
70 // --------------------
71 
72 const int NumCodeBytes = 16;	// Number of code bytes to record.
73 const int MaxStackDump = 2048;	// Maximum number of DWORDS in stack dumps.
74 const int StackColumns = 8;		// Number of columns in stack dump.
75 
76 // --------------------
77 //
78 // Internal Functions
79 //
80 // --------------------
81 
82 // hprintf behaves similarly to printf, with a few vital differences.
83 // It uses wvsprintf to do the formatting, which is a system routine,
84 // thus avoiding C run time interactions. For similar reasons it
85 // uses WriteFile rather than fwrite.
86 // The one limitation that this imposes is that wvsprintf, and
87 // therefore hprintf, cannot handle floating point numbers.
hprintf(HANDLE LogFile,char * Format,...)88 static void hprintf(HANDLE LogFile, char* Format, ...)
89 {
90 	char buffer[2000];	// wvsprintf never prints more than one K.
91 
92 	va_list arglist;
93 	va_start( arglist, Format);
94 	wvsprintf(buffer, Format, arglist);
95 	va_end( arglist);
96 
97 	DWORD NumBytes;
98 	WriteFile(LogFile, buffer, lstrlen(buffer), &NumBytes, 0);
99 }
100 
101 // Print the specified FILETIME to output in a human readable format,
102 // without using the C run time.
PrintTime(char * output,FILETIME TimeToPrint)103 static void PrintTime(char *output, FILETIME TimeToPrint)
104 {
105 	WORD Date, Time;
106 	if (FileTimeToLocalFileTime(&TimeToPrint, &TimeToPrint) &&
107 				FileTimeToDosDateTime(&TimeToPrint, &Date, &Time))
108 	{
109 		// What a silly way to print out the file date/time. Oh well,
110 		// it works, and I'm not aware of a cleaner way to do it.
111 		wsprintf(output, "%d/%d/%d %02d:%02d:%02d",
112 					(Date / 32) & 15, Date & 31, (Date / 512) + 1980,
113 					(Time / 2048), (Time / 32) & 63, (Time & 31) * 2);
114 	} else {
115 		output[0] = 0;
116 	}
117 }
118 
119 // Print information about a code module (DLL or EXE) such as its size,
120 // location, time stamp, etc.
ShowModuleInfo(HANDLE LogFile,HINSTANCE ModuleHandle)121 static void ShowModuleInfo(HANDLE LogFile, HINSTANCE ModuleHandle)
122 {
123 	char ModName[MAX_PATH];
124 #ifdef _MSC_VER
125 	__try {
126 #endif
127 		if (GetModuleFileName(ModuleHandle, ModName, sizeof(ModName)) > 0) {
128 			// If GetModuleFileName returns greater than zero then this must
129 			// be a valid code module address. Therefore we can try to walk
130 			// our way through its structures to find the link time stamp.
131 			IMAGE_DOS_HEADER *DosHeader = (IMAGE_DOS_HEADER*)ModuleHandle;
132 			if (IMAGE_DOS_SIGNATURE != DosHeader->e_magic) {
133 	    	    return;
134 			}
135 
136 			IMAGE_NT_HEADERS *NTHeader = (IMAGE_NT_HEADERS*)((char *)DosHeader + DosHeader->e_lfanew);
137 			if (IMAGE_NT_SIGNATURE != NTHeader->Signature) {
138 	    	    return;
139 			}
140 
141 			// Open the code module file so that we can get its file date
142 			// and size.
143 			HANDLE ModuleFile = CreateFile(ModName, GENERIC_READ,
144 						FILE_SHARE_READ, 0, OPEN_EXISTING,
145 						FILE_ATTRIBUTE_NORMAL, 0);
146 			char TimeBuffer[100] = "";
147 			DWORD FileSize = 0;
148 			if (ModuleFile != INVALID_HANDLE_VALUE) {
149 				FileSize = GetFileSize(ModuleFile, 0);
150 				FILETIME	LastWriteTime;
151 				if (GetFileTime(ModuleFile, 0, 0, &LastWriteTime)) {
152 					wsprintf(TimeBuffer, " - file date is ");
153 					PrintTime(TimeBuffer + lstrlen(TimeBuffer), LastWriteTime);
154 				}
155 				CloseHandle(ModuleFile);
156 			}
157 			hprintf(LogFile, "%s, loaded at 0x%08x - %d bytes - %08x%s\r\n",
158 						ModName, ModuleHandle, FileSize,
159 						NTHeader->FileHeader.TimeDateStamp, TimeBuffer);
160 		}
161 #ifdef _MSC_VER
162 	}
163 	// Handle any exceptions by continuing from this point.
164 	__except(EXCEPTION_EXECUTE_HANDLER)
165 	{
166 	}
167 #endif
168 }
169 
170 // Scan memory looking for code modules (DLLs or EXEs). VirtualQuery is used
171 // to find all the blocks of address space that were reserved or committed,
172 // and ShowModuleInfo will display module information if they are code
173 // modules.
174 
RecordModuleList(HANDLE LogFile)175 static void RecordModuleList(HANDLE LogFile)
176 {
177 	hprintf(LogFile, "\r\n"
178 					 "\tModule list: names, addresses, sizes, time stamps "
179 			"and file times:\r\n");
180 	SYSTEM_INFO	SystemInfo;
181 	GetSystemInfo(&SystemInfo);
182 	const size_t PageSize = SystemInfo.dwPageSize;
183 	// Set NumPages to the number of pages in the 4GByte address space,
184 	// while being careful to avoid overflowing ints.
185 	const size_t NumPages = 4 * size_t(ONEG / PageSize);
186 	size_t pageNum = 0;
187 	void *LastAllocationBase = 0;
188 	while (pageNum < NumPages) {
189 		MEMORY_BASIC_INFORMATION	MemInfo;
190 		if (VirtualQuery((void *)(pageNum * PageSize), &MemInfo, sizeof(MemInfo))) {
191 			if (MemInfo.RegionSize > 0) {
192 
193 				// Adjust the page number to skip over this block of memory.
194 				pageNum += MemInfo.RegionSize / PageSize;
195 				if (MemInfo.State == MEM_COMMIT && MemInfo.AllocationBase > LastAllocationBase) {
196 					// Look for new blocks of committed memory, and try
197 					// recording their module names - this will fail
198 					// gracefully if they aren't code modules.
199 					LastAllocationBase = MemInfo.AllocationBase;
200 					ShowModuleInfo(LogFile, (HINSTANCE)LastAllocationBase);
201 				}
202 			} else {
203 				pageNum += SIXTYFOURK / PageSize;
204 			}
205 		} else {
206 			// If VirtualQuery fails we advance by 64K because that is the
207 			// granularity of address space doled out by VirtualAlloc().
208 			pageNum += SIXTYFOURK / PageSize;
209 		}
210 	}
211 }
212 
213 // Record information about the user's system, such as processor type, amount
214 // of memory, etc.
215 
RecordSystemInformation(HANDLE LogFile)216 static void RecordSystemInformation(HANDLE LogFile)
217 {
218 	FILETIME	CurrentTime;
219 	GetSystemTimeAsFileTime(&CurrentTime);
220 	char TimeBuffer[100];
221 	PrintTime(TimeBuffer, CurrentTime);
222 	hprintf(LogFile, "Error occurred at %s.\r\n", TimeBuffer);
223 	char	ModuleName[MAX_PATH];
224 	if (GetModuleFileName(0, ModuleName, sizeof(ModuleName)) <= 0) {
225 		lstrcpy(ModuleName, "Unknown");
226 	}
227 	char	UserName[200];
228 	DWORD UserNameSize = sizeof(UserName);
229 	if (!GetUserName(UserName, &UserNameSize)) {
230 		lstrcpy(UserName, "Unknown");
231 	}
232 	hprintf(LogFile, "%s, run by %s.\r\n", ModuleName, UserName);
233 
234 	SYSTEM_INFO	SystemInfo;
235 	GetSystemInfo(&SystemInfo);
236 	hprintf(LogFile, "%d processor(s), type %d.\r\n",
237 				SystemInfo.dwNumberOfProcessors, SystemInfo.dwProcessorType);
238 
239 	MEMORYSTATUS	MemInfo;
240 	MemInfo.dwLength = sizeof(MemInfo);
241 	GlobalMemoryStatus(&MemInfo);
242 	// Print out the amount of physical memory, rounded up.
243 	hprintf(LogFile, "%d MBytes physical memory.\r\n", (MemInfo.dwTotalPhys +
244 				ONEM - 1) / ONEM);
245 }
246 
247 // Translate the exception code into something human readable.
248 
GetExceptionDescription(DWORD ExceptionCode)249 static const char *GetExceptionDescription(DWORD ExceptionCode)
250 {
251 	struct ExceptionNames
252 	{
253 		DWORD	ExceptionCode;
254 		char*	ExceptionName;
255 	};
256 
257 	ExceptionNames ExceptionMap[] =
258 	{
259 		{0x40010005, "a Control-C"},
260 		{0x40010008, "a Control-Break"},
261 		{0x80000002, "a Datatype Misalignment"},
262 		{0x80000003, "a Breakpoint"},
263 		{0xc0000005, "an Access Violation"},
264 		{0xc0000006, "an In Page Error"},
265 		{0xc0000017, "a No Memory"},
266 		{0xc000001d, "an Illegal Instruction"},
267 		{0xc0000025, "a Noncontinuable Exception"},
268 		{0xc0000026, "an Invalid Disposition"},
269 		{0xc000008c, "a Array Bounds Exceeded"},
270 		{0xc000008d, "a Float Denormal Operand"},
271 		{0xc000008e, "a Float Divide by Zero"},
272 		{0xc000008f, "a Float Inexact Result"},
273 		{0xc0000090, "a Float Invalid Operation"},
274 		{0xc0000091, "a Float Overflow"},
275 		{0xc0000092, "a Float Stack Check"},
276 		{0xc0000093, "a Float Underflow"},
277 		{0xc0000094, "an Integer Divide by Zero"},
278 		{0xc0000095, "an Integer Overflow"},
279 		{0xc0000096, "a Privileged Instruction"},
280 		{0xc00000fD, "a Stack Overflow"},
281 		{0xc0000142, "a DLL Initialization Failed"},
282 		{0xe06d7363, "a Microsoft C++ Exception"},
283 	};
284 
285 	for (int i = 0; i < sizeof(ExceptionMap) / sizeof(ExceptionMap[0]); i++) {
286 		if (ExceptionCode == ExceptionMap[i].ExceptionCode) {
287 			return ExceptionMap[i].ExceptionName;
288 		}
289 	}
290 
291 	return "Unknown exception type";
292 }
293 
GetFilePart(char * source)294 static char* GetFilePart(char *source)
295 {
296 	char *result = strrchr(source, '\\');
297 	if (result) {
298 		result++;
299 	} else {
300 		result = source;
301 	}
302 	return result;
303 }
304 
305 // --------------------
306 //
307 // External Functions
308 //
309 // --------------------
310 
311 SCP_string safe_string;
312 
313 // Entry point into the main exception handling routine.  This routine is put into an except()
314 // statment at the beginning of a thread and is called anytime that there is a program exception
315 // The data is stored in a file called ErrorLog.txt in the data directory.
316 //
317 // data:			pointer to the exception data
318 // Message:		Any message	that should be printed out in the error log file
319 //
320 // returns:
321 //
RecordExceptionInfo(PEXCEPTION_POINTERS data,const char * Message)322 int __cdecl RecordExceptionInfo(PEXCEPTION_POINTERS data, const char *Message)
323 {
324 	static bool BeenHere = false;
325 
326 	// Going recursive! That must mean this routine crashed!
327 	if (BeenHere) {
328 		return EXCEPTION_CONTINUE_SEARCH;
329 	}
330 
331 	BeenHere = true;
332 
333 	char	ModuleName[MAX_PATH];
334 	char	FileName[MAX_PATH] = "Unknown";
335 	// Create a filename to record the error information to.
336 	// Storing it in the executable directory works well.
337 	if (GetModuleFileName(0, ModuleName, sizeof(ModuleName)) <= 0) {
338 		ModuleName[0] = 0;
339 	}
340 
341 	char *FilePart = GetFilePart(ModuleName);
342 
343 	// Extract the file name portion and remove it's file extension. We'll
344 	// use that name shortly.
345 	lstrcpy(FileName, FilePart);
346 	char *lastperiod = strrchr(FileName, '.');
347 	if (lastperiod) {
348 		lastperiod[0] = 0;
349 	}
350 
351 	// Replace the executable filename with our error log file name.
352 	lstrcpy(FilePart, "errorlog.txt");
353 	HANDLE LogFile = CreateFile(ModuleName, GENERIC_WRITE, 0, 0,
354 				OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 0);
355 	if (LogFile == INVALID_HANDLE_VALUE) {
356 		OutputDebugString("Error creating exception report");
357 		return EXCEPTION_CONTINUE_SEARCH;
358 	}
359 
360 	// Append to the error log.
361 	SetFilePointer(LogFile, 0, 0, FILE_END);
362 	// Print out some blank lines to separate this error log from any previous ones.
363 	hprintf(LogFile, "\r\n\r\n\r\n\r\n");
364 	PEXCEPTION_RECORD	Exception = data->ExceptionRecord;
365 	PCONTEXT			Context = data->ContextRecord;
366 
367 	char	CrashModulePathName[MAX_PATH];
368 	char	*CrashModuleFileName = "Unknown";
369 	MEMORY_BASIC_INFORMATION	MemInfo;
370 	// VirtualQuery can be used to get the allocation base associated with a
371 	// code address, which is the same as the ModuleHandle. This can be used
372 	// to get the filename of the module that the crash happened in.
373 	if (VirtualQuery((void*)Context->Eip, &MemInfo, sizeof(MemInfo)) && GetModuleFileName((HINSTANCE)MemInfo.AllocationBase, CrashModulePathName, sizeof(CrashModulePathName)) > 0) {
374 		CrashModuleFileName = GetFilePart(CrashModulePathName);
375 	}
376 
377 	// Print out the beginning of the error log in a Win95 error window
378 	// compatible format.
379 	hprintf(LogFile, "%s caused %s in module %s at %04x:%08x.\r\n",
380 				FileName, GetExceptionDescription(Exception->ExceptionCode),
381 				CrashModuleFileName, Context->SegCs, Context->Eip);
382 	hprintf(LogFile, "Exception handler called in %s.\r\n", Message);
383 	RecordSystemInformation(LogFile);
384 	// If the exception was an access violation, print out some additional
385 	// information, to the error log and the debugger.
386 	if (Exception->ExceptionCode == STATUS_ACCESS_VIOLATION && Exception->NumberParameters >= 2) {
387 		char DebugMessage[1000];
388 		const char* readwrite = "Read from";
389 		if (Exception->ExceptionInformation[0]) {
390 			readwrite = "Write to";
391 		}
392 
393 		wsprintf(DebugMessage, "%s location %08x caused an access violation.\r\n", readwrite, Exception->ExceptionInformation[1]);
394 
395 #ifdef	_DEBUG
396 		// The VisualC++ debugger doesn't actually tell you whether a read
397 		// or a write caused the access violation, nor does it tell what
398 		// address was being read or written. So I fixed that.
399 		OutputDebugString("Exception handler: ");
400 		OutputDebugString(DebugMessage);
401 #endif
402 
403 		hprintf(LogFile, "%s", DebugMessage);
404 	}
405 
406 	// Print out the register values in a Win95 error window compatible format.
407 	hprintf(LogFile, "\r\n");
408 	hprintf(LogFile, "Registers:\r\n");
409 	hprintf(LogFile, "EAX=%08x CS=%04x EIP=%08x EFLGS=%08x\r\n",
410 				Context->Eax, Context->SegCs, Context->Eip, Context->EFlags);
411 	hprintf(LogFile, "EBX=%08x SS=%04x ESP=%08x EBP=%08x\r\n",
412 				Context->Ebx, Context->SegSs, Context->Esp, Context->Ebp);
413 	hprintf(LogFile, "ECX=%08x DS=%04x ESI=%08x FS=%04x\r\n",
414 				Context->Ecx, Context->SegDs, Context->Esi, Context->SegFs);
415 	hprintf(LogFile, "EDX=%08x ES=%04x EDI=%08x GS=%04x\r\n",
416 				Context->Edx, Context->SegEs, Context->Edi, Context->SegGs);
417 	hprintf(LogFile, "Bytes at CS:EIP:\r\n");
418 
419 	// Print out the bytes of code at the instruction pointer. Since the
420 	// crash may have been caused by an instruction pointer that was bad,
421 	// this code needs to be wrapped in an exception handler, in case there
422 	// is no memory to read. If the dereferencing of code[] fails, the
423 	// exception handler will print '??'.
424 	unsigned char *code = (unsigned char*)Context->Eip;
425 	for (int codebyte = 0; codebyte < NumCodeBytes; codebyte++) {
426 #ifdef _MSC_VER
427 		__try {
428 #endif
429 			hprintf(LogFile, "%02x ", code[codebyte]);
430 #ifdef _MSC_VER
431 		}
432 		__except(EXCEPTION_EXECUTE_HANDLER) {
433 			hprintf(LogFile, "?? ");
434 		}
435 #endif
436 	}
437 
438 #ifdef _MSC_VER
439 	// Time to print part or all of the stack to the error log. This allows
440 	// us to figure out the call stack, parameters, local variables, etc.
441 	hprintf(LogFile, "\r\n"
442 					 "Stack dump:\r\n");
443 	__try {
444 		// Esp contains the bottom of the stack, or at least the bottom of
445 		// the currently used area.
446 		DWORD* pStack = (DWORD *)Context->Esp;
447 		DWORD* pStackTop;
448 		__asm
449 		{
450 			// Load the top (highest address) of the stack from the
451 			// thread information block. It will be found there in
452 			// Win9x and Windows NT.
453 			mov	eax, fs:[4]
454 			mov pStackTop, eax
455 		}
456 		if (pStackTop > pStack + MaxStackDump) {
457 			pStackTop = pStack + MaxStackDump;
458 		}
459 
460 		int Count = 0;
461 		// Too many calls to WriteFile can take a long time, causing
462 		// confusing delays when programs crash. Therefore I implemented
463 		// simple buffering for the stack dumping code instead of calling
464 		// hprintf directly.
465 		char	buffer[1000] = "";
466 		const int safetyzone = 50;
467 		char*	nearend = buffer + sizeof(buffer) - safetyzone;
468 		char*	output = buffer;
469 		while (pStack + 1 <= pStackTop) 	{
470 			if ((Count % StackColumns) == 0) {
471 				output += wsprintf(output, "%08x: ", pStack);
472 			}
473 
474 			char *Suffix = " ";
475 			if ((++Count % StackColumns) == 0 || pStack + 2 > pStackTop) {
476 				Suffix = "\r\n";
477 			}
478 
479 			output += wsprintf(output, "%08x%s", *pStack, Suffix);
480 			pStack++;
481 			// Check for when the buffer is almost full, and flush it to disk.
482 			if (output > nearend) {
483 				hprintf(LogFile, "%s", buffer);
484 				buffer[0] = 0;
485 				output = buffer;
486 			}
487 		}
488 		// Print out any final characters from the cache.
489 		hprintf(LogFile, "%s", buffer);
490 	}
491 	__except(EXCEPTION_EXECUTE_HANDLER) {
492 		hprintf(LogFile, "Exception encountered during stack dump.\r\n");
493 	}
494 #endif
495 
496 #ifndef NDEBUG
497 	if (!safe_string.empty())
498 		hprintf(LogFile, "Last safe point: %s\r\n", safe_string.c_str());
499 #endif
500 
501 	RecordModuleList(LogFile);
502 
503 	CloseHandle(LogFile);
504 	// Return the magic value which tells Win32 that this handler didn't
505 	// actually handle the exception - so that things will proceed as per
506 	// normal.
507 
508 	return EXCEPTION_CONTINUE_SEARCH;
509 }
510 
511 #endif // _WIN32
512 #endif
513