Welcome to MSDN Blogs Sign
in | Join
| Help

not actually a .NET blog

The Old New
Thing

This Blog

Syndication

RSS 2.0

Atom 1.0

Search

Go

Tags

A modest proposal

Code

Dead computers

email

Excursions into East Asian pop music

Exploiting the inattentive

History

Microspeak

News flash

No good deed goes unpunished

Non-Computer

Other

Reading the fine print

The social skills of a thermonuclear device

The wisdom of seventh graders

Things I've written that have amused other people

Time

Tips/Support

When people ask for security holes as features

News

Holy cow, I wrote
a book!

Archives

April
2007 (33)

March
2007 (39)

February
2007 (39)

January
2007 (35)

December
2006 (32)

November
2006 (28)

October
2006 (36)

September
2006 (37)

August
2006 (39)

July 2006
(39)

June 2006
(34)

May 2006
(35)

April
2006 (34)

March
2006 (41)

February
2006 (34)

January
2006 (36)

December
2005 (33)

November
2005 (40)

October
2005 (41)

September
2005 (39)

August
2005 (42)

July 2005
(37)

June 2005
(35)

May 2005
(25)

April
2005 (26)

March
2005 (33)

February
2005 (22)

January
2005 (27)

December
2004 (34)

November
2004 (27)

October
2004 (25)

September
2004 (30)

August
2004 (29)

July 2004
(33)

June 2004
(46)

May 2004
(47)

April
2004 (38)

March
2004 (52)

February
2004 (33)

January
2004 (44)

December
2003 (45)

November
2003 (30)

October
2003 (37)

September
2003 (38)

August
2003 (29)

July 2003
(10)

Basics

RSS 2.0

Atom 1.0

Subscribe via
email

Ground
Rules

Suggestion
Box

Win32
Platform SDK

Contact Me

Blogroll

The Daily WTF

Michael Kaplan

Jensen Harris

Lockergnome

Techdirt

TV Squad

Categories

Code

History

Non-Computer

Other (Computer)

Tips/Support

Miniseries

News for dummies

Swedish

German

Identifying an object whose underlying DLL has been unloaded

Okay, so I gave it away in the title, but follow along anyway.

Your program chugs along and then suddenly it crashes like this:

eax=06bad8e8 ebx=00000000 ecx=1e1cfdf0 edx=00000000 esi=06b9a680 edi=01812950
eip=1180ab57 esp=001178b4 ebp=001178c0 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
ABC!FunctionX+0x1f:
1180ab57 ff5108 call dword ptr [ecx+8] ds:0023:1e1cfdf8=????????
0:000>>

Instantly you recognize the following:

This is a virtual method call. (Call indirect through register plus
offset.) — Very high confidence.

The vtable is in ecx. (That is the base register of the
indirect call.) — Very high confidence.

The underlying DLL for this object has been unloaded. (The memory that
contains the vtable is not valid and its address is consistent with once
having been in valid code.) — High confidence.

This is a IUnknown::Release call. (Release is
the third function of IUnknown and therefore resides at offset 8
on x86.) — High confidence.

Of course, all of the above "instant conclusions" are merely "highly-educated
guesses", but life is full of highly-educated guesses. (Every morning, I guess
that my plates are still in the cupboard.)

Let's run with our theory that the object was in an unloaded DLL and look for
confirmation.

0:000> lm
start end module name
...
Unloaded modules:
10340000 10348000 DEF.DLL
1e1c0000 1e781000 GHI.DLL
25a90000 25a96000 JKL.DLL
0:000>

Aha, our presumed vtable address lies right inside the address space where
GHI.DLL used to be loaded. Let's see what used to be loaded at that
address. For this, I borrow a trick from Doron, namely loading a
module as a dump file. This "virtually loads" the library so you can poke
around inside it.

C:\Program Files\ABC> ntsd -z GHI.DLL

Microsoft (R) Windows Debugger
Copyright (c) Microsoft Corporation. All rights reserved.

Loading Dump File [C:\Program Files\ABC\GHI.DLL]
...
ModLoad: 15800000 15dc1000 C:\Program Files\ABC\GHI.DLL
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=15807366 esp=00000000 ebp=00000000 iopl=0 nv up di pl nz na pe nc
cs=0000 ss=0000 ds=0000 es=0000 fs=0000 gs=0000 efl=00000000
GHI!_DllMainCRTStartup:
15807366 8bff mov edi,edi
0:000>

That module-load notification tells you where the DLL got virtually-loaded;
in our case, it got loaded to 0x15800000. This isn't the same address as it was
in our crashed process, so we'll have to do some mental arithmetic to account
for the discrepancy.

Going back to the original register dump, we see that our putative vtable is
at ecx=1e1cfdf0 relative to the load address 1e1c0000.
Since our DLL-loaded-as-a-dump-file was loaded at 0x1580000 we need
to adjust the address to be relative to the new location.

// working with the second copy of ntsd
0:000> ln 0x1580fdf0
(1580fdf0) GHI!CAlphaStream::`vftable'

That magic number 0x1580fdf0 is just the result of some mental
arithmetic. First:

0x1e1cfdf0

-0x1e1c0000

0x0000fdf0

This is the address of the vtable in the crashed process relative to the load
address of the DLL in the crashed process. Next:

0x15800000

+0x0000fdf0

0x1580fdf0

This is the address of the vtable in the DLL-loaded-as-a-dump-file relative
to the load address of the DLL in the DLL-loaded-as-a-dump-file. The math really
isn't that hard, as you can see, since a lot of things cancel out. This happens
a lot.

When we asked the debugger to tell us what symbol is nearest to that address,
we hit the jackpot: It is exactly a vtable for the CAlphaStream
object. This confirms our original theory. We can even confirm the
IUnknown::Release theory by dumping the vtable.

0:000> dds 1580fdf0
1580fdf0 159234b3 GHI!CAlphaStream::QueryInterface
1580fdf4 15810539 GHI!CBetaState::AddRef
1580fdf8 15923cfc GHI!CAlphaStream::Release
1580fdfc 15923d30 GHI!CAlphaStream::Read
...

Yup, that's a CAlphaStream vtable all right.

Since I'm not familiar with the GHI.DLL file, let's ask the
debugger where the source code is so we can take a closer look:

0:000> .lines
Line number information will be loaded
0:000> dds 1580fdf0
1580fdf0 159234b3 GHI!CAlphaStream::QueryInterface
[c:\dev\fabricam\synergy\proactive\winwin.cpp @ 2624]
1580fdf4 15810539 GHI!CBetaState::AddRef
[c:\dev\fabricam\leverage\paradigm\initiative.cpp @ 427]
1580fdf8 15923cfc GHI!CAlphaStream::Release
[c:\dev\fabricam\synergy\proactive\winwin.cpp @ 2638]
1580fdfc 15923d30 GHI!CAlphaStream::Read
[c:\dev\fabricam\synergy\proactive\winwin.cpp @ 2649]

Now that we know where the source code to CAlphaStream is, we
can hop on over to take a quick peek and confirm that, oh look, the object
doesn't increment the DLL object count when it is constructed (or decrement it
when it is destructed). As a result, when COM calls
DllCanUnloadNow, the GHI.DLL says, "Sure, go ahead!"
The DLL is unloaded even though ABC still has a reference to it,
and then when ABC goes to release that reference, we crash because
GHI is already gone.

After I wrote this up, I discovered that Tony Schreiner went through pretty
much the
same exercise with a third-party Internet Explorer toolbar, except he had
the extra bonus challenge of not having source code for the plug-in!

Published Wednesday, April 25, 2007 7:00 AM by oldnewthing

Filed under:
Other

Comment Notification

If you would like to receive an email when updates are made to this post,
please register here

Subscribe to this post's comments using RSS

Comments

#
re: Identifying an object whose underlying DLL has been unloaded

Wednesday, April 25,
2007 11:09 AM by richard

Are you running OS/2?

My copy of ntsd says that the -z option is "reserved for OS/2
debugging".

#
re: Identifying an object whose underlying DLL has been unloaded

Wednesday, April 25,
2007 11:35 AM by Rhomboid

Rather than doing the address calculation manually, couldn't you run rebase
on a copy of GHI.DLL so that it loads at the same place it did in the
app?

#
re: Identifying an object whose underlying DLL has been unloaded

Wednesday, April 25,
2007 12:04 PM by James Schend

Richard...

I checked the version of NTSD that ships with Windows XP SP2. -z is
documented as:

-z <CrashDmpFile> specifies the name of a crash dump file to debug

-zp <CrashPageFile> specifies the name of a page.dmp file

Are you using the Windows 2000 version? If it was reserved for OS/2, they
might have recycled it for XP.

#
re: Identifying an object whose underlying DLL has been unloaded

Wednesday, April 25,
2007 12:13 PM by richard

Yes, I am using Windows 2000. I realized after I posted that I had omitted
some vital information (such as the version of the OS and tools I was
running).

#
re: Identifying an object whose underlying DLL has been unloaded

Wednesday, April 25,
2007 12:22 PM by Mark

Often seen in C++ apps when you have a global COM smart pointer, so the
destructor tries calling Release on the COM object after main has completed, and
CoUninitialize has been called.

#
re: Identifying an object whose underlying DLL has been unloaded

Wednesday, April 25,
2007 1:14 PM by Pavel Lebedinsky

I think you can also make debugger believe that GHI.DLL is still loaded by
doing this:

0:000> ? 1e781000 - 1e1c0000

Evaluate expression: 6033408 = 005c1000

0:000> .reload GHI.DLL=1e1c0000,005c1000

This is convenient when you need to translate multiple addresses (such as
when there is a stack trace with several return addresses from the unloaded DLL
on the stack).

(Richard/James - ntsd from system32 is very old and doesn't have any
extensions. You should download the latest version from http://www.microsoft.com/whdc/DevTools/Debugging/default.mspx).

Leave a Comment

Title
(required)*

Name required*

Your URL

Comments
(required)*

Remember Me?