[C#] .NET 4.5 베타에서이 FatalExecutionEngineError의 원인은 무엇입니까? [닫은]

아래 샘플 코드는 자연스럽게 발생했습니다. 갑자기 내 코드는 매우 불쾌한 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를 사용하고 있습니다 (7 월?).

더 많은 정보 :

  • IL 코드 (컴파일 된 디버그 / 모든 CPU / 4.0 / VS2010 (IDE가 중요하지 않습니까?)) : http://codepad.org/boZDd98E
  • VS 2010에는 4.0이 표시되지 않습니다. 최적화, 다른 대상 CPU, 디버거 연결 / 연결되지 않음 등으로 충돌하지 않거나 충돌하지 않음 -Tim Medora
  • AnyCPU를 사용하면 2010 년에 충돌이 발생합니다. x86에서는 정상입니다. 플랫폼 대상 = AnyCPU를 사용하지만 Visual Studio 2010 SP1에서 충돌하지만 플랫폼 대상 = x86에서는 문제가 있습니다. 이 시스템에는 VS2012RC도 설치되어 있으므로 4.5 교체가 가능합니다. 사용 anycpu를하고에 targetPlatform 그런 다음이 Framework.-의 회귀 같은 외모 때문에 충돌하지 않는 3.5 = colinsmith
  • 4.0이 설치된 VS2010의 x86, x64 또는 AnyCPU에서 재생할 수 없습니다. – 후지
  • x64, (2012rc, Fx4.5)에만 해당-Henk Holterman
  • Win8 RP의 VS2012 RC. .NET 4.5를 타겟팅 할 때 처음에는이 MDA가 표시되지 않습니다. .NET 4.0을 대상으로 전환했을 때 MDA가 나타났습니다. 그런 다음 .NET 4.5로 다시 전환 한 후 MDA가 유지됩니다. – 웨인


답변

이것은 전체 답변이 아니지만 몇 가지 아이디어가 있습니다.

.NET JIT 팀의 답변이없는 사람없이 찾을 수있을만큼 좋은 설명을 찾았습니다.

최신 정보

좀 더 깊게 보았고 문제의 원인을 찾았습니다. JIT 유형 초기화 논리의 버그와 JIT가 의도 한대로 작동한다는 가정에 의존하는 C # 컴파일러의 변경으로 인해 발생하는 것으로 보입니다. JIT 버그는 .NET 4.0에 존재했지만 .NET 4.5의 컴파일러 변경으로 인해 발견되지 않았다고 생각합니다.

나는 이것이 beforefieldinit유일한 문제 라고 생각하지 않습니다 . 나는 그것보다 간단하다고 생각합니다.

System.String.NET 4.0의 mscorlib.dll 형식 에는 정적 생성자가 포함되어 있습니다.

.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는 없습니다.하지만 할 수 없었습니다. 이러한 모든 유형은 .cctorIL 에서 메소드를 갖습니다 .

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 사이에서 두 가지가 바뀌 었다는 것입니다.

첫째 : String.Empty관리되지 않는 코드에서 자동으로 초기화되도록 EE가 변경되었습니다 . 이 변경 사항은 .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정적 생성자 를 주석 처리 하면 코드가 실패하지 않습니다. Trimand에 파고 들어 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>...

두 번째.

주목해야 할 또 다른 사항은 A<int>.ctor(out string):에 대한 JITed 코드 가 제네릭이 아닌 버전과 동일 하다는 것 입니다.


답변

난 강력하게이 발생 의심 (관련이 최적화 BeforeFieldInit) .NET 4.0 인치

내가 정확하게 기억한다면 :

정적 생성자를 명시 적으로 선언하면 정적 멤버 액세스 전에beforefieldinit 정적 생성자 를 실행해야한다고 런타임에 알립니다 .

내 추측:

나는 그들이 어떻게 든 64의 JITer에이 사실을 망친 것을 추측에는 요 그래서 그 때 다른 유형의 정적 멤버는 그 클래스에서 액세스 자신의 정적 생성자 이미 실행하고있다, 어떻게 든 건너 뜁니다 (잘못된 순서로 또는 실행하는)를 실행하는 정적 생성자이므로 충돌이 발생합니다. (당신은 널 포인터 예외가 발생하지 않습니다 아마 이 null로 초기화되지 때문에.)

나는 한 하지 이 부분이 잘못 될 수 있도록, 당신의 코드를 실행 -하지만 난 또 다른 추측을해야한다면, 나는 뭔가 수 있습니다 말하고 싶지만 string.Format(또는 Console.WriteLine내부에서의이 같은 충돌을 일으키는 것을 유사)에 액세스해야 아마도 명시적인 정적 구성이 필요한 로케일 관련 클래스 일 것입니다.

다시 한 번 테스트하지는 않았지만 데이터를 가장 잘 추측합니다.

내 가설을 자유롭게 테스트하고 어떻게 진행되는지 알려주십시오.


답변

관찰이지만 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);
    }
}


답변