c# - start - Posible problema.NET x86 JIT?
guia de c# (1)
El siguiente código se comporta de manera diferente cuando se genera en modo de lanzamiento (o depuración con optimizaciones activadas) y se ejecuta sin el depurador de Visual Studio conectado.
También parece replicarse solo si se usa x86 JITter. Lo probé en una máquina x86 y también en WOW64 en una máquina x64 (estableciendo el objetivo de la plataforma en x86).
Solo he intentado esto con .NET 4.0.
Cuando se ejecuta fuera del depurador en la Versión I, veo:
Value is 4
Cuando se ejecuta dentro del depurador, la porción e.Value.Length
de la llamada WriteLine
arroja NullReferenceException
, que es lo que esperaba que sucediera.
El código:
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);
}
}
}
Mi observación del código generado por JIT me hace pensar que es un error en esa pieza, pero quería verificarlo aquí antes de enviar esto a MS Connect.
Puedo reproducir tu comportamiento:
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)
La opción /checked
compilada del compilador no hace diferencia. Tampoco lo hace la generación de depuración de datos /debug+
. Y el problema persiste cuando se llama a Console.ReadLine()
(para dar la oportunidad de adjuntar el depurador y ver el código optimizado).
Hice una pequeña modificación en Main
para permitir la depuración del código optimizado:
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);
}
Y el desmontaje real:
--- 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
Cuando el programa se inicia bajo el depurador, este código se genera en su lugar (y produce una 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
Creo que he comentado todas las líneas de código relevantes en la versión incorrecta. El registro claro de edi
se usa para mantener un puntero a e.Value
, que consiste muy probablemente en un puntero al contenido (en el desplazamiento 0) y una longitud (en el desplazamiento 4) de la tabla v (en el desplazamiento 0), longitud (en offset 4), seguido inmediatamente por el contenido. Lamentablemente, e.Value
(la cadena "test"
) se copia en edi
antes de que e.Value
se e.Value
, por lo que se recupera la longitud utilizando el puntero de cadena incorrecto. ¡Ay!
Error presentado en Connect (por favor, ¡voto favorable!): X86 JIT reordena incorrectamente la carga de campo de tipo genérico con asignación a valores predeterminados (T)