34 votes

Problème potentiel .NET x86 JIT?

Le code suivant se comporte différemment, construit en mode Release (ou Debug avec les optimisations sur) et de l'exécuter sans le débogueur Visual Studio attaché.

De plus, cela ne semble reproduire si le x86 Gigue est utilisé. J'ai testé sur une machine x86 ainsi que l'exécution en WOW64 sur une machine x64 (par réglage de la Plate-forme cible x86).

J'ai seulement essayé cela avec .NET 4.0.

Lors de l'exécution à l'extérieur du débogueur dans la Version que je vois:

Value is 4

Lors de l'exécution dans le débogueur l' e.Value.Length partie de l' WriteLine appel jette NullReferenceException, ce qui est ce que j'attendais qu'il arrive.

Le 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);
        }
    }
}

Mon peering à l'JIT code généré me fait penser que c'est un bug dans cette pièce, mais je voulais vérifier ici avant de les transmettre à MME Connecter.

21voto

Ben Voigt Points 151460

Je peux reproduire votre comportement:

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)

L' /checked option de compilateur ne fait aucune différence. Ni le fait de la génération de données de débogage /debug+. Et le problème persiste lors de l' Console.ReadLine() est appelé (pour donner une chance à attacher le débogueur et voir le code optimisé.


J'ai fait une légère modification à l' Main à autoriser le débogage du code optimisé:

    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);
    }

Et le réel de démontage:

--- 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 

Lorsque le programme démarre avec le débogueur, ce code est généré à la place (et de ne produire un 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 

Je pense que j'ai commenté toutes les lignes de code dans la version incorrecte. Clairement registre edi est utilisé pour contenir un pointeur vers e.Value, qui se compose d'un pointeur vers le contenu (à l'offset 0) et une longueur (au décalage 4) de la v-table (à l'offset 0), longueur (au décalage 4), suivi immédiatement par le contenu. Malheureusement, e.Value (la chaîne de caractères "test") est copié dans l' edi avant e.Value est désactivée, de sorte que la longueur est récupérée à l'aide de la mauvaise pointeur de la chaîne. Ouch!


Bug déposé sur Connect (veuillez upvote!): JIT x86 mal réorganise la charge de champ de type générique avec affectation par défaut(T)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X