Why program crashes even when caught by a global exception hook?

1k views Asked by At

Problem Summary: Some code in UartComm.OnGetIdRES() raises an ERangeError, which crashes my program. This bug isn't the problem, what matters is why my application-global exception hook catches the exception, but my program still crashes. I expect the hook to catch all unhandled exceptions and suppress them; the program should keep running.

Here is the unit responsible for the global exception hook:

unit LogExceptions;

interface
uses
  Windows, SysUtils, Classes, JclDebug, JclHookExcept;

procedure AppendToLog(Msg: String; const LogFileLevel: TLogFileLevel);

implementation
uses Main;

procedure HookGlobalException(ExceptObj: TObject; ExceptAddr: Pointer;
                              OSException: Boolean);
var
  Trace: TStringList;
  DlgErrMsg: String;
begin
  { Write stack trace to `error.log`. }
  Trace := TStringList.Create;
  try
    Trace.Add(
        Format('{ Original Exception - %s }', [Exception(ExceptObj).Message]));
    JclLastExceptStackListToStrings(Trace, False, True, True, False);
    Trace.Add('{ _______End of the exception stact trace block_______ }');
    Trace.Add(' ');

    Trace.LineBreak := sLineBreak;
    LogExceptions.AppendToLog(Trace.Text, lflError);

    { Show an dialog to the user to let them know an error occured. }
    DlgErrMsg := Trace[0] + sLineBreak +
      Trace[1] + sLineBreak +
      sLineBreak +
      'An error has occured. Please check "error.log" for the full stack trace.';
    frmMain.ShowErrDlg(DlgErrMsg);
  finally
    Trace.Free;
  end;
end;

procedure AppendToLog(Msg: String; const LogFileLevel: TLogFileLevel);
{ .... irrelevant code ....}

initialization
  Include(JclStackTrackingOptions, stTraceAllExceptions);
  Include(JclStackTrackingOptions, stRawMode);

  // Initialize Exception tracking
  JclStartExceptionTracking;

  JclAddExceptNotifier(HookGlobalException, npFirstChain);
  JclHookExceptions;

finalization
  JclUnhookExceptions;
  JclStopExceptionTracking;

end.

(If it's helpful here's a link to JclDebug.pas and JclHookExcept.pas)

All I do to activate the hook is to add LogExceptions to the interface uses list in Main.pas.

Now here is a step-by-step of the crash:

  1. Execution enters UartComm.OnGetIdRES()
  2. ERangeError is raised when I try to set the Length of a dynamic array to -7:

    SetLength(InfoBytes, InfoLength);

  3. We enter LogExceptions.HookGlobalException(). The call stack shown in the IDE at this moment is this (I left out memory addresses):

    ->  LogExceptions.HookGlobalException
        :TNotifierItem.DoNotify
        :DoExceptNotify
        :HookedRaiseException
        :DynArraySetLength
        :DynArraySetLength
        :@DynArraySetLength
        UartComm.TfrmUartComm.OnSpecificRES // This method runs `OnGetIdRES()`
        UartComm.TfrmUartComm.OnSpecificPktRX
        UartComm.TfrmUartComm.DisplayUartFrame
        UartComm.TfrmUartComm.UartVaComm1RxChar
        VaComm.TVaCustommComm.HandleDataEvent
        VaComm.TVaCommEventThread.DoEvent
        { ... }
        { ... Some low-level calls here .... }
    
  4. As soon we come out of HookGlobalException the debugger throws a dialog:

    raised exception class ERangeError with message 'Range check error'

If I press "Continue" program is still frozen work. Without the debugger the program also freezes at this point.

  1. If I click "Break" and keep stepping with the debugger, execution falls through the stack all the way into VaComm.TVaCommEventThread.DoEvent and executes the line:

    Application.HandleException(Self);
    

After which it does nothing (I stepped into this routine with the debugger and program is "running" forever).

Even if I don't use the JCL library for the hook, and instead point Application.OnException to some empty routine, the exact same thing happens.

Why is the exception caught by the hook and then re-raised when the hook returns? How can I suppress the exception so that the program doesn't crash but keeps running?

UPDATE: I made 3 great discoveries:

  • The JCL hook was actually catching ALL exceptions, whether handled or unhandled. That's why GlobalExceptHook() before the exception falls throurgh the call stack.
  • Application.OnException was assigned re-assigned somewhere else in the code.
  • Application.HandleException executed OnException (but the debugger didn't show me that when I tried to step inside) and there was a line there that tried to close a COM port. THIS is the line that made my program's GUI just freeze.

I'll write an answer when I figure everything out.

2

There are 2 answers

0
DBedrenko On BEST ANSWER

The problem was that UartComm.TfrmUartComm.UartVaComm1RxChar was event triggered. When an unhandled exception was raised in this routine, execution fell through the call stack until it reached Application.OnException.

Inside OnException I tried to close the COM port with VaComm1.Close(). Part of Close() was a call to stop the VaComm1 thread and WaitFor() the thread to finish. But remember that the UartVaComm1RxChar never returned! The never finished! So this WaitFor() is waiting forever.

The solution was to enable a TTimer inside OnException and move the VaComm1.Close() routine inside this Timer. The program finished handling the raised exception and returned back to executing the "main" loop, with the event finished. Now the TTimer fires and closes the COM port.

More details here.

3
Dsm On

I don't understand exactly what you mean by 'carry on'. If you mean carry on from where the exception was raised that makes no sense. That is the point of exceptions, to filter up the stack until you reach a point where you can build in a recovery mechanism and safely resume. This means you need to find a sensible point or points to enable resumption of your program and at that point use a try...except...end block. Generally exceptions behave just the way you want except that the the log is not written, which is where Application.OnException comes in, and the message is not what you would like, which is where you would need appropriate try... except...end blocks. If your program really crashes, i.e. terminates or freezes, that is more to do with the nature of what caused the exception than exception handling per se and no amount of fudging will hide this.