?
Solved

Jedi Code Library - Runtime Call Stack

Posted on 2003-02-20
19
Medium Priority
?
4,455 Views
Last Modified: 2017-06-17
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
0
Comment
Question by:micja491
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 8
  • 7
  • 2
  • +2
19 Comments
 
LVL 11

Expert Comment

by:robert_marquardt
ID: 7987312
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.
0
 
LVL 11

Expert Comment

by:robert_marquardt
ID: 7987320
You can also ask on our newsgroups news://forums.talkto.net
0
 

Author Comment

by:micja491
ID: 7987712
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?
0
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
LVL 3

Expert Comment

by:sfock
ID: 7988499
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;
0
 

Author Comment

by:micja491
ID: 7992282
Thanks that starts to look like something.

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

/Mike
0
 
LVL 3

Expert Comment

by:sfock
ID: 7993350
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 :-(
0
 

Author Comment

by:micja491
ID: 8024846
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
0
 

Author Comment

by:micja491
ID: 8024924
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
0
 
LVL 3

Expert Comment

by:sfock
ID: 8025127
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
0
 

Author Comment

by:micja491
ID: 8026245
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.


0
 
LVL 3

Expert Comment

by:sfock
ID: 8026570
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 ...


0
 
LVL 3

Accepted Solution

by:
sfock earned 1860 total points
ID: 8026595
uups, in this version
>function traceException(var e:exception; EAddr : Pointer): string;

the call by reference is naturely not nesesary :
or you add it as parameter as i did
function traceException(e:exception; EAddr : Pointer): string;



0
 

Author Comment

by:micja491
ID: 8056238
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.
0
 
LVL 3

Expert Comment

by:sfock
ID: 8056295
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;
0
 

Author Comment

by:micja491
ID: 8071130
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
0
 
LVL 3

Expert Comment

by:sfock
ID: 8071608
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

0
 

Expert Comment

by:clark79865
ID: 8445060
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?
0
 
LVL 3

Expert Comment

by:sfock
ID: 8446006
What's your problem with it clark?
0
 

Expert Comment

by:silviosantoz
ID: 15045744
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.
0

Featured Post

On Demand Webinar: Networking for the Cloud Era

Ready to improve network connectivity? Watch this webinar to learn how SD-WANs and a one-click instant connect tool can boost provisions, deployment, and management of your cloud connection.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Michael from AdRem Software explains how to view the most utilized and worst performing nodes in your network, by accessing the Top Charts view in NetCrunch network monitor (https://www.adremsoft.com/). Top Charts is a view in which you can set seve…
Do you want to know how to make a graph with Microsoft Access? First, create a query with the data for the chart. Then make a blank form and add a chart control. This video also shows how to change what data is displayed on the graph as well as form…
Suggested Courses

765 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question