One of the enhancements to the Delphi native-code debugger in Delphi 2005 is the ability of the Call Stack View to show a more complete picture of how your program arrived at its current location. In previous Delphi versions, the only stack frames shown in the Call Stack view were those frames in code compiled with debug info. This limitation always bothered me and because of it, I would, on occasion, resort to debugging Delphi code with a version of Borland C++Builder (BCB). Why? Well, BCB's stack crawling code was far superior to that of Delphi's.
Thankfully, starting with Delphi 2005, I no longer have to do this in Delphi. The debugger in Delphi 2005 will now show a complete stack trace, including stack frames that do not have debug info.
Great, right? Well, sure, it's great. . . unless you are (for whatever reasons) not able to migrate to Delphi 2005 yet. What are you supposed to do? Fortunately, you can manually crawl the stack in Delphi 7 (and previous versions of Delphi) to figure out how your program arrived at the current location.
How do you do this? Well, it involves using the CPU view. (dramatic pause... waiting for audience to gasp and then admonish the speaker with a barrage of “Booooooooooooo“). Yes, yes, I realize that mere sight of the CPU view is enough to make some Delphi developers cringe and run away. But before you run away (and stop reading), allow me to point out one very useful aspect of the CPU view.
Using the stack pane in the CPU view (this is the pane that appears in the lower right hand corner of the view), in conjunction with the registers pane, you can manually crawl through the stack to see your program's call chain. One caveat to this technique is that it assumes that the code is built using stack frames. In order to get the hang of doing this, it will be a good idea to do it in code that has debug info first (so you can actually understand what you're doing).
Create a VCL application. For purposes of this example, you'll want to make sure that Optimizations are disabled and that Stack Frames are enabled in the compiler options. Create an OnCreate handler for the form by double-clicking the form and add the following code to your source file:
function AddOne(A: Integer): Integer;
begin
Result := A + 1; //set breakpoint here
end;
function AddUp(A, B: Integer): Integer;
begin
Result := A + AddOne(B);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Caption := IntToStr(AddUp(1, 2));
end;
Set a breakpoint in the “AddOne” function, run the program. This will trigger the breakpoint in AddOne.
When you hit the breakpoint, open up the CPU view (View | Debug Windows | CPU)
In the stack pane, locate the address indicated by the EBP register (the base pointer register). To do this, you can either manually locate the address given for EBP in the register pane or you can right click in the stack pane, choose “Goto Address” and enter “EBP” for the address to position to. You will see something that looks like this:
You'll want to refer to this screenshot when reading the following text. Note, too that the actual addresses you see if you follow these steps will probably differ.
In this screenshot, the EBP register contains the address $0012F550. You'll notice that stack location contains the address $0012F564 (this is highlighted in the screenshot). Also notice that right above the EBP location (at offset $0012F554), contains the address $0045EA8C. These two addresses contain the info needed to decipher the call stack. First we'll start with the second location (the one that contains the address $0045EA8C). Highlight that address and right click and choose “Follow | Near Code” (or use the shortcut key Ctrl+E). When you do this, you'll see that the disassembly pane highlights the instruction at offset $0045EA8C. This is the return site of the current function call. If you select that instruction in the disassembly pane and press Ctrl+V (View Source), you'll see the function (AddUp) that called the current function (AddOne). You've now just manually crawled up the call stack one location.
What if you want to go further up the call stack? Click back to the CPU view and again select the address indicated by EBP ($0012F550 in this case). Note again the address contained by that offset ($0012F564). Now right click on this address, and choose “Follow | Offset to Stack” (or use the shortcut key Ctrl+S). This takes you to the previous stack frame which contains the info on the next call stack location.
You should now have the stack location $0012F564 highlighted. The address contained at that location ($0012F584) is the location of the previous stack frame. The address contained just above this (at $0012F568) is again the return site of this function (this function is the AddUp function). In this case, the return site is $0045EACA. Again, use the the Ctrl+E shortcut to locate the return site in the disassembly pane. Once, there, use the Ctrl+V shortcut to look at the code for this return site. Note that it takes you to the TForm1.FormCreate method. You have now manually crawled the stack from the current location (in AddOne) to the AddUp function and then to the TForm1.FormCreate method. You can continue to do this all the way up the call stack.
If you're not building against Debug DCUs, you'll notice that the next stack frame (for TCustomForm.DoCreate) does not have debug info. Let's crawl up to that stack frame to illustrate how you can use this technique to view frames without debug info. Select the address at $0012F564 (which is $0012F584) and press Ctrl+S to select that stack location. The address contained just above this (at $0012F588) is $0043595C. With this location highlighted, press Ctrl+E to once again position the disassembly view to the return site of this method call. Note that in the dissassembly pane, you are now at a location that has no debug info (no source line info is shown). Another nice thing about the CPU view is that it can show you which method you are looking at even if there is no debug info for that location. In the disassembly pane, scroll up a page, and you will see that you are in the TCustomForm.DoCreate method.
You have now manually crawled the stack to locations with debug info and to a location without debug info. Of course, the best thing to do is to get Delphi 2005 so you don't have to manually crawl the stack. For those of you who are still using Delphi 7 (or earlier versions), you can use this technique to get a clearer picture of how your program arrived at its current location.
One more thing I'd like to point out. This technique of manually following the EBP chain in the stack pane of the CPU view can also be used if your stack gets corrupted (causing an Access Violation). A likely situation where this technique will come in handy is when the stack gets corrupted due to mismatched calling conventions. When this happens, EBP or ESP may not actually point to the top frame and top of stack as they should, and the call stack view itself will not show you any information to help you decipher how your program got to where it is. In this case, you can manually scan the stack pane in the CPU view looking for pairs of addresses that resemble the EBP chain. When you find a pair of addresses that look like it could be part of the EBP chain, use the Ctrl+E and Ctrl+S shortcuts to see if you can find the valid code that caused the crash. If all else fails, you can start at ESP and press Ctrl+E on each address up the stack until you see valid code in the disassembly pane.