How can I log the callstack with JCL without using raise exception - delphi

Background
We have a problem that sometimes the grid in Devexpress raise exception
"Raised EConvertError: Cannot assign a nil to a TFont".
But to trace the real cause of this we have changed Font in cxEdit to be a public property instead with a get and set method. Here I want to log the callstack.
My question
Normally JCL is used to log callstacks when exceptions appear. But how can I log the callstack without using raise exception and show a dialog for the user about this ?
I have found the lines:
var
GlobalStackList: TJclGlobalStackList;
in JclDebug but I fail to use it. If someone have a example how to get the callstack I would be happy.
Regards Roland Bengtsson

This answer shows how to do it with JCL by calling JclCreateStackList.

Related

Can a Range check error be generated without being specifically enabled?

A customer has reported a Range check error on a machine embedded in a factory running an older Delphi 7 application. We have not (yet) been able to reproduce the error. They sent us a photo:
The software can operate correctly for days on end, and we currently only have vague clues as to how this might be generated or reliably reproduced. My understanding is that this error occurs in two general scenarios:
(1) An array or string has been accessed outside its bounds
(2) The variable is assigned a value out-of-range for its type
An example of (1) is accessing array[50] if there are only 40 elements. An example of (2) is assigning the value "300" to an an unsigned BYTE.
This comes from SO question 1 and SO question 2. I've got lots of careful checking to do to try and identify the offending lines!
My question is how this error is generated in the first place. Both the above-mentioned questions refer to the {$R+} compiler directive. In Project Options > Compiler > Runtime errors, Range checking is off, and nowhere in the code is {$R+} (nor {$R-}) used. How does this error occur? Shouldn't the application crash or generate a different exception?
I'll answer the question:
Can a Range check error be generated without being specifically enabled?
However, the problem you're experiencing at your client's site will need further investigation for you to resolve it. Typically this kind of thing requires a combination of:
Exception logging: tools that produce a stack trace when exceptions occur to determine exactly what code was being called.
Tracing: logging messages that provide clues to the state of the application to assist in your investigation.
Yes, range check error can be generated without being specifically enabled.
Range check errors can be raised at any time with raise ERangeError.Create(...);. If called, this will raise the error regardless of the state of the range-check setting.
Note code can if so written, also honour the setting as follows:
{$IFOPT R+}
raise ERangeError.Create(...);
{$ENDIF}
But the point is that as soon as raise <SomeClass>.Create(...) is called, an exception will be raised. If you search the Delphi source code, you'll find a few places where ERangeError is raised. Loki's answer provides an example.
The second important thing to note is that the {$R} setting is not global. If you compile a project with this setting turn off, but link in DCUs which were compiled with the option turned on: then the setting will still be on for those DCUs.
Furthermore, the setting can be locally changed for specific sections of code. E.g.
{$IFOPT R-} {$R+} {$DEFINE TOGGLE_ROFF} {$ENDIF}
{ Ensure range-checking is on, but turn off again if it was already off.}
procedure MustUseRangeChecking(...);
begin
...
end;
{$IFDEF TOGGLE_ROFF} {$R-} {$ENDIF}
NOTE: You can use {$IFOPT} in your own code to check the state of the range-checking directive.
yes it's can be raise without having Range check error specifically enabled
exemple just look the TStream.SetSize function :
procedure TStream.SetSize(const NewSize: Int64);
begin
if (NewSize < Low(Integer)) or (NewSize > High(Integer)) then
raise ERangeError.CreateRes(#SRangeError);
SetSize(LongInt(NewSize));
end;
so it's will raise the exception with or without Range check error enabled. you have in delphi several function like this.

How to get the Error Code of CDO.Message

I wrote my routines for using CDO.Message. It's working long time ago.
But now, in some site they installed a certificate and then the priorly working version is making errors with all calls.
I used simple variants to hold the CDO.Message COM object.
When I used Send method(?) it returns a HResult.
But it's interesting, because the HResult is unusable for get the error code, because the Send is seems to be a real method which makes Exception on problems.
So the result code is -1 if I set to this value before.
I tried to get the last error code with GetLastError. But this is 0.
I can catch the Exception, but it contains only the error message what is:
"The transport failed to connect to the server"
The VB codes can get the error code which could provide extra information about the problem (or not).
Do you know about a technic to get the error code value from Delphi XE3?
It would be better if we had some source code at hand. Specifically the Delphi declaration of the COM interface.
My guess is that the method is declared as safecall. What this means is that the compiler understands that the method is actually stdcall returning HRESULT, and re-writes the parameters to match. If the true COM method returns an HRESULT other than S_OK then the compiler writes code to check for that and convert the error into an exception.
The exception that is raised will be EOleSysError and that has the property ErrorCode which contains the HRESULT that you are looking for.
So, you need to:
Add an exception handler to catch EOleSysError.
Read the ErrorCode property of the EOleSysError exception instance that you catch.
This is all a little bit round the houses. If you'd prefer to avoid exception handling, then you can always re-write the COM interface declaration to be a true stdcall method returning an HRESULT.

Xcode exception breakpoint doesn't print details of the exception being thrown

SUMMARY
When I set an exception breakpoint, I don't get the exception message. How do I get the exception message? I already know how to get the stack trace, but that doesn't include the exception message.
DETAILS
In the past I developed iOS Apps with Xcode and when there was a problem, I'd get an error/exception. The exception would often have a message like "can't dereference null" or whatever.
Now, using Xcode 4.6.x for the past several weeks I've never gotten an exception message. I'll often get a SIGABRT. I put in the break on exception breakpoint and it will break there, but it's off in some assembly within the iOS SDK and I never get a message.
In fact, I can't remember the last time I saw anything show up in the debugger console.
Did exception info dissappear with the migration to LLVM?
It's very frustrating to have my app just crash in the SDK without knowing why. I check the last function to make sure things are set up correctly (objects allocated, etc) and they are which means I'm left with no clues.
Is it possibly a build setting held over from the past is somehow turning off exception messages?
Please reopen question. It now has an answer!
In the comments an excellent answer has been given. This should be promoted to full answer, and so I can mark the question answered and others who have this common issue can find it. In order for that to happen, the question needs to be reopened! (I'll delete this plea after that happens.)
I will update Jeff's answer here:
To have both the line causing the exception highlighted (and not UIApplicationMain() in main.m) AND to see the reason for the exception (e.g., "error: A fetch request must have an entity."), do this:
In the Breakpoint navigator:
Add (+), Add Exception Breakpoint
Select the new breakpoint, Control-Click, Edit Breakpoint
Add Action
Enter: po $arg1
The relevant part of the stack trace will be in the nagivator area.
This seems to still work in Xcode 9
Here is my addition for use with Xcode 6 and below.
Enter: po (NSException*) $eax
In Xcode 6 you must explicitly provide the object type because it is no longer inferred.
For Xcode 7-9 (based off Jeff's answer):
In the Breakpoint navigator:
Add (+), Add Exception Breakpoint
Select the new breakpoint, Control-Click, Edit Breakpoint
Add Action
Enter: po $arg1
To have both the line causing the exception highlighted (and not UIApplicationMain() in main.m) AND to see the reason for the exception (e.g., "error: A fetch request must have an entity."), do this:
In the Breakpoint navigator:
Add (+), Add Exception Breakpoint
Select the new breakpoint, Contorl-Click, Edit Breakpoint
Add Action
Enter: po $eax
The relevant part of the stack trace will be in the nagivator area.
Yes xcode is not so friendly for debugging. I like this article which helps me to understand crash logs a bit clearly))
Demystifying iOS Application Crash Logs
Also do this if you see error "message sent to deallocated instance"
'Products -> Edit Scheme -> Enable Zombie Objects'
this will enable zombie objects and when you do profile to your project choose
"zombie", cause error and you will be able to see which objects was deallocated e.g NSArray *myArray
The information I get from po $eax or po (NSException *)$eax seems to be different from what Xcode would print if no exception breakpoints are set. So I do the following,
Add an exception breakpoint
Exception occurs, breakpoint was hit -> I know the location
Temporarily disable breakpoints (second button on the left in Debug area)
Continue program execution (third button on the left in Debug area)
Details are printed -> I know the cause
Obviously not very elegant and flexible, but at least I two big questions are answered (where and why).
You can use bt or thread backtrace command to print error trace
Show the stack backtrace for the current thread.
The same stack trace you can find in crash reports
Information about current thread use currentThread
//Objective-C
po [NSThread currentThread]
//Swift
po Thread.currentThread
*Sometimes you can use fr v(or just v from XCode 10.2) when po is not working

How do I debug a stack overflow?

I was wondering if anyone has had a similar experience. I am trying to trace the source of a problem but am coming up with nil. I have a project in Delphi 5 which has Report Builder reports on it. I needed an upgraded version of reportbuilder so I tried running the project in Delphi 7. When my project runs and I click a button to view a report, it views fine. However, if I use a paramstr to run the report (showmainform is set false) and show report procedure runs, I get get a stack overflow error.
The original code was :
if lowercase(ParamStr(1)) = 'termsexceeded' then begin
reportsdata.termsexceeded.close;
reportsdata.termsexceeded.open;
reports.ppTermsExceeded.print;
reportsdata.termsexceeded.close;
application.terminate;
end;
And it gave me the stack overflow error on the .print function.
The code that works in Delphi 7 is :
if lowercase(ParamStr(1)) = 'termsexceeded' then begin
reportsdata.termsexceeded.close;
reportsdata.termsexceeded.open;
reports.left := -10000;
reports.show;
reports.ppTermsExceeded.print;
reportsdata.termsexceeded.close;
application.terminate;
end;
Has anybody got a suggestion on how I could debug this to see if the problem lies with my Delphi 7 or with Reportbuilder ?
There are no events on the .show event of the reports form.
Any advice on how to get to the bottom of this would be appreciated.
Regards
When you get a stack overflow, use the debugger. It will interrupt your program when the OS throws the exception, and at that time, you can use the debugger's call stack window to see the path a function calls that lead there. You'll probably see a certain function or sequence of functions repeated many times.
When you've found the repeating pattern, check the code to see why it's repeating. Look for a condition is supposed to have changed, but doesn't.

Still get error popup even when ApplyUpdates is inside try...except

Solution found, see my comment below
D5, odbc to mysql database
This code:
with QryCmdPerf do begin
Close;
ParamByName('ACCTID').AsInteger:= AcctId;
ParamByName('FROMDT').AsString:= MySQLDate(FromDt);
ParamByName('TODT').AsString:= MySQLDate(ToDt);
Open;
first;
try
edit;
FieldByName('PnL').AsFloat:= 97979;
ApplyUpdates;
except
close;
end;
end; // with
(specifically the "ApplyUpdates") causes a popup to appear with the text "Update Failed" if the PnL field already has the value 97979, evidently because of this code:
procedure TUpdateSQL.ExecSQL(UpdateKind: TUpdateKind);
begin
with Query[UpdateKind] do
begin
Prepare;
ExecSQL;
if RowsAffected <> 1 then DatabaseError(SUpdateFailed);
end;
end;
in DBTables.pas. Anyway, I want to be able to issue ApplyUpdates, and not have to worry about a popup if it doesn't do any updating. But if "try...except" doesn't work, what will?
TIA
You're confusing the dialog displayed by the debugger with a dialog displayed by your program. Please see this article I wrote a few years ago:
Why do I continue getting error messages even after I have written an exception handler?
It describes several ways to avoid the debugger interfering:
Use "advanced breakpoints" to temporarily disable the debugger around the code that throws exceptions.
Configure the debugger to ignore certain exception types. (Read the debugger's message more carefully to see exactly what exception class you're dealing with.)
Configure the debugger not to interrupt on any exceptions.
Turn off integrated debugger entirely.
The short answer is, you have to set up an eventhandler for OnUpdateError or no amount of "try...except" blocks will block the popup. The long answer is it appears to be a bug with odbc. The repro is here: http://www.codeupload.com/3919 for anyone who wants to take a look at it. You can skip the MySQL stuff, any odbc database will do.
There are two things that can be going wrong here.
Option 1
First, some "very bad code" may be short-circuiting the unwinding of the call stack on exceptions. E.g. ApplyUpdates or one of its child routines may also have a try...except block that calls Application.HandleException directly.
To test this, if you put a breakpoint on QryCmdPerf.Close, do you reach it?
If not, then Application.HandleException (or worse Application.ShowException) has been called directly.
Solving this requires a custom exception handler hooked to the Application.OnException event. You may have to set temporary state to know when this particular exception can be ignored.
Yes it's messy, that why calling Application.HandleException directly, is "very bad code".
Option 2
If you do reach the breakpoint, but the exception is being raised again, then it should be a lot simpler to solve.
The Close method is probably attempting to save any pending changes, so is effectively applying the updates again. Rather than simply closing the data set, call CancelChanges or equivalent.

Resources