为什么程序即使被全局异常挂钩捕获也会崩溃?
Why program crashes even when caught by a global exception hook?
问题总结:UartComm.OnGetIdRES()
中的某些代码引发了 ERangeError
,这使我的程序崩溃。
这个错误不是问题,重要的是为什么我的应用程序全局异常挂钩捕获了异常,但我的程序仍然崩溃。
我希望钩子捕获所有未处理的异常并抑制它们;该程序应保持 运行.
这里是负责全局异常挂钩的单位:
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.
(如果有帮助,这里是 link 到 JclDebug.pas
and JclHookExcept.pas
)
我为激活挂钩所做的一切就是将 LogExceptions
添加到 Main.pas
中的 interface uses
列表中。
下面是崩溃的详细步骤:
- 执行进入
UartComm.OnGetIdRES()
当我尝试将动态数组的 Length
设置为 -7
: 时,会引发 ERangeError
SetLength(InfoBytes, InfoLength);
进入LogExceptions.HookGlobalException()
。此时IDE中显示的调用栈是这样的(我省略了内存地址):
-> 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 .... }
我们一从 HookGlobalException
出来,调试器就会抛出一个对话框:
引发异常 class ERangeError 消息 'Range check error'
如果我按"Continue"程序仍然冻结工作。如果没有调试器,程序也会在此时冻结。
如果我单击 "Break" 并继续使用调试器,执行将通过堆栈一直进入 VaComm.TVaCommEventThread.DoEvent
并执行行:
Application.HandleException(Self);
之后它什么都不做(我用调试器进入这个程序,程序永远 "running")。
即使我不使用 JCL 库作为挂钩,而是将 Application.OnException
指向某个空例程,也会发生完全相同的事情。
为什么异常被钩子捕获然后在钩子时重新引发returns?我怎样才能抑制异常,使程序不会崩溃但保持 运行?
更新:我有 3 大发现:
- JCL 挂钩实际上捕获了所有异常,无论是已处理的还是未处理的。这就是为什么
GlobalExceptHook()
在异常落入调用堆栈之前。
Application.OnException
已在代码中的其他地方重新分配。
Application.HandleException
执行了 OnException
(但当我试图进入时调试器没有向我显示)并且那里有一行试图关闭 COM 端口。这是使我的程序的 GUI 冻结的行。
等我弄明白了再写答案。
我不明白你说的 'carry on' 到底是什么意思。如果你的意思是从引发异常的地方继续,那是没有意义的。这就是异常点,过滤堆栈,直到达到可以构建恢复机制并安全恢复的程度。这意味着您需要找到一个或多个合理的点来恢复您的程序,然后使用 try...except...end 块。一般来说,异常的行为就像你想要的那样,除了日志没有被写入,这是 Application.OnException 进来的地方,并且消息不是你想要的,这是你需要适当的尝试...除了...结束块。如果你的程序真的崩溃了,即终止或冻结,那更多的是与导致异常的性质有关,而不是异常处理本身,再多的捏造也无法掩盖这一点。
问题是 UartComm.TfrmUartComm.UartVaComm1RxChar
是事件触发的。当在此例程中引发未处理的异常时,执行将通过调用堆栈下降,直到到达 Application.OnException
.
在 OnException
中,我试图用 VaComm1.Close()
关闭 COM 端口。 Close()
的一部分是调用停止 VaComm1
线程和 WaitFor()
线程以完成。但请记住 UartVaComm1RxChar
永远不会返回!从未完成!所以这个WaitFor()
永远等着。
解决方案是在 OnException
中启用 TTimer
并将 VaComm1.Close()
例程移到此计时器中。程序完成处理引发的异常并返回执行 "main" 循环,事件结束。现在 TTimer 触发并关闭 COM 端口。
更多详情here。
问题总结:UartComm.OnGetIdRES()
中的某些代码引发了 ERangeError
,这使我的程序崩溃。
这个错误不是问题,重要的是为什么我的应用程序全局异常挂钩捕获了异常,但我的程序仍然崩溃。
我希望钩子捕获所有未处理的异常并抑制它们;该程序应保持 运行.
这里是负责全局异常挂钩的单位:
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.
(如果有帮助,这里是 link 到 JclDebug.pas
and JclHookExcept.pas
)
我为激活挂钩所做的一切就是将 LogExceptions
添加到 Main.pas
中的 interface uses
列表中。
下面是崩溃的详细步骤:
- 执行进入
UartComm.OnGetIdRES()
当我尝试将动态数组的 ERangeError
SetLength(InfoBytes, InfoLength);
进入
LogExceptions.HookGlobalException()
。此时IDE中显示的调用栈是这样的(我省略了内存地址):-> 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 .... }
我们一从
HookGlobalException
出来,调试器就会抛出一个对话框:引发异常 class ERangeError 消息 'Range check error'
Length
设置为 -7
: 时,会引发 如果我按"Continue"程序仍然冻结工作。如果没有调试器,程序也会在此时冻结。
如果我单击 "Break" 并继续使用调试器,执行将通过堆栈一直进入
VaComm.TVaCommEventThread.DoEvent
并执行行:Application.HandleException(Self);
之后它什么都不做(我用调试器进入这个程序,程序永远 "running")。
即使我不使用 JCL 库作为挂钩,而是将 Application.OnException
指向某个空例程,也会发生完全相同的事情。
为什么异常被钩子捕获然后在钩子时重新引发returns?我怎样才能抑制异常,使程序不会崩溃但保持 运行?
更新:我有 3 大发现:
- JCL 挂钩实际上捕获了所有异常,无论是已处理的还是未处理的。这就是为什么
GlobalExceptHook()
在异常落入调用堆栈之前。 Application.OnException
已在代码中的其他地方重新分配。Application.HandleException
执行了OnException
(但当我试图进入时调试器没有向我显示)并且那里有一行试图关闭 COM 端口。这是使我的程序的 GUI 冻结的行。
等我弄明白了再写答案。
我不明白你说的 'carry on' 到底是什么意思。如果你的意思是从引发异常的地方继续,那是没有意义的。这就是异常点,过滤堆栈,直到达到可以构建恢复机制并安全恢复的程度。这意味着您需要找到一个或多个合理的点来恢复您的程序,然后使用 try...except...end 块。一般来说,异常的行为就像你想要的那样,除了日志没有被写入,这是 Application.OnException 进来的地方,并且消息不是你想要的,这是你需要适当的尝试...除了...结束块。如果你的程序真的崩溃了,即终止或冻结,那更多的是与导致异常的性质有关,而不是异常处理本身,再多的捏造也无法掩盖这一点。
问题是 UartComm.TfrmUartComm.UartVaComm1RxChar
是事件触发的。当在此例程中引发未处理的异常时,执行将通过调用堆栈下降,直到到达 Application.OnException
.
在 OnException
中,我试图用 VaComm1.Close()
关闭 COM 端口。 Close()
的一部分是调用停止 VaComm1
线程和 WaitFor()
线程以完成。但请记住 UartVaComm1RxChar
永远不会返回!从未完成!所以这个WaitFor()
永远等着。
解决方案是在 OnException
中启用 TTimer
并将 VaComm1.Close()
例程移到此计时器中。程序完成处理引发的异常并返回执行 "main" 循环,事件结束。现在 TTimer 触发并关闭 COM 端口。
更多详情here。