Over a million developers have joined DZone.

Pure Virtual Function Call (Destructor Madness)

DZone's Guide to

Pure Virtual Function Call (Destructor Madness)

· ·
Free Resource

How badly, on a scale of 1 to 10, do you like this error message?


Not the friendliest one to see, I bet, when you’re trying to make your way through a day without calling any pure virtual functions… Well, a pure virtual function call is not a rare thing to see in the C++ world, and there are numerous reasons for something like this to happen, not the least of which being calling a pure virtual method from the base class’ constructor. In this post we’re going to see something slightly less traditional. Let’s try to verify what’s going on here.

First, I captured a dump of the application at the moment this dialog was shown. On Vista and above it’s really easy using Task Manager; on earlier OS versions you can use cdb, ntsd, WinDbg, ADPlus—whatever suits you best.

Next, I opened the dump in my trusty old friend WinDbg. Just a couple of threads here:

0:000> ~*
. 0 Id: 1b40.c98 Suspend: 1 Teb: 7efdd000 Unfrozen
Start: DestructorsAndVirtualMethods!ILT+420(_mainCRTStartup) (013b11a9)
Priority: 0 Priority class: 32 Affinity: 3
1 Id: 1b40.140c Suspend: 1 Teb: 7efda000 Unfrozen
Start: DestructorsAndVirtualMethods!ILT+265(?thread_startthreadCGKPAXZ) (013b110e)
Priority: 0 Priority class: 32 Affinity: 3

How about these two threads, then? Well, the main thread most certainly didn’t show me that nasty message box:

0:000> k
ChildEBP RetAddr
0020f9c8 757b0816 ntdll!NtWaitForSingleObject+0x15
0020fa34 76031184 KERNELBASE!WaitForSingleObjectEx+0x98
0020fa4c 76031138 kernel32!WaitForSingleObjectExImplementation+0x75
0020fa60 013b18ad kernel32!WaitForSingleObject+0x12
0020fb48 013b184b DestructorsAndVirtualMethods!thread::~thread+0x3d
0020fc28 013b15c5 DestructorsAndVirtualMethods!my_thread::~my_thread+0x2b
0020fd20 013b2c0f DestructorsAndVirtualMethods!main+0x65
0020fd70 013b2a3f DestructorsAndVirtualMethods!__tmainCRTStartup+0x1bf
0020fd78 76033677 DestructorsAndVirtualMethods!mainCRTStartup+0xf
0020fd84 77a19d72 kernel32!BaseThreadInitThunk+0xe
0020fdc4 77a19d45 ntdll!__RtlUserThreadStart+0x70
0020fddc 00000000 ntdll!_RtlUserThreadStart+0x1b

(It’s in the middle of my_thread’s destructor, which called thread’s destructor, which is waiting for something.) But the second thread most certainly did:

0:001> k
ChildEBP RetAddr
009d548c 76442674 user32!NtUserWaitMessage+0x15
009d54c8 7644288a user32!DialogBox2+0x222
009d54f4 7647f8d0 user32!InternalDialogBox+0xe5
009d55a8 7647fbac user32!SoftModalMessageBox+0x757
009d5700 7647fcaf user32!MessageBoxWorker+0x269
009d576c 7647fea5 user32!MessageBoxTimeoutW+0x52
009d578c 7647fee7 user32!MessageBoxExW+0x1b
009d57a8 5272a58c user32!MessageBoxW+0x18
009d5800 52724a2c MSVCR100D!__crtMessageBoxW+0x20c
009d7a6c 5272bcf0 MSVCR100D!__crtMessageWindowW+0x3bc
009dfb0c 52724662 MSVCR100D!_VCrtDbgReportW+0x8e0
009dfb2c 5272461b MSVCR100D!_CrtDbgReportWV+0x22
009dfb54 526589ee MSVCR100D!_CrtDbgReportW+0x2b
009dfd90 527290c5 MSVCR100D!_NMSG_WRITE+0x5e]
009dfda0 013b1703 MSVCR100D!_purecall+0x25
009dfe80 76033677 DestructorsAndVirtualMethods!thread::thread_start+0x33
009dfe8c 77a19d72 kernel32!BaseThreadInitThunk+0xe
009dfecc 77a19d45 ntdll!__RtlUserThreadStart+0x70
009dfee4 00000000 ntdll!_RtlUserThreadStart+0x1b

Hmm. That’s too much of a coincidence—the main thread is inside the thread class destructor, and the secondary thread is inside the thread_start method… That method performs the pure virtual function call. Could the two threads be working on the same object?

Here’s the offending disassembly of the secondary thread:

013b16fb 8b4df8          mov     ecx,dword ptr [ebp-8]
013b16fe 8b4204 mov eax,dword ptr [edx+4]
013b1701 ffd0 call eax
013b1703 3bf4 cmp esi,esp

So we should focus on EBP-8, and in there we find:

0:001> kn
# ChildEBP RetAddr
00 009d548c 76442674 user32!NtUserWaitMessage+0x15
01 009d54c8 7644288a user32!DialogBox2+0x222
02 009d54f4 7647f8d0 user32!InternalDialogBox+0xe5
03 009d55a8 7647fbac user32!SoftModalMessageBox+0x757
04 009d5700 7647fcaf user32!MessageBoxWorker+0x269
05 009d576c 7647fea5 user32!MessageBoxTimeoutW+0x52
06 009d578c 7647fee7 user32!MessageBoxExW+0x1b
07 009d57a8 5272a58c user32!MessageBoxW+0x18
08 009d5800 52724a2c MSVCR100D!__crtMessageBoxW+0x20c
09 009d7a6c 5272bcf0 MSVCR100D!__crtMessageWindowW+0x3bc
0a 009dfb0c 52724662 MSVCR100D!_VCrtDbgReportW+0x8e0
0b 009dfb2c 5272461b MSVCR100D!_CrtDbgReportWV+0x22
0c 009dfb54 526589ee MSVCR100D!_CrtDbgReportW+0x2b
0d 009dfd90 527290c5 MSVCR100D!_NMSG_WRITE+0x5e
0e 009dfda0 013b1703 MSVCR100D!_purecall+0x25
0f 009dfe80 76033677 DestructorsAndVirtualMethods!thread::thread_start+0x33
10 009dfe8c 77a19d72 kernel32!BaseThreadInitThunk+0xe
11 009dfecc 77a19d45 ntdll!__RtlUserThreadStart+0x70
12 009dfee4 00000000 ntdll!_RtlUserThreadStart+0x1b

0:001> .frame /r 0f
0f 009dfe80 76033677 DestructorsAndVirtualMethods!thread::thread_start+0x33
eax=00000000 ebx=0020fd04 ecx=00000000 edx=00000000 esi=009dfda8 edi=009dfe80
eip=013b1703 esp=009dfda8 ebp=009dfe80 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
013b1703 3bf4 cmp esi,esp

0:001> dd 009dfe80-0x8 L1
009dfe78 0020fd04

This guy is a thread object (actually, a my_thread object), so let’s take a look at it:

0:001> dt DestructorsAndVirtualMethods!thread 0020fd04
+0x000 __VFN_table : 0x013b785c
+0x004 h_ : 0x0000002c
+0x008 context_ : (null)

And what’s in that table over at offset 4?

0:001> dd 0x013b785c+0x4 L1
013b7860 013b11d1

0:001> ln 013b11d1
(013b11d1) DestructorsAndVirtualMethods!ILT+460(__purecall) | (013b11d6) DestructorsAndVirtualMethods!ILT+465(__RTC_CheckEsp)
Exact matches:

Yikes. That’s __purecall, our good friend. So how come we have a screwed up vtable in a perfectly legitimate object method? Let’s take a look at the main thread:

0:000> u DestructorsAndVirtualMethods!thread::~thread L20
013b1870 55 push ebp
013b1871 8bec mov ebp,esp
013b1873 81eccc000000 sub esp,0CCh
013b1879 53 push ebx
013b187a 56 push esi
013b187b 57 push edi
013b187c 51 push ecx
013b187d 8dbd34ffffff lea edi,[ebp-0CCh]
013b1883 b933000000 mov ecx,33h
013b1888 b8cccccccc mov eax,0CCCCCCCCh
013b188d f3ab rep stos dword ptr es:[edi]
013b188f 59 pop ecx
013b1890 894df8 mov dword ptr [ebp-8],ecx
013b1893 8b45f8 mov eax,dword ptr [ebp-8]
013b1896 c7005c783b01 mov dword ptr [eax],offset DestructorsAndVirtualMethods!thread::`vftable' (013b785c)
013b189c 8bf4 mov esi,esp
013b189e 6aff push 0FFFFFFFFh
013b18a0 8b45f8 mov eax,dword ptr [ebp-8]
013b18a3 8b4804 mov ecx,dword ptr [eax+4]
013b18a6 51 push ecx
013b18a7 ff1550b23b01 call dword ptr [DestructorsAndVirtualMethods!_imp__WaitForSingleObject (013bb250)]
013b18ad 3bf4 cmp esi,esp

Do you see that huge prologue? One of the things it does is restore the vptr to the base class’ vtable! So while the destructor is running, the object has the base vptr which contains the pure virtual method. Voila.

If I lost you somewhere in the debugging spew above, here’s a quick recap:

  • Thread 0 enters the object’s destructor. The derived destructor runs (in this case, it’s empty) and then the base destructor runs.
  • The base destructor restores the vptr to point to the base class’ vtable, and enters a wait.
  • Thread 1 attempts to call a virtual method that is pure virtual in the base class and overridden in the derived class. The vtable belongs to the base class, so there’s a __purecall entry in it that shows us the beautiful message box we started with.

A completely different approach is what you’d do when you have the debugger and the source code handy. In that case, simply set a data breakpoint in Visual Studio (or WinDbg) on the address of the object’s vptr:


And then let the program run until it stops in the debugger—inspecting the disassembly immediately shows the same result we’ve seen in WinDbg:


Which gives away the culprit, again—the base class destructor restoring the base class vtable prior to entering the destructor code.



Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}