Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

RunTime Debug and Error Handling Part II - Find that elusive bug

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • RunTime Debug and Error Handling Part II - Find that elusive bug

    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
    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
    Demo for ErrorHandler
    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
    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
    Last edited by Cliff Nichols; 19 May 2009, 07:13 PM.
    Engineer's Motto: If it aint broke take it apart and fix it

    "If at 1st you don't succeed... call it version 1.0"

    "Half of Programming is coding"....."The other 90% is DEBUGGING"

    "Document my code????" .... "WHYYY??? do you think they call it CODE? "
Working...
X