如何从已处理/捕获的异常中获取堆栈跟踪并将其转储到跟踪日志?

4
我们使用Bob Swart的白皮书作为指南,创建了一个Datasnap服务(使用Delphi XE)。它工作正常,我们已将其部署到测试服务器上。但是,在执行大量请求(通过JMeter)时,会发生某种内存损坏问题。一些请求成功,一些请求因访问冲突而失败。最终,它变得如此不稳定,以至于对我们自己的(而不是DSAdmin)方法的每个请求都会响应访问违规。然而,我无法获取堆栈跟踪以获取更多信息,因为异常已经在请求处理中被捕获。如果我使用这个应用程序的VCL版本进行重度测试,它仍然可以正常工作。有人知道这可能是什么问题,或者遇到过同样的问题,或者可以帮助我从捕获的异常中获取堆栈跟踪(在我无法编辑他人代码的情况下)吗?谢谢。

1
您可以添加异常处理程序(例如MadExcept或我们的开源单元),以在运行时检索堆栈跟踪。如果没有此堆栈跟踪,将无法找到根本原因。添加日志记录将有助于调试服务。 - Arnaud Bouchez
2
是的,您可以在捕获的异常上记录堆栈跟踪。从JEDI类库中获取JclDebug,并将其添加到您的应用程序中。如果您将问题编辑为“如何从已处理/捕获的异常中获取堆栈跟踪并将其转储到跟踪日志中”,我很乐意发布这样的答案。我已经多次使用这种技术(跟踪日志+ JCL Debug)来调试服务崩溃。 - Warren P
听起来你正在经历竞态条件。你考虑过线程安全了吗? - whosrdaddy
@ArnaudBouchez,是的,我知道这个。然而,正如我所提到的,我很难做到这一点,因为它已经被捕获了。 - Geerten
@WarrenP,我做了!期待你的答复...这会对我很有帮助。 - Geerten
显示剩余2条评论
2个回答

7
使用JEDI JCL记录捕获和未捕获的异常,您应该安装JEDI JCL
然后尝试使用以下代码,该代码来自jcl\examples\windows\debug\framestrack\FramesTrackDemoMain.pas
为了使其工作,您应该在Delphi项目选项中的编译器和链接器选项中都打开完整的调试信息。
请注意,您不必调用LogException,一旦添加了异常通知回调(JclAddExceptNotifier),它将自动调用。当您从此处添加它的窗体或数据模块被销毁时,不要忘记调用JclRemoveExceptNotifier,如下所示:
procedure TForm1.LogException(ExceptObj: TObject; ExceptAddr: Pointer; IsOS: Boolean);
var
  TmpS: string;
  ModInfo: TJclLocationInfo;
  I: Integer;
  ExceptionHandled: Boolean;
  HandlerLocation: Pointer;
  ExceptFrame: TJclExceptFrame;

begin
  TmpS := 'Exception ' + ExceptObj.ClassName;
  if ExceptObj is Exception then
    TmpS := TmpS + ': ' + Exception(ExceptObj).Message;
  if IsOS then
    TmpS := TmpS + ' (OS Exception)';
  mmLog.Lines.Add(TmpS);
  ModInfo := GetLocationInfo(ExceptAddr);
  mmLog.Lines.Add(Format(
    '  Exception occured at $%p (Module "%s", Procedure "%s", Unit "%s", Line %d)',
    [ModInfo.Address,
     ModInfo.UnitName,
     ModInfo.ProcedureName,
     ModInfo.SourceName,
     ModInfo.LineNumber]));
  if stExceptFrame in JclStackTrackingOptions then
  begin
    mmLog.Lines.Add('  Except frame-dump:');
    I := 0;
    ExceptionHandled := False;
    while (chkShowAllFrames.Checked or not ExceptionHandled) and
      (I < JclLastExceptFrameList.Count) do
    begin
      ExceptFrame := JclLastExceptFrameList.Items[I];
      ExceptionHandled := ExceptFrame.HandlerInfo(ExceptObj, HandlerLocation);
      if (ExceptFrame.FrameKind = efkFinally) or
          (ExceptFrame.FrameKind = efkUnknown) or
          not ExceptionHandled then
        HandlerLocation := ExceptFrame.CodeLocation;
      ModInfo := GetLocationInfo(HandlerLocation);
      TmpS := Format(
        '    Frame at $%p (type: %s',
        [ExceptFrame.ExcFrame,
         GetEnumName(TypeInfo(TExceptFrameKind), Ord(ExceptFrame.FrameKind))]);
      if ExceptionHandled then
        TmpS := TmpS + ', handles exception)'
      else
        TmpS := TmpS + ')';
      mmLog.Lines.Add(TmpS);
      if ExceptionHandled then
        mmLog.Lines.Add(Format(
          '      Handler at $%p',
          [HandlerLocation]))
      else
        mmLog.Lines.Add(Format(
          '      Code at $%p',
          [HandlerLocation]));
      mmLog.Lines.Add(Format(
        '      Module "%s", Procedure "%s", Unit "%s", Line %d',
        [ModInfo.UnitName,
         ModInfo.ProcedureName,
         ModInfo.SourceName,
         ModInfo.LineNumber]));
      Inc(I);
    end;
  end;
  mmLog.Lines.Add('');
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
  JclAddExceptNotifier(Form1.LogException);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  JclRemoveExceptNotifier(Form1.LogException);
end;

这是常见的初始化代码:
initialization

  JclStackTrackingOptions := JclStackTrackingOptions + [stExceptFrame];
  JclStartExceptionTracking;

这是JCL FramesTrackExample.dproj演示的运行情况:

enter image description here

针对您的需求,将添加一行到TMemo.Lines的代码更改为写入磁盘上的日志文件。如果您已经有一个日志系统,那就太好了,如果没有,则考虑使用Log4D


谢谢你的回答。我希望明天能够研究一下,这些日子有点忙。 - Geerten
谢谢,我已经搞定了!不过只有一个小注释:ExceptFrame.ExcFrame 应该改成 ExceptFrame.FrameLocation。(我在你提供的最新下载链接中找到了这个) - Geerten
我猜自从我上次看它以来,JCL调试已经被微调了一点。 - Warren P

-1
每个新的Web服务调用都是一个新的线程。当下一个服务调用到来时,前一个线程可以分配一些资源,而新线程尝试访问它们。或者当另一个线程尝试使用它们时,某些资源可能会被释放。您应该使用TCriticalSection确保所有资源仅对一个线程可用。还要确保TCriticalSection是全局变量,并且所有实例都可以访问。

或者根本不使用全局资源。正如我在问题评论中提到的那样,这不是非线程安全代码的问题。 - Geerten
很不可能这些极其通用的建议适用于提问者。你试图提供帮助,但我已经点了踩,因为你没有任何具体详细的指示适用于他的特定情况。(将某物设为全局的还是非全局的?什么意思?) - Warren P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接