/************************************************************************\
 * File Version Information
 * $Header: /Altair32v2/seh.c 4     11/08/06 9:21p Racini $
 ************************************************************************/
/************************************************************************\
  MITS Altair Emulator
  Fault Handler
 
  Copyright (c) 2001-2006 Richard A. Cini
  Copyright (c) 1997 Matt Pietrek and MSJ

  This module installs an exception fault handler so we can catch (and
  deal with) any faults generated by the Altair32 without getting the
  nasty Windows "Application Fault" dialog. 

  The code is based on example code from the 5/97 issue of MSJ, modified
  to work with the Altair32. Thank you Matt for another great piece of
  code.

  The recommendation is to avoid using CRT functions because runtime
  library data might be invalid at the time of an exception. Rely only
  on Win32 functions!
  
  2006/05/12  RAC -- RELEASE MARKER -- v3.10.0200
  2006/11/15  RAC -- RELEASE MARKER -- v3.20.0400
\************************************************************************/
#include <windows.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <tchar.h>
#include <imagehlp.h>
#include "altair32.h"


/**  Typedefs  ************************************************/
// Make typedefs for some IMAGEHLP.DLL functions so they can be
// used with GetProcAddress
#define EFLAGS_SIZE	31	// sizeof(EFLAGS)-1 EFLAGS is 32-bits

typedef BOOL (__stdcall *SYMINITIALIZEPROC)( HANDLE, LPSTR, BOOL );
typedef BOOL (__stdcall *SYMCLEANUPPROC)( HANDLE );
typedef BOOL (__stdcall *STACKWALKPROC)
				( DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID,
				PREAD_PROCESS_MEMORY_ROUTINE,PFUNCTION_TABLE_ACCESS_ROUTINE,
				PGET_MODULE_BASE_ROUTINE, PTRANSLATE_ADDRESS_ROUTINE );
typedef LPVOID (__stdcall *SYMFUNCTIONTABLEACCESSPROC)( HANDLE, DWORD );
typedef DWORD (__stdcall *SYMGETMODULEBASEPROC)( HANDLE, DWORD );
typedef BOOL (__stdcall *SYMGETSYMFROMADDRPROC)
					( HANDLE, DWORD, PDWORD, PIMAGEHLP_SYMBOL );
static SYMINITIALIZEPROC _SymInitialize;
static SYMCLEANUPPROC _SymCleanup;
static STACKWALKPROC _StackWalk;
static SYMFUNCTIONTABLEACCESSPROC _SymFunctionTableAccess;
static SYMGETMODULEBASEPROC _SymGetModuleBase;
static SYMGETSYMFROMADDRPROC _SymGetSymFromAddr;


/**  Local Variables  *****************************************/
static FILE  *sgLogFile;
static LPTOP_LEVEL_EXCEPTION_FILTER previousSEHFilter;
static HANDLE hReportFile;
static TCHAR szLogFileName[MAX_PATH];


/**  Forward Prototypes  **************************************/
extern void InstallFaultHandler(void);
extern void UninstallFaultHandler(void);
LONG WINAPI Win32FaultHandler(struct _EXCEPTION_POINTERS *);
static void GenerateExceptionReport( PEXCEPTION_POINTERS );


// helper functions
static LPTSTR GetExceptionString( DWORD );
static BOOL GetLogicalAddress( PVOID, PTSTR, DWORD, DWORD *, DWORD * );
static void IntelStackWalk( PCONTEXT );
static void ImagehlpStackWalk( PCONTEXT pContext );
static int __cdecl _tprintf(const TCHAR * format, ...);
static BOOL InitImagehlpFunctions( void );


/**************************************************************\
*
*  FUNCTION:    InstallFaultHandler
*
*  INPUTS:      None
*
*  RETURNS:     Nothing
*
*  COMMENTS:	Function to install local fault handler
*
\**************************************************************/
void InstallFaultHandler()
{

	PTSTR pszDot;


    previousSEHFilter = SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER) Win32FaultHandler);

	// Name the report the same as the module name
	GetModuleFileName( 0, szLogFileName, MAX_PATH );

    // Look for the '.' before the "EXE" extension.  Replace the extension
	// with an appropriate alternative
	pszDot = _tcsrchr( szLogFileName, _T('.') );
	if ( pszDot ) {
		//pszDot++;	// advance past the '.'
		if ( _tcslen(pszDot) >= 3 )
			_tcscpy( pszDot, _T("_fault.log") );
	}
}


/**************************************************************\
*
*  FUNCTION:    UninstallFaultHandler
*
*  INPUTS:      None
*
*  RETURNS:     Nothing
*
*  COMMENTS:	Function to uninstall local fault handler by
*				reinstalling the previous fault handler in the
*				chain.
*
\**************************************************************/
void UninstallFaultHandler()
{
	SetUnhandledExceptionFilter( previousSEHFilter );
}


/**************************************************************\
*
*  FUNCTION:    Win32FaultHandler
*
*  INPUTS:      EXCEPTION_POINTER structure
*
*  RETURNS:     EXCEPTION_DISPOSITION
*
*  COMMENTS:	Fault handler callback
*
\**************************************************************/
LONG WINAPI Win32FaultHandler(struct _EXCEPTION_POINTERS *  pExceptionInfo)
{

	LPCTSTR errmsg = {"The application has caused an exception and will be closed. \
Please see the log file for details. If this error persists, \
submit the fault log to the author for analysis.\n"};


	hReportFile = CreateFile( szLogFileName,
								GENERIC_WRITE,
								0,
 								0,
								OPEN_ALWAYS,
								FILE_FLAG_WRITE_THROUGH,
								0 );

	if ( hReportFile ){
		// MessageBox with hwnd=NULL means dialog has no owner. Task Modal.
		MessageBox(NULL, errmsg, SZWINDOWTEXT, MB_OK | MB_ICONERROR | MB_TASKMODAL);
		SetFilePointer( hReportFile, 0, 0, FILE_END );
		GenerateExceptionReport( pExceptionInfo );
		CloseHandle( hReportFile );
		hReportFile = 0;
	}

	if ( previousSEHFilter )
		return previousSEHFilter( pExceptionInfo );
	else
		return EXCEPTION_CONTINUE_SEARCH;
}


//===========================================================================
// Open the report file, and write the desired information to it.  Called by 
// the Win32FaultHandler during an exception.
//===========================================================================
void GenerateExceptionReport( PEXCEPTION_POINTERS pExceptionInfo )
{

	DWORD section = 0, offset = 0;
	int i = 0;
	PCONTEXT pCtx;
	PEXCEPTION_RECORD pExceptionRecord;
	SYSTEMTIME systime;
	TCHAR szFaultingModule[MAX_PATH];


	GetLocalTime(&systime);	// this is local time; GetSystemTime uses UTC
	pExceptionRecord = pExceptionInfo->ExceptionRecord;
	
    // Start out with a banner
	_tprintf( _T("****************************************************\n") );
	_tprintf( _T("*** A Programm Fault occured on: \n") );
	_tprintf( _T("*** %02d/%02d/%02d - %02d:%02d\n"), systime.wMonth,
					systime.wDay, systime.wYear, systime.wHour, systime.wMinute);
	_tprintf( _T("*** Exception code: %08X %s\n"),
               pExceptionRecord->ExceptionCode,
               GetExceptionString(pExceptionRecord->ExceptionCode) );
	_tprintf( _T("****************************************************\n") );

	// Now print information about where the fault occured
	GetLogicalAddress( pExceptionRecord->ExceptionAddress,
 						szFaultingModule,
						sizeof( szFaultingModule ),
						&section, &offset );

	// added address reference on section and offset
	_tprintf( _T("Fault address and module:\n"));
	_tprintf( _T("Address  Sect:Offset Module Name\n"));
	_tprintf( _T("%08X %02X:%08X %s\n"), pExceptionRecord->ExceptionAddress,
				section, offset, szFaultingModule );

	pCtx = pExceptionInfo->ContextRecord;

	// Show the registers - Intel only
#ifdef _M_IX86
	_tprintf( _T("\nRegisters:\n") );

	_tprintf(_T("EAX: %08X\nEBX: %08X\nECX: %08X\nEDX: %08X\nESI: %08X\nEDI: %08X\n"),
            pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi );

	_tprintf( _T("CS:EIP: %04X:%08X\n"), pCtx->SegCs, pCtx->Eip );
	_tprintf( _T("SS:ESP: %04X:%08X  EBP: %08X\n"),
                pCtx->SegSs, pCtx->Esp, pCtx->Ebp );
	_tprintf( _T("DS: %04X  ES: %04X  FS: %04X  GS: %04X\n"),
                pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs );
//	_tprintf( _T("Flags: %08X\n"), pCtx->EFlags );

	// print eflags in binary. assumes eflags is 32-bits wide.
	_tprintf( _T("Flags: %08X  ("), pCtx->EFlags );
	for(i = EFLAGS_SIZE; i >= 0; i--){
		if((1 << i) & pCtx->EFlags) _tprintf( _T("1"));
        else _tprintf( _T("0"));
    }
	_tprintf( _T(")b\n"));
#endif

// added
	if ( !InitImagehlpFunctions() ){
		OutputDebugString(_T("IMAGEHLP.DLL or its exported procs not found."));
        
#ifdef _M_IX86  // Intel Only!
		// Walk the stack using x86 specific code
		IntelStackWalk( pCtx );
#endif

		return;
	}

    ImagehlpStackWalk( pCtx );

    _SymCleanup( GetCurrentProcess() );
// end of add
    
	_tprintf( _T("*** End of Exception *******************************\n\n") );
}


//======================================================================
// Given an exception code, returns a pointer to a static string with a 
// description of the exception                                         
//======================================================================
LPTSTR GetExceptionString( DWORD dwCode )
{
	#define EXCEPTION( x ) case EXCEPTION_##x: return _T(#x);
	static TCHAR szBuffer[512];

	switch ( dwCode ){
		EXCEPTION( ACCESS_VIOLATION )
		EXCEPTION( DATATYPE_MISALIGNMENT )
		EXCEPTION( BREAKPOINT )
		EXCEPTION( SINGLE_STEP )
		EXCEPTION( ARRAY_BOUNDS_EXCEEDED )
		EXCEPTION( FLT_DENORMAL_OPERAND )
		EXCEPTION( FLT_DIVIDE_BY_ZERO )
		EXCEPTION( FLT_INEXACT_RESULT )
		EXCEPTION( FLT_INVALID_OPERATION )
		EXCEPTION( FLT_OVERFLOW )
		EXCEPTION( FLT_STACK_CHECK )
		EXCEPTION( FLT_UNDERFLOW )
		EXCEPTION( INT_DIVIDE_BY_ZERO )
		EXCEPTION( INT_OVERFLOW )
		EXCEPTION( PRIV_INSTRUCTION )
		EXCEPTION( IN_PAGE_ERROR )
		EXCEPTION( ILLEGAL_INSTRUCTION )
		EXCEPTION( NONCONTINUABLE_EXCEPTION )
		EXCEPTION( STACK_OVERFLOW )
		EXCEPTION( INVALID_DISPOSITION )
		EXCEPTION( GUARD_PAGE )
		EXCEPTION( INVALID_HANDLE )
	}

	// If not one of the "known" exceptions, try to get the string
	// from the message table in NTDLL.DLL.
	szBuffer[0] = '\0';

	FormatMessage( FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE,
                    GetModuleHandle( _T("NTDLL.DLL") ),
                    dwCode, 0, szBuffer, sizeof( szBuffer ), 0 );

	return szBuffer;
}


//==============================================================================
// Given a linear address, locates the module, section, and offset containing  
// that address.                                                               
//                                                                             
// Note: the szModule paramater buffer is an output buffer of length specified 
// by the len parameter (in characters!)                                       
//==============================================================================
BOOL GetLogicalAddress( PVOID addr, PTSTR szModule, DWORD len,
						DWORD *section, DWORD *offset )
{
	

	DWORD hMod;
	DWORD rva;
	DWORD sectionStart;
	DWORD sectionEnd;
	MEMORY_BASIC_INFORMATION mbi;
	PIMAGE_DOS_HEADER pDosHdr;
	PIMAGE_NT_HEADERS pNtHdr; 
	PIMAGE_SECTION_HEADER pSection;
	unsigned i;
	

	if ( !VirtualQuery( addr, &mbi, sizeof(mbi) ) )
		return FALSE;

	hMod = (DWORD)mbi.AllocationBase;

	if ( !GetModuleFileName( (HMODULE)hMod, szModule, len ) )
        return FALSE;

	// Point to the DOS header in memory
	pDosHdr = (PIMAGE_DOS_HEADER)hMod;

	// From the DOS header, find the NT (PE) header
	pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew);
	pSection = IMAGE_FIRST_SECTION( pNtHdr );
	rva = (DWORD)addr - hMod; // RVA is offset from module load address

	// Iterate through the section table, looking for the one that encompasses
	// the linear address.
	for ( i = 0; i < pNtHdr->FileHeader.NumberOfSections; i++, pSection++ )
	{
		sectionStart = pSection->VirtualAddress;
		sectionEnd = sectionStart + max(pSection->SizeOfRawData, pSection->Misc.VirtualSize);

		// Is the address in this section???
		if ( (rva >= sectionStart) && (rva <= sectionEnd) ){
			// Yes, address is in the section.  Calculate section and offset,
			// and store in the "section" & "offset" params, which were
			// passed by reference.
			*section = i + 1;
			*offset = rva - sectionStart;
			return TRUE;
		}
	}

	return FALSE;   // Should never get here!
}


//============================================================
// Walks the stack, and writes the results to the report file 
//============================================================
void IntelStackWalk( PCONTEXT pContext )
{
	DWORD pc;
	PDWORD pFrame, pPrevFrame;		
	DWORD section = 0, offset = 0;
	TCHAR szModule[_MAX_PATH] = _T("");

	_tprintf( _T("\nCall stack:\n") );
	_tprintf( _T("Address   Frame     Logical addr  Module\n") );

	pc = pContext->Eip;
	pFrame = (PDWORD)pContext->Ebp;

	do {
		szModule[0] = '\0';
		section = 0;
		offset = 0;

		GetLogicalAddress((PVOID)pc, szModule, sizeof(szModule), &section, &offset );

		_tprintf( _T("%08X  %08X  %04X:%08X %s\n"),
                    pc, pFrame, section, offset, szModule );

		pc = pFrame[1];
		pPrevFrame = pFrame;
		pFrame = (PDWORD)pFrame[0]; // precede to next higher frame on stack

		if ( (DWORD)pFrame & 3 )    // Frame pointer must be aligned on a
            break;                  // DWORD boundary.  Bail if not so.

		if ( pFrame <= pPrevFrame )
            break;

		// Can two DWORDs be read from the supposed frame address?          
		if ( IsBadWritePtr(pFrame, sizeof(PVOID)*2) )
			break;

	} while ( 1 );
}


//============================================================
// Walks the stack, and writes the results to the report file 
//============================================================
void ImagehlpStackWalk( PCONTEXT pContext )
{

	BYTE symbolBuffer[ sizeof(IMAGEHLP_SYMBOL) + 512 ];
	DWORD symDisplacement = 0;  // Displacement of the input address,
								// relative to the start of the symbol
	PIMAGEHLP_SYMBOL pSymbol;
	STACKFRAME sf;
	TCHAR szModule[MAX_PATH] = _T("");
	DWORD section = 0, offset = 0;

	_tprintf( _T("\nCall stack:\n") );
	_tprintf( _T("Address   Frame\n") );

	// Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag
	memset( &sf, 0, sizeof(sf) );

	// Initialize the STACKFRAME structure for the first call.  This is only
	// necessary for Intel CPUs, and isn't mentioned in the documentation.
	sf.AddrPC.Offset       = pContext->Eip;
	sf.AddrPC.Mode         = AddrModeFlat;
	sf.AddrStack.Offset    = pContext->Esp;
	sf.AddrStack.Mode      = AddrModeFlat;
	sf.AddrFrame.Offset    = pContext->Ebp;
	sf.AddrFrame.Mode      = AddrModeFlat;

	while ( 1 ) {
		if ( ! _StackWalk(  IMAGE_FILE_MACHINE_I386,
                            GetCurrentProcess(),
                            GetCurrentThread(),
                            &sf,
                            pContext,
                            0,
                            _SymFunctionTableAccess,
                            _SymGetModuleBase,
                            0 ) )
			break;

		if ( 0 == sf.AddrFrame.Offset ) // Basic sanity check to make sure
            break;                      // the frame is OK.  Bail if not.

		_tprintf( _T("%08X  %08X  "), sf.AddrPC.Offset, sf.AddrFrame.Offset );

		// IMAGEHLP is wacky, and requires you to pass in a pointer to a
		// IMAGEHLP_SYMBOL structure.  The problem is that this structure is
		// variable length. That is, you determine how big the structure is
		// at runtime. This means that you can't use sizeof(struct).
		// So...make a buffer that's big enough, and make a pointer
		// to the buffer. We also need to initialize not one, but TWO
		// members of the structure before it can be used.

		pSymbol = (PIMAGEHLP_SYMBOL)symbolBuffer;
		pSymbol->SizeOfStruct = sizeof(symbolBuffer);
		pSymbol->MaxNameLength = 512;
                        
		if ( _SymGetSymFromAddr(GetCurrentProcess(), sf.AddrPC.Offset,
                                &symDisplacement, pSymbol) )
		{
			_tprintf( _T("%hs+%X\n"), pSymbol->Name, symDisplacement );
            
		}
		else    // No symbol found.  Print out the logical address instead.
		{

		GetLogicalAddress( (PVOID)sf.AddrPC.Offset,
                                szModule, sizeof(szModule), &section, &offset );

			_tprintf( _T("%04X:%08X %s\n"),
                        section, offset, szModule );
		}
	}

}


//=========================================================================
// Load IMAGEHLP.DLL and get the address of functions in it that we'll use 
//=========================================================================
BOOL InitImagehlpFunctions( void )
{
	HMODULE hModImagehlp;

	hModImagehlp = LoadLibrary( _T("IMAGEHLP.DLL") );
	if ( !hModImagehlp )
		return FALSE;

	_SymInitialize = (SYMINITIALIZEPROC)GetProcAddress( hModImagehlp,
                                                        "SymInitialize" );
	if ( !_SymInitialize )
		return FALSE;

	_SymCleanup = (SYMCLEANUPPROC)GetProcAddress( hModImagehlp, "SymCleanup" );
	if ( !_SymCleanup )
		return FALSE;

	_StackWalk = (STACKWALKPROC)GetProcAddress( hModImagehlp, "StackWalk" );
	if ( !_StackWalk )
		return FALSE;

	_SymFunctionTableAccess = (SYMFUNCTIONTABLEACCESSPROC)
		GetProcAddress( hModImagehlp, "SymFunctionTableAccess" );

	if ( !_SymFunctionTableAccess )
		return FALSE;

	_SymGetModuleBase = (SYMGETMODULEBASEPROC)GetProcAddress( hModImagehlp,
                                                        "SymGetModuleBase");
	if ( !_SymGetModuleBase )
		return FALSE;

	_SymGetSymFromAddr = (SYMGETSYMFROMADDRPROC)GetProcAddress( hModImagehlp,
                                                        "SymGetSymFromAddr" );
	if ( !_SymGetSymFromAddr )
		return FALSE;

	if ( !_SymInitialize( GetCurrentProcess(), 0, TRUE ) )
		return FALSE;

	return TRUE;        
}


//============================================================================
// Helper function that writes to the report file, and allows the user to use 
// printf style formating                                                     
//============================================================================
int __cdecl _tprintf(const TCHAR * format, ...)
{
	TCHAR szBuff[1024];
	int retValue;
	DWORD cbWritten;
	va_list argptr;
          
	va_start( argptr, format );
	retValue = wvsprintf( szBuff, format, argptr );
	va_end( argptr );

	WriteFile( hReportFile, szBuff, retValue * sizeof(TCHAR), &cbWritten, 0 );

	return retValue;
}

/*	end of file: seh.c	*/
