Misreporting of variable values when debugging x64 code with the Visual Studio 2010 debugger

How we came across the bug

Today we were debugging some unit tests which passed on a local machine, but were failing on our build server.  We decided to step through the code using the Visual Studio 2010 debugger.  We had real trouble understanding how the test ever passed at all as the debugger was showing a variable having a value of 0 when, we knew it should have a value of 103.4, and the test should definitely fail if the value was 0.

This resulted in a great deal of head scratching, and eventually resorting to writing to the Debug window, which proved the variable did hold the value of 103.4 even though the debugger repeatedly showed the variable holding the value of 0.  The initial tests used the ReSharper test runner, so we ran a check that debugging a standalone version of NUnit produced the same value of 0, and it did.  So it wasn’t a tooling issue, and unlikely to be specific to NUnit.

As our code was part of a suite of unit tests, which in turn was part of a medium sized solution with many dependencies, we had to produce a repeatable test with the minimum number of lines of code.  We created a new console application and created the three lines of codes with two methods that were the bare minimum to mirror the code in our unit test project.

However, we couldn’t make it reproduce the fault we’d seen.  We nearly gave up, but as I had Visual Studio 2010 Ultimate installed on my machine I thought it was a good chance to use IntelliTrace to see if that changed how variables were handled in the debugger.  However, we were surprised to see in the help files that IntelliTrace only worked when a project Platform Target is set to target a x86 CPU. This is when the penny dropped – our projects target Any CPU which on our systems means x64.  The moment I set the Platform Target to x64 the error was reproduced.

Show me the code!

Here is how to reproduce the issue. In Visual Studio 2010, create a Visual C#, Windows application, using the Console Application template. In the code file Program.cs replace the code with the following code, 

using System; 

namespace MisreportingValuesInNullableTypes

{

          class Program

    {

        static void Main(string[] args)

        {

            Decimal result = GetNullableValue() ?? GetNonNullableValue();

            Console.Write(result);

        }

        static decimal? GetNullableValue()

        {

            return (decimal)200.22;

        }

        static decimal GetNonNullableValue()

        {

            return (decimal)100.11;

              }       


         }

 

What does the code do?

The code has two methods. GetNullableValue, may return null or a decimal  value. A second method, GetNonNullableValue, is guaranteed to return a decimal value every time. 

The result variable in the Main method is assigned using the null-coalescing operator, ??, which will assign the return value of the first method call unless it has a null value, in which case it will assign the return value of the second method.  This guarantees that there will always be a decimal value assigned to the variable result.

Reproducing the problem

To view the variable result with the debugger set a breakpoint on the line containing Console.Write. If you left the project with the default settings, the Platform Target should be set to x86.  When you run the program, it will reach the breakpoint, and if you hover over the result variable it will show the value 200.22.

Now right click on the console project in the Solution Explorer and configure the Platform Target to x64.  Now when you run the program, it will reach the breakpoint, and if you hover over the result variable it will show the value 0.

In either case the actual value held in the variable is 200.22.  You can see this when the Platform Target is set to x64 by reaching the breakpoint and pressing F10 to step one line further in the code. If you switch to the console application window, there is the correct value of 200.22 even though the debugger showed the variable holding the value 0 when you hover over it in the code window.

Incorrect variable value displayed in x64 debugger

Even weirder behaviour ...

During trying to reproduce the problem, we edited our original code to see if I could ‘fix’ the problem by using a different method of assigning the variable.  This is when we discovered that if you add an additional variable assignment after the line making the assignment to result,

            bool AnyOldVariableWeDontActuallUse = true;

this resolved the issue and the debugger displayed the correct value.  This is true even if the variable is not actually used by any other code. This additional variable can be of any type but it must be initialised if it is to work. If you leave this additional variable with a default value it won’t resolve the issue with the debugger.

Conclusion

Our tests were performed in Windows 7 64-bit, with Visual Studio 2010 Professional RTM, Visual Studio 2010 Ultimate RTM and Visual Studio 2010 Ultimate SP1, and all exhibited the same fault in the debugger when the Platform Target was set to x64.

I haven’t delved into the IL code generated, as clearly the actual code and .Net Framework is functioning as expected.  The fault appears only to lie in the x64 implementation of the Visual Studio 2010 debugger.  As Visual Studio 2010 Professional doesn’t include IntelliTrace the x86 debugger is the standard debugger for Visual Studio, one would expect this to be identical to that in the x64 version.

So if you are debugging an Any CPU or x64 don’t necessarily believe the value that the debugger might tell you is stored in a variable. Not a comforting thought.

Note

I have placed this issue on Microsoft Connect if you wish to provide additional feedback to Microsoft on this issue,

https://connect.microsoft.com/VisualStudio/feedback/details/655793/edit-this-entry-misreporting-of-variable-values-when-debugging-x64-code-with-the-visual-studio-2010-debugger

Print | posted on Thursday, March 31, 2011 9:30 AM

Feedback

# re: Misreporting of variable values when debugging x64 code with the Visual Studio 2010 debugger

left by Ian Halliday at 8/30/2011 6:20 AM Gravatar
Thanks for reporting this issue, Liam.

Here's the reply I posted on Connect for anyone who reads this blog and is curious:

We've identified the problem and fixed it. The fix will appear in the C# compiler in the next release of .Net and Visual Studio.

This behavior is occuring because the 64 bit CLR JIT is associating the MSIL store instruction that saves the value of the null-coalescing operation into result with the IL for the statement that follows. The JIT is doing this because the ?? operator introduces a branch, and both paths of the branch merge back at the store instruction, but the C# compiler does not include a NOP or debug sequence point at this merge point, and the JIT cannot decide how to map the native code back to the IL and back to the source. The JIT has a heurestic that maps the store instruction's native code with the following debug sequence point which happens to be for the next statement.

So what happens in the debugger is that it actually stops before the store instruction is executed when you step over the declaration for result. So result still has its zero-initialized value, which in this case is 0.0m. Stepping over the next statement finally executes that store instruction and then result gets its expected value in the debugger display.

This scenario happens to work fine in the x86 JIT by accident and it also works in the x64 JIT if the struct type being stored into is 64 bits or smaller. Decimal is larger than 64 bits and so we see this behavior instead. Guid also exhibits this behavior for example.

Note there is nothing wrong with the compiled code. It is just the CLR's diagnostic heurestics that aren't working out. The solution we are taking is to make the compiler insert a NOP instruction at the IL merge point. A NOP instruction tells the JIT that there is an implicit debug sequence point at that instruction and the JIT is able to correctly map the native code back to source.


Also note this applies to the ?: operator as well, which has also been fixed.
E.g. the following has the same behavior as your example.

Decimal? decnul = GetNullableValue();
Decimal result = decnul.HasValue ? decnul.Value : GetNonNullableValue();
Console.WriteLine(result);


Ian Halliday

Microsoft
VB & C# Compiler SDE

# re: Misreporting of variable values when debugging x64 code with the Visual Studio 2010 debugger

left by Liam Westley at 9/7/2011 12:19 AM Gravatar
@Ian

Thanks for the update.
Post A Comment
Title:
Name:
Email:
Comment:
Verification: