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


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를 사용하고 있습니다 (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가 유지됩니다. - 웨인

나는 정적 생성자를 공개 생성자와 함께 만들 수 있다는 것을 결코 알지 못했습니다. 정적 생성자가 존재한다는 것을 결코 알지 못했습니다.
Cole Johnson

아이디어가 있습니다 : B를 정적 클래스에서 정적 Main 클래스로 변경했기 때문에?
Cole Johnson

@ChrisSinclair, 나는 그렇게 생각하지 않습니다. 랩톱에서이 코드를 테스트 한 결과 동일한 결과를 얻었습니다.
Gleno

@ColeJohnson 네, IL은 하나의 명백한 장소를 제외하고 모두 일치합니다. C # 컴파일러에는 버그가없는 것으로 보입니다.
Michael Graczyk

14
여기에보고 한 최초의 포스터와 그의 훌륭한 분석에 대해 Michael에게 감사합니다. CLR의 상대방은 여기에서 버그를 재현하려고 시도했지만 64 비트 CLR의 "릴리스 후보"버전에서는 재현하지만 최종 "제조 릴리스"버전에서는 재생되지 않는다는 사실을 발견했습니다. RC. (RTM 버전은 2012 년 8 월 15 일에 일반인에게 공개 될 예정입니다.) 따라서 여기에보고 된 것과 동일한 문제라고 생각합니다. connect.microsoft.com/VisualStudio/feedback/details/737108/…
에릭 Lippert

답변:


114

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

.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 코드 가 제네릭이 아닌 버전과 동일 하다는 것 입니다.


3
비슷한 경로를 따라 답변을 검색했지만 어디에서도 이끌어 내지는 않는 것 같습니다. 이것은 문자열 클래스 문제로 보이며 더 일반적인 문제는 아닙니다. 그래서 지금 나는 소스 코드를 가진 누군가 (Eric)가 와서 무엇이 잘못되었는지 설명하고 다른 것이 영향을 미치는지를 설명하기를 기다리고 있습니다. 작은 혜택으로이 토론은 이미 어떤 것을 사용해야하는지 string.Empty또는 ""... :) 토론을 해결했습니다.
Gleno

그들 사이의 IL이 동일합니까?
Cole Johnson

49
좋은 분석! BCL 팀에 전달하겠습니다. 감사!
Eric Lippert

2
@EricLippert 및 기타 : typeof(string).GetField("Empty").SetValue(null, "Hello world!"); Console.WriteLine(string.Empty);.NET 4.0과 .NET 4.5에서 와 같은 코드 는 다른 결과를 제공 한다는 것을 발견했습니다 . 이 변경이 위에서 설명한 변경과 관련이 있습니까? .NET 4.5 에서 필드 값 변경을 기술적으로 어떻게 무시할 수 있습니까? 아마도 이것에 대해 새로운 질문을해야할까요?
Jeppe Stig Nielsen

4
@JeppeStigNielsen : 귀하의 질문에 대한 답변은 "아마도", "쉽게, 명백하게", "이것은 질문과 답변 사이트입니다. 예, 더 나은 답변을 원한다면 좋은 생각입니다 '아마도'보다
Eric Lippert

3

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

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

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

내 추측:

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

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

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

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


B정적 생성자가없는 경우에도 버그가 여전히 발생 A하며 값 유형으로 수정 된 경우에는 발생하지 않습니다 . 좀 더 복잡하다고 생각합니다.
Michael Graczyk 1

@ MichaelGraczyk : 나는 다시 추측 할 수 있다고 생각합니다. B정적 생성자를 갖는 것은 중요하지 않습니다. A정적 ctor가 있기 때문에 런타임은 다른 네임 스페이스의 일부 로캘 관련 클래스와 비교할 때 실행 순서를 엉망으로 만듭니다. 따라서 해당 필드는 아직 초기화되지 않았습니다. 그러나 A값 유형으로 인스턴스화 하는 경우 인스턴스화 를 통한 런타임의 두 번째 패스 일 수 있습니다 A(CLR이 이미 최적화로 참조 유형을 사용하여 사전 인스턴스화했을 가능성이 높으므로). .
user541686

@MichaelGraczyk : 비록 이것이 설명이 아니더라도, 주어진 beforefieldinit최적화가 근본 원인 이라고 확신합니다 . 실제 설명 중 일부는 내가 언급 한 것과 다를 수 있지만 근본 원인은 같은 것입니다.
user541686

나는 일리노이를 더 조사했고, 당신이 무언가에 있다고 생각합니다. 두 번째 패스 아이디어가 여기에 관련이 있다고 생각하지 않습니다 A<object>.ctor(). 임의로 코드를 여러 번 호출하면 코드가 여전히 실패하기 때문에
Michael Graczyk

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

그리고 그 속성? 우리는 이미 ""해결했습니다.
Henk Holterman

"성능이 중요합니다 ..."속성은 속성이 장식하는 메소드가 아니라 속성 생성자 자체에 영향을줍니다.
Michael Graczyk

내부입니다. 내 고유의 동일한 속성을 정의해도 여전히 MDA가 발생하지 않습니다. JITter가 특정 속성을 찾고 있다면 내 것을 찾지 못할 것입니다.
lesscode
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.