Làm cho ngôn ngữ .NET của bạn chính xác bước trong trình gỡ lỗi


135

Đầu tiên, tôi xin lỗi về độ dài của câu hỏi này.

Tôi là tác giả của IronScheme . Gần đây tôi đã làm việc chăm chỉ để phát ra thông tin gỡ lỗi đàng hoàng, để tôi có thể sử dụng trình gỡ lỗi .NET 'bản địa'.

Trong khi điều này đã thành công một phần, tôi đang gặp phải một số vấn đề về mọc răng.

Vấn đề đầu tiên liên quan đến bước.

Do lược đồ là ngôn ngữ biểu thức, mọi thứ có xu hướng được gói trong ngoặc đơn, không giống như các ngôn ngữ .NET chính dường như là câu lệnh (hoặc dòng) dựa trên.

Mã ban đầu (Scheme) trông giống như:

(define (baz x)
  (cond
    [(null? x) 
      x]
    [(pair? x) 
      (car x)]
    [else
      (assertion-violation #f "nooo" x)]))

Tôi có mục đích đặt ra từng biểu thức trên một dòng mới.

Mã được phát ra biến đổi thành C # (thông qua ILSpy) trông giống như:

public static object ::baz(object x)
{
  if (x == null)
  {
    return x;
  }
  if (x is Cons)
  {
    return Builtins.Car(x);
  }
  return #.ironscheme.exceptions::assertion-violation+(
     RuntimeHelpers.False, "nooo", Builtins.List(x));
}

Như bạn thấy, khá đơn giản.

Lưu ý: Nếu mã được chuyển thành biểu thức có điều kiện (? :) trong C #, toàn bộ điều sẽ chỉ là một bước gỡ lỗi, hãy ghi nhớ điều đó.

Đây là đầu ra IL với số nguồn và số dòng:

  .method public static object  '::baz'(object x) cil managed
  {
    // Code size       56 (0x38)
    .maxstack  6
    .line 15,15 : 1,2 ''
//000014: 
//000015: (define (baz x)
    IL_0000:  nop
    .line 17,17 : 6,15 ''
//000016:   (cond
//000017:     [(null? x) 
    IL_0001:  ldarg.0
    IL_0002:  brtrue     IL_0009

    .line 18,18 : 7,8 ''
//000018:       x]
    IL_0007:  ldarg.0
    IL_0008:  ret

    .line 19,19 : 6,15 ''
//000019:     [(pair? x) 
    .line 19,19 : 6,15 ''
    IL_0009:  ldarg.0
    IL_000a:  isinst [IronScheme]IronScheme.Runtime.Cons
    IL_000f:  ldnull
    IL_0010:  cgt.un
    IL_0012:  brfalse    IL_0020

    IL_0017:  ldarg.0
    .line 20,20 : 7,14 ''
//000020:       (car x)]
    IL_0018:  tail.
    IL_001a:  call object [IronScheme]IronScheme.Runtime.Builtins::Car(object)
    IL_001f:  ret

    IL_0020:  ldsfld object 
         [Microsoft.Scripting]Microsoft.Scripting.RuntimeHelpers::False
    IL_0025:  ldstr      "nooo"
    IL_002a:  ldarg.0
    IL_002b:  call object [IronScheme]IronScheme.Runtime.Builtins::List(object)
    .line 22,22 : 7,40 ''
//000021:     [else
//000022:       (assertion-violation #f "nooo" x)]))
    IL_0030:  tail.
    IL_0032:  call object [ironscheme.boot]#::
       'ironscheme.exceptions::assertion-violation+'(object,object,object)
    IL_0037:  ret
  } // end of method 'eval-core(033)'::'::baz'

Lưu ý: Để ngăn trình gỡ lỗi chỉ cần làm nổi bật toàn bộ phương thức, tôi tạo điểm nhập phương thức chỉ rộng 1 cột.

Như bạn có thể thấy, mỗi biểu thức ánh xạ chính xác đến một dòng.

Bây giờ vấn đề với bước (thử nghiệm trên VS2010, nhưng vấn đề tương tự / tương tự trên VS2008):

Đây là với IgnoreSymbolStoreSequencePointskhông áp dụng.

  1. Gọi baz với null arg, nó hoạt động chính xác. (null? x) theo sau là x.
  2. Gọi baz với Cons arg, nó hoạt động chính xác. (null? x) rồi (cặp? x) rồi (xe x).
  3. Gọi baz với arg khác, nó thất bại. (null? x) rồi (cặp? x) rồi (xe x) rồi (vi phạm khẳng định ...).

Khi nộp đơn IgnoreSymbolStoreSequencePoints(theo khuyến nghị):

  1. Gọi baz với null arg, nó hoạt động chính xác. (null? x) theo sau là x.
  2. Gọi baz với Cons arg, nó thất bại. (null? x) sau đó (cặp? x).
  3. Gọi baz với arg khác, nó thất bại. (null? x) rồi (cặp? x) rồi (xe x) rồi (vi phạm khẳng định ...).

Tôi cũng thấy trong chế độ này, một số dòng (không được hiển thị ở đây) được tô sáng không chính xác, chúng bị tắt bởi 1.

Dưới đây là một số ý tưởng những gì có thể là nguyên nhân:

  • Tailcalls nhầm lẫn trình gỡ lỗi
  • Các vị trí chồng lấp (không hiển thị ở đây) gây nhầm lẫn cho trình gỡ lỗi (nó rất tốt khi đặt điểm dừng)
  • ????

Vấn đề thứ hai, nhưng cũng nghiêm trọng là trình gỡ lỗi không phá được / chạm các điểm dừng trong một số trường hợp.

Nơi duy nhất mà tôi có thể khiến trình gỡ lỗi phá vỡ chính xác (và nhất quán), là tại điểm nhập phương thức.

Tình hình trở nên tốt hơn một chút khi IgnoreSymbolStoreSequencePointskhông được áp dụng.

Phần kết luận

Có thể là trình gỡ lỗi VS chỉ là lỗi đơn giản :(

Người giới thiệu:

  1. Tạo một ngôn ngữ CLR / .NET có thể gỡ lỗi

Cập nhật 1:

Mdbg không hoạt động cho các cụm 64 bit. Vì vậy, đó là ra. Tôi không còn máy 32-bit để kiểm tra. Cập nhật: Tôi chắc chắn đây không phải là vấn đề lớn, có ai có cách khắc phục không? Chỉnh sửa: Có, ngớ ngẩn với tôi, chỉ cần bắt đầu mdbg dưới dấu nhắc lệnh x64 :)

Cập nhật 2:

Tôi đã tạo một ứng dụng C # và cố gắng phân tích thông tin dòng.

Phát hiện của tôi:

  • Sau bất kỳ brXXXhướng dẫn nào, bạn cần phải có một điểm thứ tự (nếu không hợp lệ hay còn gọi là '#line hidden', phát ra a nop).
  • Trước bất kỳ brXXXhướng dẫn nào , hãy phát ra một '#line hidden' và a nop.

Áp dụng điều này, tuy nhiên không khắc phục được các vấn đề (một mình?).

Nhưng thêm vào sau đây, cho kết quả mong muốn :)

  • Sau đó ret, phát ra một '#line ẩn' và a nop.

Đây là sử dụng chế độ IgnoreSymbolStoreSequencePointskhông được áp dụng. Khi áp dụng, một số bước vẫn bị bỏ qua :(

Đây là đầu ra IL khi ở trên đã được áp dụng:

  .method public static object  '::baz'(object x) cil managed
  {
    // Code size       63 (0x3f)
    .maxstack  6
    .line 15,15 : 1,2 ''
    IL_0000:  nop
    .line 17,17 : 6,15 ''
    IL_0001:  ldarg.0
    .line 16707566,16707566 : 0,0 ''
    IL_0002:  nop
    IL_0003:  brtrue     IL_000c

    .line 16707566,16707566 : 0,0 ''
    IL_0008:  nop
    .line 18,18 : 7,8 ''
    IL_0009:  ldarg.0
    IL_000a:  ret

    .line 16707566,16707566 : 0,0 ''
    IL_000b:  nop
    .line 19,19 : 6,15 ''
    .line 19,19 : 6,15 ''
    IL_000c:  ldarg.0
    IL_000d:  isinst     [IronScheme]IronScheme.Runtime.Cons
    IL_0012:  ldnull
    IL_0013:  cgt.un
    .line 16707566,16707566 : 0,0 ''
    IL_0015:  nop
    IL_0016:  brfalse    IL_0026

    .line 16707566,16707566 : 0,0 ''
    IL_001b:  nop
    IL_001c:  ldarg.0
    .line 20,20 : 7,14 ''
    IL_001d:  tail.
    IL_001f:  call object [IronScheme]IronScheme.Runtime.Builtins::Car(object)
    IL_0024:  ret

    .line 16707566,16707566 : 0,0 ''
    IL_0025:  nop
    IL_0026:  ldsfld object 
      [Microsoft.Scripting]Microsoft.Scripting.RuntimeHelpers::False
    IL_002b:  ldstr      "nooo"
    IL_0030:  ldarg.0
    IL_0031:  call object [IronScheme]IronScheme.Runtime.Builtins::List(object)
    .line 22,22 : 7,40 ''
    IL_0036:  tail.
    IL_0038:  call object [ironscheme.boot]#::
      'ironscheme.exceptions::assertion-violation+'(object,object,object)
    IL_003d:  ret

    .line 16707566,16707566 : 0,0 ''
    IL_003e:  nop
  } // end of method 'eval-core(033)'::'::baz'

Cập nhật 3:

Vấn đề với 'bán sửa chữa' ở trên. Peverify báo cáo lỗi trên tất cả các phương pháp do nopsau ret. Tôi không hiểu vấn đề thực sự. Làm thế nào có thể nopphá vỡ xác minh sau một ret. Nó giống như mã chết (ngoại trừ mã KHÔNG phải là mã) ... Ồ, thử nghiệm vẫn tiếp tục.

Cập nhật 4:

Quay trở về nhà ngay bây giờ, xóa mã 'không thể xác minh', chạy trên VS2008 và mọi thứ còn tệ hơn rất nhiều. Có lẽ chạy mã không thể kiểm chứng vì mục đích gỡ lỗi thích hợp có thể là câu trả lời. Trong chế độ 'phát hành', tất cả đầu ra vẫn có thể kiểm chứng được.

Cập nhật 5:

Bây giờ tôi đã quyết định ý tưởng trên của tôi là lựa chọn khả thi duy nhất cho đến bây giờ. Mặc dù mã được tạo là không thể xác minh, tôi vẫn chưa tìm thấy bất kỳ VerificationException. Tôi không biết tác động sẽ ra sao đối với người dùng cuối với kịch bản này.

Như một phần thưởng, vấn đề thứ hai của tôi cũng đã được giải quyết. :)

Đây là một chút screencast về những gì tôi đã kết thúc với. Nó đạt điểm dừng, thực hiện bước thích hợp (vào / ra / qua), v.v ... Tất cả trong tất cả, hiệu quả mong muốn.

Tôi, tuy nhiên, tôi vẫn không chấp nhận điều này như là cách để làm điều đó. Nó cảm thấy quá khó khăn với tôi. Có một xác nhận về vấn đề thực sự sẽ là tốt đẹp.

Cập nhật 6:

Chỉ cần thay đổi để kiểm tra mã trên VS2010, dường như có một số vấn đề:

  1. Cuộc gọi đầu tiên bây giờ không bước chính xác. (khẳng định-vi phạm ...) bị đánh. Các trường hợp khác hoạt động tốt. Một số mã cũ phát ra các vị trí không cần thiết. Đã xóa mã, hoạt động như mong đợi. :)
  2. Nghiêm trọng hơn, các điểm dừng không thành công trong lần gọi thứ hai của chương trình (sử dụng biên dịch trong bộ nhớ, lắp ráp kết xuất vào tệp dường như làm cho các điểm dừng vui vẻ trở lại).

Cả hai trường hợp này hoạt động chính xác theo VS2008. Sự khác biệt chính là theo VS2010, toàn bộ ứng dụng được biên dịch cho .NET 4 và dưới VS2008, biên dịch sang .NET 2. Cả hai đều chạy 64 bit.

Cập nhật 7:

Giống như đã đề cập, tôi có mdbg chạy dưới 64-bit. Thật không may, nó cũng có vấn đề về điểm dừng khi nó không bị hỏng nếu tôi chạy lại chương trình (điều này ngụ ý rằng nó được biên dịch lại, do đó không sử dụng cùng một cụm, nhưng vẫn sử dụng cùng một nguồn).

Cập nhật 8:

Tôi đã gửi một lỗi tại trang web MS Connect liên quan đến vấn đề điểm dừng.

Cập nhật: Đã sửa

Cập nhật 9:

Sau một thời gian dài suy nghĩ, cách duy nhất để làm cho trình gỡ lỗi hài lòng dường như là thực hiện SSA, vì vậy mọi bước có thể được tách biệt và tuần tự. Tôi vẫn chưa chứng minh khái niệm này mặc dù. Nhưng nó có vẻ hợp lý. Rõ ràng, việc dọn dẹp temps từ SSA sẽ phá vỡ việc gỡ lỗi, nhưng điều đó rất dễ để chuyển đổi và khiến chúng không có nhiều chi phí.


Bất kỳ ý tưởng nào được chào đón, tôi sẽ thử chúng và nối kết quả cho câu hỏi. 2 ý tưởng đầu tiên, hãy thử mdbg và viết cùng một mã trong C # và so sánh.
leppie

6
Câu hỏi là gì?
George Stocker

9
Tôi nghĩ rằng câu hỏi của anh ấy là dọc theo dòng chữ "tại sao ngôn ngữ của tôi không bước chính xác?"
McKay

1
Nếu bạn không vượt qua peverify, bạn sẽ không thể chạy trong các tình huống tin cậy một phần, như từ một ổ đĩa được ánh xạ hoặc trong nhiều thiết lập lưu trữ plugin. Làm thế nào bạn tạo IL, Reflection.Emit hoặc thông qua văn bản + ilasm? Trong cả hai trường hợp, bạn luôn có thể đặt vào .line 16707566,16707566: 0,0 '' theo cách đó mà bạn không phải lo lắng về việc họ sẽ đặt một cái nop sau khi trở về.
Hippiehunter

@Hippiehunter: Cảm ơn. Thật không may nếu không có nops, bước không thành công (tôi sẽ xác minh lại điều này một lần nữa để chắc chắn). Đó là một sự hy sinh tôi đoán tôi sẽ phải thực hiện. Nó không giống như VS thậm chí có thể chạy mà không có quyền quản trị viên :) Btw sử dụng Reflection.Emit thông qua DLR (một nhánh rất sớm bị hack).
leppie

Câu trả lời:


26

Tôi là một kỹ sư trong nhóm Trình gỡ lỗi Visual Studio.

Sửa lỗi cho tôi nếu tôi sai, nhưng có vẻ như vấn đề duy nhất còn lại là khi chuyển từ PDB sang định dạng biểu tượng biên dịch động .NET 4, một số điểm dừng bị bỏ sót.

Chúng tôi có lẽ sẽ cần một repro để chẩn đoán chính xác vấn đề, tuy nhiên đây là một số lưu ý có thể giúp ích.

  1. VS (2008+) có thể chạy như một người không phải quản trị viên
  2. Có bất kỳ biểu tượng tải ở tất cả các lần thứ hai xung quanh? Bạn có thể kiểm tra bằng cách đột nhập (thông qua ngoại lệ hoặc gọi System.Diagnellect.Debugger.Break ())
  3. Giả sử rằng các biểu tượng tải, có một repro mà bạn có thể gửi cho chúng tôi?
  4. Sự khác biệt có thể là định dạng ký hiệu cho mã được biên dịch động khác nhau 100% giữa .NET 2 (luồng PDB) và .NET 4 (IL DB tôi nghĩ họ gọi nó là gì?)
  5. Âm thanh của 'nop về bên phải. Xem quy tắc để tạo các điểm trình tự ngầm bên dưới.
  6. Bạn thực sự không cần phải phát ra những thứ trên các dòng khác nhau. Theo mặc định, VS sẽ bước 'tuyên bố biểu tượng' trong đó, với tư cách là người viết trình biên dịch bạn có thể định nghĩa 'tuyên bố biểu tượng' nghĩa là gì. Vì vậy, nếu bạn muốn mỗi biểu thức là một thứ riêng biệt trong tệp biểu tượng, nó sẽ hoạt động tốt.

JIT tạo một điểm chuỗi ẩn dựa trên các quy tắc sau: 1. Hướng dẫn IL nop 2. IL stack điểm trống 3. Lệnh IL ngay lập tức theo lệnh gọi

Nếu hóa ra chúng tôi cần một bản sửa lỗi để giải quyết vấn đề của bạn, bạn có thể gửi một lỗi kết nối và tải lên các tệp một cách an toàn thông qua phương tiện đó.

Cập nhật:

Chúng tôi đang khuyến khích những người dùng khác gặp phải vấn đề này để dùng thử Bản xem trước dành cho nhà phát triển của Dev11 từ http://www.microsoft.com/doad/en/details.aspx?displaylang=en&id=27543 và nhận xét với bất kỳ phản hồi nào. (Phải nhắm mục tiêu 4,5)

Cập nhật 2:

Leppie đã xác minh bản sửa lỗi để hoạt động cho anh ta trên phiên bản Beta của Dev11 có sẵn tại địa chỉ http://www.microsoft.com/visualstudio/11/en-us/doads như đã lưu ý trong lỗi kết nối https://connect.microsoft. com / VisualStudio / phản hồi / chi tiết / 684089 / .

Cảm ơn,

Luke


Cảm ơn rất nhiều. Tôi sẽ thử làm repro, nếu cần.
leppie

Đối với xác nhận, có bạn là chính xác, nhưng tôi vẫn muốn giải quyết các vấn đề bước mà không phá vỡ tính xác minh. Nhưng như đã nói, tôi sẵn sàng hy sinh rằng nếu tôi có thể chứng minh rằng nó sẽ không bao giờ ném thời gian chạy VerificationException.
leppie

Đối với điểm 6, tôi chỉ đơn giản làm cho nó 'dựa trên dòng' cho câu hỏi này. Trình gỡ lỗi thực tế thực hiện chính xác các bước vào / ra / trên các biểu thức như tôi dự định nó hoạt động :) Vấn đề duy nhất với cách tiếp cận là đặt các điểm dừng trên các biểu thức 'bên trong' nếu biểu thức 'bên ngoài' che phủ nó; trình gỡ lỗi trong VS có xu hướng muốn tạo ra biểu thức bên ngoài nhất làm điểm dừng.
leppie

Tôi nghĩ rằng tôi đã cô lập vấn đề điểm dừng. Trong khi chạy theo CLR2, các điểm dừng dường như được đánh giá lại khi chạy lại mã, mặc dù mã mới. Trên CLR4, các điểm dừng chỉ 'dính' vào mã gốc. Tôi đã thực hiện một đoạn ghi hình nhỏ về hành vi CLR4 @ screencast.com/t/eiSilNzL5Nr . Lưu ý rằng tên loại thay đổi giữa các yêu cầu.
leppie

Tôi đã nộp một lỗi trên kết nối. Việc repro khá dễ dàng :) connect.microsoft.com/VisualStudio/feedback/details/684089
leppie

3

Tôi là kỹ sư của nhóm Trình gỡ lỗi SharpDevelop :-)

Bạn đã giải quyết vấn đề chưa?

Bạn đã thử gỡ lỗi nó trong SharpDevelop chưa? Nếu có một lỗi trong .NET, tôi tự hỏi liệu chúng ta có cần thực hiện một số cách giải quyết không. Tôi không nhận thức được vấn đề này.

Bạn đã thử gỡ lỗi nó trong ILSpy chưa? Đặc biệt là không có biểu tượng gỡ lỗi. Nó sẽ gỡ lỗi mã C #, nhưng nó sẽ cho chúng ta biết nếu các hướng dẫn IL có thể gỡ lỗi được hay không. (Lưu ý rằng trình gỡ lỗi ILSpy là beta)

Ghi chú nhanh về mã IL gốc:

  • .line 19,19: 6,15 '' xảy ra hai lần?
  • .line 20,20: 7,14 '' không bắt đầu ở điểm chuỗi ẩn (ngăn xếp không trống). tôi lo lắng
  • .line 20,20: 7,14 '' bao gồm mã cho "xe x" (tốt) cũng như "#f nooo x" (xấu?)
  • liên quan đến nop sau khi ret. Còn stloc, ldloc, ret thì sao? Tôi nghĩ rằng C # sử dụng thủ thuật này để tạo ra một điểm chuỗi khác biệt.

David


+1 Cảm ơn phản hồi, là điểm đầu tiên, tác dụng phụ đáng tiếc từ trình tạo mã, nhưng dường như vô hại. Điểm thứ hai, đây chính xác là những gì tôi cần, nhưng tôi thấy quan điểm của bạn, sẽ xem xét nó. Không chắc chắn những gì bạn có ý nghĩa của điểm cuối cùng.
leppie

Điểm thứ ba là .line 22,22: 7,40 '' nên trước IL_0020. Hoặc nên có một cái gì đó trước IL_0020, nếu không thì mã vẫn được tính là .line 20,20: 7,14 ''. Điểm thứ tư là "ret, nop" có thể được thay thế bằng "sloc, .line, ldloc, ret". Tôi đã thấy mô hình trước đây, có thể nó là dư thừa, nhưng có lẽ nó có lý do.
DSrbecky

Tôi không thể thực hiện stloc, ldloc trước khi ret, vì tôi sẽ mất cuộc gọi đuôi. Ah tôi hiểu bạn, bạn đang đề cập đến đầu ra ban đầu?
leppie
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.