Announcement

Collapse
No announcement yet.

Exception handlers

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

    Exception handlers

    Just posted in the source code forum, an example of installing an exception handler in Windows.
    Windows has the facility to do this but the calls aren't widely known.
    For more information seach the internet for "Structured exception handling".



    Paul.

    #2
    I'm trying to use the code with PB Win

    Getting a compile error 540 here

    !fldcw NewControlWord 'set the FPU to use the new control word

    "Float opcode with a register variable"

    Comment


      #3
      Steve,
      you must have removed the #REGISTER NONE line from the code.
      If you want to use registers in your code then add #REGISTER NONE as the first line in FUNCTION SetupExceptionHandler()

      Paul.

      Comment


        #4
        Thanks. Installed now.

        Comment


          #5
          Great code, even traps stack overflow.

          Comment


            #6
            Paul,

            I was really impressed with the ease of implementation and objectivity of your example. No doubt about the usefulness. It would be interesting if you could adapt this example for new compilers and so more people can see for themselves all that you demonstrated so well here.

            Thank you for this good explanation!

            Arthur.
            "The trouble with quotes on the Internet is that you can never know if they are genuine." - Abraham Lincoln.

            Comment


              #7
              The original program was written for PBCC4.
              Changes to the later versions of the win32api.inc files prevented the original from compiling with later versions of the compiler.
              I've fixed the code to run with later compilers by extracting all the required definitions from the PBCC4 .inc files and including them directly in the program.

              It now appears to work OK in PBCC6. I haven't tested it with other versions.

              Paul.

              Comment


                #8
                > I haven't tested it with other versions.

                Version for CC 4.04 works with CC 5.05 and with CC 3.04 but with 2 minor adjustments in the declaration of local variables.
                "The trouble with quotes on the Internet is that you can never know if they are genuine." - Abraham Lincoln.

                Comment


                  #9
                  >Version for CC 4.04 works with CC 5.05 and with CC 3.04 b

                  As Paul points out, "Works/doesn't work with compiler version ..." is not accurate! It's NOT the "compiler version" which is the source of any difficulties!

                  His code should work with ANY version of compiler, provided you use the same Windows' headers files he did.

                  Compile-time errors on "different headers" is really a Good Thing. Where you really get into trouble with "different headers" is when there are subtle differences which allow the program to complile cleanly but execute differently!

                  MCM
                  Michael Mattias
                  Tal Systems (retired)
                  Port Washington WI USA
                  [email protected]
                  http://www.talsystems.com

                  Comment


                    #10
                    Nice work Paul.

                    By knowing that these errors are reported in apps using your enhancement, we can now make assertions about quality which may even open new doors for those of us who sell PB based products.

                    Comment


                      #11
                      Has anyone taken the time to convert this to PBWin 10?

                      Comment


                        #12
                        Has anyone taken the time to convert this to PBWin 10?
                        You mean the code linked in post #1? Follow the link... post #4 there as well as post #7 here both say code works fine with PB/C 6, which is the same "guts" as PB/Win 10.

                        And see also my post #9 here .. which notes problems are not due to compiler versions, they are due to differences in header files.

                        Michael Mattias
                        Tal Systems (retired)
                        Port Washington WI USA
                        [email protected]
                        http://www.talsystems.com

                        Comment


                          #13
                          There's no real conversion to do to make it work in PBWin10..
                          The PRINTs and WAITKEYs are there to show how the demonstration is progressing and they'd all be removed in any real application.

                          To run it in PBWin, replace them with MSBBOX or TXT.WINDOW/TXT.PRINT/TXT.WAITKEY

                          Code:
                          'PBWin10 program
                          'This code attempts to handle exceptions not normally trapped by PowerBASIC.
                          'It consists of 3 routines.
                          '1) PBMain just a short routine to show how to use the other 2 functions
                          '2) SetupExceptionHandler which installs or removes the exception handler
                          '3) ExceptionHandler  which is called, via the operating system, when an exception occurs.
                          '
                          'Floating point exceptions need to be explicitly allowed by clearing one or more of 6 mask bits in the FPU control word.
                          'Other exceptions happen anyway but this program shows how to intercept them to do something useful before the default
                          '  exception handler of the OS gets them and reports a the error.
                          '
                          ' It is possible, though sometimes difficult, to resume execution of the program from after the point of the exception.
                          '  This code shows how to resume at the next address after a PrivelidgedInstruction exception.
                          '  It also shows how to resume at another address such as an error handler. This should be in the same SUB/FUNCTION as the error.
                          '
                          'The default in this program is to enable all FP exceptions apart from INEXACT_RESULT as this will trip up most people
                          '  more than it helps them! e.g. 1/3 will trigger the exception as the result cannot be exactly represented in binary.
                          '  If you want to trap INEXACT_RESULT then set %FPTrapInexactResultException = 1   below.
                          '
                          'The only ASM here is to set the FPU control register bits so don't be put off by it.
                          '
                          'Three distict outcomes are shown in this code.
                          ' If there is a priveliged instruction exception caused by CLI or STI (clear or set interrupt flag) then the inxtruction is ignored
                          '  and execution continues at the next address as if the instruction wasn't there. Other priviliged instructions are passed to the
                          '  default handler (usually the OS).
                          '
                          'If there is a floating point exception then it is reported along with a dump of the FP registers, it's then passed to the default handler.
                          '
                          'If there is an integer divide by zero then the error is reported and execution continues at the error handler specified in the call
                          '   when the Exception Handler was installed.
                          '
                          'There are lots of potential problems here. This code is just posted to demonstrate how exceptions can be dealt with.
                          'In particular, returning execution to the ResumeAddress will only work if the exception occurs in the same function as the ResumeAddress
                          ' but calls to outside functions in maths routines are very common so care needs to be taken.
                          '
                          'Just for demonstration purposes I print things out in the ExceptionHandler to inform the user what has happened. Normally you wouldn't
                          '  do this if you are returning execution to the program.
                          
                          
                          #COMPILE EXE
                          #DIM ALL
                          #REGISTER NONE
                          
                          #INCLUDE "win32api.inc"
                          
                          
                          %FPTrapInexactResultException = 0    'set to non zero if you want to trap FPInexactResults (specialised use only)
                          %enable = 1
                          %disable= 0
                          
                          %CLI= &hfa     'opcode of STI instruction    'these are just to test the PRIVILEGED_INSTRUCTION exception
                          %STI= &hfb     'opcode of CLI instruction
                          
                          
                          GLOBAL ExeptionHandlerResumeAddress AS DWORD
                          
                          
                          
                          FUNCTION SetupExceptionHandler(EnableTrap AS LONG,ResumeAddress AS DWORD) AS LONG
                          'EnableTrap = 0 for uninstall handler, not 0 for install handler
                          'ResumeAddress = address WITHIN THE ERRING FUNCTION/SUB to which you would like the code to return after the exception is handled
                          '              =0 for don't resume.
                          
                          STATIC hHandler AS LONG
                          
                          LOCAL HandlerPointer AS LONG
                          LOCAL NewControlWord, mask AS INTEGER    'control word is a 16 bit register
                          LOCAL OriginalControlWord AS INTEGER
                          
                          ExeptionHandlerResumeAddress = ResumeAddress
                          
                              IF EnableTrap THEN
                                  'install the exception handler
                                  HandlerPointer = CODEPTR(ExceptionHandler)
                                  hHandler = AddVectoredExceptionHandler( 1  , HandlerPointer )
                          
                                  IF hHandler = 0 THEN
                                     'Failed to add handler
                                      FUNCTION = 0
                                  ELSE
                                      'Handler installed
                                      FUNCTION = 1
                          
                                      'now set the FPU control register to enable exceptions on all conditions
                                      !fstcw OriginalControlWord  'save the original control word
                                      !mov ax,OriginalControlWord 'get the control word
                          
                                      #IF %FPTrapInexactResultException = 0
                                      !and ax,&hffe0              'zero the exception mask bits except for InexactResult
                                      #ELSE
                                      !and ax,&hffc0              'zero the exception mask bits (lower 6 bits or control word) to allow all 6 exceptions
                                      #ENDIF
                          
                                      !mov NewControlWord,ax      'save the new control word
                                      !fldcw NewControlWord       'set the FPU to use the new control word
                          
                                  END IF
                          
                              ELSE
                                  'remove the exception handler
                                  hHandler = RemoveVectoredExceptionHandler(hHandler)
                                  IF hHandler = 0 THEN
                                      'Couldn't remove exception handler
                                      FUNCTION = 0
                          
                                  ELSE
                                      'Exception handler removed
                                      FUNCTION = 1
                                      !fldcw OriginalControlWord  'restore the original control word
                          
                                  END IF
                          
                              END IF
                          
                          END FUNCTION
                          
                          
                          
                          FUNCTION ExceptionHandler(BYVAL pException_Pointers AS Exception_Pointers PTR) AS LONG
                          
                          LOCAL ThisExceptionPointer AS Exception_Pointers
                          LOCAL ThisExceptionRecordPointer AS Exception_Record PTR
                          LOCAL ThisExceptionRecord AS Exception_Record
                          LOCAL temp AS BYTE PTR
                          LOCAL r AS LONG
                          LOCAL pt AS DWORD
                          LOCAL FPControlWord AS INTEGER
                          LOCAL FPControlWordNew AS INTEGER
                          LOCAL WhatToDoNext  AS LONG
                          
                          
                          ThisExceptionPointer = @pException_Pointers
                          ThisExceptionRecordPointer = ThisExceptionPointer.ExceptionRecord
                          ThisExceptionRecord = @ThisExceptionRecordPointer
                          
                          WhatToDoNext=%EXCEPTION_CONTINUE_SEARCH 'set the default for all exceptions I don't handle here.
                                                                  'the 2 options are:
                                                                  '%EXCEPTION_CONTINUE_SEARCH which passes the exception on to the next handler in line (the OS?)
                                                                  '%EXCEPTION_CONTINUE_EXECUTION which resumes execution of the code
                                                                  'If execution is resumed, the address to resume at can be set within the handler
                          
                          !fnstcw FPControlWord   'get FP control word
                          !fnclex                 'clear all FP exeptions. Got to do this as following lines use the FPU and may cause a recurring exception
                          !mov ax,FPControlWord
                          !or ax,&h3f             'mask all exception while in the exception routine
                          !mov FPControlWord,ax
                          !fldcw FPControlWord    'load new FP control word
                          
                          'now handle each possible exception
                          
                          SELECT CASE ThisExceptionRecord.ExceptionCode
                              CASE  BITS(DWORD,%STATUS_FLOAT_OVERFLOW)
                          
                                  TXT.PRINT "There has been a FP overflow at address &h";HEX$(ThisExceptionRecord.ExceptionAddress)
                                  TXT.PRINT "here are the FPU details at the time of the exception"
                          
                                  TXT.PRINT "ControlWord   ";HEX$(@[email protected])
                                  TXT.PRINT "StatusWord    ";HEX$(@[email protected])
                                  TXT.PRINT "TagWord       ";HEX$(@[email protected])
                                  TXT.PRINT "ErrorOffset   ";HEX$(@[email protected])
                                  TXT.PRINT "ErrorSelector ";HEX$(@pException_Pointers.@ContextRecord.FloatSave.ErrorSelector)
                                  TXT.PRINT "DataOffset    ";HEX$(@[email protected])
                                  TXT.PRINT "DataSelector  ";HEX$(@[email protected])
                          
                                  pt=VARPTR(@[email protected](0))
                          
                                  REDIM reg(0 TO 7) AS EXT AT pt
                                  FOR r& = 0 TO 7
                                      TXT.PRINT "ST(";r&;")=";reg(r&)
                                  NEXT
                          
                                  TXT.PRINT
                                  TXT.PRINT "Press a key to pass error to default exception handler"
                                  TXT.WAITKEY$
                          
                          
                              CASE BITS(DWORD,%STATUS_FLOAT_UNDERFLOW)
                                  TXT.PRINT "There has been a FP underflow at address &h";HEX$(ThisExceptionRecord.ExceptionAddress)
                                  TXT.WAITKEY$
                          
                              CASE BITS(DWORD,%STATUS_FLOAT_INVALID_OPERATION)
                                  TXT.PRINT "There has been an Invalid FP Operation at address &h";HEX$(ThisExceptionRecord.ExceptionAddress)
                                  TXT.WAITKEY$
                          
                              CASE BITS(DWORD,%STATUS_FLOAT_INEXACT_RESULT)
                          
                          
                              CASE BITS(DWORD,%STATUS_FLOAT_DIVIDE_BY_ZERO)
                          
                          
                              CASE BITS(DWORD,%STATUS_FLOAT_DENORMAL_OPERAND)
                          
                          
                              CASE BITS(DWORD,%STATUS_ILLEGAL_INSTRUCTION)
                          
                          
                              CASE BITS(DWORD,%STATUS_ACCESS_VIOLATION)
                          
                          
                              CASE BITS(DWORD,%STATUS_PRIVILEGED_INSTRUCTION)
                                  TXT.PRINT "STATUS_PRIVILEGED_INSTRUCTION exception has occurred"
                          
                                  temp = @[email protected]    'get the EIP register to see where the instruction was that caused the exception
                          
                                  IF @temp = %STI OR  @temp = %CLI THEN   '@temp = the instruction itself
                                      'if its set/clear interrupt flag I'll just increment the return address pointer to ignore the exception
                                      INCR @[email protected]    'return execution to the byte after the one that caused the exception
                          
                                      TXT.PRINT "You can't mess with the interrupt flag. I'll ignore it."
                                      TXT.PRINT "Execution will now continue"
                                      TXT.PRINT
                                      WhatToDoNext=%EXCEPTION_CONTINUE_EXECUTION
                                  ELSE
                                      'it's not an interrupt flag instruction so pass it to the default handler
                                      TXT.PRINT "You aren't allowed to use that instruction at address &h";HEX$(ThisExceptionRecord.ExceptionAddress)
                                      TXT.PRINT "I'm going to crash now."
                          
                                      WhatToDoNext=%EXCEPTION_CONTINUE_SEARCH
                                  END IF
                          
                          
                          
                              CASE BITS(DWORD,%STATUS_STACK_OVERFLOW)
                          
                          
                              CASE BITS(DWORD,%STATUS_FLOAT_MULTIPLE_FAULTS)
                          
                          
                              CASE BITS(DWORD,%STATUS_INTEGER_OVERFLOW)
                          
                          
                              CASE BITS(DWORD,%STATUS_INTEGER_DIVIDE_BY_ZERO)
                                  TXT.PRINT "Integer divide by zero at address &h";HEX$(ThisExceptionRecord.ExceptionAddress)
                                  TXT.PRINT
                                  'as a test we'll try to return to the faulty function's error trap
                                  IF ExeptionHandlerResumeAddress <>0 THEN
                                      @[email protected] = ExeptionHandlerResumeAddress
                                  END IF
                          
                                  WhatToDoNext=%EXCEPTION_CONTINUE_EXECUTION
                          
                          END SELECT
                          
                          
                          IF WhatToDoNext =%EXCEPTION_CONTINUE_SEARCH THEN
                              FUNCTION = %EXCEPTION_CONTINUE_SEARCH
                          ELSE
                              FUNCTION = %EXCEPTION_CONTINUE_EXECUTION
                              !fldcw FPControlWord   'restore the FPUControl word if I'm going to return to executing my code
                          
                          END IF
                          
                          
                          END FUNCTION
                          
                          
                          
                          FUNCTION PBMAIN () AS LONG
                          
                          LOCAL sum AS SINGLE
                          LOCAL ResumeAddress AS LONG
                          
                          LOCAL hTxtWin AS LONG
                          TXT.WINDOW("Exception handler tests",1,1,40,80) TO hTxtWin
                          
                          ResumeAddress = CODEPTR(ErrorTrap)
                          
                          
                          IF SetupExceptionHandler(%enable,ResumeAddress) = 0 THEN
                              TXT.PRINT "Failed to add exception handler"
                          ELSE
                              TXT.PRINT "Exception Handler installed "
                              TXT.PRINT "Press a key to cause a Privileged Instruction exception"
                              TXT.PRINT
                              TXT.WAITKEY$
                          
                          '############################################
                          '# cause a Privileged Instruction exception
                          '############################################
                          
                          !cli
                          
                          
                              TXT.PRINT  "Press a key to cause an Integer Divide by zero exception"
                              TXT.PRINT
                              TXT.WAITKEY$
                          
                          '############################################
                          '# cause an integer divide by zero exception
                          '############################################
                          LOCAL a,b,c AS LONG
                          
                          a=0
                          b=1
                          c=b\a
                          
                              'in this example the code doesn't reach this point but in real code you should uninstall the exception handler
                              ''like this when you're finished with it.
                              IF SetupExceptionHandler(%disable,0)= 0 THEN
                                  TXT.PRINT  "Couldn't remove exception handler"
                              ELSE
                                  TXT.PRINT  "Exception handler removed"
                              END IF
                          
                          
                          END IF
                          GOTO xit
                          
                          ErrorTrap:
                          TXT.PRINT  "I've resumed execution at the error trap."
                          TXT.PRINT  "press a key to cause a fatal FP overflow"
                          TXT.PRINT
                          TXT.WAITKEY$
                          
                          '############################################
                          '# cause a FP overflow exception
                          '############################################
                          
                          
                              sum = 1
                              DO
                                  sum = sum + sum        'cause an overflow exception by counting too high
                              LOOP
                          
                          
                          xit:
                          TXT.WAITKEY$
                          END FUNCTION

                          Comment

                          Working...
                          X
                          😀
                          🥰
                          🤢
                          😎
                          😡
                          👍
                          👎