One of the problems that confuses people from time to time here at work is that if you happen to hit a condition that trips the “invalid parameter” handler for VC8, and you’ve got a debugger attached to the process that fails, then the process mysteriously exits without giving the debugger a chance to inspect the condition of the program in question.
For those unfamiliar with the concept, the “invalid parameter” handler is a new addition to the Microsoft CRT, which kills the process if various invalid states are encountered. For example, dereferencing a bogus iterator in a release build might trip the invalid parameter handler if you’re lucky (if not, you might see random memory corruption, of course).
The reason why there is no debugger interaction here is that the default CRT invalid parameter handler (present in invarg.c if you’ve got the CRT source code handy) invokes UnhandledExceptionFilter in an attempt to (presumably) give the debugger a crack at the exception. Unfortunately, in reality, UnhandledExceptionFilter will just return immediately if a debugger is attached to the process, assuming that this will cause the standard SEH dispatcher logic to pass the event to the debugger. Because the default invalid parameter handler doesn’t really go through the SEH dispatcher but is in fact simply a direct call to UnhandledExceptionFilter, this results in no notification to the debugger whatsoever.
This counter-intuitive behavior can be more than a little bit confusing when you’re trying to debug a problem, since from the debugger, all you might see in a case like a bad iterator dereference would be this:
0:000:x86> g ntdll!NtTerminateProcess+0xa: 00000000`7759053a c3 ret
If we pull up a stack trace, then things become a bit more informative:
0:000:x86> k RetAddr ntdll32!ZwTerminateProcess+0x12 kernel32!TerminateProcess+0x20 MSVCR80!_invoke_watson+0xe6 MSVCR80!_invalid_parameter_noinfo+0xc TestApp!wmain+0x10 TestApp!__tmainCRTStartup+0x10f kernel32!BaseThreadInitThunk+0xe ntdll32!_RtlUserThreadStart+0x23
However, while we can get a stack trace for the thread that tripped the invalid parameter event in cases like this with a simple single threaded program, adding multiple threads will throw a wrench into the debuggability of this scenario. For example, with the following simple test program, we might see the following when running the process under the debugger after we continue the initial process breakpoint (this example is being run as a 32-bit program under Vista x64, though the same principle should apply elsewhere):
0:000:x86> g ntdll!RtlUserThreadStart: sub rsp,48h 0:000> k Call Site ntdll!RtlUserThreadStart
What happened? Well, the last thread in the process here happened to be the newly created thread instead of the thread that called TerminateProcess. To make matters worse, the other thread (which was the one that caused the actual problem) is already gone, killed by TerminateProcess, and its stack has been blown away. This means that we can’t just figure out what’s happened by asking for a stack trace of all threads in the process:
0:000> ~*k . 0 Id: 1888.1314 Suspend: -1 Unfrozen Call Site ntdll!RtlUserThreadStart
Unfortunately, this scenario is fairly common in practice, as most non-trivial programs use multiple threads for one reason or another. If nothing else, many OS-provided APIs internally create or make use of worker threads.
There is a way to make out useful information in a scenario like this, but it is unfortunately not easy to do after the fact, which means that you’ll need to have a debugger attached and at your disposal before the failure happens. The simplest way to catch the culprit red-handed here is to just breakpoint on ntdll!NtTerminateProcess. (A conditional breakpoint could be employed to check for NtCurrentProcess ((HANDLE)-1) in the first parameter if the process frequently calls TerminateProcess, but this is typically not the case and often it is sufficient to simply set a blind breakpoint on the routine.)
For example, in the case of the provided test program, we get much more useful results with the breakpoint in place:
0:000:x86> bp ntdll32!NtTerminateProcess 0:000:x86> g Breakpoint 0 hit ntdll32!ZwTerminateProcess: mov eax,29h 0:000:x86> k RetAddr ntdll32!ZwTerminateProcess kernel32!TerminateProcess+0x20 MSVCR80!_invoke_watson+0xe6 MSVCR80!_invalid_parameter_noinfo+0xc TestApp!wmain+0x2e TestApp!__tmainCRTStartup+0x10f kernel32!BaseThreadInitThunk+0xe ntdll32!_RtlUserThreadStart+0x23
That’s much more diagnosable than a stack trace for the completely wrong thread.
Note that from an error reporting perspective, it is possible to catch these errors by registering an invalid parameter handler (via _set_invalid_parameter_handler), which is rougly analogus to the mechanism one uses to register a custom handler for pure virtual function call failures.