[英]Potential .NET x86 JIT issue?
The following code behaves differently when built in Release mode (or Debug with optimizations on) and run without the Visual Studio debugger attached.以下代码在以发布模式(或启用优化的调试)构建并在未附加 Visual Studio 调试器的情况下运行时表现不同。
It also only seems to replicate if the x86 JITter is used.如果使用 x86 JITter,它似乎也只会复制。 I have tested this on an x86 machine as well as running in WOW64 on a x64 machine (by setting the Platform target to x86).我已经在 x86 机器上进行了测试,并在 x64 机器上在 WOW64 中运行(通过将平台目标设置为 x86)。
I've only tried this with .NET 4.0.我只用 .NET 4.0 试过这个。
When running outside the debugger in Release I see:在 Release 中的调试器之外运行时,我看到:
Value is 4
When running inside the debugger the e.Value.Length
portion of the WriteLine
call throws NullReferenceException
, which is what I expected to have happen.在调试器中运行时, WriteLine
调用的e.Value.Length
部分会抛出NullReferenceException
,这是我预期会发生的。
The code:编码:
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);
}
}
}
My peering at the JIT generated code makes me think it is a bug in that piece, but I wanted to double check here before forwarding this on to MS Connect.我查看 JIT 生成的代码让我认为这是该部分中的一个错误,但我想在将其转发到 MS Connect 之前仔细检查这里。
I can reproduce your behavior:我可以重现你的行为:
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)
The /checked
compiler option makes no difference. /checked
编译器选项没有区别。 Neither does generation of debug data /debug+
.也不会生成调试数据/debug+
。 And the problem persists when Console.ReadLine()
is called (to give a chance to attach the debugger and see optimized code.当调用Console.ReadLine()
时问题仍然存在(以便有机会附加调试器并查看优化的代码。
I made a slight modification to Main
to permit debugging of the optimized code:我对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);
}
And the actual disassembly:以及实际的拆解:
--- 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
When the program starts under the debugger, this code is generated instead (and does produce a NullReferenceException
:当程序在调试器下启动时,会生成此代码(并且确实会产生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
I think I've commented all the relevant lines of code in the incorrect version.我想我已经在不正确的版本中注释了所有相关的代码行。 Clearly register edi
is used to hold a pointer to e.Value
, which consists
most likely of a pointer to content (at offset 0) and a length (at offset 4)
of the v-table (at offset 0), length (at offset 4), followed immediately by the content.显然,寄存器edi
用于保存指向e.Value
的指针,它
很可能由指向内容的指针(在偏移量 0 处)和 v 表的长度(在偏移量 4 处)
(在偏移量 0 处)、长度(在偏移量 4),紧随其后的是内容。 Unfortunately e.Value
(the string "test"
) is copied into edi
before e.Value
is cleared, so the length is fetched using the wrong string pointer.不幸的是e.Value
(字符串"test"
)在e.Value
被清除之前被复制到edi
中,因此使用错误的字符串指针获取长度。 Ouch!哎哟!
Bug filed on Connect (please upvote:): x86 JIT improperly reorders load of field of generic type with assignment to default(T)在 Connect 上提交的错误(请投票:): x86 JIT 不正确地重新排序泛型类型字段的负载并分配为默认值(T)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.