Diagnosing Timer Callback Crashes During .NET Process Shutdown
Exception code c0020001 typically indicates RPC_E_SYS_CALL_FAILED, though this description rarely clarifies the actual failure mechanism. When examining a memory dump where an application terminates unexpected during shutdown, the initial investigation focuses on the exception context.
0:040> !analyze -v
CONTEXT: (.ecxr)
eax=0afdf5dc ebx=0698ade8 ecx=00000001 edx=00000000 esi=0698ade8 edi=7eec0000
eip=7753c5af esp=0afdf5dc ebp=0afdf62c iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
KERNELBASE!RaiseException+0x58:
7753c5af c9 leave
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 7753c5af (KERNELBASE!RaiseException+0x00000058)
ExceptionCode: c0020001
ExceptionFlags: 00000001
NumberParameters: 1
Parameter[0]: 8007042b
PROCESS_NAME: application.exe
The stack trace reveals the execution path leading to the fault:
0:040> k
# ChildEBP RetAddr
00 0afdf62c 70e75e0b KERNELBASE!RaiseException+0x58
01 0afdf648 70f63bf5 clr!COMPlusThrowBoot+0x1a
02 0afdf654 70b6f1da clr!UMThunkStubRareDisableWorker+0x25
03 0afdf67c 77a9571e clr!UMThunkStubRareDisable+0x9
04 0afdf6bc 77a80f0b ntdll!RtlpTpTimerCallback+0x7a
05 0afdf6e0 77a809b1 ntdll!TppTimerpExecuteCallback+0x10f
06 0afdf830 75c4344d ntdll!TppWorkerThread+0x562
07 0afdf83c 77a69802 kernel32!BaseThreadInitThunk+0xe
08 0afdf87c 77a697d5 ntdll!__RtlUserThreadStart+0x70
The thread originates from the Windows Thread Pool timer mechanism, attempting to transition into managed code through an unmanaged thunk. The CLR explicitly raises an exception within UMThunkStubRareDisableWorker when detecting an invalid runtime state.
Examining the CLR source logic explains this behavior:
extern "C" VOID __stdcall UMThunkStubRareDisableWorker(
Thread* currentThread,
UMEntryThunk* entryThunk,
Frame* transitionFrame)
{
// Validate runtime availability before managed code execution
if (!IsRuntimeActive())
{
currentThread->m_fPreemptiveGCDisabled = 0;
COMPlusThrowBoot(E_PROCESS_SHUTDOWN_REENTRY);
}
}
BOOL IsRuntimeActive(BOOL userErrorContext, HINSTANCE instance)
{
// Block execution during runtime termination
if (g_fForbidEnterEE == TRUE)
return FALSE;
// Prevent execution during finalization phase unless on finalizer thread
if ((g_fEEShutDown & ShutDown_Finalize2) &&
!GCHeap::GetGCHeap()->IsCurrentThreadFinalizer())
return FALSE;
// Verify essential runtime objects exist
if (g_pPreallocatedOutOfMemoryException == NULL)
return FALSE;
return TRUE;
}
The variable g_fForbidEnterEE controls access to the Execution Engine. Verification within the dump confirms the runtime is shutting down:
0:040> dp clr!g_fForbidEnterEE L1
712a2684 00000001
A value of 1 indicates the CLR has entered shutdown, typically triggered by Environment.Exit or process termination. The main thread stack confirms this state:
0:000> k
00 0028d3b0 77549cd4 ntdll!NtQueryAttributesFile+0x12
...
39 0028ebc0 70d2684b clr!WaitForEndOfShutdown_OneIteration+0x81
3a 0028ebc8 70d300e2 clr!WaitForEndOfShutdown+0x1b
3b 0028ec08 70d1329e clr!EEShutDown+0xad
3c 0028ec14 70d132fb clr!HandleExitProcessHelper+0x4d
3d 0028ec70 70d2ff99 clr!EEPolicy::HandleExitProcess+0x50
3e 0028ec70 7115af3b clr!ForceEEShutdown+0x31
3f 0028ec70 702a9faf clr!SystemNative::Exit+0x4f
To identify the specific managed method attempting execution, inspect the UMEntryThunk structure:
class UMEntryThunk
{
private:
const BYTE* m_pManagedTarget; // Managed method entry point
PTR_MethodDesc m_pMD; // Method descriptor for profiling
};
Locating this structure in the crash dump:
0:040> kb 5
# ChildEBP RetAddr Args to Child
00 0afdf62c 70e75e0b c0020001 00000001 00000001 KERNELBASE!RaiseException+0x58
01 0afdf648 70f63bf5 006e0fe0 0afdf67c 70b6f1da clr!COMPlusThrowBoot+0x1a
02 0afdf654 70b6f1da 0698ade8 00580a38 0698ade8 clr!UMThunkStubRareDisableWorker+0x25
0:040> dp 00580a38 L2
00580a38 00386580 008f2eb8
0:040> !U 00386580
Unmanaged code
00386580 e9ab390000 jmp 00389f30
0:040> !ip2md 00389f30
MethodDesc: 0018af94
Method Name: Application.TimerCallback(IntPtr, Boolean)
Class: 00435a7c
MethodTable: 0018afd8
mdToken: 06000034
Module: 0018a6a8
IsJitted: yes
CodeAddr: 00389f30
The managed method represents a callback registered with the Windows Thread Pool timer infrastructure. The failure occurs because this timer fires after the CLR begins shutdown but before the process terminates.
The resolution requires explicit cleanup of timer resources before initiating process exit. Implement IDisposable pattern for components hosting timers, ensuring Dispose calls CloseHandle or equivalent on the timer queue, or use managed Timer classes that properly synchronize with the CLR shutdown sequence.
Note that the internal parameter 8007042b (ERROR_PROCESS_ABORTED) is hardcoded in the CLR and does not indicate a system corruption requiring repair utilities. The assembly confirms this is a constant:
0:000> ub 70f63bf5
clr!UMThunkStubRareDisableWorker+0x7:
70f63bd7 c9 leave
70f63bd8 e8d47fc3ff call clr!CanRunManagedCode (70b9bbb1)
70f63bdd 8b7508 mov esi,dword ptr [ebp+8]
70f63be0 85c0 test eax,eax
70f63be2 7511 jne clr!UMThunkStubRareDisableWorker+0x25
70f63be4 b92b040780 mov ecx,8007042Bh ; Hardcoded constant
70f63be9 c7460800000000 mov dword ptr [esi+8],0
70f63bf0 e8f721f1ff call clr!COMPlusThrowBoot