为什么程序即使被全局异常挂钩捕获也会崩溃?

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 列表中。

下面是崩溃的详细步骤:

  1. 执行进入UartComm.OnGetIdRES()
  2. 当我尝试将动态数组的 Length 设置为 -7:

    时,会引发
  3. ERangeError

    SetLength(InfoBytes, InfoLength);

  4. 进入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 .... }
    
  5. 我们一从 HookGlobalException 出来,调试器就会抛出一个对话框:

    引发异常 class ERangeError 消息 'Range check error'

如果我按"Continue"程序仍然冻结工作。如果没有调试器,程序也会在此时冻结。

  1. 如果我单击 "Break" 并继续使用调试器,执行将通过堆栈一直进入 VaComm.TVaCommEventThread.DoEvent 并执行行:

    Application.HandleException(Self);
    

之后它什么都不做(我用调试器进入这个程序,程序永远 "running")。

即使我不使用 JCL 库作为挂钩,而是将 Application.OnException 指向某个空例程,也会发生完全相同的事情。

为什么异常被钩子捕获然后在钩子时重新引发returns?我怎样才能抑制异常,使程序不会崩溃但保持 运行?

更新:我有 3 大发现:

等我弄明白了再写答案。

我不明白你说的 '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