Membuat langkah bahasa .NET Anda dengan benar di debugger

136

Pertama, saya mohon maaf atas panjangnya pertanyaan ini.

Saya adalah penulis IronScheme . Baru-baru ini saya telah bekerja keras untuk memancarkan info debug yang layak, sehingga saya dapat menggunakan debugger .NET 'asli'.

Meskipun ini sebagian berhasil, saya mengalami beberapa masalah gigi.

Masalah pertama terkait dengan melangkah.

Karena Skema menjadi bahasa ekspresi, semuanya cenderung dibungkus dalam tanda kurung, tidak seperti bahasa .NET utama yang tampaknya berbasis pernyataan (atau baris).

Kode asli (Skema) terlihat seperti:

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

Saya sengaja meletakkan setiap ekspresi di baris baru.

Kode yang dipancarkan berubah menjadi C # (melalui ILSpy) terlihat seperti:

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

Seperti yang Anda lihat, sangat sederhana.

Catatan: Jika kode diubah menjadi ekspresi kondisional (? :) di C #, semuanya hanya akan menjadi satu langkah debug, ingatlah itu.

Berikut adalah keluaran IL dengan sumber dan nomor baris:

  .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'

Catatan: Untuk mencegah debugger hanya menyorot seluruh metode, saya membuat titik masuk metode hanya selebar 1 kolom.

Seperti yang Anda lihat, setiap ekspresi dipetakan dengan benar ke sebuah garis.

Sekarang masalah dengan menginjak (diuji pada VS2010, tetapi masalah yang sama / serupa pada VS2008):

Ini dengan IgnoreSymbolStoreSequencePointstidak diterapkan.

  1. Panggil baz dengan null arg, ini berfungsi dengan benar. (null? x) diikuti oleh x.
  2. Panggil baz dengan Cons arg, ini berfungsi dengan benar. (null? x) lalu (pair? x) lalu (car x).
  3. Panggil baz dengan arg lain, gagal. (null? x) lalu (pair? x) lalu (mobil x) lalu (pelanggaran pernyataan ...).

Saat melamar IgnoreSymbolStoreSequencePoints(seperti yang disarankan):

  1. Panggil baz dengan null arg, ini berfungsi dengan benar. (null? x) diikuti oleh x.
  2. Panggil baz dengan Cons arg, gagal. (null? x) lalu (pair? x).
  3. Panggil baz dengan arg lain, gagal. (null? x) lalu (pair? x) lalu (mobil x) lalu (pelanggaran pernyataan ...).

Saya juga menemukan dalam mode ini bahwa beberapa baris (tidak diperlihatkan di sini) salah disorot, mereka mati dengan 1.

Berikut beberapa ide yang mungkin menjadi penyebabnya:

  • Tailcall membingungkan debugger
  • Lokasi yang tumpang tindih (tidak ditampilkan di sini) membingungkan debugger (melakukannya dengan sangat baik saat menyetel breakpoint)
  • ????

Masalah kedua, tetapi juga serius, adalah debugger gagal untuk merusak / mencapai breakpoint dalam beberapa kasus.

Satu-satunya tempat di mana saya bisa mendapatkan debugger untuk istirahat dengan benar (dan secara konsisten), adalah di titik masuk metode.

Situasi menjadi sedikit lebih baik jika IgnoreSymbolStoreSequencePointstidak diterapkan.

Kesimpulan

Mungkin debugger VS hanya berisi bug :(

Referensi:

  1. Membuat Bahasa CLR / .NET Dapat Di-debug

Pembaruan 1:

Mdbg tidak berfungsi untuk rakitan 64-bit. Jadi itu sudah keluar. Saya tidak memiliki mesin 32-bit lagi untuk mengujinya. Pembaruan: Saya yakin ini bukan masalah besar, apakah ada yang punya perbaikan? Sunting: Ya, konyol saya, mulai saja mdbg di bawah prompt perintah x64 :)

Perbarui 2:

Saya telah membuat aplikasi C #, dan mencoba membedah info baris.

Temuan saya:

  • Setelah brXXXinstruksi apa pun, Anda harus memiliki titik urutan (jika tidak valid alias '#line hidden', keluarkan a nop).
  • Sebelum brXXXinstruksi apa pun , keluarkan '#line hidden' dan a nop.

Menerapkan ini, bagaimanapun, tidak memperbaiki masalah (sendiri?).

Tetapi menambahkan yang berikut ini, memberikan hasil yang diinginkan :)

  • Setelah itu ret, keluarkan '#line hidden' dan a nop.

Ini menggunakan mode di mana IgnoreSymbolStoreSequencePointstidak diterapkan. Saat diterapkan, beberapa langkah masih dilewati :(

Berikut adalah keluaran IL ketika di atas telah diterapkan:

  .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'

Perbarui 3:

Masalah dengan 'semi-fix' di atas. Peverify melaporkan kesalahan pada semua metode karena nopsetelahnya ret. Saya benar-benar tidak mengerti masalahnya. Bagaimana bisa nopverifikasi istirahat setelah ret. Ini seperti kode mati (kecuali bahwa ini BUKAN kode) ... Oh well, eksperimen terus berlanjut.

Pembaruan 4:

Kembali ke rumah sekarang, hapus kode 'tidak dapat diverifikasi', berjalan di VS2008 dan segalanya menjadi jauh lebih buruk. Mungkin menjalankan kode yang tidak dapat diverifikasi demi debugging yang tepat mungkin jawabannya. Dalam mode 'rilis', semua keluaran masih dapat diverifikasi.

Perbarui 5:

Sekarang saya telah memutuskan bahwa ide saya di atas adalah satu-satunya pilihan yang layak untuk saat ini. Meskipun kode yang dihasilkan tidak dapat diverifikasi, saya belum menemukan kode apa pun VerificationException. Saya tidak tahu apa dampaknya pada pengguna akhir dengan skenario ini.

Sebagai bonus, masalah kedua saya juga telah diselesaikan. :)

Ini sedikit screencast dari hasil akhir saya. Ini mengenai breakpoint, melakukan langkah yang tepat (masuk / keluar / atas), dll. Secara keseluruhan, efek yang diinginkan.

Saya, bagaimanapun, masih belum menerima ini sebagai cara untuk melakukannya. Rasanya terlalu hacky bagi saya. Memiliki konfirmasi tentang masalah sebenarnya akan menyenangkan.

Pembaruan 6:

Baru saja perubahan untuk menguji kode pada VS2010, sepertinya ada beberapa masalah:

  1. Panggilan pertama sekarang tidak berjalan dengan benar. (pelanggaran-pernyataan ...) dipukul. Kasus lain berfungsi dengan baik. Beberapa kode lama mengeluarkan posisi yang tidak perlu. Kode dihapus, berfungsi seperti yang diharapkan. :)
  2. Lebih serius lagi, breakpoint gagal pada pemanggilan kedua program (menggunakan kompilasi dalam memori, membuang assembly ke file tampaknya membuat breakpoint senang lagi).

Kedua kasus ini bekerja dengan benar di bawah VS2008. Perbedaan utamanya adalah bahwa di bawah VS2010, seluruh aplikasi dikompilasi untuk .NET 4 dan di bawah VS2008, dikompilasi ke .NET 2. Keduanya menjalankan 64-bit.

Pembaruan 7:

Seperti disebutkan, saya menjalankan mdbg di bawah 64-bit. Sayangnya, ia juga memiliki masalah breakpoint di mana ia gagal rusak jika saya menjalankan ulang program (ini berarti ia akan dikompilasi ulang, jadi tidak menggunakan rakitan yang sama, tetapi masih menggunakan sumber yang sama).

Perbarui 8:

Saya telah mengajukan bug di situs MS Connect terkait masalah breakpoint.

Pembaruan: Diperbaiki

Pembaruan 9:

Setelah berpikir panjang, satu-satunya cara untuk membuat debugger senang adalah dengan melakukan SSA, sehingga setiap langkah dapat diisolasi dan berurutan. Saya belum membuktikan gagasan ini. Tapi sepertinya logis. Jelas, membersihkan temps dari SSA akan menghentikan debugging, tetapi itu mudah untuk diubah, dan membiarkannya tidak memiliki banyak overhead.

leppie
sumber
Setiap ide diterima, saya akan mencobanya dan menambahkan hasilnya ke pertanyaan. 2 ide pertama, coba mdbg dan tulis kode yang sama di C # dan bandingkan.
leppie
6
Apa pertanyaannya?
George Stocker
9
Saya pikir pertanyaannya adalah "mengapa bahasa saya tidak melangkah dengan benar?"
McKay
1
Jika Anda tidak lolos peverify, Anda tidak akan dapat berjalan dalam situasi kepercayaan parsial, seperti dari drive yang dipetakan atau di banyak pengaturan hosting plugin. Bagaimana Anda menghasilkan IL, refleksi.Emit atau melalui teks + ilasm? Dalam kedua kasus tersebut, Anda selalu dapat memasukkan sendiri .line 16707566,16707566: 0,0 '' dengan cara demikian, Anda tidak perlu khawatir mereka akan memberikan nop setelah pengembalian.
Hippiehunter
@Hippiehunter: Terima kasih. Sayangnya tanpa nops, langkah gagal (saya akan memverifikasi ini lagi untuk memastikan). Kurasa itu adalah pengorbanan yang harus aku lakukan. Ini tidak seperti VS bahkan dapat berjalan tanpa hak administrator :) Btw menggunakan Reflection.Emit melalui DLR (yang bercabang awal yang sangat diretas).
leppie

Jawaban:

26

Saya seorang insinyur di tim Visual Studio Debugger.

Koreksi saya jika saya salah, tetapi sepertinya satu-satunya masalah yang tersisa adalah bahwa ketika beralih dari PDB ke format simbol kompilasi dinamis .NET 4, beberapa breakpoint tidak terjawab.

Kami mungkin memerlukan repro untuk mendiagnosis masalah dengan tepat, namun berikut adalah beberapa catatan yang mungkin membantu.

  1. VS (2008+) can-to dijalankan sebagai non-admin
  2. Apakah ada simbol yang dimuat untuk kedua kalinya? Anda mungkin menguji dengan menerobos (melalui pengecualian atau memanggil System.Diagnostic.Debugger.Break ())
  3. Dengan asumsi simbol dimuat, apakah ada repro yang dapat Anda kirimkan kepada kami?
  4. Perbedaan yang mungkin terjadi adalah bahwa format simbol untuk kode yang dikompilasi dinamis 100% berbeda antara .NET 2 (aliran PDB) dan .NET 4 (IL DB, saya pikir mereka menyebutnya?)
  5. Suara 'nop benar. Lihat aturan untuk menghasilkan titik urutan implisit di bawah ini.
  6. Anda sebenarnya tidak perlu memancarkan benda-benda di jalur yang berbeda. Secara default, VS akan menginjak 'symbol-statement' di mana, sebagai penulis kompilator Anda bisa mendefinisikan apa arti 'symbol-statement'. Jadi jika Anda ingin setiap ekspresi menjadi hal yang terpisah dalam file simbol, itu akan berfungsi dengan baik.

JIT membuat titik urutan implisit berdasarkan aturan berikut: 1. Instruksi nop IL 2. IL menumpuk titik kosong 3. Instruksi IL segera mengikuti instruksi panggilan

Jika ternyata kami memerlukan repro untuk menyelesaikan masalah Anda, Anda dapat mengajukan bug connect dan mengunggah file dengan aman melalui media itu.

Memperbarui:

Kami mendorong pengguna lain yang mengalami masalah ini untuk mencoba Pratinjau Pengembang Dev11 dari http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=27543 dan berkomentar dengan umpan balik apa pun. (Harus menargetkan 4,5)

Perbarui 2:

Leppie telah memverifikasi perbaikan untuk bekerja untuknya pada versi Beta dari Dev11 yang tersedia di http://www.microsoft.com/visualstudio/11/en-us/downloads seperti yang tercantum dalam bug koneksi https://connect.microsoft. com / VisualStudio / feedback / details / 684089 / .

Terima kasih,

Luke

Luke Kim
sumber
Terima kasih banyak. Saya akan mencoba membuat repro, jika diperlukan.
leppie
Mengenai konfirmasinya, ya Anda benar, tetapi saya masih lebih suka menyelesaikan masalah loncatan tanpa melanggar verifikasi. Tapi seperti yang dikatakan, saya rela berkorban jika saya bisa membuktikan bahwa itu tidak akan pernah menghasilkan runtime VerificationException.
leppie
Adapun poin 6, saya membuatnya 'berbasis garis' untuk pertanyaan ini. Debugger sebenarnya melangkah dengan benar dalam / keluar / over ekspresi seperti yang saya maksudkan untuk bekerja :) Satu-satunya masalah dengan pendekatan adalah mengatur breakpoint pada ekspresi 'batin' jika ekspresi 'luar' menutupinya; debugger di VS cenderung ingin membuat ekspresi terluar sebagai breakpoint.
leppie
Saya rasa saya telah mengisolasi masalah breakpoint. Saat berjalan di bawah CLR2, titik putus tampaknya dievaluasi ulang saat menjalankan kode lagi, meskipun kode baru. Di CLR4, breakpoint hanya 'menempel' ke kode asli. Saya telah membuat screencast kecil dari perilaku CLR4 @ screencast.com/t/eiSilNzL5Nr . Perhatikan bahwa nama tipe berubah di antara pemanggilan.
leppie
Saya telah melaporkan bug di koneksi. Repro cukup mudah :) connect.microsoft.com/VisualStudio/feedback/details/684089
leppie
3

Saya seorang insinyur di tim Debugger SharpDevelop :-)

Apakah Anda menyelesaikan masalahnya?

Apakah Anda mencoba men-debugnya di SharpDevelop? Jika ada bug di .NET, saya ingin tahu apakah kita perlu menerapkan beberapa solusi. Saya tidak mengetahui masalah ini.

Apakah Anda mencoba men-debugnya di ILSpy? Terutama tanpa simbol debug. Ini akan men-debug kode C #, tetapi akan memberi tahu kami jika instruksi IL dapat di-debug dengan baik. (Ingatlah bahwa debugger ILSpy adalah beta)

Catatan cepat pada kode IL asli:

  • .line 19,19: 6,15 '' muncul dua kali?
  • .line 20,20: 7,14 '' tidak dimulai pada titik urutan implisit (tumpukan tidak kosong). saya khawatir
  • .line 20,20: 7,14 '' menyertakan kode untuk "car x" (good) serta "#f nooo x" (bad?)
  • mengenai nop setelah ret. Bagaimana dengan stloc, ldloc, ret? Saya pikir C # menggunakan trik ini untuk membuat ret titik urutan yang berbeda.

David

dsrbecky.dll
sumber
+1 Terima kasih atas umpan baliknya, sebagai poin pertama, efek samping yang disayangkan dari generator kode, tetapi tampaknya tidak berbahaya. Poin kedua, inilah yang saya butuhkan, tetapi saya mengerti maksud Anda, akan menyelidikinya. Tidak yakin apa yang Anda maksud dengan poin terakhir.
leppie
Poin ketiga adalah bahwa. Baris 22,22: 7,40 '' harus sebelum IL_0020. Atau harus ada sesuatu sebelum IL_0020, jika tidak kodenya masih dihitung sebagai .line 20,20: 7,14 ''. Poin keempat adalah "ret, nop" mungkin diganti dengan "sloc, .line, ldloc, ret". Saya telah melihat polanya sebelumnya, mungkin itu mubazir, tapi mungkin ada alasannya.
dsrbecky
Saya tidak bisa melakukan stloc, ldloc sebelum ret, karena saya akan kehilangan panggilan ekor. Ah saya mengerti, Anda mengacu pada keluaran aslinya?
leppie