В чем причина этой ошибки FatalExecutionEngineError в бета-версии .NET 4.5? [закрыто]

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.

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

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

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

  • IL-код (скомпилированный Debug / Any CPU / 4.0 / VS2010 (разве IDE не имеет значения?)): Http://codepad.org/boZDd98E
  • Не видел VS 2010 с 4.0. Не вылетает с / без оптимизаций, другой целевой процессор, отладчик подключен / не подключен и т. Д. - Тим Медора
  • Сбои в 2010 году, если я использую AnyCPU, нормально в x86. Сбои в Visual Studio 2010 с пакетом обновления 1 (SP1) при использовании Platform Target = AnyCPU, но хорошо при использовании Platform Target = x86. На этой машине также установлен VS2012RC, поэтому 4.5, возможно, выполняет замену на месте. Используйте AnyCPU и TargetPlatform = 3.5, тогда он не падает, поэтому выглядит как регрессия в Framework.- colinsmith
  • Невозможно воспроизвести на 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 #.
Михаил Грачик
14
Спасибо как оригинальному постеру за сообщение здесь, так и Майклу за его превосходный анализ. Мои коллеги из CLR попытались воспроизвести ошибку здесь и обнаружили, что она воспроизводится в 64-разрядной версии CLR «Release Candidate», но не в окончательной версии «Released To Manufacturing», которая имела ряд исправлений ошибок после RC. (Версия RTM будет доступна для общественности 15 августа 2012 года.) Поэтому они считают, что это та же проблема, о которой сообщалось здесь: connect.microsoft.com/VisualStudio/feedback/details/737108/…
Эрик Липперт

Ответы:

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метод в 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 ссылочного типа.

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

оригинал

Прежде всего, WOW Люди BCL стали очень креативными с некоторыми оптимизациями производительности. Многие из Stringметодов теперь выполняются с использованием статического кешируемого StringBuilderобъекта Thread .

Некоторое время я следовал этому примеру, но StringBuilderне использовался в Trimпути кода, поэтому решил, что это не может быть статической проблемой Thread.

Я думаю, что нашел странное проявление той же ошибки, хотя.

Этот код завершается с нарушением прав доступа:

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.

К вашему сведению, код JIT для двух версий:

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.ctorin B.Main(), где IL для первой версии содержит:

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

против

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

во-вторых.

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

Михаил Грачик
источник
3
Я искал ответы по очень похожему пути, но, похоже, он никуда не ведет. Похоже, что это проблема класса строк и, надеюсь, не более общая проблема. Так что сейчас я жду, когда кто-нибудь (Эрик) с исходным кодом придет и объяснит, что пошло не так, и если что-нибудь еще произойдет. В качестве небольшого преимущества это обсуждение уже string.Empty""
решило
Является ли IL между ними одинаковыми?
Коул Джонсон
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, что похоже) необходимо для внутреннего доступа, который вызывает сбой, например, возможно локалите о связанном классе , который нуждается в явной статическую конструкции.

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

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

user541686
источник
Ошибка по-прежнему возникает, когда Bнет статического конструктора, и не возникает, когда Aиспользуется тип значения. Я думаю, что это немного сложнее.
Михаил Грачик,
@MichaelGraczyk: Я думаю, что могу объяснить это (опять же, с догадками). Bналичие статического конструктора не имеет большого значения. Так как Aимеет статический ctor, среда выполнения портит порядок, в котором она запускается, по сравнению с некоторым классом, связанным с локалью, в каком-то другом пространстве имен. Так что это поле еще не инициализировано. Однако, если вы создаете экземпляр Aс типом значения, то это может быть второй проход во время выполнения A(например, CLR уже предварительно создал его со ссылочным типом в качестве оптимизации), поэтому порядок срабатывает, когда он запускается во второй раз. ,
user541686
@MichaelGraczyk: Даже если это не совсем объяснение, я думаю, я вполне уверен, что данная beforefieldinitоптимизация является основной причиной. Возможно, что некоторые из фактических объяснений отличаются от того, что я упоминал, но основная причина, вероятно, та же самая.
user541686
Я посмотрел в IL больше, и я думаю, что вы на что-то. Я не думаю, что идея второго прохода будет здесь уместна, потому что код все равно не работает, если я произвожу произвольно много вызовов A<object>.ctor().
Михаэль Грачик
@MichaelGraczyk: Рад слышать, и спасибо за этот тест. К сожалению, я не могу воспроизвести его на своем ноутбуке. (2010 4.0 x64) Можете ли вы проверить, действительно ли это связано с форматированием строки (т. Е. С локалью)? Что произойдет, если вы удалите эту часть?
user541686
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);
    }
}
lesscode
источник
И с этим атрибутом? Мы уже установили, ""решает это.
Хенк Холтерман
Атрибут «Критически важен для производительности ...» влияет на сам конструктор Атрибута, а не на методы, которые украшает атрибут.
Михаил Грачик
Это внутреннее. Когда я определяю свой собственный идентичный атрибут, он все равно не вызывает MDA. Не то чтобы я ожидал этого - если JITter ищет этот конкретный атрибут, он не найдет мой.
меньше кода