Sign in to follow this  
Endar

stepping into destructors in VC++ express 2005

Recommended Posts

Endar    668
Maybe I'm wrong, but when you're debugging, and you come upon a delete statement for an object (pointer to an object) that has a defined destructor, and attempt to step into it, shouldn't it ... you know ... work? I just tried, on two of my projects, to run up to a 'delete' call and step into it, but it just skipped over the call. I am able to find the destructor and explicitly run up to the destructor, but it doesn't work when I attempt to step into a delete call. This is possible right? Tell me I haven't just been imagining it all this time.

Share this post


Link to post
Share on other sites
jpetrie    13101
"Standard?" Insofar as the behavior of a particular IDE's debugger can be called "standard," I suppose it is. You'll notice you can't step directly into a constructor from the "new foobar" statement as well.

The reason you can't step into these calls directly is because there's no information in the .pdb to get file/line information from -- because there is no C++ source file or, indeed, source code at all.

For non-trivial types (with constructors and/or destructors), a "call" to new or delete is more than just an invocation of a global operator new or delete function someplace. The compiler generates a bunch of code and injects it right there at the call site to call constructors or destructors at the appropriate point; there's no C++ source code to "step into" immediately (your actual C++ for your constructor/destructor is a couple jumps away yet), just machine code, so the IDE does the next best thing -- it steps over the call instead.

Examining the disassembly output for various examples of this situation can be very educational, I'd be happy to walk through an example or two if you aren't familiar enough with the practice.

Share this post


Link to post
Share on other sites
Endar    668
Quote:
Original post by jpetrie
Examining the disassembly output for various examples of this situation can be very educational, I'd be happy to walk through an example or two if you aren't familiar enough with the practice.


Yeah, definately. I'll never pass up an opportunity to learn.

Have you got some examples, or do you want to tell me how to scrounge some up? I assume it's no more difficult that getting the compiler to stop compiling at the assembly stage (even though I have no idea how to do that for VC++), and knowing where to look.

Share this post


Link to post
Share on other sites
hplus0603    11347
Bump for what?

Yes, it's a known and acknowledged bug in Visual Studio .NET, all versions, that stepping into destructors doesn't work. You have to set a breakpoint inside the destructor. I believe the thread already said as much?

Share this post


Link to post
Share on other sites
Endar    668
It was a bump for jpetrie to come back and walk me through a couple of destructor calls, like he offered.

I mean, since he offered, we might as well do it here instead of starting a new thread.

Share this post


Link to post
Share on other sites
jpetrie    13101
I don't use VC++ at work, so I had to defer my follow up until after I got home, sorry it took so long.

Anyway, consider the following simple example:

struct foo
{
foo()
{
}
~foo()
{
}
};

int main()
{
foo *p = new foo;
delete p;

int *q = new int;
delete q;
}


Place breakpoints on all four lines of main() and debug the program. Once you hit a breakpoint, right-click in the editor window and pick "Go to disassembly." This will show you the breakdown of the source lines and their "matching" assembly.

It looks something like:

foo *p = new foo;
004113FD push 1
004113FF call operator new (411186h)
00411404 add esp,4
00411407 mov dword ptr [ebp-11Ch],eax
0041140D mov dword ptr [ebp-4],0
00411414 cmp dword ptr [ebp-11Ch],0
0041141B je main+70h (411430h)
0041141D mov ecx,dword ptr [ebp-11Ch]
00411423 call foo::foo (411109h)
00411428 mov dword ptr [ebp-130h],eax
0041142E jmp main+7Ah (41143Ah)
00411430 mov dword ptr [ebp-130h],0
0041143A mov eax,dword ptr [ebp-130h]
00411440 mov dword ptr [ebp-128h],eax
00411446 mov dword ptr [ebp-4],0FFFFFFFFh
0041144D mov ecx,dword ptr [ebp-128h]
00411453 mov dword ptr [ebp-14h],ecx

delete p;
00411456 mov eax,dword ptr [ebp-14h]
00411459 mov dword ptr [ebp-104h],eax
0041145F mov ecx,dword ptr [ebp-104h]
00411465 mov dword ptr [ebp-110h],ecx
0041146B cmp dword ptr [ebp-110h],0
00411472 je main+0C9h (411489h)
00411474 push 1
00411476 mov ecx,dword ptr [ebp-110h]
0041147C call foo::`scalar deleting destructor' (4110B9h)
00411481 mov dword ptr [ebp-130h],eax
00411487 jmp main+0D3h (411493h)
00411489 mov dword ptr [ebp-130h],0

int *q = new int;
00411493 push 4
00411495 call operator new (411186h)
0041149A add esp,4
0041149D mov dword ptr [ebp-0F8h],eax
004114A3 mov eax,dword ptr [ebp-0F8h]
004114A9 mov dword ptr [ebp-20h],eax

delete q;
004114AC mov eax,dword ptr [ebp-20h]
004114AF mov dword ptr [ebp-0ECh],eax
004114B5 mov ecx,dword ptr [ebp-0ECh]
004114BB push ecx
004114BC call operator delete (411091h)
004114C1 add esp,4


Some observations: both new-expressions can be stepped into, the "delete p" cannot (as expected), and the "delete q" can. It's the call instructions that are the important parts, here. Trimming out the other instructions, then, leaves us with:

foo *p = new foo;
004113FF call operator new (411186h)
00411423 call foo::foo (411109h)

delete p;
0041147C call foo::`scalar deleting destructor' (4110B9h)

int *q = new int;
00411495 call operator new (411186h)

delete q;
004114BC call operator delete (411091h)


Notice how the compiler has injected the constructor call for foo (int obviously has no constructors or destructors). Also notice that "delete p" doesn't result in a call to operater delete. This is the root cause of our "problem."

While you are in disassembly mode, step and step-into work on a per-instruction level (very useful). You can step-into the call to that weird "foo::`scalar deleting destructor'" thing -- you'll most likely be taken to some random-looking collection of jmp instructions, this is not relevant to our discussion so step-into the jmp instruction, and you'll end up here:

foo::`scalar deleting destructor':
00411550 push ebp
00411551 mov ebp,esp
00411553 sub esp,0CCh
00411559 push ebx
0041155A push esi
0041155B push edi
0041155C push ecx
0041155D lea edi,[ebp-0CCh]
00411563 mov ecx,33h
00411568 mov eax,0CCCCCCCCh
0041156D rep stos dword ptr es:[edi]
0041156F pop ecx
00411570 mov dword ptr [ebp-8],ecx
00411573 mov ecx,dword ptr [this]
00411576 call foo::~foo (41119Fh)
0041157B mov eax,dword ptr [ebp+8]
0041157E and eax,1
00411581 je foo::`scalar deleting destructor'+3Fh (41158Fh)
00411583 mov eax,dword ptr [this]
00411586 push eax
00411587 call operator delete (411091h)
0041158C add esp,4
0041158F mov eax,dword ptr [this]
00411592 pop edi
00411593 pop esi
00411594 pop ebx
00411595 add esp,0CCh
0041159B cmp ebp,esp
0041159D call @ILT+315(__RTC_CheckEsp) (411140h)
004115A2 mov esp,ebp
004115A4 pop ebp
004115A5 ret 4


Note the lack of C++ source line annotations -- this is purely compiler-generated code here, and that's why you can't step into it. The calls to functions that there's code for -- operater delete and foo::~foo are buried in this code.

I don't have inside knowledge as to how the debugger is written, but I imagine when doing source-level debugging the debugger will simply look up a block of information in the .pdb related to the function it is about to call (when doing a step-into). That block of information would include, if available, the file/line information for the C++ source matching that function. As we've seen, the call generated by a delete-expression for a class type doesn't have matching file/line information, so the debugger just steps over instead.

It may be possible to implement the ability to step into destructors from the delete expression, however since its a long-standing issue I don't think its quite as easy as it may seem from where we stand. Furthermore, I think the larger reason why VC++ doesn't implement that feature is that it wouldn't be strictly "correct" to do so, and a debugger should generally strive to be as correct as it possibly can.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this