The Exception class in Delphi 2009 received a number of new features. A number of them are related to getting a stacktrace:
property StackTrace: string read GetStackTrace;
property StackInfo: Pointer read FStackInfo;
class var GetExceptionStackInfoProc: function (P: PExceptionRecord): Pointer;
class var GetStackInfoStringProc: function (Info: Pointer): string;
class var CleanUpStackInfoProc: procedure (Info: Pointer);
Has anyone used these to obtain a stack trace yet? Yeah, I know there are other ways to get a stack trace, but if it is supported natively in the Exception class I would rather leverage that.
Update: There is an interest blog post about this. Covers it in a lot of depth.
To me this looks like a framework where you can plug in your own stack tracing. I guess this might be used internally in the IDE with JCLDebug. Perhaps it's intended for users to be able to supply madExcept or another implementation.
No, I haven't used them yet (currently using madExcept for that, and also did some experiments with JclDebug) - but thanks for the tip!
TOndrej is correct. The new features added to Exception class are for third-parties to hook their own stack tracing code into the RTL. The default implementation of the Exception class does not produce its own stack traces.
You would be well advised to look at MadExcept. Not only does it provide excellent handling of any unhandled exceptions (screen grab, email etc) but it has a nice set of callable routines to hand you back a stack trace that you can use almost anywhere.
Bri
Related
I'm trying to figure out how to obtain a stack trace after an exception is thrown in Delphi. However, when I try to read the stack in the Application.OnException event using the function below, the stack already seems to be flushed and replaced by the throwing procedures.
function GetStackReport: AnsiString;
var
retaddr, walker: ^pointer;
begin
// ...
// History of stack, ignore esp frame
asm
mov walker, ebp
end;
// assume return address is present above ebp
while Cardinal(walker^) <> 0 do begin
retaddr := walker;
Inc(retaddr);
result := result + AddressInfo(Cardinal(retaddr^));
walker := walker^;
end;
end;
Here's what kind of results I'm getting:
001A63E3: TApplication.HandleException (Forms)
00129072: StdWndProc (Classes)
001A60B0: TApplication.ProcessMessage (Forms)
That's obviously not what I'm looking for, although it's correct. I'd like to retrieve the stack as it was just before the exception was thrown, or in other words the contents before (after would do too) the OnException call.
Is there any way to do that?
I am aware that I'm reinventing the wheel, because the folks over at madExcept/Eurekalog/jclDebug have already done this, but I'd like to know how it's done.
It is not possible to manually obtain a viable stack trace from inside the OnException event. As you have already noticed, the stack at the time of the error is already gone by the time that event is triggered. What you are looking for requires obtaining the stack trace at the time the exception is raised. Third-party exception loggers, like MadExcept, EurekaLog, etc handle those details for you by hooking into key functions and core exception handlers inside of the RTL itself.
In recent Delphi versions, the SysUtils.Exception class does have public StackTrace and StackInfo properties now, which would be useful in the OnException event except for the fact that Embarcadero has chosen NOT to implement those properties natively for unknown reasons. It requires third-party exception loggers to assign handlers to various callbacks exposed by the Exception class to generate stack trace data for the properties. But if you have JclDebug installed, for instance, then you could provide your own callback handlers in your own code that use JCL's stack tracing functions to generate the stack data for the properties.
I'd like to retrieve the stack as it was just before the
exception was thrown, or in other words the contents before
(after would do too) the OnException call.
Actually, you don't want the stack before the OnException call. That's what you've already got. You want the stack at the point at which the exception was raised. And that requires the stack tracing to happen ASAP after the raise. It's too late in the OnException call because the exception has propagated all the way to the top-level handler.
madExcept works by hooking all the RTL functions that handle exceptions. And it hooks the lowest level functions. This takes some serious effort to bring about. With these routines hooked the code can capture stack traces and so on. Note that the hooking is version specific and requires reverse engineering of the RTL.
What's more the stack walking is very much more advanced than your basic code. I don't mean that in a derogatory way, it's just that stack walking on x86 is a tricky business and the madExcept code is very well honed.
That's the basic idea. If you want to learn more then you can obtain the source code of JclDebug for free. Or buy madExcept and get its source.
In a Delphi 7 project we installed FastMM. Soon after that we noticed one of the forms started to issue Abstract Error message on close. I have debugged this extensively and I can't find the reason so far. The usual reason for this error message doesn't seem to apply here. The application doesn't define abstract classes. I also searched the form for a possible use of TStrings or something like that. Most importantly, we didn't (well, we think we didn't) make any changes to this form. It just broke.
Are there some other possible causes for this error besides trying to call unimplemented method?
Is there some possibilty that FastMM has enabled some obscure bug in the application, that remained hidden until now?
If the answer to these questions is no, then I'll just continue to search for an unimplemented method call, relieved that I am not missing something else.
If there is memory corruption then all sort of errors can be raised and it is very difficult to find the cause.
To answer your questions: 1) Yes abstract error can also be caused by memory corruption, and 2) Yes enabling FastMM can make bugs visible that normally pass unnoticed (but should still be fixed).
Some general advice for finding memory errors:
Try "FullDebugMode" setting in FastMM.
Make sure everything you Create is matched with a Free.
Make sure nothing is freed more than once.
Make sure an object is not used after it has been freed (or before it has been created).
Turn on hints and warnings (and fix them when they occur).
"It just broke" - it was probably always broke but now you know.
I have seen problems when closing a form as part of a button event. The form gets destroyed and then the remainder of the button messages get dispatched to a no-longer existing button. The Release method avoids this by (from memory) posting a wm_close message back to the form
Answer to question 1 "Are there some other possible causes for this error besides trying to call unimplemented method?"
Yes. This is what caused in my case an Abstract Error:
TWinControl(Sender).Visible:= FALSE;
This worked when sender was a TButton but raised the error (of course) when the sender was something else (like TAction). It was obviously my fault. I should have used "as" instead of a hard typecast.
Answer to question 2: Yes. I have seen that happening too. We should be very clear that this doesn't mean that FastMM is buggy. The bug was 'dormant'. FastMM only triggered it.
Actually you should rely on FastMM even more to find your issue. Switch FastMM to full debug mode for this. It will help you with:
Make sure an object is not used after it has been freed (or before it
has been created)
Also, in a few cases, the whole project was screwed up and I got the Abstract error. Nothing worked until I deleted the DPROJ file. Just do a compare between your current DPROJ file and the one in your back and you will see how the IDE f**** up the file.
You MUST also fix ALL warnings the compiler shows! The compiler is serious about that. It wouldn't raise an warning without a valid reason. Fix that and you will probably fix your problem.
In this particular case I would also replace all .Free with FreeAndNil().
You could try to add u_dzAbstractHandler to your project. It should raise the abstract error where the method was called, so it is easier to debug it. Of course this only helps when the error occurs when running in the debugger.
https://osdn.net/projects/dzlib-tools/scm/svn/blobs/head/dzlib/trunk/src/u_dzAbstractHandler.pas
Could be that one of your abstract functions/procedures in the base class is not implemented;
try this :
e.g
type
TBaseClass = class (TObject)
public
procedure DoSomething; virtual; abstract; //not implemented procedure
end;
type
TInheritedClass = class (TBaseClass)
public
procedure DoSomething; override;
end;
//Implementation
procedure TInheritedClass.DoSomething;
begin
//your code
end;
When introducing new exception types I am always a but unsure how to do this correctly. Is there a common convention? How do you do it?
I am interested in the scope you organize them (Keep them in the unit they are used in? Have a unit on component level? Package level? Application?)
This also influences naming. How much context do you include? Is it better to make them very specific (like EPersonIDNotFoundError) or try to make them reusable (like ENotFoundError)?
And what about the suffix "Error" - when should I add it and when leave it? I cannot see the logic e.g. in Classes.pas:
EWriteError = class(EFilerError);
EClassNotFound = class(EFilerError);
EResNotFound = class(Exception);
The only real convention I know of, is to prefix them with E.
I haven't really given it much thought in the past, but now I think of it, it seems to me that both Error and Exception are commonly used as a postfix. If you mix them, I'd say that Exception relates to something that goes wrong unexpectedly, like a connection that is broken, or a file that turns out to be unreadable, while an error relates more to wrong input, for instance a number was expected, but someone typed text.
The VCL seems to follow certain conventions too, but it seems to add a postfix only if it wouldn't be clearly and error without it, for instance
EConvertError, EMathError, EVariantError
vs
EAccessViolation, EInvalidPointer, EZeroDivide
The latter describe the error itself, where the first list need the postfix to indicate an error in a certain process or entity.
These examples are found in SysUtils, maybe you can take a look there, because it contains many exception classes as well as base classes for an even larger amount of exception. Very few of those end in Exception, except for some very specific errors that indeed you hope to never come across, like EHeapException and ESafecallException.
When creating a new exception I make it application wide. I start from the most detailed error to the most general, like class(Exception) and I name them accordingly.
So, in your example, I would use EPersonIDNotFoundError, ENotFoundError, Exception.
It really depends on how much detail you want from your error messages and what you include in your log (if you keep a log of errors)
Normally for simple applications you can get away with Exception.Create('ErrorMessage'). Where exceptions get powerful is being able to detect the kind of response required by looking at the class. Delphi already does this by the Abort procedure, which raises EAbort. EAbort is 'special' in that it does not trigger an 'error' as such, i.e. it is a kind of 'silent' exception. You can use this class-specific action to examine an exception and do different things. You could create a EMyWarning, EMyVeryNastyError etc, each descended from the basic Exception type.
Further, you can define a new exception class to carry more information out to the point where the exception is trapped. For example with the code (not checked):
EMyException = class( Exception )
constructor Create( const AErrorMessage : string; AErrorCode : integer ); reintroduce;
PUBLIC
ErrorCode : integer
end;
constructor EMyException.Create( const AErrorMessage : string; AErrorCode : integer );
begin
inherited Create( AErrorMessage );
ErrorCode := AErrorCode;
end;
You now have the possibility to set 'ErrorCode' when you raise the exception and you have it available when the exception is caught. Exceptions are pretty powerful.
Which scope to organize them in?
Use one unit for the whole application where you try to fit in the most general exceptions. Everything else goes into the unit where the exception is thrown. If you need these exceptions in other units then move them to a common unit used by the subsystem you are working on.
How about naming?
Try to make one or two levels of "general" exceptions like ENotFoundError. Put these in the application global file. Don't try too hard to generalize because you can't know what exception will come later requiring you to change everything. Create specialized exceptions on unit level inheriting from the global ones.
What about "Error" postfix?
Stop thinking about it. Add it. Unless it sounds stupid.
I'm asking this because I'm out of good ideas...hoping for someone else's fresh perspective.
I have a user running our 32-bit Delphi application (compiled with BDS 2006) on a Windows 7 64-bit system. Our software was "working fine" until a couple weeks ago. Now suddenly it isn't: it throws an Access Violation while initializing (instancing objects).
We've had him reinstall all our software--starting all over from scratch. Same AV error. We disabled his anti-virus software; same error.
Our stack tracing code (madExcept) for some reason wasn't able to provide a stack trace to the line of the error, so we've sent a couple error logging versions for the user to install and run, to isolate the line which generates the error...
Turns out, it's a line which instances a simple TStringList descendant (there's no overridden Create constructor, etc.--basically the Create is just instancing a TStringList which has a few custom methods associated with the descendant class.)
I'm tempted to send the user yet another test .EXE; one which just instances a plain-vanilla TStringList, to see what happens. But at this point I feel like I'm flailing at windmills, and risk wearing out the user's patience if I send too many more "things to try".
Any fresh ideas on a better approach to debugging this user's problem? (I don't like bailing out on a user's problems...those tend to be the ones which, if ignored, suddenly become an epidemic that 5 other users suddenly "find".)
EDIT, as Lasse requested:
procedure T_fmMain.AfterConstruction;
begin
inherited;
//Logging shows that we return from the Inherited call above,
//then AV in the following line...
FActionList := TAActionList.Create;
...other code here...
end;
And here's the definition of the object being created...
type
TAActionList = class(TStringList)
private
FShadowList: TStringList; //UPPERCASE shadow list
FIsDataLoaded : boolean;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
procedure DataLoaded;
function Add(const S: string): Integer; override;
procedure Delete(Index : integer); override;
function IndexOf(const S : string) : Integer; override;
end;
implementation
procedure TAActionList.AfterConstruction;
begin
Sorted := False; //until we're done loading
FShadowList := TStringList.Create;
end;
I hate these kind of problems, but I reckon you should focus on what's happening recently BEFORE the object tries to get constructed.
The symptoms you describe sound like typical heap corruption, so maybe you have something like...
An array being written to outside bounds? (turn bounds checking on, if you have it off)
Code trying to access an object which has been deleted?
Since my answer above, you've posted code snippets. This does raise a couple of possible issues that I can see.
a: AfterConstruction vs. modified constructor:
As others have mentioned, using AfterConstruction in this way is at best not idiomatic. I don't think it's truly "wrong", but it's a possible smell. There's a good intro to these methods on Dr. Bob's site here.
b: overridden methods Add, Delete, IndexOf
I'm guessing these methods use the FshadowList item in some way. Is it remotely possible that these methods are being invoked (and thus using FShadowList) before the FShadowList is created? This seems possible because you're using the AfterConstruction methods above, by which time virtual methods should 'work'. Hopefully this is easy to check with a debugger by setting some breakpoints and seeing the order they get hit in.
You should never override AfterConstruction and BeforeDestruction methods in your programs. They are not meant for what you're doing with them, but for low-level VCL hacking (like reference adding, custom memory handling or such).
You should override the Create constructor and Destroy destructor instead and put your initialization code here, like such:
constructor TAActionList.Create;
begin
inherited;
// Sorted := False; // not necessary IMHO
FShadowList := TStringList.Create;
end;
Take a look at the VCL code, and all serious published Delphi code, and you'll see that AfterConstruction and BeforeDestruction methods are never used. I guess this is the root cause of your problem, and your code must be modified in consequence. It could be even worse in future version of Delphi.
Clearly there is nothing suspicious about what TAActionList is doing at time of construction. Even considering ancestor constructors and possible side-effects of setting Sorted := False indicate there shouldn't be a problem. I'm more interested in what's happening inside T_fmMain.
Basically something is happening that causes FActionList := TAActionList.Create; to fail, even though there is nothing wrong in the implementation of TAActionList.Create (a possibility is that the form may have been unexpectedly destroyed).
I suggest you try changing T_fmMain.AfterConstruction as follows:
procedure T_fmMain.AfterConstruction;
begin
//This is safe because the object created has no form dependencies
//that might otherwise need to be initialised first.
FActionList := TAActionList.Create;
//Now, if the ancestor's AfterConstruction is causing the problem,
//the above line will work fine, and...
inherited AfterConstruction;
//... your error will have shifted to one of these lines here.
//other code here
end;
If an environment issue with a component used by your form is causing it destroy the form during AfterConstruction, then it's the assignment of the new TAActionList.Create instance to FActionList that's actually causing the AV. Another way to test would be to first create the object to a local variable, then assign it to the class field: FActionList := LActionList.
Environment problems can be subtle. E.g. We use a reporting component which we discovered requires that a printer driver is installed, otherwise it prevents our application from starting up.
You can confirm the destruction theory by setting a global variable in the form's destructor. Also you may be able to output a stack trace from the destructor to confirm the exact sequence leading to the destruction of the form.
Our software was "working fine" until a couple weeks ago... suddenly become an epidemic that 5 other users suddenly "find".) :
Sounds like you need to do some forensic analysis, not debugging: You need to discover what changed in that user's environment to trigger the error. All the more so if you have other users with the same deployment that don't have the problem (sounds like that's your situation). Sending a user 'things to try' is one of the best ways to erode user confidence very quickly! (If there is IT support at the user site, get them involved, not the user).
For starters, explore these options:
*) If possible, I'd check the Windows Event Log for events that may have occurred on that machine around the time the problem arose.
*) Is there some kind of IT support person on the user's side that you can talk to about possible changes/problems in that user's environment?
*) Was there some kind of support issue/incident with that user around the time the error surfaced that may be connected to it, and/or caused some kind of data or file corruption particular to them?
(As for the code itself, I agree with #Warran P about decoupling etc)
Things to do when MadExcept is NOT Enough (which is rare, I must say):
Try Jedi JCL's JCLDEBUG instead. You might get a stack traceback with it, if you change out MadExcept for JCLDEBUG, and write directly the stack trace to the disk without ANY UI interaction.
Run a debug-viewer like MS/SysInternals debugview, and trace output things like the Self pointers of the objects where the problems are happening. I suspect that somehow an INVALID instance pointer is ending up in there.
Decouple things and refactor things, and write unit tests, until you find the really ugly thing that's trashing you. (Someone suggested heap corruption. I often find heap corruption goes hand in hand with unsafe ugly untested code, and deeply bound UI+model cascading failures.)
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 5 years ago.
Improve this question
It happens to me quite often that I call a function Foo and want to know what exceptions this function might throw. In order to find out I then look into the implementation of Foo, but that is not enough. Foo might indeed call a function Bar that raises an exception.
Sometimes I even miss Java's checked exception handling.
So it is obivous to me that it is necessary to document the exceptions each function can throw: the question is: how? Are there any best practices on how to document exceptions? How do you handle this problem?
I think this covers some part of the problem you became aware of
Cleaner, more elegant and wrong
Cleaner, more elegant and harder to recognize
Most Delphi applications are VCL applications. They do not require a checked exception, because the main message loop has a try/except block catching everything.
It can be good practice to document which exceptions can be explicitly raised by your code though.
I'd use XMLDoc for that (there are various questions on XMLDoc her on SO, and here is some documentation from Embarcadero).
Note however that underlying code can also raise exceptions. Depending on the influence you have on libraries, you can or cannot assure those are always the same. A different thing is the OS: depending on where you run, you can get different exceptions.
--jeroen
We use Javadoc style comments for documentation. We extract the info and generate the output with some simple text scripts. We have used DelphiCodeToDoc, too.
Documenting exceptions, we have mandated to use the #throws tag.
this is looking great for documenting code - Documentation Insight from DevJet.net
I use XMLDoc comments. It's basically adding a specialized type of comment to your code in the interface section, just above the property or method declarations. Here's a nonsensical (of course) example. If you add similar style comments in your code, they'll pop up in Code Insight when you invoke it while writing code, just like the VCL's documentation does.
type
{$REGION 'TMyClass description'}
/// <summary>TMyClass is a descendent of TComponent
/// which performs some function.</summary>
{$ENDREGION}
TMyClass=class(TComponent)
private
// your private stuff
FSomeProp: Boolean;
procedure SetSomeProp(Value: Boolean);
protected
// your protected stuff
public
{$REGION 'TMyClass constructor'}
/// <summary> TMyClass constructor.</summary>
/// <remarks>Creates an instance of TMyClass.</remarks>
/// <param>Owner: TObject. The owner of the instance of TMyClass</param>
/// <exception>Raises EMyObjectFailedAlloc if the constructor dies
/// </exception>
{$ENDREGION}
constructor Create(Owner: TObject); override;
published
{$REGION 'TMyClass.Someprop'}
/// <summary>Someprop property</summary>
/// <remarks>Someprop is a Boolean property. When True, the
/// thingamajig automatically frobs the widget. Changing this
/// property also affects the behavior of SomeOtherProp.</remarks>
{$ENDREGION}
property Someprop: Boolean read FSomeProp write SetSomeProp;
end;
I prefer to wrap these XMLDoc comments in regions, so they can be collapsed out of the way unless I want to edit them. I've done so above; if you don't like them, remove the lines with {$REGION } and {$ENDREGION}
I use PasDoc for documenting almost all of my Delphi projects. It includes a "raises" tag which does what you seem to be asking for.
Regards
- turino