After exhaustive searching, reading, and searching again. I submit my latest attempt at logging errors that happen and you did NOT plan for.
Maybe with this you may track down that bug you can not find. (or closer to it)
ErrorHandling.inc
Demo for ErrorHandler
Discussion can be found at RunTime Debug and Error Handling Part II - Find that elusive bug
Including points I have not figured out, or maybe points I have not thought of yet
Maybe with this you may track down that bug you can not find. (or closer to it)
ErrorHandling.inc
Code:
'*** ErrorHandling.inc '*** Written by: Cliff Nichols - 08-28-2008 '*** Modified by: Cliff Nichols - 05-19-2009 '*** Compiler: 8.04/9.0/9.01 '*** Should work on: 7/8/9 '*** Tested on: XP-Pro SP3, Vista-Ultimate '*** Should work on: 95/98/ME/NT/2K/XP/Vista '*** Purpose - Beefed up Error Handling Routines to attempt to "Bullet-Proof" apps '-------------------------------------------------------------------------------- '*** Now for the FUN Stuff '*** PB has 'On Error' for handling errors on the DDT side '*** PB (DDT) can set an error with ERR = some number (does not raise an error, just sets the value) '*** PB (DDT) can raise an error just by ERROR = some number '*** PB (DDT) can test an error state by checking the value of ERR '*** PB appears to work with 'On Error' for handling errors on the SDK side '<--- Unsure if Windows (SDK) has an equivelent 'On Error' api '*** Windows (SDK) can set an error with 'SetLastError (does not raise an error, just sets the value) '*** Windows (SDK) can set an error with parameters using 'SetLastErrorEX (does not raise an error, just sets the value) '*** Windows (SDK) can raise an error with 'RaiseException' api call '*** Windows (SDK) can test an error state by 'GetLastError' '*** Windows has "Exceptions" (You've seen them...the default usually shows the typical "*.exe has encountered a problem and needs to close" message) '*** Windows "Exceptions" can be raised, but default handler is typically fatal, but you can override that fatality '*** Manually inserting Line Numbers can definitely nail down an error, but either needs to be done by hand, or pre-compile to do it for you '*** PB Line numbers (if inserted) can assist in locating where an error occured (larger projects its smarter to contain line numbers to their functions) '*** PB FUNCNAME$ in conjunction with the line numbers can nail down what line the error occured on '*** Conjunction, junction, whats your function??? :-) '*** Out of the ordinary ideas: '*** No Globals (Not 100% on this yet, but no problems either) '*** What makes it all work???? '*** MACRO's baby, yeah baby yeah...(Finally got this one when larger code all had the same multiple commands and got tedious to type or copy/paste) '*** Functions to store or set what would be GLOBAL Variables '<--- Not 100% sure if this is safe yet, or just luck of the straw '*** '*** '-------------------------------------------------------------------------------- '*** If not already declared in your code, then declare this INC #IF NOT %DEF(%ERRORHANDLING) %ERRORHANDLING = 1 '*** Globals '*** NONE '*** Constants %UNKNOWN_VALUE = -1 %RESUME = 1 %RESUMENEXT = 0 'Use 0 for Resume Next in case flag is not set to avoid infinite loops %NORESUME = -1 %CRASH = %INVALID_HANDLE_VALUE 'Purposely allow crash of App '*** Constants not found in Win32Api.Inc %EXCEPTION_ILLEGAL_INSTRUCTION = &HC000001D %EXCEPTION_STACK_OVERFLOW = &HC00000FD %EXCEPTION_INVALID_DISPOSITION = &HC0000026 %EXCEPTION_NONCONTINUABLE_EXCEPTION = &HC0000025 TYPE ErrorCodes lpEP AS EXCEPTION_POINTERS PTR 'Windows EXCEPTIONS (Errors) ErrorLogNumber AS LONG 'Handle to log file LogError AS LONG 'If logging errors PbErrorValue AS LONG 'PB ERR WinErrorValue AS LONG 'GetLastError AppErrorValue AS LONG 'Error value reguardless of PB Error or Windows Error PbErrorDesc AS ASCIIZ * %MAX_PATH 'PB Description of Error in plain english WinErrorDesc AS ASCIIZ * %MAX_PATH 'Windows Description of Error in plain english AppErrorDesc AS ASCIIZ * %MAX_PATH 'Application Specific Description of Error CodeModule AS ASCIIZ * %MAX_PATH 'Function that caused the error '<--- MOST Important thing I have always been after in larger projects) LineLabelNumber AS ASCIIZ * %MAX_PATH 'Line number (or Variable) that was last passed before the error OverRideError AS LONG 'Variable for if to continue even if there is an error Desc AS ASCIIZ * %MAX_PATH 'Description for loggin purposes to help track down a problem END TYPE '*** Declare Functions '************************************************************************************************************* '*** Macro's - When Compiled, replace the name of the macro with all the functions and processes in the macro '*** As if you had done it yourself in each and every SUB/FUNCTION it was called '*** Do not add line numbers to Macro's unless you want to renumber functions that use the macros '************************************************************************************************************* '*** Using SetUnhandledExceptionFilter you can over-ride the default error handler for windows '*** SetUnhandledExceptionFilter %NULL = Default handling within UnhandledExceptionFilter '*** SetUnhandledExceptionFilter %EXCEPTION_EXECUTE_HANDLER = Return from UnhandledExceptionFilter and execute the associated exception handler. '*** (Usually results in process termination.) '*** SetUnhandledExceptionFilter %EXCEPTION_CONTINUE_EXECUTION = Return from UnhandledExceptionFilter and continue execution from the point of the exception. '*** Note that the filter function is free to modify the continuation state by modifying the exception information supplied through its LPEXCEPTION_POINTERS parameter '*** SetUnhandledExceptionFilter %EXCEPTION_CONTINUE_SEARCH = Proceed with normal execution of UnhandledExceptionFilter . That means obeying the SetErrorMod flags, or invoking the Application Error pop-up message box. '*** OnError MACRO OnError ON ERROR GOTO ErrHandler 'PB command for On Error LOCAL lpEP AS EXCEPTION_POINTERS 'If Windows Crops up an error LOCAL ErrFunc AS LONG 'If Error within my function LOCAL SoftwarePathName AS ASCIIZ * %MAX_PATH LOCAL ErrorLogNumber AS LONG LOCAL ErrorCode AS ErrorCodes LOCAL Continuable AS LONG SetUnhandledExceptionFilter( CODEPTR (ExceptionHandler)) 'Over-Ride Windows default and let me decide if a FATAL error or to 'Fix-And-Continue' SetGetErrorCodes VARPTR(ErrorCode), %UNKNOWN_VALUE, %TRUE 'Set a handle to ErrorCodes without using GLOBALS '<--- Not 100% on this yet GetModuleFileName(GetModuleHandle(BYVAL %NULL), SoftwarePathName, %MAX_PATH) 'Get Parent PathName and Name SoftwarePathName = MID$(SoftwarePathName, INSTR(-1, SoftwarePathName,"\") + 1) 'Path Name SoftwarePathName = MID$(SoftwarePathName, 1, INSTR(SoftwarePathName,".") - 1) 'Strip the .Dll or .Exe '** Using TRACE caused errors yet to be tested (mostly VISTA), so replaced it with my own log ' TRACE NEW SoftwarePathName + " Error Log.txt" ' TRACE ON ' 'TRACE PRINT FUNCNAME$ ErrorLogNumber = FREEFILE 'Typical FreeFile ErrorCode.ErrorLogNumber = ErrorLogNumber 'Store the handle for use in Exception Handling OPEN SoftwarePathName + " Error Log.txt" FOR APPEND LOCK SHARED AS #ErrorLogNumber 'Open the log file END MACRO '*** HandleErrors MACRO HandleErrors '*** Return Error Handling to default '<--- Will be skipped over until reached by no errors ' TRACE OFF 'TRACE commands caused errors ' TRACE CLOSE 'TRACE commands caused errors CLOSE ErrorLogNumber 'End log file ' SetUnhandledExceptionFilter %NULL 'Supersede top-level exception handler that Win32 puts at the top of each thread and process ' exit '<--- Just 'EXIT' in a macro does not work although docs say "Using EXIT by itself will leave the most recently executed structure." ???? EXIT MACRO 'Exit (do not care if sub/function/method/property ' EXIT function 'Exit (do not care if sub/function/method/property ErrHandler: 'Only gets here if an error '*** The following Error Information has to stay in the macro due to the error is cleared when it leaves the function that it was raised in. ErrorCode.PbErrorValue = ERR 'Get PB Last Error ErrorCode.WinErrorValue = GetLastError 'Get API Last Error ExceptionInfo(ErrorCode) 'Fill Information for logging SELECT CASE ErrorCode.OverRideError 'Decide how to continue CASE %UNKNOWN_VALUE Continuable = MSGBOX("Error: " + STR$(ErrorCode.AppErrorValue) + SPACE$(5) + "Desc: " + ErrorCode.AppErrorDesc + $CR _ + " Occurred in " + CALLSTK$(1) + $CR + $CR + "Ignore this error?", %MB_ICONERROR OR %MB_YESNO, "Windows Error") SELECT CASE Continuable CASE %IDYES RESUME NEXT CASE %IDNO END SELECT CASE %NORESUME CASE %RESUME RESUME CASE %RESUMENEXT RESUME NEXT END SELECT END MACRO '*** Test function to remove the need for GLOBALS, buy calling a function and Set or Get the value FUNCTION SetGetErrorCodes(ValueToSet AS LONG, ValueResults AS LONG, ResetValue AS LONG) AS LONG STATIC FunctionValue AS LONG 'Static to hold current value SELECT CASE ResetValue 'Decide whether to Set or to Get the current value CASE %False, %UNKNOWN_VALUE 'If set to False, or -1 Then Get Current Value ValueResults = FunctionValue 'Return Results as a parameter CASE = %TRUE 'If set to True then Reset the Current Value FunctionValue = ValueToSet 'Reset the value ValueResults = FunctionValue 'Return Results as a parameter END SELECT FUNCTION = %False 'Return if Function Failed END FUNCTION '*** Test function to remove the need for GLOBALS, buy calling a function and Set or Get the value FUNCTION SetGetLogErrors(ValueToSet AS LONG, ValueResults AS LONG, ResetValue AS LONG) AS LONG STATIC FunctionValue AS LONG 'Static to hold current value SELECT CASE ResetValue 'Decide whether to Set or to Get the current value CASE %False, %UNKNOWN_VALUE 'If set to False, or -1 Then Get Current Value ValueResults = FunctionValue 'Return Results as a parameter CASE = %TRUE 'If set to True then Reset the Current Value FunctionValue = ValueToSet 'Reset the value ValueResults = FunctionValue 'Return Results as a parameter END SELECT FUNCTION = %False 'Return if Function Failed END FUNCTION '-------------------------------------------------------------------------------- '*** Now for the DOOZYYYYyyyy....Handling GPF's and other major fatal errors '*** Commented fields are ones that I have no value for (yet) but MSDN say they exist '-------------------------------------------------------------------------------- '************************************************************************************************************* '*** ExceptionInfo is information that can be caught by 'ON ERROR' type of errors '************************************************************************************************************* FUNCTION ExceptionInfo(BYREF ErrorCode AS ErrorCodes) AS LONG STATIC TerminateInProcess AS LONG 'In case an error occurs while in the midst of ending my app '*** Get the PB and Windows Error Descriptions (not caring which caused the error) ErrorCode.PbErrorDesc = ERROR$(ErrorCode.PbErrorValue) 'Set the PB Error Description FormatMessage %FORMAT_MESSAGE_FROM_SYSTEM, BYVAL %NULL, ErrorCode.WinErrorValue, %NULL, ErrorCode.WinErrorDesc, SIZEOF(ErrorCode.WinErrorDesc), BYVAL %NULL 'Format the Windows Error Description ErrorCode.AppErrorDesc = "" 'Clear the Application Specific Description ' ErrorCode.CodeModule = CALLSTK$(CALLSTKCOUNT - 1) 'Figure out what function was the 'bad puppy' that caused the error ErrorCode.CodeModule = CALLSTK$(2) 'Figure out what function was the 'bad puppy' that caused the error '*** Revision check for if capable of logging the line Number that the error occurred on, or not SELECT CASE %PB_REVISION CASE > = &H900 ErrorCode.LineLabelNumber = ERL$ CASE < &H900 ErrorCode.LineLabelNumber = "" END SELECT '*** Check if logging errors (Commented out TRACE commands until fixed) SELECT CASE ErrorCode.LogError CASE %FALSE CASE %TRUE TRACE ON ErrorCode.Desc = "" ErrorCode.Desc = "Error PB = " + TRIM$(STR$(ErrorCode.PbErrorValue)) ErrorCode.Desc = ErrorCode.Desc + SPACE$(5) ErrorCode.Desc = ErrorCode.Desc + "Desc = " + ErrorCode.PbErrorDesc REPLACE $CR WITH "" IN ErrorCode.Desc 'TRACE PRINT ErrorCode.Desc IF ErrorCode.LogError THEN PRINT# ErrorCode.ErrorLogNumber, ErrorCode.Desc ErrorCode.Desc = "" ErrorCode.Desc = "Error Windows = " + TRIM$(STR$(ErrorCode.WinErrorValue)) ErrorCode.Desc = ErrorCode.Desc + SPACE$(5) ErrorCode.Desc = ErrorCode.Desc + "Desc = " + ErrorCode.WinErrorDesc REPLACE $CR WITH "" IN ErrorCode.Desc 'TRACE PRINT ErrorCode.Desc IF ErrorCode.LogError THEN PRINT# ErrorCode.ErrorLogNumber, ErrorCode.Desc SELECT CASE ErrorCode.PbErrorValue CASE 0 SELECT CASE ErrorCode.WinErrorValue CASE 0 ErrorCode.AppErrorValue = 0 ErrorCode.AppErrorDesc = "The operation completed successfully CASE ELSE ErrorCode.AppErrorValue = ErrorCode.WinErrorValue ErrorCode.AppErrorDesc = ErrorCode.WinErrorDesc END SELECT CASE ELSE ErrorCode.AppErrorValue = ErrorCode.PbErrorValue ErrorCode.AppErrorDesc = ErrorCode.PbErrorDesc END SELECT ErrorCode.Desc = "" ErrorCode.Desc = "Error App = " + TRIM$(STR$(ErrorCode.AppErrorValue)) ErrorCode.Desc = ErrorCode.Desc + SPACE$(5) ErrorCode.Desc = ErrorCode.Desc + "Desc = " + ErrorCode.AppErrorDesc REPLACE $CR WITH "" IN ErrorCode.Desc 'TRACE PRINT ErrorCode.Desc IF ErrorCode.LogError THEN PRINT# ErrorCode.ErrorLogNumber, ErrorCode.Desc ErrorCode.Desc = "" ErrorCode.Desc = ErrorCode.Desc + SPACE$(5) ErrorCode.Desc = ErrorCode.Desc + "Error Occurred in " + ErrorCode.CodeModule SELECT CASE ErrorCode.LineLabelNumber CASE "" CASE ELSE SELECT CASE ErrorCode.LineLabelNumber CASE ErrorCode.CodeModule CASE ELSE ErrorCode.Desc = ErrorCode.Desc + SPACE$(5) ErrorCode.Desc = ErrorCode.Desc + "Last Passed Label/Line Number = " + ErrorCode.LineLabelNumber END SELECT END SELECT REPLACE $CR WITH "" IN ErrorCode.Desc REPLACE $LF WITH "" IN ErrorCode.Desc 'TRACE PRINT ErrorCode.Desc IF ErrorCode.LogError THEN PRINT# ErrorCode.ErrorLogNumber, ErrorCode.Desc END SELECT IF ErrorCode.LogError THEN PRINT# ErrorCode.ErrorLogNumber, $CRLF '*** <--- Possibly add assembly values later, but for now took them out END FUNCTION '************************************************************************************************************* '*** ExceptionHandler is MUCH more difficult. (and Difficult to understand the documentation) '************************************************************************************************************* '*** <--- MEGA-IMPORTANT!!!! '*** According to http://www.debuginfo.com/articles/debugfilters.html '*** Custom filters for unhandled exceptions are not called at all when the application is running under debugger. '*** So this will not be called if using debugger '*** '************************************************************************************************************* '************************************************************************************************************* '*** '*** Exception Handler is what gets called if raising a Windows Error, or the infamous '*** "HEY we found an Error and Have to Close" '*** '************************************************************************************************************* FUNCTION ExceptionHandler(BYREF lpEP AS EXCEPTION_POINTERS) AS LONG STATIC TerminateInProcess AS LONG LOCAL ErrorRecord AS EXCEPTION_RECORD POINTER LOCAL ErrorCode AS ErrorCodes POINTER LOCAL TempErrorCode AS LONG LOCAL Continuable AS LONG LOCAL i AS LONG SetGetErrorCodes TempErrorCode, TempErrorCode, %UNKNOWN_VALUE 'Since Windows NEEDS 'lpEP AS EXCEPTION_POINTERS' and only accepts 1 parameter, I had to find a work-around to Exceptions only wanting Exception Pointers ErrorCode = TempErrorCode @ErrorCode.lpEP = VARPTR(lpEP) SELECT CASE TerminateInProcess 'If App is unloading and a Error appears '<--- To be developed later CASE %TRUE CASE %FALSE 'If App is unloading and a Error appears '<--- To be developed later ' TerminateInProcess = %TRUE SELECT CASE @[email protected] 'What Error caused me? CASE 0 CASE ELSE ErrorRecord = @[email protected] 'Detect the actual exception record SELECT CASE @ErrorRecord.pExceptionRecord '<--- Array out of bounds if not checked for null pointer first CASE 0 CASE ELSE DO UNTIL @ErrorRecord.pExceptionRecord = 0 'Gather the exception record(s) CALL MoveMemory(@ErrorRecord, @ErrorRecord.pExceptionRecord, SIZEOF(ErrorRecord)) LOOP END SELECT '*** Alert the log to 'FATAL' Errors that could occur @ErrorCode.Desc = "" SELECT CASE @ErrorRecord.ExceptionCode CASE %EXCEPTION_ACCESS_VIOLATION @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_ACCESS_VIOLATION" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) SELECT CASE @ErrorRecord.ExceptionInformation(0) CASE 0 @ErrorCode.Desc = @ErrorCode.Desc + " Desc = " + "The thread tried to READ from virtual address" _ + STR$(@ErrorRecord.ExceptionInformation(1)) + " for which it does not have the appropriate access" CASE 1 @ErrorCode.Desc = @ErrorCode.Desc + " Desc = " + "The thread tried to WRITE to virtual address" _ + STR$(@ErrorRecord.ExceptionInformation(1)) + " for which it does not have the appropriate access" CASE 8 @ErrorCode.Desc = @ErrorCode.Desc + " Desc = " + "The thread tried to cause a user-mode data execution prevention (DEP) violation at virtual address" _ + STR$(@ErrorRecord.ExceptionInformation(1)) END SELECT CASE %EXCEPTION_ARRAY_BOUNDS_EXCEEDED @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_ARRAY_BOUNDS_EXCEEDED" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The thread tried to access an array element that is out of bounds and the underlying hardware supports bounds checking." CASE %EXCEPTION_BREAKPOINT @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_BREAKPOINT" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "A breakpoint was encountered." CASE %EXCEPTION_DATATYPE_MISALIGNMENT @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_DATATYPE_MISALIGNMENT" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The thread tried to READ or WRITE data that is misaligned on hardware that does not provide alignment." _ + "For example, 16-bit values must be aligned on 2-byte boundaries" CASE %EXCEPTION_FLT_DENORMAL_OPERAND @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_FLT_DENORMAL_OPERAND" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "One of the operands in a floating-point operation is denormal." _ + "A denormal value is one that is too small to represent as a standard floating-point value." CASE %EXCEPTION_FLT_DIVIDE_BY_ZERO @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_FLT_DIVIDE_BY_ZERO" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The thread tried to divide a floating-point value by a floating-point divisor of zero." CASE %EXCEPTION_FLT_INEXACT_RESULT @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_FLT_INEXACT_RESULT" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The result of a floating-point operation cannot be represented exactly as a decimal fraction." CASE %EXCEPTION_FLT_INVALID_OPERATION @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_FLT_INVALID_OPERATION" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "This exception represents any floating-point exception not included in this list." CASE %EXCEPTION_FLT_OVERFLOW @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_FLT_OVERFLOW" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The exponent of a floating-point operation is greater than the magnitude allowed by the corresponding type." CASE %EXCEPTION_FLT_STACK_CHECK @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_FLT_STACK_CHECK" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The stack overflowed or underflowed as the result of a floating-point operation." CASE %EXCEPTION_FLT_UNDERFLOW @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_FLT_UNDERFLOW" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The exponent of a floating-point operation is less than the magnitude allowed by the corresponding type." CASE %EXCEPTION_ILLEGAL_INSTRUCTION @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_ILLEGAL_INSTRUCTION" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The thread tried to execute an invalid instruction." CASE %EXCEPTION_IN_PAGE_ERROR @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_IN_PAGE_ERROR" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) SELECT CASE @ErrorRecord.ExceptionInformation(0) CASE 0 @ErrorCode.Desc = @ErrorCode.Desc + " Desc = " + "The thread tried to READ from virtual address" _ + STR$(@ErrorRecord.ExceptionInformation(1)) + " for which it does not have the appropriate access" _ + $CRLF + "NTSTATUS = " + STR$(@ErrorRecord.ExceptionInformation(2)) CASE 1 @ErrorCode.Desc = @ErrorCode.Desc + " Desc = " + "The thread tried to WRITE to virtual address" _ + STR$(@ErrorRecord.ExceptionInformation(1)) + " for which it does not have the appropriate access" _ + $CRLF + "NTSTATUS = " + STR$(@ErrorRecord.ExceptionInformation(2)) CASE 8 @ErrorCode.Desc = @ErrorCode.Desc + " Desc = " + "The thread tried to cause a user-mode data execution prevention (DEP) violation at virtual address" _ + STR$(@ErrorRecord.ExceptionInformation(1)) _ + $CRLF + "NTSTATUS = " + STR$(@ErrorRecord.ExceptionInformation(2)) END SELECT ' @ErrorCode.Desc = @ErrorCode.Desc + SPACE$(5) + "The thread tried to access a page that was not present, and the system was unable to load the page." ' @ErrorCode.Desc = @ErrorCode.Desc + SPACE$(5) + "For example, this exception might occur if a network connection is lost while running a program over " ' @ErrorCode.Desc = @ErrorCode.Desc + SPACE$(5) + "the network." CASE %EXCEPTION_INT_DIVIDE_BY_ZERO @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_INT_DIVIDE_BY_ZERO" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The thread tried to divide an integer value by an integer divisor of zero." CASE %EXCEPTION_INT_OVERFLOW @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_INT_OVERFLOW" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The result of an integer operation caused a carry out of the most significant bit of the result." CASE %EXCEPTION_INVALID_DISPOSITION @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_INVALID_DISPOSITION" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "An exception handler returned an invalid disposition to the exception dispatcher. " _ + "Programmers using a high-level language such as C should never encounter this exception." CASE %EXCEPTION_NONCONTINUABLE_EXCEPTION @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_NONCONTINUABLE_EXCEPTION" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The thread tried to continue execution after a noncontinuable exception occurred." CASE %EXCEPTION_PRIV_INSTRUCTION @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_PRIV_INSTRUCTION" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The thread tried to execute an instruction whose operation is not allowed in the current machine mode." CASE %EXCEPTION_SINGLE_STEP @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_SINGLE_STEP" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "A trace trap or other single-instruction mechanism signaled that one instruction has been executed." CASE %EXCEPTION_STACK_OVERFLOW @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%EXCEPTION_STACK_OVERFLOW" _ + " (" + STR$(@ErrorRecord.ExceptionCode) + ") "_ + $CRLF + SPACE$(5) _ + " Desc = " + "The thread used up its stack." CASE ELSE @ErrorCode.Desc = @ErrorCode.Desc _ + "Error Windows = " + "%Unhandled Error: " + STR$(@ErrorRecord.ExceptionCode) END SELECT ' 'TRACE PRINT ErrorCode.Desc IF @ErrorCode.LogError THEN PRINT# @ErrorCode.ErrorLogNumber, @ErrorCode.Desc IF @ErrorCode.LogError THEN PRINT# @ErrorCode.ErrorLogNumber, SPACE$(5) + "Error Occurred in " + CALLSTK$(2) @ErrorCode.Desc = "CallStack at Error" + $CRLF FOR i = CALLSTKCOUNT TO 1 STEP -1 @ErrorCode.Desc = @ErrorCode.Desc + SPACE$(5) + CALLSTK$(i) + $CRLF NEXT i IF @ErrorCode.LogError THEN PRINT# @ErrorCode.ErrorLogNumber, @ErrorCode.Desc IF @ErrorCode.LogError THEN PRINT# @ErrorCode.ErrorLogNumber, $CRLF '*** Only Re-Enable if wanting to pretend that PB Error = Win Error = Win Exception because the DON'T !!!!! ' ERR = @ErrorRecord.ExceptionCode ' SetLastError @ErrorRecord.ExceptionCode ' @ErrorCode.PbErrorValue = @ErrorRecord.ExceptionCode ' @ErrorCode.WinErrorValue = @ErrorRecord.ExceptionCode ' @ErrorCode.AppErrorValue = @ErrorRecord.ExceptionCode ' ExceptionInfo(@ErrorCode) END SELECT '*** Code for writing a crash-log, save opened documents etc... '*** In My case, this is where I decide whether to continue, or exit '>>> 1st attempt to RESUME NEXT I thought would be the following line to Resume Execution after the Exception '>>> [email protected] = [email protected] + SIZEOF([email protected]) '>>> Lo and Behold thanx to Paul Dixon's Post at http://www.powerbasic.com/support/pbforums/showthread.php?t=40575#post315530 '>>> Led me to his post in the source code forum at http://www.powerbasic.com/support/pbforums/showthread.php?t=37821 '>>> Although it is for PBCC and using Assembly code, '>>> I did some hunting and pecking and figured out that [email protected] = [email protected] + SIZEOF([email protected]) '>>> Is the flag I need to set to 'Resume Next' (or any address I wish to jump to '>>> Turns out where I marked myself for <--- Possibly add assembly values later, but for now took them out '>>> Is EXACTLY the section I needed to RESUME NEXT.....So now I will have to serious research this part :-) SELECT CASE @ErrorCode.OverRideError CASE %RESUME FUNCTION = %EXCEPTION_CONTINUE_EXECUTION 'Ignore the Error and continue CASE %RESUMENEXT [email protected] = [email protected] + SIZEOF([email protected]) FUNCTION = %EXCEPTION_CONTINUE_EXECUTION 'Ignore the Error and continue CASE %UNKNOWN_VALUE 'Decide what to do after an error Continuable = MSGBOX("A Fatal Error Occurred in " + CALLSTK$(2) + $CR + $CR + "Ignore this error?", %MB_ICONERROR OR %MB_YESNO, "Windows Error") SELECT CASE Continuable CASE %IDYES FUNCTION = %EXCEPTION_CONTINUE_EXECUTION CASE %IDNO FUNCTION = %EXCEPTION_EXECUTE_HANDLER END SELECT CASE %NORESUME 'Let Windows handle the error FUNCTION = %EXCEPTION_EXECUTE_HANDLER CASE %CRASH FUNCTION = %EXCEPTION_CONTINUE_SEARCH 'Similar to Let Windows handle the error, but if Windows can fix it, it will and then continue ' FUNCTION = %EXCEPTION_EXECUTE_HANDLER END SELECT END SELECT END FUNCTION #ENDIF
Code:
#DEBUG ERROR ON #COMPILE EXE #DIM ALL #INCLUDE "Win32Api.inc" #INCLUDE "ErrorHandling.inc" FUNCTION PBMAIN () AS LONG OnError SetGetLogErrors %TRUE, ErrorCode.LogError, %TRUE 'Set flag for logging errors ' SetGetLogErrors %TRUE, ErrorCode.LogError, %FALSE 'Set flag for logging errors '*** OverRideError flag can be one of the following values: '*** %UNKNOWN_VALUE = Unknown until user input at run-time '*** %NORESUME = Do not resume '*** %RESUME = Resume at same line of code that caused the error. '*** (Error must be corrected or will result in an infinite loop) '*** %RESUMENEXT = Resume at line after code that caused the error. '*** %CRASH = Purposely allow a Crash (GPF) to happen. '*** '*** PowerBasic Errors DemoPbError '*** <--- Minor bug here. RESUMENEXT skips a line of executable code for some reason???? (Inserting a blank line does not fix this either) DemoRaisePbError '*** Windows Errors DemoWinError '*** Windows EXCEPTION ERRORS (The almighty "Windows has encountered an error in your app and has to close") DemoRaiseWinError '*** UnderFlow (registers used for the calculation cannot hold the value) DemoUnderflow '*** CRASH!!!! (GPF) DemoGPF '*** Macro's for error handling HandleErrors END FUNCTION FUNCTION DemoPbError()AS LONG OnError ' ErrorCode.LogError = %TRUE 'Log Errors? %TRUE/%FALSE SetGetLogErrors %UNKNOWN_VALUE, ErrorCode.LogError, %UNKNOWN_VALUE 'Get flag for logging errors ErrorCode.OverRideError = %RESUMENEXT 'Continue after an error? '<--- Do NOTTTTT change this to %RESUME or you will be stuck in a logging loop PRINT# ErrorCode.ErrorLogNumber, STRING$(40, "-") + SPACE$(5) + FUNCNAME$ + SPACE$(5) + STRING$(40, "-") ERR = 69 'Set Error Code (Error is not raised) PRINT# ErrorCode.ErrorLogNumber, STRING$(20, "*") + "Notice that setting ERR does NOT raise an error, it just merely setting a flag" + STRING$(20, "*") PRINT# ErrorCode.ErrorLogNumber, STRING$(40, "-") + SPACE$(5) + "End " + FUNCNAME$ + SPACE$(5) + STRING$(40, "-") + $CRLF + $CRLF '*** Macro's for error handling HandleErrors END FUNCTION FUNCTION DemoRaisePbError()AS LONG OnError ' ErrorCode.LogError = %TRUE 'Log Errors? %TRUE/%FALSE SetGetLogErrors %UNKNOWN_VALUE, ErrorCode.LogError, %UNKNOWN_VALUE 'Get flag for logging errors ErrorCode.OverRideError = %RESUMENEXT 'Continue after an error? '<--- Do NOTTTTT change this to %RESUME or you will be stuck in a logging loop PRINT# ErrorCode.ErrorLogNumber, STRING$(40, "-") + SPACE$(5) + FUNCNAME$ + SPACE$(5) + STRING$(40, "-") ERROR 69 'Set Error Code (Error is raised, jump to error handler) '*** Test for faulty "RESUME NEXT" PRINT# ErrorCode.ErrorLogNumber, "If this line of code is not in your error log, then let me know so I can submit to PB as to if an error in my code or an error in RESUME NEXT????" '*** If line above not seen in log, then I need to submit to PB PRINT# ErrorCode.ErrorLogNumber, "If the line that says <QUOTE>If this line of code is not in your error log, then let me know so I can submit to PB as to if an error in my code or an error in RESUME NEXT????</QUOTE> is not seen then" PRINT# ErrorCode.ErrorLogNumber, SPACE$(5) + "---> CONTACT PB about possible 'bug' either in how RESUME NEXT works, or in my code (most likely my code)" + $CRLF + $CRLF PRINT# ErrorCode.ErrorLogNumber, STRING$(40, "-") + SPACE$(5) + "End " + FUNCNAME$ + SPACE$(5) + STRING$(40, "-") + $CRLF + $CRLF '*** Macro's for error handling HandleErrors END FUNCTION FUNCTION DemoWinError()AS LONG OnError ' ErrorCode.LogError = %TRUE 'Log Errors? %TRUE/%FALSE SetGetLogErrors %UNKNOWN_VALUE, ErrorCode.LogError, %UNKNOWN_VALUE 'Get flag for logging errors ErrorCode.OverRideError = %RESUMENEXT 'Continue after an error? '<--- Do NOTTTTT change this to %RESUME or you will be stuck in a logging loop '*** Use 'SetLastError' if no flags PRINT# ErrorCode.ErrorLogNumber, STRING$(40, "-") + SPACE$(5) + FUNCNAME$ + SPACE$(5) + STRING$(40, "-") SetLastError 5 PRINT# ErrorCode.ErrorLogNumber, STRING$(20, "*") + "Notice that SetLastError does NOT raise an error, it just merely setting a flag" + STRING$(20, "*") '*** Use 'SetLastErrorEx' if flags, possible flags are '*** %SLE_ERROR Invalid data was passed to the function, and complete failure has occurred '*** SLE_MINORERROR Invalid data was passed to the function, but the function has recovered. '*** SLE_WARNING Potentially invalid data was passed to the function, but the function has recovered '*** 0 The last-error code is set without reporting anything to the debugger. Specifying this value is equivalent to using the SetLastError function. SetLastErrorEx(69, %SLE_MINORERROR) PRINT# ErrorCode.ErrorLogNumber, STRING$(20, "*") + "Notice that SetLastErrorEx does NOT raise an error, it just merely setting a flag" + STRING$(20, "*") PRINT# ErrorCode.ErrorLogNumber, STRING$(40, "-") + SPACE$(5) + "End " + FUNCNAME$ + SPACE$(5) + STRING$(40, "-") + $CRLF + $CRLF '*** Macro's for error handling HandleErrors END FUNCTION FUNCTION DemoRaiseWinError()AS LONG OnError ' ErrorCode.LogError = %TRUE 'Log Errors? %TRUE/%FALSE SetGetLogErrors %UNKNOWN_VALUE, ErrorCode.LogError, %UNKNOWN_VALUE 'Get flag for logging errors ErrorCode.OverRideError = %UNKNOWN_VALUE ' ErrorCode.OverRideError = %CRASH '*** Raising an exception of no error, and continuable raises a FATAL error if I had not over-ridden the natural "HAS TO CLOSE" window PRINT# ErrorCode.ErrorLogNumber, STRING$(40, "-") + SPACE$(5) + FUNCNAME$ + SPACE$(5) + STRING$(40, "-") RaiseException %NULL, %NULL, %NULL, %NULL 'Bogus Fatal Error for Demo (or not fatal since I have control) PRINT# ErrorCode.ErrorLogNumber, STRING$(20, "*") + "Continued from 1st error that was raised" + STRING$(20, "*") '*** Did this twice just to show how you can continue after a Crash (GPF) RaiseException %EXCEPTION_ACCESS_VIOLATION, %NULL, %NULL, %NULL 'Bogus Fatal Error for Demo (or not fatal since I have control) PRINT# ErrorCode.ErrorLogNumber, STRING$(20, "*") + "Continued from 2nd error that was raised" + STRING$(20, "*") PRINT# ErrorCode.ErrorLogNumber, STRING$(40, "-") + SPACE$(5) + "End " + FUNCNAME$ + SPACE$(5) + STRING$(40, "-") + $CRLF + $CRLF '*** Macro's for error handling HandleErrors END FUNCTION FUNCTION DemoUnderflow()AS LONG OnError ' ErrorCode.LogError = %TRUE 'Log Errors? %TRUE/%FALSE SetGetLogErrors %UNKNOWN_VALUE, ErrorCode.LogError, %UNKNOWN_VALUE 'Get flag for logging errors ErrorCode.OverRideError = %UNKNOWN_VALUE 'Continue after an error? '<--- Do NOTTTTT change this to %RESUME or you will be stuck in a logging loop LOCAL B AS BYTE ' DO WHILE B < 256 ' B = B + 1 ' if B = 255 then msgbox "Hold" ' LOOP PRINT# ErrorCode.ErrorLogNumber, STRING$(40, "-") + SPACE$(5) + FUNCNAME$ + SPACE$(5) + STRING$(40, "-") B = 255 + 1 PRINT# ErrorCode.ErrorLogNumber, STRING$(20, "*") + SPACE$(5) + "Performing Both B + 1 to overflow and B = 255 + 1: Results in PB protecting me so I get no error" + SPACE$(5) + STRING$(40, "-") + $CRLF + $CRLF PRINT# ErrorCode.ErrorLogNumber, STRING$(40, "-") + SPACE$(5) + "End " + FUNCNAME$ + SPACE$(5) + STRING$(40, "-") + $CRLF + $CRLF '*** Macro's for error handling HandleErrors END FUNCTION FUNCTION DemoGPF()AS LONG OnError ' ErrorCode.LogError = %TRUE 'Log Errors? %TRUE/%FALSE SetGetLogErrors %UNKNOWN_VALUE, ErrorCode.LogError, %UNKNOWN_VALUE 'Get flag for logging errors '*** Let Windows Crash MSGBOX "I will now perform a fatal error, and not recover from it", %MB_ICONINFORMATION, "Fatal Error" ErrorCode.OverRideError = %CRASH PRINT# ErrorCode.ErrorLogNumber, STRING$(40, "-") + SPACE$(5) + FUNCNAME$ + SPACE$(5) + STRING$(40, "-") RaiseException %NULL, %NULL, %NULL, %NULL 'Bogus Fatal Error for Demo (or not fatal since I have control) PRINT# ErrorCode.ErrorLogNumber, "I continued from a crash" 'Should never get here PRINT# ErrorCode.ErrorLogNumber, STRING$(40, "-") + SPACE$(5) + "End " + FUNCNAME$ + SPACE$(5) + STRING$(40, "-") + $CRLF + $CRLF '*** Macro's for error handling HandleErrors END FUNCTION
Including points I have not figured out, or maybe points I have not thought of yet
