Process Shutdown is evil, as Raymond Chen recently blogged about in wonderful detail. This prompts me to comment about managed debugging and debuggee shutdown.
Here are some main problems that around debugging + exit-process.
1. You aren’t guaranteed to get managed debug events from shutdown. Your debugger is guaranteed to get the ExitProcess event, but the others aren’t guaranteed. For example, your debugger may not get UnloadModule, UnloadAssembly, AppDomainExit, ExitThread events, etc. A debugger can certainly infer these events if it gets a process exit.
This also came up on the forums here.
2. The asynchronous nature of Exit Process means that it can interrupt any debuggee activity. For example, a debugger may issue a func-eval and then expect a func-eval complete notification. But the debuggee may exit during the func-eval and thus the eval may not compete. This is mainly a pitfall that debugger authors have to be aware of. psuedo code like:
wait for func-eval complete
is buggy and may introduce a hang because the complete may never come.
3. The CLR Debugging Service’s Helper Thread may get killed off during shutdown before the process exits. This means there’s a window on shutdown where the process isn’t dead yet, but the managed debugging services can no longer inspect it.
Why aren’t exit events gauranteed?
Let me drill into point #1 above. The reasoning is that most managed debug events are sent by the debuggee fro user-mode code. For example, when the CLR is unloading an appdomain, it also notifies the debugger. If the debuggee process is rudely killed, then the CLR never has a chance to notify its ICorDebug counterpart in the debugger process.
ExitProcess is special because the ICorDebug (in the debugger process) does not require cooperation from the debuggee for this notification. Instead, it explicitly listens on the debuggee’s process handle. Once ICorDebug notices the debuggee exits, it can dispose all of the objects it handed out to the debugger (see CORDBG_E_OBJECT_NEUTERED) and dispatch an ExitProcess callback to the debugger.
An interesting design dilemma:
This poses a design question: if exit events aren’t guaranteed to come, should we even have them at all? If the events come “most of the time”, it allows a debugger author to have a false sense of security and rely on them. And then the debugger may break when they don’t come.This is related to the problem of what to do about a feature that only works 90% of the time.