Какая причина этого FatalExecutionEngineError в .NET 4.5 beta?

Пример кода ниже происходил естественным образом. Внезапно мой код был очень неприятным звуком 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 screenshot

Является ли это известной ошибкой, в чем причина и что я могу сделать для ее смягчения? Моя текущая работа заключается в том, чтобы не использовать string.Empty, но я лаяю неправильное дерево? Изменение чего-либо в этом коде делает его функцией, как и следовало ожидать - например, удаление пустого статического конструктора A или изменение параметра типа от object до int.

Я пробовал этот код на своем ноутбуке, и он не жаловался. Тем не менее, я попробовал свое основное приложение, и он тоже упал на ноутбуке. Я, должно быть, что-то убрал, уменьшив проблему, посмотрю, смогу ли я понять, что это было.

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

Дополнительная информация:

  • IL Code (скомпилированный Debug/Any CPU/4.0/VS2010 (не то, что IDE должна иметь значение?)): http://codepad.org/boZDd98E li >
  • Не видел VS 2010 с 4.0. Не сбой с/без оптимизации, другой целевой процессор, отладчик, прикрепленный/не подключенный, и т.д. - Tim Medora
  • Сбой в 2010 году, если я использую AnyCPU, отлично подходит для x86. Сбой в Visual Studio 2010 SP1 с использованием Platform Target = AnyCPU, но отлично с Platform Target = x86. У этого аппарата также установлен VS2012RC, поэтому 4.5 возможно замена на месте. Используйте AnyCPU и TargetPlatform = 3.5, после чего он не падает, поэтому выглядит как регрессия в Framework.- colinsmith
  • Невозможно воспроизвести на x86, x64 или AnyCPU в VS2010 с 4.0. - Fuji
  • Только для x64, (2012rc, Fx4.5) - Хенк Холтерман
  • VS2012 RC на Win8 RP. Изначально не видел этот MDA при настройке .NET 4.5. При переключении на таргетинг на .NET 4.0 появился MDA. Затем, после перехода на .NET 4.5, MDA остается. - Wayne

Ответ 1

Это также не полный ответ, но у меня есть несколько идей.

Я считаю, что я нашел хорошее объяснение, которое мы найдем, если кто-нибудь из команды .NET JIT не ответит.

UPDATE

Я посмотрел немного глубже, и я считаю, что нашел источник проблемы. По-видимому, это вызвано сочетанием ошибки в логике инициализации типа JIT и изменением компилятора С#, который основан на предположении, что 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

В версии mscorlib.dll.NET 4.5 String.cctor (статический конструктор) явно отсутствует:

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

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

.class public auto ansi serializable sealed beforefieldinit System.String

Я попытался создать тип, который бы скомпилировал IL, так что он имеет статические поля, но не статический конструктор .cctor), но я не мог этого сделать. Все эти типы имеют метод .cctor в IL:

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 в методах ссылки тип reified generic classes.

Наконец, я считаю, что ошибка указывает на более глубокую проблему в логике инициализации типа 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) идентична между двумя версиями.

ИЗМЕНИТЬ

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

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

против

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

во втором.

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

Ответ 2

Я сильно подозреваю, что это вызвано этой оптимизацией (связанной с BeforeFieldInit) в .NET 4.0.

Если я правильно помню:

Когда вы объявляете статический конструктор явно, BeforeFieldInit испускается, сообщая исполняемой среде, что статический конструктор должен быть запущен до того, как будет доступ к статическому члену.

Мое предположение:

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

У меня не запустите ваш код, так что эта часть может быть неправильной, но если бы мне пришлось сделать другое предположение, я бы сказал, что это может быть что-то string.Format (или Console.WriteLine, который аналогичен) должен иметь внутренний доступ, который вызывает сбой, например, язык, связанный с локалью, который нуждается в явной статической конструкции.

Опять же, я не тестировал его, но это мое лучшее предположение для данных.

Не стесняйтесь проверить мою гипотезу и дайте мне знать, как это происходит.

Ответ 3

Наблюдение, но 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);
    }
}