One day I’m going to write a long, detailed post about an incredible tool called Managed Debugging Assistants (MDAs). But today is not that day. Instead, I would like to ignite your interest in MDAs by showing you how they immediately make obvious a non-trivial debugging scenario.
[…] if you run [this code] on a background thread and continue to do additional operations […] it will crash, sometimes with a null reference exception, sometimes with attempt to write to protected memory, etc.
There is a very subtle bug here, can you figure out what it is?
Luckily, this bug is
not subtle enough to escape automatic detection. But first things first.
I wrote a simple test case that is fairly similar in spirit to the bug
Oren is referring to. Then, I ran the executable and received the
An access violation, all right, but what’s the precise location that caused the problem? The DoWork method is a P/Invoke call, but is it responsible for the memory corruption?
Opening the application’s crash dump yields the following call stack for the crash:
NativeDll!DoWork+0x41 [nativedll.cpp @ 10]
0:000> u 00120b92
00120b92 0000 add byte ptr [eax],al
00120b94 0000 add byte ptr [eax],al
00120b96 0000 add byte ptr [eax],al
00120b98 9f lahf
00120b99 f79d6a286a00 neg dword ptr [ebp+6A286Ah]
00120b9f 1c01 sbb al,1
00120ba1 0000 add byte ptr [eax],al
00120ba3 0000 add byte ptr [eax],al
The function invoked by DoWork looks
like a random bunch of assembly instructions… It’s not immediately
evident from what we’ve seen so far where the culprit lies. However,
let’s see what happens if we run the application from within Visual
Studio, with the debugger attached:
Visual Studio hands us the error on a silver platter—or, to be specific, the CallbackOnCollectedDelegate MDA hands us this bug. This is an automatic diagnostic tool that pops up in the middle of your debugging session and shows you the error of your ways. It appears that the delegate must be kept alive until the underlying P/Invoke call is done—if the delegate is garbage collected and a call is attempted through it from unmanaged code, havoc ensues.
CallbackOnGarbageDelegate has many other MDA friends lurking in the Visual Studio “Exceptions” dialog. You can enable and disable MDAs during your Visual Studio debugging session, or in advance using a special configuration file.
As I said, I’m looking forward to writing a couple more posts on MDAs. Until then, you might find the following resources useful: