Search code examples
delphidelphi-xedatasnap

How do I get a stack trace from a handled/caught exception and dump it to a trace log


We've created a Datasnap service (with Delphi XE), using Bob Swart's white paper as a guide. It works fine, and we've deployed it to our test server.

Now a problem occurs, when we have executed a large number of requests (through JMeter), some sort of memory corruption occurs. Some requests succeed, some fail with an access violation. In the end, it has become so corrupt, that every request to our OWN (not the DSAdmin) methods responds with an access violation.

However, I can't get my hands on a stacktrace to get more info, because the exception is already catched in the processing of the request.

If I test heavily with the VCL version of this application, it remains working correctly.

Has anyone any clue what this could be, or experienced the same problem, or can you help me get the stack trace from a caught exception (in someone else's code, which I can't edit)?

Thanks in advance.


Solution

  • To log both caught and uncaught exceptions using JEDI JCL, you should install the JEDI JCL.

    Then try some code like this code taken from jcl\examples\windows\debug\framestrack\FramesTrackDemoMain.pas:

    You should compile with full Debug information on in both the Compiler and Linker options in your delphi project options, for this to work.

    Note that you don't have to call LogException, it's called automatically one you've added the exception notifier callback (JclAddExceptNotifier). don't forget to also call JclRemoveExceptNotifier, when the form or data module you are adding it from is destroyed, as shown here:

    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;
    

    This is the usual initialization code:

    initialization
    
      JclStackTrackingOptions := JclStackTrackingOptions + [stExceptFrame];
      JclStartExceptionTracking;
    

    Here's the JCL FramesTrackExample.dproj demo running:

    enter image description here

    For your purposes, change the code that adds a line to TMemo.Lines, to write to a log file on disk instead. If you already have a logging system, that's great, and if not, then consider Log4D.