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:
- Execution enters
UartComm.OnGetIdRES()
ERangeError
is raised when I try to set theLength
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:
- 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
executedOnException
(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 reachedApplication.OnException
.Inside
OnException
I tried to close the COM port withVaComm1.Close()
. Part ofClose()
was a call to stop theVaComm1
thread andWaitFor()
the thread to finish. But remember that theUartVaComm1RxChar
never returned! The never finished! So thisWaitFor()
is waiting forever.The solution was to enable a
TTimer
insideOnException
and move theVaComm1.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.