A failure report came in to the shell team
because Explorer crashed at shutdown in what the debugger
reported as
unloaded_themeui.dll
.
Time to dig in.
ntdll!RtlpCallVectoredHandlers+0xeb ntdll!RtlDispatchException+0x81 ntdll!KiUserExceptionDispatch+0x50 <Unloaded_themeui.dll>+0x2bbfbd 0x1fbdebe0 0x1fbdebc0 0x2357ef80 <Unloaded_themeui.dll>+0x2bbfbd: 00007ff8`e384bfbd ?? ???
Yup, there’s nothing loaded there all right. But let’s see what was loaded there before.
0:001> lm ... Unloaded modules: ... 00007ff8`e3590000 00007ff8`e385d000 themeui.dll 00007ff8`e3840000 00007ff8`e385d000 abcdefg.dll
So there were two DLLs that used to be loaded at the address that crashed. Which could it be?
0:001> !reload /unl themeui.dll 0:001> u 00007ff8`e384bfbd themeui!ext-ms-win-com-ole32-l1-1-1_NULL_THUNK_DATA_DLA <PERF> (themeui+0x2bffdd): 00007ff8`e384bfbd 40 ???
Well, that doesn’t look like code. How about abcdefg?
0:001> !reload /unl abcdefg.dll 0:001> u 00007ff8`e384bfbd-80 ... abcdefg!AbcdefgImageList::GetClassImageList+0x1f 00007ff8`e384bfb7 ff1593a20000 call [abcdefg!_imp_SetupDiGetClassImageList] 00007ff8`e384bfbd 85c0 test eax,eax
That looks a lot more promising.
What appears to have happened is that abcdefg.dll
called SetupDiGetClassImageList
,
and while the call was in progress, the DLL got unloaded.
When the call to
SetupDiGetClassImageList
finally returned, it returned to an unloaded DLL,
which is the source of the crash.
Reconstructing the stack
revealed a chain of calls that made sense
in the context of abcdefg.dll
,
so this diagnosis is probably correct.
(I’ve anonymized the name of the other DLL to protect the guilty.)
What happened is that during Explorer startup,
abcdefg.dll
registered a wait
with the thread pool on an event,
and at shutdown it unregisters the wait.
But it unregisters with
the UnregisterWait
function.
If a callback is running at the time the wait is unregistered,
the function returns ERROR_
,
but nobody checks.
The shutdown code proceeds to unload
abcdefg.dll
,
and then we are left executing code that was freed.
The code in
abcdefg.dll
needs to handle the case
where the callback is still running at the time the
wait is unregistered.
You can use
the UnregisterWaitEx
function,
which lets you pass an event that is set when the callback completes,
or pass INVALID_
to wait synchronously
for the callback to complete before returning.