Can't step into for loop - delphi

I've been having a strange problem that I've never seen before. Now I have a for loop:
var
a,counter: byte;
begin
a:=0;
for counter := 1 to 10 do//I put a breakpoint at this line
begin
a:=a*5;
a:=a+counter;
end;
end;
If I put a breakpoint at the line above and try to step into the loop I can't do it. The debugger immediately steps over the loop and goes to the end.In the end I get the right result, but I can't follow the loop step by step. I mean this is just a simple example and not the real task. I just want to know when in what circumstances does this happen? I definitely remember tracking through all the steps of a loop. I work with Delphi 2010.

Both lines of code in the loop can be completely optimized away; you do nothing with a outside the loop, so both of the assignments are unnecessary. After the optimization, the compiler is leaving
for counter := 1 to 10 do
;
Actually, if you didn't have a breakpoint there, the loop would be removed as well, as it does nothing.
If you're having problems with your code, and the info above doesn't help (using the variable a after the loop runs), you need to post your real code. This made-up code is very clear to analyze; the problem in your actual code may be this simple, or much more complex to analyze.

See does turning off optimization makes difference - in project options -> Compiling -> Code generation.

In a comment to Ken's answer, Mikayil hinted that the code is inside a procedure.
This would also be a sound assumption looking at the code.
So if we set up a test like this :
Procedure Test;
var
a,counter: byte;
begin
a:=0;
for counter := 1 to 10 do//I put a breakpoint at this line
begin
a:=a*5;
a:=a+counter;
end;
end;
begin
Test;
end.
Set optimization on : Result - as observed by Mikayil, no stepping into loop possible.
Set optimization off : Result - stepping into loop possible, just as ain suggested.
Now also take into consideration, Mikayil's question in Ken's answer :
whether the inability to step into the loop was because of the local scope of the a.
Ken answered no, but this is not the case :
var
a : byte; // scope of a is outside of the procedure
Procedure Test;
var
counter: byte;
begin
a:=0;
for counter := 1 to 10 do//I put a breakpoint at this line
begin
a:=a*5;
a:=a+counter;
end;
end;
begin
Test;
end.
Now it does not matter whether optimization is on or off, stepping into the loop is possible anyway.
So, ain is absolute correct in his answer. (Tested in XE2)
Update :
For enabling stepping into the loop there are three possibilities :
Set optimization off.
Declare a outside your local scope.
Insert a dummy operation using a after the loop. Like : if (a < counter) then;
Neither of these steps are uncommon debug procedures, which I find this question is all about.

Related

What is the best way in Delphi to generate a list of forms/units in the order they load?

I had an issue where a file kept deleting on startup and I couldn't track down the code responsible. I wound up adding Vcl.Dialogs to all the units and creating an initialization section that looked like this:
initialization
begin
ShowMessage('Inside [Unit Name Here]');
end;
This was quite a pain. Is there an easy way to generate a list of forms/units in the order in which they fire off?
UPDATE: 2019-08-01 (Helpful MAP links)
Here are two links that may assist in understanding DELPHI map files
http://docwiki.embarcadero.com/RADStudio/Rio/en/API_%28%2A.map%29
Understanding Delphi MAP File
You really didn't need to go to all that trouble modifying your source units. I think you'll find that using the method below will find the misbehaving unit
much more quickly than somehow generating a list of units and then ploughing
your way through it.
If you look in System.Pas, you'll find a procedure InitUnits like this (from D7).
procedure InitUnits;
var
Count, I: Integer;
Table: PUnitEntryTable;
P: Pointer;
begin
if InitContext.InitTable = nil then
exit;
Count := InitContext.InitTable^.UnitCount;
I := 0;
Table := InitContext.InitTable^.UnitInfo;
[...]
try
while I < Count do
begin
P := Table^[I].Init;
Inc(I);
InitContext.InitCount := I;
if Assigned(P) then
begin
TProc(P)();
end;
end;
except
FinalizeUnits;
raise;
end;
end;
This is the code which causes the initialization code of each unit to be called. It works its way through the units and calls the initialization section (if any)
of each unit via the call
TProc(P)();
You can inspect the value of Count prior to the loop; don't be surprised if its upwards
of a couple of hundreds even for a relatively simple project.
Put a breakpoint on the TProc(P)(); line and right-click and set the PassCount to
half the value of Count. Run your app and when the breakpoint trips, check whether
the file has been deleted.
You can then do a binary search through the values of
Count (by continuing the current run if the file is still there, or resetting the app
and halving the Pass Count) to establish exactly which unit causes the file to be deleted.
Because you can use a binary search to do this, it will rapidly converge on the
unit which is deleting the file. Of course, you can trace into the unit's
initialization code (if it has been compiled with debug info) when the breakpoint
trips by pressing F7 on TProc(P)();
You can inspect the segments section of the map file. The entries with C=ICODE are those units with initialization parts in the order they are executed.

delphi dll-finalization: how to debug

I have a problem in a dll (including a COM object): when the dll is unloaded some finalization sections are executed and some are not.
in the debugger I could manage to locate the problem in System FinalizeUnits(). pseudo code of this function - for your convenience:
procedure FinalizeUnits;
var
Count: Integer;
Table: PUnitEntryTable;
P: Pointer;
begin
if InitContext.InitTable = nil then
exit;
Count := InitContext.InitCount;
Table := InitContext.InitTable^.UnitInfo;
try
while Count > 0 do
begin
Dec(Count);
InitContext.InitCount := Count;
P := Table^[Count].FInit;
if Assigned(P) and Assigned(Pointer(P^)) then
begin
// ISSUE: when this is called for item x the debugging just stops
// breakpoints in the except block or at the end of the function are not reached!
TProc(P)();
end;
end;
except
FinalizeUnits; { try to finalize the others }
raise;
end;
end;
there is one specific finalization call that causes the problem:
i.e. the InitContext.InitCount is about 400 and when item x (e.g. 363) is executed, the debugger just stops: it does not proceed to the except block and also not to the end of the FinalizeUnits() function (where I have set breakpoints).
BTW: how is that possible? I thought the except block (or the line after it) must be called in any case.
notes:
when I manually avoid this special call, all other functions are executed normally.
when I step into the problematic TProc I end up in TCriticalSection.Enter (then Acquire - FSection.Enter - EnterCriticalSection - WSACleanup)
some more info to the WSACleanup call: I use a 3rd party library UniDAC that opens a TCP/IP connection to a database - I think this library calls WSACleanup in one of it's finalization sections (but I don't have this source code).
The strange thing is, that when the debugger is on the WSACleanup line:
function WSACleanup; external winsocket name 'WSACleanup';
and I want to step over it (i.e. F8), the debugger just stops (as if the application had exited normally) - but it should continue the loop in FinalizeUnits: how is this possible? i.e. if it were a deadlock it would not stop, but hang forever, right?
The question is: how can I debug this issue? is it possible that a deadlock causes this problem (i.e. that the debugger just stops)?
Try switching to CPU view before stepping into the problematic TProc using F7. Sometimes this can give you a good hint.
You could also try looking up the address of "P" in the map file.

Delphi debugger - go to line when exception happens

I got switched to other project at work and I noticed that Delphi XE2 debugger does not show the line that raised exception. When i got back at home i started to investigate. Then I found out that it can be disabled in Tools -> Options -> Debugger options and check Integrated debugging. Also I unchecked everything under Language exceptions in Exception types to ignore list. Notify on Language Exceptions left checked. Project -> Options -> Compiling, I have defaults there and Overflow and Range cheking enabled. I am running Debug build. I Cleaned it.
I have not noticed before, but now Delphi debugger doesn't give me the line when I call this code:
procedure TForm1.BitBtn1Click(Sender: TObject);
var
_List: TStringList;
begin
_List := TStringList.Create;
try
Caption := _List[0]; // 'List index out of bounds (0)' here
finally
FreeAndNil(_List);
end;
end;
but this works (provided only to show that debugger does show the line for some things):
{$R+} // Range check is ON
procedure TForm1.BitBtn2Click(Sender: TObject);
var
_myArray: array [1 .. 5] of string;
i: integer;
begin
for i := 0 to 5 do
begin
_myArray[i] := 'Element ' + IntToStr(i); // Range check error here
ShowMessage('myArray[' + IntToStr(i) + '] = ' + _myArray[i]);
end;
end;
What is happening here? How to make the debugger to show as much as possible?
Thank you.
Let me answer the question first.
How to make the compiler show as much as possible
The compiler shows you that the error is in the call to the btnclick.
The trick is to put a breakpoint on the first line of the btnclick proc with F5.
Then rebuild (!) the application and run again.
The execution will stop that the breakpoint.
Step through the code using F8 until the error shows up.
Put a breakpoint F5 on the line that generated the error.
Abort and rerun the application.
When you get to the second breakpoint instead of pressing F8, press F7 to step inside the routine that causes the error, keep on pressing F7/F8 until you see what exactly is causing the problem.
Why is this happening?
The compiler traces back the source of the exception by following the stack trace.
Because in your case the code that generated the exception does not have stack trace (because it is not debug code), the compiler cannot do this trick and instead follows the stack trace that is does have; it moves one level up in your code and flags the exception there.
A look at this in detail
You're comparing apples and oranges.
This code (Exhibit A):
Caption := _List[0]; // 'List index out of bounds (0)' here
Has absolutely nothing in common with this code (Exhibit B):
_myArray: array [1 .. 5] of string;
....
_myArray[0]:= 'Hallo';
Exhibit A uses the TStringList class's items property, which is defined more or less as follows (I've simplified it a bit, but the fundamentals are correct):
type
TStringList = class(TStrings)
strict private
FList: array of string;
....
private
procedure Put(index: integer; const value: string);
function Get(index: integer): string;
published
property Items[index: integer]: string read Get write Put; default;
// ------------------------------------------------------^^^^^^^
....
end;
Notice the default keyword at the end of the Items property.
What this all means is that when you call _List[0], you are really calling _List.Items[0], which gets translated into Caption:= _List.Getitems(0), because of the read modifier on the property.
The default keyword allows you to omit the .Items.
The Get code looks like this:
function TStringList.Get(Index: Integer): string;
begin
if Cardinal(Index) >= Cardinal(FCount) then
Error(#SListIndexError, Index); <<-Here is the line that generates the error*
Result := FList[Index].FString;
end;
*Actually the error is generated inside the Error routine
Unless you have the RTL/VCL source code and you're running with debug DCU's you will not get a break on the exact trigger of the exception (which is inside the system.classes) unit.
Note that this error does not depend on range checking, it will always fire.
Because Delphi does not have debug info for the exact line where the error is generated, it does the next best thing and tries to make a guess.
Short version
The stringlist is a complex abstraction pretending to be an array.
Lots of code gets called, making the pinpointing of the error difficult for the compiler.
In Exhibit B:
_myArray: array [1 .. 5] of string;
....
i:= 0;
_myArray[i]:= 'Hallo';
Either a range check error or an access violation is generated.
Both of these errors occur on the exact line, allowing the compiler to stop at the correct spot.
Short version
The plain array is a basic building block with no hidden calls to code elsewhere, making pinpointing errors very easy for the compiler.
Understanding properties
class and record properties (and now class operators) look like simple assignments/operations to/with variables, but are in fact calls to (possibly complex) subroutines.

result in simple code when variables are uninitialized

Today one of my friend ask me about below code :
var
a: Integer;
begin
ShowMessage(IntToStr(a));
end;
This is local variable and not been initialized , ok ?
Put code in OnClick event of a button component and then run code in three diffrent ways below :
Click on the button and see result , result = 1635841
Press Enter key and see result , result = 1
Press Space key and see result , reuslt = 1636097
I test code in two diffrent computer & see same result , any idea about this ?
Since the variable is not initialized, its value can be anything. Since your result is 'something', there is nothing unusual at all going on here.
procedure TForm1.Button1Click(Sender: TObject);
var
a: Integer;
begin
ShowMessage(IntToStr(Integer(a)));
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
ShowMessage(IntToStr(Integer(Pointer(TButtonControl(Button1)))));
end;
on my machine this code produces same message as compiler uses ebx register for a variable, while TButtonControl.WndProc uses ebx to store pointer to Self(as EAX will be overwritten after WinAPI function calls from TbuttonControl.WndProc) which is button1 before calling the actual handler Button1Click. So alas, on Delphi 2007 message text is too predictable.
[edited]
You can see what's happening inside VCL while debugging if you turn on Use debug DCUs option in your project compiler options Compiler->Debugging->Use debug DCUs.
See this similar Stackoverflow question.
In Delphi local variables are not initialised by default. The programmer is responsible for that and should always set a value before reading it. The value of an unitialised variable depends on the content of the actual allocated memory cells used for that variable. So any value is possible here.

Stack Overflow in Delphi

I am posting my Stack Overflow problem on StackOverflow.com. Irony at its best!
Anyways. I am calling this procedure on my SkypeReply event handler, which gets fired a lot:
Procedure OnCategoryRename;
Var
CategoryID : Integer;
sCtgName : String;
Begin
if (AnsiContainsStr(pCommand.Reply,'GROUP')) and (AnsiContainsStr(pCommand.Reply,'DISPLAYNAME')) then
begin
sCtgName := pCommand.Reply;
Delete(sCtgName,1,Pos('GROUP',sCtgName)+5);
CategoryID := StrToInt(Trim(LeftStr(sCtgName,Pos(' ',sCtgName))));
sCtgName := GetCategoryByID(CategoryID).DisplayName; // Removing THIS line does not produce a Stack Overflow!
ShowMessage(sCtgName);
end;
The idea of this is to loop thru my list of Skype Groups, to see what group has been renamed. AFAIK thats of no importance, as my S.O has been traced to appear here
Function GetCategoryByID(ID : Integer):IGroup;
Var
I : Integer;
Category : IGroup;
Begin
// Make the default result nil
Result := nil;
// Loop thru the CUSTOM CATEGORIES of the ONLY SKYPE CONTROL used in this project
// (which 100% positive IS attached ;) )
for I := 1 to frmMain.Skype.CustomGroups.Count do
Begin
// The Category Variable
Category := frmMain.Skype.CustomGroups.Item[I];
// If the current category ID returned by the loop matches the passed ID
if Category.Id = ID then
begin
// Return the Category as Result (IGroup)
Result := Category;
// Exit the function.
Exit;
end;
End;
End;
When I set a breakpoint at Result := Category; and Single Step thru, those 2 lines get executed over and over, right after each other!
And when I comment out the sCtgName := GetCategoryByID(CategoryID).DisplayName; in the first code snippet, there is no Overflow, the message gets shown that one time it is supposed to. However, the GetCategoryByID is a function I wrote, and I wrote one similar, too, which works just fine (GetCategoryByName), so I don't get why it decided to repeat the
// Return the Category as Result (IGroup)
Result := Category;
// Exit the function.
Exit;
over and over again.
EDIT: Here is how you can reproduce it: https://gist.github.com/813389
EDIT: Here is my CallStack, as requested:
Edit2: More info:
Make sure to compile your project with "optimization" off, "stack frames" on and "use debug .dcu" on to get the most detailed callstack possible. Then post the callstack you get when you hit the stack overflow here (if you have trouble identifying the nature of the problem from it).
What doesn't show up in your question :
the "OnCategoryRename" function you posted up here is a subfunction called from a "TForm.Skype1Reply" callback.
To see this, I had to click on your github link - yet I think it is an important point of your problem.
My guess :
Your "GetCategoryById" function actually sends a query, which triggers "Skype1Reply".
If the groupname has changed, "Skype1Reply" calls "OnCategoryRename".
"OnCategoryRename" calls "GetCategoryById"
"GetCategoryById" triggers "Skype1Reply"
Somehow, the test saying "if groupname has changed" is still true, so "Skype1Reply" calls "OnCategoryRename"
"OnCategoryRename" calls "GetCategoryById"
rinse, repeat
I think a quick and dirty fix would be to change
sCtgName := GetCategoryByID(CategoryID).DisplayName; // Removing THIS line does not produce a Stack Overflow!
with
sCtgName := //find another way to get the new name, which you can probably get from your ICommand object
pCommand.Reply.ReadDataFromReplyAndGetNewDisplayName;
In the future, I suggest you post your complete code sample for this kind of question.
Stack overflows could be caused by endless recursion.
You have to be very careful when you write code that has event handlers in it.
One thing you can do to help you debug this is, as David says, step INTO rather than over such calls. F7 steps into a call.
Another thing you can do is put a breakpoint at the top of the function GetCategoryById. Now look at your Call Stack. Do you see the repeated name in the stack? This should make it very clear.

Resources