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
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
You can also ask on our newsgroups news://forums.talkto.net
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?
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(MapModul eName,
ProcedureName: String; Line: Integer; FileName: String) : string;
var
ds: String;
begin
ds := ' Module: '+MapModuleName+' / Procedure: '+ProcedureName+ ' / Line:'+IntToStr(Line)+' / Source File: '+FileName;
Result := TAWIDebugOutputStream.Outp utDebugStr ing(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,M apModuleNa me,Procedu reName,Lin e);
Result := Result + TraceStackLineOut(MapModul eName,Proc edureName, Line,FileN ame);
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(frame List.Items [i].CodeLo cation));
MapOfAddr(Pointer(frameLis t.Items[i] .CodeLocat ion),FileN ame,MapMod uleName, ProcedureName,Line);
if MapModuleName = '' then
MapModuleName := ModuleName;
Result := Result + TraceStackLineOut(MapModul eName,Proc edureName, Line,FileN ame);
end;
end;
stackList := JCLLastExceptStackList;//J clCreateSt ackList(Fa lse,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(stack List.Items [i].StackI nfo.Caller Adr));
MapOfAddr(Pointer(stackLis t.Items[i] .StackInfo .CallerAdr ),FileName ,MapModule Name,
ProcedureName,Line);
if MapModuleName = '' then
MapModuleName := ModuleName;
Result := Result + TraceStackLineOut(MapModul eName,Proc edureName, Line,FileN ame);
inc(i);
end;
function TraceStackLineOut(MapModul
ProcedureName: String; Line: Integer; FileName: String) : string;
var
ds: String;
begin
ds := ' Module: '+MapModuleName+' / Procedure: '+ProcedureName+ ' / Line:'+IntToStr(Line)+' / Source File: '+FileName;
Result := TAWIDebugOutputStream.Outp
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
Result := Result + EDescription;
Result := Result + ' occured at:';
MapOfAddr(EAddr,FileName,M
Result := Result + TraceStackLineOut(MapModul
frameList := JclLastExceptFrameList;
for i := 0 to FrameList.Count-1 do
begin
if frameList.Items[i].Handles
begin
Result := Result + 'Exception will be handled by:';
ModuleName := ModuleOfAddr(Pointer(frame
MapOfAddr(Pointer(frameLis
if MapModuleName = '' then
MapModuleName := ModuleName;
Result := Result + TraceStackLineOut(MapModul
end;
end;
stackList := JCLLastExceptStackList;//J
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(stack
MapOfAddr(Pointer(stackLis
ProcedureName,Line);
if MapModuleName = '' then
MapModuleName := ModuleName;
Result := Result + TraceStackLineOut(MapModul
inc(i);
end;
ASKER
Thanks that starts to look like something.
But from where should I call this function and what should I send as the inparamters?
/Mike
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 :-(
sorry i forgot the function header which could be :
function traceException(e:exception
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 :-(
ASKER
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.Outp utDebugStr ing(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,M apModuleNa me,Procedu reName,Lin e);
What is the first inparameter EAddr... the compiler won't accept it. Where is it declared?
Thank you for all your help
-Mike
The first thing is on the last line of the TraceStackLineOut function:
Result := TAWIDebugOutputStream.Outp
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,M
What is the first inparameter EAddr... the compiler won't accept it. Where is it declared?
Thank you for all your help
-Mike
ASKER
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.Outp utDebugStr ing(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,M apModuleNa me,Procedu reName,Lin e);
What is the first inparameter EAddr... the compiler won't accept it. Where is it declared?
Thank you for all your help
-Mike
The first thing is on the last line of the TraceStackLineOut function:
Result := TAWIDebugOutputStream.Outp
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,M
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
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
ASKER
OK.. so the first part I'll just change to
Result := ds;
instead of
Result := TAWIDebugOutputStream.Outp utDebugStr ing(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.
Result := ds;
instead of
Result := TAWIDebugOutputStream.Outp
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 ...
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
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
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 ApplicationEvents1Exceptio n(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.ApplicationEvents1E xception(S ender: TObject;
E: Exception);
var Estackstr: string;
begin
Estackstr := TraceException(E, @E);
Application.MessageBox(PAn siChar(Est ackstr), 'cap');
end;
end.
unit Unit2;
interface
uses SysUtils;
function TraceException(E: Exception; EAddr: Pointer): string;
implementation
uses JclDebug, ComObj, Windows;
function TraceStackLineOut(MapModul eName,
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(MapModul eName, 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(frame List.Items [i].CodeLo cation));
MapOfAddr(Pointer(frameLis t.Items[i] .CodeLocat ion), FileName, MapModuleName, ProcedureName, Line);
if MapModuleName = '' then
MapModuleName := ModuleName;
Result := Result + TraceStackLineOut(MapModul eName, ProcedureName, Line, FileName);
end;
end;
stackList := JCLLastExceptStackList;//J clCreateSt ackList(Fa lse,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(stack List.Items [i].StackI nfo.Caller Adr));
MapOfAddr(Pointer(stackLis t.Items[i] .StackInfo .CallerAdr ), FileName, MapModuleName, ProcedureName,Line);
if MapModuleName = '' then
MapModuleName := ModuleName;
Result := Result + TraceStackLineOut(MapModul eName, ProcedureName, Line, FileName);
inc(i);
end;
end;
end.
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 ApplicationEvents1Exceptio
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses unit2, jcldebug;
procedure TForm1.Button1Click(Sender
var CrashList: TList;
begin
CrashList[10] := nil;
CrashList.Last;
end;
procedure TForm1.Button2Click(Sender
var
crasharray: array [1..10] of string;
i: Integer;
begin
for i := 0 to 200 do
crasharray[i] := '42';
end;
procedure TForm1.Button3Click(Sender
begin
raise Exception.Create('Ooops!')
end;
procedure TForm1.ApplicationEvents1E
E: Exception);
var Estackstr: string;
begin
Estackstr := TraceException(E, @E);
Application.MessageBox(PAn
end;
end.
unit Unit2;
interface
uses SysUtils;
function TraceException(E: Exception; EAddr: Pointer): string;
implementation
uses JclDebug, ComObj, Windows;
function TraceStackLineOut(MapModul
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(MapModul
FrameList := JclLastExceptFrameList;
for i := 0 to FrameList.Count-1 do
begin
if frameList.Items[i].Handles
begin
Result := Result + 'Exception will be handled by:';
ModuleName := ModuleOfAddr(Pointer(frame
MapOfAddr(Pointer(frameLis
if MapModuleName = '' then
MapModuleName := ModuleName;
Result := Result + TraceStackLineOut(MapModul
end;
end;
stackList := JCLLastExceptStackList;//J
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(stack
MapOfAddr(Pointer(stackLis
if MapModuleName = '' then
MapModuleName := ModuleName;
Result := Result + TraceStackLineOut(MapModul
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(PAn siChar(Est ackstr), '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;
just another comment :
Don't use the Form's Exception Event use the Application's like this:
procedure TForm1.AppHandleException(
var
StackTrace : string;
begin
try
StackTrace := traceException(e, @e);
Application.MessageBox(PAn
except
on ex : exception do begin
Application.MessageBox('OH
end;
end;
end;
procedure TForm1.Form1Create(Sender:
begin
Application.OnException := AppHandleException;
end;
ASKER
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
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
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.
Maybe calling it in a initialization section, or in a create handler is best.
Hope it helps.
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.