Blog

All Blog Posts  |  Next Post  |  Previous Post

Run-time call-stack tracing with Delphi

Bookmarks: 

Thursday, May 12, 2022

TMS Software Delphi  Components

We are used to inspect a call-stack when something goes wrong in our application and it crashes. Delphi has excellent add-on tools that log detailed call-stack information when an application crashes.

But it is not always when an application crashes that call-stack information can be useful. We experience ourselves from time to time that having detailed call-stack information during a regular running application could help us trace undesired situations. Recently, it happened that in rare cases, a component property ended up with an unexpected value. In the application where the component was used, it was from many parts of the code and VCL framework itself that this property was set and it was mostly a matter of knowing from where exactly the unexpected value was set. Once we knew, we could understand and address the problem. The ability to just take at runtime a snapshot of the call-stack when a specific value was set was sufficient to find the culprit.

So, while this capability saved us already for several cases an enormous amount of time, we thought it was interesting to make it also available to you in TMS MemInsight.

While TMS MemInsight will also give you at run-time insight into the call-stack when exceptions occur, now there is a simple method GetCallStack(sl: TStrings) that you can call from anywhere and as many times as you want and it will return detailed call-stack information. Only requirement is that you link your application with a detailed map information (see Linker options under Project options)

How it works?

Imagine there is a class TMyClass with a property Text:

  TMyClass = class(TPersistent)
  protected
    procedure SetText(const Value: string);
  public
    property Text: string read FText write SetText;
  end;

Now, we want to know from where exactly in our application, this class Text property would be set with an empty string value.

We can write the property setter in the following way:

uses
   TMS.MI.Core;

interface

type
  TMyClass = class(TPersistent)
  protected
    procedure SetText(const Value: string);
  public
    property Text: string read FText write SetText;
  end;

implementation

proceduer TMyClass.SetText(const Value: string);
var
  sl: TStringList;
begin
  FText := Value;
  if FText = '' then
  begin
    sl := TStringList.Create;
    try
      GetCallStack(sl);
      sl.SaveToFile('c:\temp\callstack.log');
    finally
      sl.Free;
    end;
  end;
end;

Now, at application level we can use this class for example like:

  MyClass.Text := Edit1.Text;

and when the text property is being set with an empty string, we'll get a nice stack-trace report giving us the information we wanted.

For this example, we create at TForm class level the method to set the myClass.Text via:

  TForm1 = class(TForm)
  private
    { Private declarations }
    procedure SetClassText(s: string);
  end;

implementation

procedure TForm1.SetClassText(s: string);
begin
  myclass.Text := s;
end;
  
and when we call this from both the an edit control's OnChange event and a form button OnClick event:

procedure TForm1.Button1Click(Sender: TObject);
begin
  SetClassText(Edit1.Text);
end;

procedure TForm1.Edit1Change(Sender: TObject);
begin
  SetClassText(Edit1.Text);
end;

Now, we get a nice stack trace log when the class Text property is set with an empty string value:

TMyClass.SetText (57)
TForm1.SetClassText (77)
TForm1.Button1Click (43)
TControl.Click (7443)
TWinControl.WndProc (10162)
TButtonControl.WndProc (2617)
....
indicating this was called from the button OnClick.

Available now in TMS MemInsight v1.1

TMS MemInsight v1.1 is released and now includes this feature in addition to the many run-time memory usage inspection capabilities it already included. For active licensed users, this update is free and has some other performance optimizations as well. Check it out! It might be very valuable when you encounter your next challenge to debug a rare / difficult to find problem in your application.




Bruno Fierens


Bookmarks: 

This blog post has not received any comments yet.



Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post