潜在的.NET x86 JIT问题?

时间:2011-07-11 22:17:29

标签: c# c#-4.0 jit

以下代码在发布模式(或启用了优化的调试)中构建时的行为会有所不同,并且运行而不使用附加的Visual Studio调试器。

如果使用x86 JITter,它似乎也只能复制。我在x86机器上测试了这个,并在x64机器上运行WOW64(通过将Platform目标设置为x86)。

我只在.NET 4.0中试过这个。

在Release I中调试器外部运行时,请看:

Value is 4

在调试器内部运行时,e.Value.Length调用的WriteLine部分会引发NullReferenceException,这是我预期会发生的事情。

代码:

namespace Test
{
    class UsingReleasable<T>
    {
        public UsingReleasable(T obj)
        {
            m_obj = obj;
        }

        public T Release()
        {
            T tmp = m_obj;
            m_obj = default(T);
            return tmp;
        }

        public T Value
        {
            get { return m_obj; }
        }

        T m_obj;
    }

    class Program
    {
        static void Main(string[] args)
        {
            var e = new UsingReleasable<string>("test");
            e.Release();
            System.Console.WriteLine("Value is {0}", e.Value.Length);
        }
    }
}

我对JIT生成的代码的窥视使我认为这是一个错误,但我想在将此转发到MS Connect之前仔细检查。

1 个答案:

答案 0 :(得分:21)

我可以重现你的行为:

R:\>csc /platform:x86 releasable.cs
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1
Copyright (C) Microsoft Corporation. All rights reserved.


R:\>releasable

Unhandled Exception: System.NullReferenceException: Object reference not set to
an instance of an object.
   at Test.Program.Main(String[] args)

R:\>csc /o+ /platform:x86 releasable.cs
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1
Copyright (C) Microsoft Corporation. All rights reserved.


R:\>releasable
Value is 4

R:\>csc /platform:anycpu releasable.cs
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1
Copyright (C) Microsoft Corporation. All rights reserved.


R:\>releasable

Unhandled Exception: System.NullReferenceException: Object reference not set to
an instance of an object.
   at Test.Program.Main(String[] args)

R:\>csc /o+ /platform:anycpu releasable.cs
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1
Copyright (C) Microsoft Corporation. All rights reserved.


R:\>releasable

Unhandled Exception: System.NullReferenceException: Object reference not set to
an instance of an object.
   at Test.Program.Main(String[] args)

/checked编译器选项没有区别。也没有生成调试数据/debug+。调用Console.ReadLine()时问题仍然存在(为了有机会附加调试器并查看优化代码。


我对Main做了一些修改,以允许调试优化代码:

    static void Main(string[] args)
    {
        var e = new UsingReleasable<string>("test");
        System.Console.WriteLine("attach now");
        System.Console.ReadLine();
        e.Release();
        System.Console.WriteLine("Value is {0}", e.Value.Length);
    }

实际的反汇编:

--- r:\releasable.cs -----------------------------------------------------------
            var e = new UsingReleasable<string>("test");
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        edi 
00000004  push        esi 
00000005  mov         ecx,1839B0h 
0000000a  call        FFF81FB0 
0000000f  mov         esi,eax 
00000011  mov         eax,dword ptr ds:[03772030h] 
00000017  lea         edx,[esi+4] 
0000001a  call        60452F70 
            System.Console.WriteLine("attach now");
0000001f  call        5E927060 
00000024  mov         ecx,eax 
00000026  mov         edx,dword ptr ds:[03772034h] 
0000002c  mov         eax,dword ptr [ecx] 
0000002e  mov         eax,dword ptr [eax+3Ch] 
00000031  call        dword ptr [eax+10h] 
00000034  call        5EEF9A40 
00000039  mov         ecx,eax 
0000003b  mov         eax,dword ptr [ecx] 
0000003d  mov         eax,dword ptr [eax+2Ch] 
00000040  call        dword ptr [eax+1Ch] 
            e.Release();
00000043  mov         edi,dword ptr [esi+4]              ; edi = e.Value
00000046  lea         esi,[esi+4]                        ; esi = &e.Value
00000049  xor         edx,edx                            ; edx = null
0000004b  mov         dword ptr [esi],edx                ; *esi = edx (e.Value = null)
0000004d  mov         ecx,5EBE28F8h
00000052  call        FFF81FB0
00000057  mov         edx,eax 
00000059  mov         eax,dword ptr [edi+4]              ; this sets EAX to 4
0000005c  mov         dword ptr [edx+4],eax 
0000005f  mov         esi,edx 
00000061  call        5E927060 
00000066  push        esi 
00000067  mov         ecx,eax 
00000069  mov         edx,dword ptr ds:[03772038h] 
0000006f  mov         eax,dword ptr [ecx] 
00000071  mov         eax,dword ptr [eax+3Ch] 
00000074  call        dword ptr [eax+18h]                ; this results in the output "Value is 4\n"
00000077  pop         esi 
        }
00000078  pop         edi 
00000079  pop         ebp 
0000007a  ret 

当程序在调试器下启动时,会生成此代码(并生成NullReferenceException

--- r:\releasable.cs -----------------------------------------------------------
            var e = new UsingReleasable<string>("test");
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  sub         esp,24h 
00000006  mov         dword ptr [ebp-4],ecx 
00000009  cmp         dword ptr ds:[001E313Ch],0 
00000010  je          00000017 
00000012  call        606B6807 
00000017  xor         edx,edx 
00000019  mov         dword ptr [ebp-0Ch],edx 
0000001c  mov         ecx,1E39B0h 
00000021  call        FFF91FB0 
00000026  mov         dword ptr [ebp-10h],eax 
00000029  mov         edx,dword ptr ds:[032E2030h] 
0000002f  mov         ecx,dword ptr [ebp-10h] 
00000032  call        dword ptr ds:[001E3990h] 
00000038  mov         eax,dword ptr [ebp-10h] 
0000003b  mov         dword ptr [ebp-0Ch],eax 
            System.Console.WriteLine("attach now");
0000003e  mov         ecx,dword ptr ds:[032E2034h] 
00000044  call        5E8D703C 
            System.Console.ReadLine();
00000049  call        5EEAA728 
0000004e  nop 
            e.Release();
0000004f  mov         ecx,dword ptr [ebp-0Ch] 
00000052  cmp         dword ptr [ecx],ecx 
00000054  call        dword ptr ds:[001E3994h] 
0000005a  nop 
            System.Console.WriteLine("Value is {0}", e.Value.Length);
0000005b  mov         eax,dword ptr ds:[032E2038h] 
00000061  mov         dword ptr [ebp-14h],eax 
00000064  mov         ecx,dword ptr [ebp-0Ch] 
00000067  cmp         dword ptr [ecx],ecx 
00000069  call        dword ptr ds:[001E3998h] 
0000006f  mov         dword ptr [ebp-18h],eax 
00000072  mov         ecx,dword ptr [ebp-18h] 
00000075  cmp         dword ptr [ecx],ecx                 ; access violation here
00000077  call        608CBA5B 
0000007c  mov         dword ptr [ebp-8],eax 
0000007f  mov         ecx,5EBE28F8h 
00000084  call        FFF91FB0
00000089  mov         dword ptr [ebp-1Ch],eax 
0000008c  mov         eax,dword ptr [ebp-14h] 
0000008f  mov         dword ptr [ebp-20h],eax 
00000092  mov         eax,dword ptr [ebp-1Ch] 
00000095  mov         edx,dword ptr [ebp-8] 
00000098  mov         dword ptr [eax+4],edx 
0000009b  mov         eax,dword ptr [ebp-1Ch] 
0000009e  mov         dword ptr [ebp-24h],eax 
000000a1  mov         ecx,dword ptr [ebp-20h] 
000000a4  mov         edx,dword ptr [ebp-24h] 
000000a7  call        5E8CD460 
        }
000000ac  nop 
000000ad  mov         esp,ebp 
000000af  pop         ebp 
000000b0  ret 

我想我已经在错误的版本中评论了所有相关的代码行。显然,注册edi用于保存指向e.Value的指针,其中最有可能是指向内容的指针(在偏移0处)和长度(在偏移4处) v-table(偏移0),长度(偏移4),紧接着是内容。遗憾的是,e.Value(字符串"test")在edi被清除之前被复制到e.Value,因此使用错误的字符串指针获取长度。哎哟!


在Connect上提交了Bug(请upvote!):x86 JIT improperly reorders load of field of generic type with assignment to default(T)