CLR SPY and Customer Debug Probes: The Object Not Kept Alive and Buffer Overrun Probes (A Quiz)
The Object Not Kept Alive and Buffer Overrun probes are unlike any other CDPs, because they do not output any messages to report bugs in your code. Instead, they change general CLR behavior with the goal of forcing non-deterministic bugs that can be almost impossible to reproduce into bugs that will happen every time you run your application. As a developer or tester, that's obviously a good thing. The lack of a specific probe message might make such bugs somewhat harder to diagnose, but once you understand the probes' behavior, you can learn to be successful with them.
The Object Not Kept Alive probe forces a garbage collection (then waits at most a second for pending finalizers) immediately before every transition from managed to unmanaged code. This is done to surface problems that could occur if a managed object happens to be collected during the small time window when transitioning to unmanaged code. I find this to be one of the most useful of all the CDPs.
The Buffer Overrun probe forces a garbage collection (then waits at most a second for pending finalizers) immediately after every transition from managed to unmanaged code. The intent of this is to catch buffer overruns, which unmanaged code has the power to cause. (Managed code does too -- even verifiable managed code if it uses methods of the Marshal class, which require unmanaged code permission.) If unmanaged code writes into memory that it's not supposed to, it could potentially corrupt the GC heap. The next time the garbage collector compresses the heap (as part of a collection), it's likely to run into an access violation. So by forcing a collection after every call into unmanaged code, the corruption can hopefully be noticed at the source of the problem.
Due to the large number of collections forced by these probes, they will significantly slow down most applications.
Now consider the following harmless-looking C# code that uses the Microsoft.Win32.RegistryKey class:
// This code has a bug!
public static void Main ()
{
// Open the key for read-only access
RegistryKey key = Registry.LocalMachine.OpenSubKey(
"Software\\Microsoft\\.NETFramework");
Console.WriteLine("Install root: " +
key.GetValue("InstallRoot"));
}
Most likely, this code will run fine if you don't enable any probes. If you enable the Object Not Kept Alive debug probe and compile this code with debugging information ("csc /debug ObjectNotKeptAlive.cs" from a command prompt, or the Debug solution configuration in Visual Studio .NET), you'll still get the output that you'd expect when running the program. For example:
Install root: c:\windows\microsoft.net\framework
But if you compile it without debugging information ("csc ObjectNotKeptAlive.cs" from a command prompt, or the Release solution configuration in Visual Studio .NET) with the Object Not Kept Alive probe still enabled, you get surprising output:
Install root:
Weird, huh?
Do you see any bugs in the code? Who can tell me why this happens with the probe enabled? Why does it only happen in the debug configuration? My last quiz was answered pretty quickly, so please contribute any of your thoughts!
Comments
- Anonymous
June 05, 2003
The comment has been removed - Anonymous
June 05, 2003
Humm. on more thought, I don't think I am right on this one. I am waiting for sharper minds than mine on solving this one. I think a stack reference must still be a reference. Plus the stack reference it self is the result of creating a new object as a result of the expression "string + obj.ToString()". Anyone got this one figured out? - Anonymous
June 05, 2003
The answer to this question can be found in Chris Brumme's (excelent!) blog (see Lifetime, GC.KeepAlive, handle recycling).RegistryKey encapsulates a handle, and presumably this handle is closed during Finalize. Forcing a garbage collection just before the transition to unmanaged code will close it, since the reference to key is no longer needed as soon as the handle is extracted from it.Why doesn't it fail with debug builds? I vaguely remember (but I am not certain) that in debug builds the lifetime of references is prolongued so that you can examine the value of objects in the debugger, and not loose them as soon as they become unreachable. - Anonymous
June 05, 2003
I disagree that the code fragment shown is buggy. The bug is actually in the implementation of RegistryKey. Chris Brumme discusses this, plus the fact that it will be solved in a future version of the .NET Framework. - Anonymous
June 06, 2003
I agree that the debug/release thing is a question of how aggressively the key variable reference is scavenged from this function (i.e. whether we keep it alive to the end of the scope to aid debugging or not).Because the probe forces a GC before we get into unmanaged code you'd imagine that at the transition between managed/unmanaged code our reference to the instance that key pointed to has gone and so when we GC there we end up disposing "key" which closes the handle.However, if the probe only causes a GC then you'd think that this would be unlikely - does the probe also make sure that we wait for any pending finalizers at this point? It would seem like a good idea to do that - if this is the right tack then maybe Adam would comment on whether the probe just does a GC or whether it does the equivalent of GC.WaitForPendingFinalizers to really flush everything through? From looking at it in a debugger it looks like it does do this.It would seem (from the CIL) that when Win32Native.ReqQueryValueEx is called we insert the probe and that forces the GC and the RegistryKey's finalizer ends up called which closes the handle and, at a later point, the unmanaged code then actually queries the value of the key.If you watch this in a debugger you should be able to see the call to RegCloseKey go through with the handle of the key that the RegistryKey class is wrapping for us (obtained the value of this handle by using the sos.dll debugging extension from MSDN) before a subsequent call to RegQueryValueEx goes through using the same (now closed) registry key handle and (if you step through the disassembly) it looks like that call to ReqQueryValueEx returns a value of 6 which is ERROR_INVALID_HANDLE.Something like that, anyway - might have got the specifics a bit wrong here.These debug probes are really, really smart :-) but then the bug is really, really tricky to nail down :-( - Anonymous
June 06, 2003
The comment has been removed - Anonymous
June 06, 2003
I just got Mike's comment and I see I repeated a lot of what he said. :)Yes, the probe does also wait at most a second for pending finalizers. - Anonymous
June 09, 2003
When I read this I could not believe it. So I typed the following code :using System;public class Toto {
}public class MainApp {~Toto() { Console.WriteLine( "Toto.Finalize" );}public void Test() { Console.WriteLine( "Toto.Test begin" ); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine( "Toto.Test end" );}
}and seeing the following display (with /o switch) on my console make me feel really disappointed :MainApp.Main beginToto.Test beginToto.FinalizeToto.Test endMainApp.Main endI can't understand such behaviour from the CLR. I'm in a method call. The "this" reference should not be garbage collected. It is still behing used. That's amazing !So virtually, any classes could crash when the finalizer thread is kicked start. Frightening.public static void Main() { Console.WriteLine( "MainApp.Main begin" ); Toto t = new Toto(); t.Test(); Console.WriteLine( "MainApp.Main end" );}
- Anonymous
June 10, 2003
Pierre,While it is true that in your example the class is collected whilst you are in the middle of executing an instance method. However, in your trivial example that is OK. Theren't aren't any roots to this object even while your Test method is executing. However, in a perhaps more realistic scenario you would reference an instance variable in a method. That stack reference on an instance variable is enough to keep the object alive. - Anonymous
June 10, 2003
OK, I might feel guilty for providing such test case.This was stupid because Test() does not need the state of its instance in order to finish properly. That's certainly why the "this" reference is GC'ed and Finalize'd. But, I still have concerns about this weird calling order in this case. - Anonymous
August 29, 2003
The comment has been removed - Anonymous
September 24, 2003
How do you enable these probes (Object Not Kept Alive probe)? - Anonymous
July 27, 2004
The comment has been removed - Anonymous
July 27, 2004
The comment has been removed - Anonymous
July 22, 2006
ringtones free