Link to home
Start Free TrialLog in
Avatar of micja491
micja491

asked on

Jedi Code Library - Runtime Call Stack

I would like to incorporate some advanced error tracking functionality in my application, that shows detailed information when an exception occurs.

The most important feature would be to display call stack data similar to that wich can be seen in the Delphi IDE while debugging.

So I basically want to get hold of this data even when running outside the IDE on a system without Delphi installed.

Ideally I'd like to see methodcalls, the unitname and source-code line nr of the call.

I've been told the JCL contains this kind of debugging possibilities but I haven't been able to find it. Does anyone know of any demo application or can provide example source of how to achieve something like this.

Thanks
/Mike
Avatar of robert_marquardt
robert_marquardt

Best get the JVCL from http://jvcl.sourceforge.net
It contains also the JCL and an IDE plugin and examples.

The MAP file for your EXE is generated on compile and tacked to your exe to give you the line number info.
You can also ask on our newsgroups news://forums.talkto.net
Avatar of micja491

ASKER

I've now downloaded and installed the entire JVCL and VCL latest versions.

And I still can't find anything useful.

The stack-trace demos I find produce something like the following:
[00461734]
[004035DB]
[0043A8F7]
[0043AA2F]
[0043A8F7]
[0043A574]
[0041DA74]
[0043A9DB]
[0043A8F7]
[0041DA74]
[00461B27]

And I'm relly not sure what to do with that. I guess it's possible to look it up in the above mentioned MAP-file, but I have no idea how that is done.

Any demo or example of this being done around?
Or any other good ideas?
try this Code :
function TraceStackLineOut(MapModuleName,
  ProcedureName: String; Line: Integer; FileName: String) : string;
var
  ds: String;
begin
  ds := ' Module: '+MapModuleName+' / Procedure: '+ProcedureName+ ' / Line:'+IntToStr(Line)+' / Source File: '+FileName;
  Result := TAWIDebugOutputStream.OutputDebugString(ds);
end;


var
    stackList                                                        :TJclStackInfoList;
    frameList                                                        :TJclExceptFrameList;
    itemCount, Line, i                                               :Integer;
    ModuleName, MapModuleName, FileName, ProcedureName, EDescription :String;
begin
  EDescription := EDescription + 'EXCEPTION TRACER Exception:'+ E.ClassName+#13#10;
  if(E is Exception) then
    EDescription := EDescription + '  Message:'+ (E as Exception).Message+#13#10;
  if(E is EOleSysError) then
    EDescription := EDescription + '  OLECode: $'+
      IntToHex((E as EOleSysError).ErrorCode,10) +#13#10;
  Result := Result + EDescription;
  Result := Result + ' occured at:';
  MapOfAddr(EAddr,FileName,MapModuleName,ProcedureName,Line);
  Result := Result + TraceStackLineOut(MapModuleName,ProcedureName,Line,FileName);
  frameList := JclLastExceptFrameList;
  for i := 0 to FrameList.Count-1 do
  begin
    if frameList.Items[i].Handles(E) then
    begin
      Result := Result + 'Exception will be handled by:';
      ModuleName := ModuleOfAddr(Pointer(frameList.Items[i].CodeLocation));
      MapOfAddr(Pointer(frameList.Items[i].CodeLocation),FileName,MapModuleName, ProcedureName,Line);
      if MapModuleName = '' then
        MapModuleName := ModuleName;
      Result := Result + TraceStackLineOut(MapModuleName,ProcedureName,Line,FileName);
    end;
  end;
  stackList := JCLLastExceptStackList;//JclCreateStackList(False,2,nil);
  itemCount := stackList.Count;
  i:=0; ;
  //Iterate thru call stack to find the first non 'Contract' Module.
  //This is the module which originally checked the Contract.
  Result := Result + 'Stack back trace for Exception:';
  while (i < itemCount) do
  begin
    ModuleName := ModuleOfAddr(Pointer(stackList.Items[i].StackInfo.CallerAdr));
    MapOfAddr(Pointer(stackList.Items[i].StackInfo.CallerAdr),FileName,MapModuleName,
        ProcedureName,Line);
    if MapModuleName = '' then
      MapModuleName := ModuleName;
    Result := Result + TraceStackLineOut(MapModuleName,ProcedureName,Line,FileName);
    inc(i);
  end;
Thanks that starts to look like something.

But from where should I call this function and what should I send as the inparamters?

/Mike
replace the TApplication.onException event and call the function here

sorry i forgot the function header which could be :

function traceException(e:exception): string;


call this function whereever you like (as long as the exception is still on the stack at least it makes sense to do this in the TApplication.onException event.

put the result of the function to whereever u need it (probably a custom Error Form or a ErrorLogFile)

If you go shure that you let the complier create a map file with detailed debug info you should get pretty good infos here

sorry that it took so long to answer, but the email notification does still not work properly :-(
Thanks for your help.. this seems great but there are two aspects of your code sample I can't get to work.

The first thing is on the last line of the TraceStackLineOut function:
Result := TAWIDebugOutputStream.OutputDebugString(ds);

What is TAWIDebugOutputStream, I can't seem to find it in the Delphi or JVCL help files.


The second thing is in the TraceException function:
MapOfAddr(EAddr,FileName,MapModuleName,ProcedureName,Line);


What is the first inparameter EAddr... the compiler won't accept it. Where is it declared?


Thank you for all your help

-Mike
Thanks for your help.. this seems great but there are two aspects of your code sample I can't get to work.

The first thing is on the last line of the TraceStackLineOut function:
Result := TAWIDebugOutputStream.OutputDebugString(ds);

What is TAWIDebugOutputStream, I can't seem to find it in the Delphi or JVCL help files.


The second thing is in the TraceException function:
MapOfAddr(EAddr,FileName,MapModuleName,ProcedureName,Line);


What is the first inparameter EAddr... the compiler won't accept it. Where is it declared?


Thank you for all your help

-Mike
well the first part:

TAWIDebugOutputStream is one of my classes i use to log tghe output. I simply forgot to erease this line, sorry!
 
MapOfAddr is defines in the Unit JclDebug wich is part of the JCL (Not sure if JVCL too) If you dont have the unit download the latest JCL (not JVCL) version from delphi-jedi.org
OK.. so the first part I'll just change to
Result := ds;
instead of
Result := TAWIDebugOutputStream.OutputDebugString(ds);

That's fine and seems to work.


Now for the second question:
I had already found the MapOfAddr Method and included the JclDebug unit. What the compile complains about is the first inparameter that you call EAddr.

What is EAddr... where is it declared and can I use that or do I need to change it to something else?

I figured it was meant to be some property of the ExceptionObject E but I don't find anything that seems to fit.


oh okay, I am really sorry, i had read you last question in a hurry.

Well the sad thing is that i copied fragments of my code and forgot a detail:

EAddr in my function is a inparameter of the function which should contain a pointer to the class reference --> @e

After you do already have the exception as parameter you might replace it.

But change the function header from
function traceException(e:exception): string;
to
function traceException(var e:exception): string;

or you add it as parameter as i did
function traceException(var e:exception; EAddr : Pointer): string;

and call it with traceException(e,@e);

Well i am really sorry i simply overlook this when i posted the solution.

Hope that was all i overlooked ...


ASKER CERTIFIED SOLUTION
Avatar of sfock
sfock

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thanks you have been more than helpful.

I now the code compiles fine. I still can't really get it to work though because some of the calls to JCLDebug Methods only return nil wich causes exceptions in my exception handling.

I have used the following Project Option Settings, is there anything else I need to set?
Map File = Detailed
Optimisation = On
Compiler/Debugging = All checked except Definitions Only


I'll include my source to see if someone can spot anything wrong. My test application is just a form with 3 buttons with events that creates exceptions in different ways, and application.Onexception set to call the TraceException method.



unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, AppEvnts;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    ApplicationEvents1: TApplicationEvents;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure ApplicationEvents1Exception(Sender: TObject; E: Exception);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses unit2, jcldebug;

procedure TForm1.Button1Click(Sender: TObject);
var CrashList: TList;
begin
  CrashList[10] := nil;
  CrashList.Last;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  crasharray: array [1..10] of string;
  i: Integer;
begin
  for i := 0 to 200 do
    crasharray[i] := '42';
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  raise Exception.Create('Ooops!');
end;

procedure TForm1.ApplicationEvents1Exception(Sender: TObject;
  E: Exception);
var Estackstr: string;
begin
  Estackstr := TraceException(E, @E);
  Application.MessageBox(PAnsiChar(Estackstr), 'cap');
end;

end.






unit Unit2;

interface

uses SysUtils;

function TraceException(E: Exception; EAddr: Pointer): string;

implementation

uses JclDebug, ComObj, Windows;

function TraceStackLineOut(MapModuleName,
 ProcedureName: String; Line: Integer; FileName: String) : string;
var
 ds: String;
begin
 ds := ' Module: '+MapModuleName+' / Procedure: '+ProcedureName+ ' / Line:'+IntToStr(Line)+' / Source File: '+FileName;
 Result := ds;
end;

function TraceException(E: Exception; EAddr: Pointer): string;
var
  StackList: TJclStackInfoList;
  FrameList: TJclExceptFrameList;
  ItemCount, Line, i: Integer;
  ModuleName, MapModuleName, FileName, ProcedureName, EDescription: string;
begin
  EDescription := EDescription + 'EXCEPTION TRACER Exception:' + E.ClassName + #13#10;
  if(E is Exception) then
    EDescription := EDescription + '  Message:' + (E as Exception).Message + #13#10;
  if(E is EOleSysError) then
    EDescription := EDescription + '  OLECode: $' + IntToHex((E as EOleSysError).ErrorCode, 10) + #13#10;
  Result := Result + EDescription;
  Result := Result + ' occured at:';

  MapOfAddr(EAddr, FileName, MapModuleName, ProcedureName, Line);
  Result := Result + TraceStackLineOut(MapModuleName, ProcedureName, Line, FileName);
  FrameList := JclLastExceptFrameList;
  for i := 0 to FrameList.Count-1 do
  begin
    if frameList.Items[i].Handles(E) then
    begin
      Result := Result + 'Exception will be handled by:';
      ModuleName := ModuleOfAddr(Pointer(frameList.Items[i].CodeLocation));
      MapOfAddr(Pointer(frameList.Items[i].CodeLocation), FileName, MapModuleName, ProcedureName, Line);
      if MapModuleName = '' then
        MapModuleName := ModuleName;
      Result := Result + TraceStackLineOut(MapModuleName, ProcedureName, Line, FileName);
    end;
  end;
  stackList := JCLLastExceptStackList;//JclCreateStackList(False,2,nil);
  itemCount := stackList.Count;
  i:=0;

  //Iterate thru call stack to find the first non 'Contract' Module.
  //This is the module which originally checked the Contract.
  Result := Result + 'Stack back trace for Exception:';
  while (i < itemCount) do
  begin
    ModuleName := ModuleOfAddr(Pointer(stackList.Items[i].StackInfo.CallerAdr));
    MapOfAddr(Pointer(stackList.Items[i].StackInfo.CallerAdr), FileName, MapModuleName, ProcedureName,Line);
    if MapModuleName = '' then
      MapModuleName := ModuleName;
    Result := Result + TraceStackLineOut(MapModuleName, ProcedureName, Line, FileName);
    inc(i);
  end;
end;

end.
what is returning nil and when?

just another comment :

Don't use the Form's Exception Event use the Application's like this:

procedure TForm1.AppHandleException(Sender: TObject; E: Exception);
var
  StackTrace : string;
begin
  try
    StackTrace := traceException(e, @e);
    Application.MessageBox(PAnsiChar(Estackstr), 'cap');
  except
    on ex : exception do begin
    Application.MessageBox('OH OH', 'cap');// include ex info here
    end;
  end;
end;

procedure TForm1.Form1Create(Sender: TObject);
begin
  Application.OnException := AppHandleException;
end;
Well the MapOfAddr returns empty strings for Filename, MapModuleName and ProcedureName

JclLastExceptFrameList returns nil
JCLLastExceptStackList also returns nil

So obviously the stack trace isn't of much use in its current state.

It only genrates more exceptions, wich it can't handle ;-)

I just tried reinstalling the JCL but nothing changes.
Any Idéas?

Or do anyone have a simple example project that works that you could send me by mail or something and I can see if I could get that to work here.

Thanks

/Mike
MapOfAddr returns empty strings if there is no detailed map file or the adresses are wrong

JclLastExceptFrameList returns nil when JCLCreateStackList has not been called. This function gets automaticliy called when an exception occures and the global Variable StackTracingEnable is set to true.

hope that helps

I am also trying to get this to work with not much luck. Did you get this running micja491? If so, any tips you'd care to share?
What's your problem with it clark?
Everything returns nil if you don't call JclStartExceptionTracking() (in JCLDebug.pas) *before* the exception occurs.
Maybe calling it in a initialization section, or in a create handler is best.
Hope it helps.