У чому причина цього FatalExecutionEngineError в .NET 4.5 beta? [зачинено]


150

Зразок коду нижче відбувся природним шляхом. Раптом мій код став дуже неприємним FatalExecutionEngineErrorвинятком. Я витратив добрі 30 хвилин, намагаючись ізолювати та мінімізувати винуватця проби. Скомпілюйте це за допомогою програми Visual Studio 2012 як консольного додатка:

class A<T>
{
    static A() { }

    public A() { string.Format("{0}", string.Empty); }
}

class B
{
    static void Main() { new A<object>(); }
}

Слід створити цю помилку в .NET Framework 4 і 4.5:

Скріншот FatalExecutionException

Це відома помилка, в чому причина і що я можу зробити, щоб пом'якшити її? Моя поточна робота навколо - це не використовувати string.Empty, але чи я гавкаю неправильне дерево? Зміна чого-небудь про цей код змушує його функціонувати так, як ви очікували - наприклад, видалення порожнього статичного конструктора Aабо зміна параметру типу з objectна int.

Я спробував цей код на своєму ноутбуці, і він не скаржився. Однак я спробував своє основне додаток, і воно розбилося і на ноутбуці. Я, мабуть, щось зіпсував при зменшенні проблеми, я побачу, чи зможу я зрозуміти, що це було.

Мій ноутбук зазнав аварії з тим же кодом, що і вище, з Framework 4.0, але основні збої навіть із 4.5. Обидві системи використовують VS'12 з останніми оновленнями (липень?).

Більше інформації :

  • Код IL (скомпільований налагодження / будь-який процесор / 4.0 / VS2010 (чи не має значення IDE?)): Http://codepad.org/boZDd98E
  • Не бачив VS 2010 з 4.0. Не відбувається збій з / без оптимізацій, різний цільовий процесор, відладчик додається / не додається тощо - Tim Medora
  • Аварії в 2010 році, якщо я використовую AnyCPU, добре в x86. Збій у Visual Studio 2010 SP1, використовуючи ціль платформи = AnyCPU, але добре з цільовою платформою = x86. На цій машині встановлено також VS2012RC, тому 4,5 можливо проводить заміну на місці. Використовуйте AnyCPU і TargetPlatform = 3.5, тоді він не виходить з ладу, так що це виглядає як регресія в Framework.- колгоспник
  • Неможливо відтворити на x86, x64 або AnyCPU у VS2010 з 4.0. - Фуджі
  • Лише для x64, (2012rc, Fx4.5) - Хенк Холтерман
  • VS2012 RC на Win8 RP. Спочатку не бачив цього MDA під час націлювання на .NET 4.5. При переході на націлювання .NET 4.0 з'явився MDA. Потім після переходу на .NET 4.5 MDA залишається. - Уейн

Я ніколи не знав, що ти можеш стати статичним конструктором разом із загальнодоступним. Чорт я ніколи не знав, що існують статичні конструктори.
Коул Джонсон

У мене є ідея: тому що ви змінюєте B з дещо статичного класу до просто класу зі статичним Main?
Коул Джонсон

@ChrisSinclair, я так не думаю. Я маю на увазі, що я перевірив цей код на своєму ноутбуці і отримав такі ж результати.
Глено

@ColeJohnson Так, IL відповідає у всіх, крім одного очевидного місця. Тут, здається, немає жодної помилки у компіляторі c #.
Michael Graczyk

14
Дякуємо як оригінальному плакату за повідомлення про нього тут, так і Майклу за його відмінний аналіз. Мої колеги з CLR намагалися відтворити помилку тут і виявили, що вона відтворюється у версії 64-розрядного CLR "Release Candidate", але не на остаточній версії "Released To Manufacturing", яка мала ряд виправлень помилок після- RC. (Версія RTM буде доступна для громадськості 15 серпня 2012 року.) Тому вони вважають, що це те саме питання, що і те, про яке повідомлялося тут: connect.microsoft.com/VisualStudio/feedback/details/737108/…
Eric Lippert

Відповіді:


114

Це теж не повна відповідь, але у мене є кілька ідей.

Я вважаю, що я знайшов таке хороше пояснення, як ми знайдемо, не відповівши хтось із команди .NET JIT.

ОНОВЛЕННЯ

Я заглянув трохи глибше, і я вважаю, що знайшов джерело проблеми. Схоже, це викликано поєднанням помилки в логіці ініціалізації типу JIT та зміною компілятора C #, який спирається на припущення, що JIT працює за призначенням. Я думаю, що помилка JIT існувала в .NET 4.0, але була виявлена ​​зміною компілятора для .NET 4.5.

Я не думаю, що beforefieldinitце єдине питання тут. Я думаю, що це простіше, ніж це.

Тип System.Stringу mscorlib.dll від .NET 4.0 містить статичний конструктор:

.method private hidebysig specialname rtspecialname static 
    void  .cctor() cil managed
{
  // Code size       11 (0xb)
  .maxstack  8
  IL_0000:  ldstr      ""
  IL_0005:  stsfld     string System.String::Empty
  IL_000a:  ret
} // end of method String::.cctor

У версії .NET 4.5 mscorlib.dll String.cctor(статичний конструктор) помітно відсутній:

..... Немає статичного конструктора :( .....

В обох версіях Stringтип прикрашений beforefieldinit:

.class public auto ansi serializable sealed beforefieldinit System.String

Я намагався створити тип, який би компілювався в IL аналогічно (так, щоб у нього були статичні поля, але не було статичного конструктора .cctor), але я не міг цього зробити. Усі ці типи мають .cctorметод ІЛ:

public class MyString1 {
    public static MyString1 Empty = new MyString1();        
}

public class MyString2 {
    public static MyString2 Empty = new MyString2();

    static MyString2() {}   
}

public class MyString3 {
    public static MyString3 Empty;

    static MyString3() { Empty = new MyString3(); } 
}

Думаю, що між .NET 4.0 і 4.5 змінилися дві речі:

По-перше: EE було змінено, щоб воно автоматично ініціалізувалося String.Emptyз некерованого коду. Ця зміна, ймовірно, була зроблена для .NET 4.0.

По-друге: Компілятор змінився так, що не випромінював статичний конструктор для рядка, знаючи, що String.Emptyце буде призначено з некерованої сторони. Здається, ця зміна була здійснена для .NET 4.5.

Здається, що EE не призначається String.Emptyдосить швидко за деякими шляхами оптимізації. Зміни, внесені до компілятора (або все, що змінилося, щоб String.cctorзникнути), очікували, що EE виконає це завдання до того, як виконуватиметься будь-який код користувача, але виявляється, що EE не робить це призначення раніше, ніж String.Emptyвикористовується в методах рефікованих загальних загальних класів.

Нарешті, я вважаю, що помилка свідчить про більш глибоку проблему в логіці ініціалізації типу JIT. Здається, зміна компілятора є особливим випадком System.String, але я сумніваюся, що JIT зробив тут особливий випадок System.String.

Оригінал

Перш за все, WOW Люди BCL стали дуже креативними з деякими оптимізаціями роботи. Багато з Stringметодів в даний час здійснюються з допомогою різьблення статичного кешованого StringBuilderоб'єкта.

Я деякий час дотримувався цього підходу, але StringBuilderне використовується на Trimшляху коду, тому вирішив, що статична проблема теми не може бути.

Я думаю, що я виявив дивне прояв тієї самої помилки.

Цей код не вдається з порушенням доступу:

class A<T>
{
    static A() { }

    public A(out string s) {
        s = string.Empty;
    }
}

class B
{
    static void Main() { 
        string s;
        new A<object>(out s);
        //new A<int>(out s);
        System.Console.WriteLine(s.Length);
    }
}

Однак, якщо ви незважаєте //new A<int>(out s);на Mainце, код працює просто чудово. Насправді, якщо Aвона повторно змінена з будь-яким типом посилань, програма виходить з ладу, але якщо Aїї повторно застосовують з будь-яким типом значення, код не виходить з ладу. Крім того, якщо ви коментуєте Aстатичний конструктор, код ніколи не виходить з ладу. Після заглиблення в Trimі Format, зрозуміло, що проблема полягає в тому Length, що вводиться, і що в цих зразках вище Stringтип не був ініціалізований. Зокрема, всередині корпусу A'конструктора, string.Emptyне призначено правильно, хоча всередині тіла Main, string.Emptyпризначено правильно.

Мені дивовижно, що ініціалізація типу Stringякось залежить від того, буде чи ні Aповторно змінена з типом значення. Моя єдина теорія полягає в тому, що існує якийсь оптимізуючи шлях коду JIT для загальної ініціалізації типу, який ділиться між усіма типами, і що цей шлях робить припущення щодо типів посилань BCL ("спеціальні типи?") Та їх стану. Швидкий погляд , хоча інші класи BCL з public staticполів показує , що в основному всі з них реалізувати статичний конструктор (навіть з порожніми конструкторами і немає даних, як System.DBNullі System.Emptyтипів. BCL значення з public staticполями , здається, не реалізувати статичний конструктор ( System.IntPtr, наприклад) Це, мабуть, вказує на те, що JIT робить деякі припущення щодо ініціалізації базового типу BCL.

FYI Ось JITed код для двох версій:

A<object>.ctor(out string):

    public A(out string s) {
00000000  push        rbx 
00000001  sub         rsp,20h 
00000005  mov         rbx,rdx 
00000008  lea         rdx,[FFEE38D0h] 
0000000f  mov         rcx,qword ptr [rcx] 
00000012  call        000000005F7AB4A0 
            s = string.Empty;
00000017  mov         rdx,qword ptr [FFEE38D0h] 
0000001e  mov         rcx,rbx 
00000021  call        000000005F661180 
00000026  nop 
00000027  add         rsp,20h 
0000002b  pop         rbx 
0000002c  ret 
    }

A<int32>.ctor(out string):

    public A(out string s) {
00000000  sub         rsp,28h 
00000004  mov         rax,rdx 
            s = string.Empty;
00000007  mov         rdx,12353250h 
00000011  mov         rdx,qword ptr [rdx] 
00000014  mov         rcx,rax 
00000017  call        000000005F691160 
0000001c  nop 
0000001d  add         rsp,28h 
00000021  ret 
    }

Решта коду ( Main) однакова між двома версіями.

EDIT

Крім того, IL з двох версій ідентичний за винятком виклику A.ctorв B.Main(), де IL для першої версії містить:

newobj     instance void class A`1<object>::.ctor(string&)

проти

... A`1<int32>...

у другій.

Ще слід зазначити, що код JITed для A<int>.ctor(out string): той самий, що і в негенеріальній версії.


3
Я шукав відповіді дуже схожим шляхом, але, схоже, нікуди не веде. Це, мабуть, є проблемою класу рядків і, мабуть, не є загальною проблемою. Тож зараз я чекаю, коли хтось (Ерік) із вихідним кодом прийде і пояснить, що пішло не так, і якщо щось ще буде здійснено. Як невелика користь, ця дискусія вже врегулювала дискусію, варто використовувати string.Emptyчи ""... :)
Глено

Чи ІЛ між ними однаковий?
Коул Джонсон

49
Хороший аналіз! Я передамо його команді BCL. Дякую!
Ерік Ліпперт

2
@EricLippert та інші. Я виявив, що подібний код typeof(string).GetField("Empty").SetValue(null, "Hello world!"); Console.WriteLine(string.Empty);дає різні результати в .NET 4.0 проти .NET 4.5. Чи пов’язана ця зміна із описаною вище зміною? Як може .NET 4.5 технічно ігнорувати мене, змінюючи значення поля? Можливо, я повинен поставити нове запитання з цього приводу?
Джеппе Стіг Нільсен

4
@JeppeStigNielsen: Відповіді на ваші запитання: "можливо", "досить легко, мабуть" і "це веб-сайт із запитаннями та відповідями, так що так, це гарна ідея, якщо ви хочете краще відповісти на своє запитання. ніж "можливо" ".
Ерік Ліпперт

3

Я дуже підозрюю, що це викликано цією оптимізацією (пов'язаною з BeforeFieldInit) в .NET 4.0

Якщо я пам'ятаю правильно:

Коли ви явно оголошуєте статичний конструктор, beforefieldinitвипромінюється, повідомляючи час виконання, що статичний конструктор повинен запускатися перед будь-яким статичним доступом члена .

Моя здогадка:

Я припускаю , що вони якісь - то чином зіпсували цей факт на 64 JITer, так що , коли різний типу по члену статичної доступу з класу , чиї власні статичний конструктор вже запущені, воно як - то скаче працює (або виконується в неправильному порядку) статичний конструктор - і тому викликає збій. (Ви не отримуєте нульового виключення вказівника, ймовірно, тому, що воно не ініціалізовано.)

Я не запустив ваш код, тому ця частина може бути помилковою, але якби мені довелося зробити іншу здогадку, я б сказав, що це може бути щось string.Format(або Console.WriteLine, що схоже) на внутрішній доступ, що викликає збій, наприклад можливо клас, пов’язаний з локальним рівнем, який потребує явної статичної побудови.

Знову ж таки, я її не перевіряв, але я найкраще здогадуюсь про дані.

Не соромтеся перевірити свою гіпотезу і дайте мені знати, як це відбувається.


Помилка все-таки виникає, коли Bне має статичного конструктора, і не виникає, коли Aвона повторно змінена з типом значення. Я думаю, що це трохи складніше.
Michael Graczyk

@MichaelGraczyk: Я думаю, що можу пояснити це (знову ж таки, здогадами). Bмати статичний конструктор не має великого значення. Оскільки Aмає статичний ctor, час виконання порушує порядок, в якому він запускається, порівняно з класом, пов’язаним з локальним розташуванням, в якомусь іншому просторі імен. Тож це поле ще не ініціалізовано. Однак, якщо ви Aстворюєте екземпляр з типом значення, то це може бути другий пропуск часу виконання через інстанціювання A(CLR, ймовірно, вже попередньо його встановив за допомогою еталонного типу, як оптимізацію), тому замовлення працює, коли його запустите вдруге .
користувач541686

@MichaelGraczyk: Хоча навіть це не зовсім пояснення - я думаю, я цілком переконаний, що дана beforefieldinitоптимізація є першопричиною. Можливо, деякі фактичні пояснення відрізняються від тих, про які я згадував, але першопричина, ймовірно, те саме.
користувач541686

Я більше заглянув у ІР, і, думаю, ти щось на те. Я не думаю, що ідея другого проходу тут буде доречною, тому що код все-таки виходить з ладу, якщо я виконую довільно багато дзвінків A<object>.ctor().
Michael Graczyk

@MichaelGraczyk: Добре чути, і дякую за тест. На жаль, я не можу відтворити його на своєму власному ноутбуці. (2010 р. 4.0 x64) Чи можете ви перевірити, чи дійсно вона пов’язана з форматуванням рядків (тобто, що стосується локалі)? Що станеться, якщо ви вилучите цю частину?
користувач541686

1

Спостереження, але DotPeek показує декомпільований рядок. Очистіть таким чином:

/// <summary>
/// Represents the empty string. This field is read-only.
/// </summary>
/// <filterpriority>1</filterpriority>
[__DynamicallyInvokable]
public static readonly string Empty;

internal sealed class __DynamicallyInvokableAttribute : Attribute
{
  [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
  public __DynamicallyInvokableAttribute()
  {
  }
}

Якщо я оголошу свій власний Emptyтак само, за винятком атрибута, більше не отримаю MDA:

class A<T>
{
    static readonly string Empty;

    static A() { }

    public A()
    {
        string.Format("{0}", Empty);
    }
}

І з тим атрибутом? Ми вже встановили ""це вирішує.
Хенк Холтерман

Цей атрибут "Критична ефективність ..." впливає на сам конструктор Attribute, а не на методи, які атрибут прикрашає.
Michael Graczyk

Це внутрішнє. Коли я визначаю свій ідентичний атрибут, він все ще не викликає MDA. Не те, щоб я очікував цього - якщо JITter шукає саме цей атрибут, він не знайде мого.
меншекод
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.