Nguyên nhân của FatalExecutEngineError trong .NET 4.5 beta là gì? [đóng cửa]


150

Mã mẫu dưới đây xảy ra một cách tự nhiên. Đột nhiên mã của tôi là một FatalExecutionEngineErrorngoại lệ nghe rất khó chịu . Tôi đã dành 30 phút cố gắng để cô lập và giảm thiểu mẫu thủ phạm. Biên dịch cái này bằng Visual Studio 2012 dưới dạng một ứng dụng console:

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

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

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

Nên tạo lỗi này trên .NET framework 4 và 4.5:

Ảnh chụp màn hình FatalExecutException

Đây có phải là một lỗi đã biết, nguyên nhân là gì và tôi có thể làm gì để giảm thiểu nó? Công việc hiện tại của tôi là không sử dụng string.Empty, nhưng tôi đang sủa sai cây? Thay đổi bất cứ điều gì về mã đó làm cho nó hoạt động như bạn mong đợi - ví dụ: loại bỏ hàm tạo tĩnh trống Ahoặc thay đổi tham số loại từ objectsang int.

Tôi đã thử mã này trên máy tính xách tay của tôi và nó đã không phàn nàn. Tuy nhiên, tôi đã thử ứng dụng chính của mình và nó cũng bị lỗi trên máy tính xách tay. Tôi đã phải xử lý một cái gì đó khi giảm bớt vấn đề, tôi sẽ xem liệu tôi có thể tìm ra đó là gì không.

Máy tính xách tay của tôi bị lỗi với cùng mã như trên, với khung 4.0, nhưng chính bị hỏng ngay cả với 4.5. Cả hai hệ thống đang sử dụng VS'12 với các bản cập nhật mới nhất (tháng 7?).

Thêm thông tin :

  • Mã IL (Gỡ lỗi được biên dịch / Bất kỳ CPU / 4.0 / VS2010 (không phải IDE nên có vấn đề?)): Http://codepad.org/boZDd98E
  • Không thấy VS 2010 với 4.0. Không gặp sự cố với / không tối ưu hóa, CPU mục tiêu khác nhau, trình gỡ lỗi được đính kèm / không được đính kèm, v.v. - Tim Medora
  • Sự cố trong năm 2010 nếu tôi sử dụng AnyCPU, sẽ ổn trong x86. Sự cố trong Visual Studio 2010 SP1, sử dụng Platform Target = AnyCPU, nhưng tốt với Platform Target = x86. Máy này đã cài đặt VS2012RC, vì vậy 4.5 có thể thực hiện thay thế tại chỗ. Sử dụng AnyCPU và TargetPl Platform = 3.5 thì nó không gặp sự cố nên trông giống như hồi quy trong Framework.- colinsmith
  • Không thể sao chép trên x86, x64 hoặc AnyCPU trong VS2010 với 4.0. - Phú Sĩ
  • Chỉ xảy ra với x64, (2012rc, Fx4.5) - Henk Holterman
  • RC VS2012 trên Win8 RP. Ban đầu Không thấy MDA này khi nhắm mục tiêu .NET 4.5. Khi chuyển sang nhắm mục tiêu .NET 4.0, MDA xuất hiện. Sau đó, sau khi chuyển trở lại .NET 4.5, MDA vẫn còn. - Wayne

Tôi không bao giờ biết bạn có thể tạo một hàm tạo tĩnh cùng với một công khai. Heck tôi không bao giờ biết các nhà xây dựng tĩnh tồn tại.
Cole Johnson

Tôi có một ý tưởng: bởi vì bạn đang thay đổi B từ một lớp tĩnh thành một lớp có Main tĩnh?
Cole Johnson

@ChrisSinclair, tôi không nghĩ vậy. Ý tôi là tôi đã kiểm tra mã này trên máy tính xách tay của mình và nhận được kết quả tương tự.
Gleno

@ColeJohnson Có IL phù hợp với tất cả nhưng một nơi rõ ràng. Dường như không có bất kỳ lỗi nào ở đây trong trình biên dịch c #.
Michael Grachot

14
Cảm ơn cả poster gốc đã báo cáo nó ở đây và Michael vì sự phân tích tuyệt vời của anh ấy. Các đối tác của tôi về CLR đã cố gắng tái tạo lỗi ở đây và phát hiện ra rằng nó sao chép trên phiên bản "Ứng cử viên phát hành" của CLR 64 bit, nhưng không phải trên phiên bản "Phát hành để sản xuất" cuối cùng, có một số bản sửa lỗi sau RC. (Phiên bản RTM sẽ có sẵn cho công chúng vào ngày 15 tháng 8, 2012.) Do đó, họ tin rằng đây là vấn đề tương tự như một trong đó đã được báo cáo ở đây: connect.microsoft.com/VisualStudio/feedback/details/737108/...
Eric Lippert

Câu trả lời:


114

Đây cũng không phải là một câu trả lời đầy đủ, nhưng tôi có một vài ý tưởng.

Tôi tin rằng tôi đã tìm thấy một lời giải thích tốt như chúng ta sẽ tìm thấy mà không có ai từ nhóm .NET JIT trả lời.

CẬP NHẬT

Tôi nhìn sâu hơn một chút, và tôi tin rằng tôi đã tìm ra nguồn gốc của vấn đề. Nó xuất hiện do sự kết hợp của một lỗi trong logic khởi tạo kiểu JIT và thay đổi trình biên dịch C # dựa trên giả định rằng JIT hoạt động như dự định. Tôi nghĩ rằng lỗi JIT tồn tại trong .NET 4.0, nhưng đã bị phát hiện bởi sự thay đổi trong trình biên dịch cho .NET 4.5.

Tôi không nghĩ đó beforefieldinitlà vấn đề duy nhất ở đây. Tôi nghĩ nó đơn giản hơn thế.

Loại System.Stringtrong mscorlib.dll từ .NET 4.0 chứa hàm tạo tĩnh:

.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

Trong phiên bản .NET 4.5 của mscorlib.dll, String.cctor(hàm tạo tĩnh) vắng mặt một cách rõ rệt:

..... Không có hàm tạo tĩnh :( .....

Trong cả hai phiên bản, Stringloại được trang trí bằng beforefieldinit:

.class public auto ansi serializable sealed beforefieldinit System.String

Tôi đã cố gắng tạo một kiểu sẽ biên dịch tương tự IL (để nó có các trường tĩnh nhưng không có hàm tạo tĩnh .cctor), nhưng tôi không thể làm được. Tất cả các loại này có một .cctorphương thức trong 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(); } 
}

Tôi đoán là hai điều đã thay đổi giữa .NET 4.0 và 4.5:

Đầu tiên: EE được thay đổi để nó sẽ tự động khởi tạo String.Emptytừ mã không được quản lý. Thay đổi này có lẽ được thực hiện cho .NET 4.0.

Thứ hai: Trình biên dịch đã thay đổi để nó không phát ra một hàm tạo tĩnh cho chuỗi, biết rằng nó String.Emptysẽ được gán từ phía không được quản lý. Thay đổi này dường như đã được thực hiện cho .NET 4.5.

Dường như EE không chỉ định String.Emptyđủ sớm theo một số đường dẫn tối ưu hóa. Thay đổi được thực hiện cho trình biên dịch (hoặc bất cứ điều gì đã thay đổi để String.cctorbiến mất) mong muốn EE thực hiện phép gán này trước khi bất kỳ mã người dùng nào thực thi, nhưng có vẻ như EE không thực hiện phép gán này trước khi String.Emptyđược sử dụng trong các phương thức của các lớp chung loại tham chiếu.

Cuối cùng, tôi tin rằng lỗi này là dấu hiệu của một vấn đề sâu sắc hơn trong logic khởi tạo kiểu JIT. Có vẻ như sự thay đổi trong trình biên dịch là một trường hợp đặc biệt System.String, nhưng tôi nghi ngờ rằng JIT đã tạo ra một trường hợp đặc biệt ở đây cho System.String.

Nguyên

Trước hết, WOW Người BCL đã rất sáng tạo với một số tối ưu hóa hiệu suất. Nhiều trong những Stringphương pháp hiện nay được thực hiện bằng cách sử dụng Chủ đề tĩnh cache StringBuilderđối tượng.

Tôi đã theo hướng dẫn đó trong một thời gian, nhưng StringBuilderkhông được sử dụng trên Trimđường dẫn mã, vì vậy tôi quyết định đó không phải là vấn đề tĩnh.

Tôi nghĩ rằng tôi đã tìm thấy một biểu hiện kỳ ​​lạ của cùng một lỗi.

Mã này không thành công với vi phạm truy cập:

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

Tuy nhiên, nếu bạn bỏ ghi chú //new A<int>(out s);trong Mainđó các mã làm việc tốt. Trong thực tế, nếu Ađược thống nhất với bất kỳ loại tham chiếu nào, chương trình sẽ thất bại, nhưng nếu Ađược thống nhất với bất kỳ loại giá trị nào thì mã không bị lỗi. Ngoài ra nếu bạn nhận xét ra hàm tạo Atĩnh, mã không bao giờ bị lỗi. Sau khi đào sâu vào TrimFormat, rõ ràng vấn đề là nó Lengthđang được nội tuyến và trong các mẫu này ở trên Stringloại chưa được khởi tạo. Cụ thể, bên trong phần thân của hàm Atạo, string.Emptykhông được gán chính xác, mặc dù bên trong phần thân của Main, string.Emptyđược gán chính xác.

Điều đáng ngạc nhiên với tôi là việc khởi tạo kiểu Stringbằng cách nào đó phụ thuộc vào việc Acó hợp nhất với loại giá trị hay không . Lý thuyết duy nhất của tôi là có một số đường dẫn mã JIT tối ưu hóa cho khởi tạo kiểu chung được chia sẻ giữa tất cả các loại và đường dẫn đó đưa ra các giả định về các loại tham chiếu BCL ("các loại đặc biệt?") Và trạng thái của chúng. Nhìn nhanh mặc dù các lớp BCL khác với public staticcác trường cho thấy về cơ bản tất cả chúng đều thực hiện một hàm tạo tĩnh (ngay cả những lớp có hàm tạo rỗng và không có dữ liệu, như System.DBNullSystem.Emptycác loại giá trị BCL với public staticcác trường dường như không triển khai hàm tạo tĩnh ( System.IntPtrví dụ) Điều này dường như chỉ ra rằng JIT đưa ra một số giả định về khởi tạo loại tham chiếu BCL.

FYI Đây là mã JITed cho hai phiên bản:

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 
    }

Phần còn lại của mã ( Main) giống hệt nhau giữa hai phiên bản.

BIÊN TẬP

Ngoài ra, IL từ hai phiên bản giống hệt nhau ngoại trừ lệnh gọi đến , A.ctortrong B.Main()đó IL cho phiên bản đầu tiên chứa:

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

đấu với

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

trong lần thứ hai

Một điều cần lưu ý là mã JITed cho A<int>.ctor(out string): giống như trong phiên bản không chung chung.


3
Tôi đã tìm kiếm câu trả lời dọc theo một con đường rất giống nhau, nhưng dường như nó không dẫn đến đâu cả. Đây dường như là một vấn đề lớp chuỗi và hy vọng không phải là một vấn đề chung hơn. Vì vậy, ngay bây giờ tôi đang đợi ai đó (Eric) có mã nguồn đi cùng và giải thích điều gì đã xảy ra, và nếu có bất cứ điều gì khác xảy ra. Như một lợi ích nhỏ, cuộc thảo luận này đã giải quyết cuộc tranh luận liệu người ta nên sử dụng string.Emptyhay ""... :)
Gleno

Là IL giữa họ giống nhau?
Cole Johnson

49
Phân tích tốt! Tôi sẽ chuyển nó cho đội BCL. Cảm ơn!
Eric Lippert

2
@EricLippert và những người khác: Tôi phát hiện ra rằng mã như typeof(string).GetField("Empty").SetValue(null, "Hello world!"); Console.WriteLine(string.Empty);cho kết quả khác nhau trên .NET 4.0 so với .NET 4.5. Sự thay đổi này có liên quan đến sự thay đổi được mô tả ở trên không? Làm thế nào .NET 4.5 về mặt kỹ thuật có thể bỏ qua việc tôi thay đổi giá trị trường? Có lẽ tôi nên hỏi một câu hỏi mới về điều này?
Jeppe Stig Nielsen

4
@JeppeStigNielsen: Các câu trả lời cho câu hỏi của bạn là: "có thể", "khá dễ dàng, rõ ràng" và "đây là một trang web hỏi và trả lời, vì vậy, đó là một ý tưởng tốt nếu bạn muốn có câu trả lời cho câu hỏi của mình tốt hơn hơn "có thể" ".
Eric Lippert

3

Tôi hoàn toàn nghi ngờ điều này là do sự tối ưu hóa này (liên quan đến BeforeFieldInit) trong .NET 4.0.

Nếu tôi nhớ chính xác:

Khi bạn khai báo một hàm tạo tĩnh một cách rõ ràng, beforefieldinitđược phát ra, báo cho bộ thực thi rằng hàm tạo tĩnh phải được chạy trước mọi truy cập thành viên tĩnh .

Tôi đoán:

Tôi đoán rằng bằng cách nào đó họ đã làm hỏng sự thật này trên x64 JITer, để khi một thành viên tĩnh của một loại khác được truy cập từ một lớp có hàm tạo tĩnh riêng của nó đã chạy, bằng cách nào đó nó bỏ qua việc chạy (hoặc thực thi sai thứ tự) hàm tạo tĩnh - và do đó gây ra sự cố. (Bạn không nhận được ngoại lệ con trỏ null, có thể vì nó không được khởi tạo null.)

Tôi chưa chạy mã của bạn, vì vậy phần này có thể sai - nhưng nếu tôi phải đoán khác, tôi có thể nói đó có thể là một cái gì đó string.Format(hoặc Console.WriteLine, tương tự) cần truy cập nội bộ gây ra sự cố, chẳng hạn như có lẽ một lớp liên quan đến miền địa phương cần xây dựng tĩnh rõ ràng.

Một lần nữa, tôi đã không kiểm tra nó, nhưng đó là phỏng đoán tốt nhất của tôi về dữ liệu.

Hãy kiểm tra giả thuyết của tôi và cho tôi biết nó diễn ra như thế nào.


Lỗi vẫn xảy ra khi Bkhông có hàm tạo tĩnh và nó không xảy ra khi Ađược thống nhất với loại giá trị. Tôi nghĩ nó phức tạp hơn một chút.
Michael Grachot

@MichaelGracot: Tôi nghĩ rằng tôi có thể giải thích điều đó (một lần nữa, với những phỏng đoán). Bcó một hàm tạo tĩnh không quan trọng lắm. Vì Acó một ctor tĩnh, thời gian chạy làm rối trật tự mà nó được chạy khi so sánh với một số lớp liên quan đến miền địa phương trong một số không gian tên khác. Vì vậy, lĩnh vực đó chưa được khởi tạo. Tuy nhiên, nếu bạn khởi tạo Avới một loại giá trị, thì đó có thể là lần thứ hai của thời gian chạy thông qua khởi tạo A(CLR có thể đã khởi tạo trước nó với một loại tham chiếu, như một tối ưu hóa) để đơn hàng hoạt động khi nó chạy lần thứ hai .
dùng541686

@MichaelGracot: Mặc dù điều này không hoàn toàn là lời giải thích, mặc dù vậy - tôi nghĩ tôi khá tin rằng beforefieldinittối ưu hóa đã cho là nguyên nhân gốc rễ. Có thể là một số lời giải thích thực tế khác với những gì tôi đã đề cập, nhưng nguyên nhân gốc rễ có thể là điều tương tự.
dùng541686

Tôi nhìn vào IL nhiều hơn, và tôi nghĩ rằng bạn đang ở một cái gì đó. Tôi không nghĩ rằng ý tưởng vượt qua thứ hai sẽ có liên quan ở đây, bởi vì mã vẫn thất bại nếu tôi tự ý thực hiện nhiều cuộc gọi đến A<object>.ctor().
Michael Grachot

@MichaelGracot: Rất vui được nghe, và cảm ơn vì bài kiểm tra đó. Thật không may, tôi không thể sao chép nó trên máy tính xách tay của mình. (2010 4.0 x64) Bạn có thể kiểm tra xem liệu nó có thực sự liên quan đến định dạng chuỗi (tức là liên quan đến ngôn ngữ không)? Điều gì xảy ra nếu bạn loại bỏ phần đó?
dùng541686

1

Một quan sát, nhưng DotPeek hiển thị chuỗi dịch ngược .Empty do đó:

/// <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()
  {
  }
}

Nếu tôi khai báo theo cách của riêng mình Emptytheo cách tương tự ngoại trừ không có thuộc tính, tôi không còn nhận được MDA:

class A<T>
{
    static readonly string Empty;

    static A() { }

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

với thuộc tính đó? Chúng tôi đã thành lập ""giải quyết nó.
Henk Holterman

Thuộc tính "Hiệu suất quan trọng ..." đó ảnh hưởng đến chính hàm tạo Thuộc tính, chứ không phải các phương thức mà thuộc tính tô điểm.
Michael Grachot

Đó là nội bộ. Khi tôi xác định thuộc tính giống hệt của mình, nó vẫn không gây ra MDA. Không phải là tôi mong đợi nó - nếu JITter đang tìm kiếm thuộc tính cụ thể đó thì nó sẽ không tìm thấy thuộc tính của tôi.
lesscode
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.