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

时间:2015-06-17 12:35:48

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

问题摘要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添加到interface uses的{​​{1}}列表中。

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

  1. 执行进入Main.pas
  2. 当我尝试将动态数组的UartComm.OnGetIdRES()设置为ERangeError时,会引发
  3. Length

    -7

  4. 我们输入SetLength(InfoBytes, InfoLength);。此时IDE中显示的调用堆栈是这样的(我遗漏了内存地址):

    LogExceptions.HookGlobalException()
  5. 一旦我们从-> 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 .... } 出来,调试器会抛出一个对话框:

    引发了异常类ERangeError,其中包含消息'范围检查错误'

  6. 如果我按下"继续"程序仍然是冻结的工作。如果没有调试器,程序也会在此时冻结。

    1. 如果我点击" Break"并继续使用调试器,执行从堆栈一直进入HookGlobalException并执行该行:

      VaComm.TVaCommEventThread.DoEvent
    2. 之后它什么都不做(我使用调试器进入这个例程,程序是"运行"永远)。

      即使我没有使用JCL库作为挂钩,而是将Application.HandleException(Self); 指向某个空例程,也会发生同样的事情。

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

      更新:我做了3个伟大的发现:

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

      当我弄明白时,我会写一个答案。

2 个答案:

答案 0 :(得分:0)

我不明白你的意思是'继续'。如果你的意思是从提出异常的地方继续进行,那是没有意义的。这是例外的一点,要过滤堆栈,直到达到可以构建恢复机制并安全恢复的程度。这意味着你需要找到一个合理的点或点来恢复你的程序,并在那时使用try ... except ... end block。一般情况下异常的行为只是你想要的方式,除了没有写入日志,这是Application.OnException的来源,而消息不是你想要的,这是你需要适当的尝试......除了.. 。块。如果你的程序真的崩溃,即终止或冻结,那更多的是导致异常的本质,而不是异常处理本身,没有多少捏造会隐藏这个。

答案 1 :(得分:0)

问题是UartComm.TfrmUartComm.UartVaComm1RxChar是事件触发的。如果在此例程中引发了未处理的异常,则执行将通过调用堆栈落到Application.OnException

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

解决方案是在TTimer内启用OnException并在此Timer中移动VaComm1.Close()例程。程序完成处理引发的异常并返回执行" main"循环,事件结束。现在,TTimer会触发并关闭COM端口。

更多详情here