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:
UartComm.OnGetIdRES()
ERangeError
is raised when I try to set the Length
of a dynamic array to -7
:
SetLength(InfoBytes, InfoLength);
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 .... }
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.
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:
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.
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.