【问题标题】:Why program crashes even when caught by a global exception hook?为什么即使被全局异常钩子捕获程序也会崩溃?
【发布时间】:2015-06-17 12:35:47
【问题描述】:

问题总结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.

(如果有帮助,这里有一个指向 JclDebug.pasJclHookExcept.pas 的链接)

激活钩子我要做的就是将LogExceptions 添加到Main.pas 中的interface uses 列表中。

现在这里是崩溃的一步一步:

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

    SetLength(InfoBytes, InfoLength);

  3. 我们输入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 .... }
    
  4. 我们一离开HookGlobalException,调试器就会抛出一个对话框:

    引发异常类 ERangeError 并带有消息“范围检查错误”

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

  1. 如果我单击“Break”并继续使用调试器单步执行,则执行会通过堆栈一直下降到 VaComm.TVaCommEventThread.DoEvent 并执行以下行:

    Application.HandleException(Self);
    

之后它什么都不做(我用调试器进入了这个例程,程序永远“运行”)。

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

为什么异常被钩子捕获,然后在钩子返回时重新引发?如何抑制异常以使程序不会崩溃但继续运行?

更新:我有 3 个重大发现:

  • JCL 挂钩实际上捕获了所有异常,无论是已处理的还是未处理的。这就是为什么在异常通过调用堆栈之前GlobalExceptHook()
  • Application.OnException 在代码中的其他位置被重新分配。
  • Application.HandleException 执行了 OnException (但当我尝试进入时调试器没有显示)并且那里有一条线试图关闭 COM 端口。这就是让我的程序 GUI 冻结的那一行。

等我弄明白了再写答案。

【问题讨论】:

  • 你为什么一开始就试图将动态数组的长度设置为负值? NewLength 应为 0 或正数。
  • @SilverWarior 是的,我发现了这个错误并修复了它。但问题是那里有更多这样的错误,我需要全局挂钩来防止这些错误使我的程序崩溃。
  • 我知道这不是你问题的答案,但让它崩溃可能比让你的应用程序做意想不到的事情更好。但是,如果您使用 Application.OnException ,您是否有相同的行为?
  • 据我所见,该函数仅将函数添加到异常链中,这意味着在每个异常上都会调用它,但随后会再次调用正常的程序线程-这意味着在全局挂钩完成后执行正常的异常处理。查看 JclHookExcept.pas 中的代码 - 它只是调用通知器然后继续。
  • @mrabat 那么如何解释如果我使用Application.OnException 而不是JCL 钩子会发生完全相同的事情呢?我在帖子中添加了“第 5 步”以提供更多信息。

标签: delphi exception-handling delphi-xe2 jedi-code-library


【解决方案1】:

我不明白你所说的“继续”是什么意思。如果您的意思是从引发异常的地方继续,那是没有意义的。这就是异常点,过滤堆栈,直到达到可以构建恢复机制并安全恢复的点。这意味着您需要找到一个或多个合理的点来恢复您的程序,然后使用 try...except...end 块。通常,异常的行为只是您想要的方式,除了日志没有写入,这是 Application.OnException 进来的地方,并且消息不是您想要的,这是您需要适当尝试的地方......除了...... .end 块。如果您的程序真的崩溃了,即终止或冻结,这更多地与导致异常的本质有关,而不是异常处理本身,并且再多的捏造也无法隐藏这一点。

【讨论】:

  • 继续我的意思是我在第 5 步中进入Application.HandleException(Self);,它不会进入任何地方,但程序会继续执行。 GUI 被冻结,但程序正在运行。我什至尝试定义Application.OnException,但它从未被输入。
  • 那么正如我所说,这更多地与问题的性质有关,而不是与异常处理有关。在这种情况下,TApplication 本身可能已损坏。例如,当您删除已删除的对象时,通常会发生这种情况。尝试同样的实验,但使用一个简单的应用程序(而不是你的真实应用程序),然后使用“raise”语句在某处创建异常。您将看到 GUI 没有冻结,并且将输入 OnException。
  • 我就是这样做的,并在我的问题上取得了进展,并且大部分都解决了。当我完全弄清楚时,我会发布答案。谢谢你的帮助:)
【解决方案2】:

问题是UartComm.TfrmUartComm.UartVaComm1RxChar 是事件触发的。当此例程中引发未处理的异常时,执行会通过调用堆栈直到到达Application.OnException

OnException 内部,我尝试使用VaComm1.Close() 关闭COM 端口。 Close() 的一部分是停止 VaComm1 线程和 WaitFor() 线程完成的调用。但请记住,UartVaComm1RxChar 从未返回!从未完成!所以这个WaitFor() 一直在等待。

解决方案是在OnException 中启用TTimer,并将VaComm1.Close() 例程移动到此计时器中。程序完成处理引发的异常并返回执行“主”循环,事件完成。现在 TTimer 触发并关闭 COM 端口。

更多详情here.

【讨论】:

    猜你喜欢
    • 2013-08-12
    • 2019-07-13
    • 2016-01-07
    • 2021-11-19
    • 1970-01-01
    • 2019-09-27
    • 2012-06-21
    • 2016-11-02
    • 1970-01-01
    相关资源
    最近更新 更多