Men-debug Kode yang Dioptimalkan dan Fungsi Sebaris
Untuk Windows 8, debugger dan pengkompilasi Windows telah ditingkatkan sehingga Anda dapat men-debug kode yang dioptimalkan dan men-debug fungsi sebaris. Debugger menampilkan parameter dan variabel lokal terlepas dari apakah mereka disimpan dalam register atau pada tumpukan. Debugger juga menampilkan fungsi sebaris dalam tumpukan panggilan. Untuk fungsi sebaris, debugger menampilkan variabel lokal, tetapi bukan parameter.
Ketika kode dioptimalkan, kode diubah untuk berjalan lebih cepat dan menggunakan lebih sedikit memori. Terkadang fungsi dihapus sebagai akibat dari penghapusan kode mati, kode yang digabungkan, atau fungsi yang ditempatkan sebaris. Variabel dan parameter lokal juga dapat dihapus. Banyak pengoptimalan kode menghapus variabel lokal yang tidak diperlukan atau digunakan; pengoptimalan lainnya menghapus variabel induksi dalam perulangan. Penghapusan sub-ekspresi umum menggabungkan variabel lokal bersama-sama.
Build ritel Windows dioptimalkan. Jadi, jika Anda menjalankan build ritel Windows, sangat membantu untuk memiliki debugger yang dirancang untuk bekerja dengan baik dengan kode yang dioptimalkan. Agar penelusuran kesalahan kode yang dioptimalkan efektif, diperlukan dua fitur utama: 1) tampilan variabel lokal yang akurat, dan 2) tampilan fungsi sebaris pada tumpukan panggilan.
Tampilan variabel dan parameter lokal yang akurat
Untuk memfasilitasi tampilan variabel dan parameter lokal yang akurat, pengkompilasi merekam informasi tentang lokasi variabel dan parameter lokal dalam file simbol (PDB). Rekaman lokasi ini melacak lokasi penyimpanan variabel dan rentang kode tertentu di mana lokasi ini valid. Catatan ini tidak hanya membantu melacak lokasi (dalam register atau di slot tumpukan) variabel, tetapi juga pergerakan variabel. Misalnya, parameter mungkin pertama kali berada di register RCX, tetapi dipindahkan ke slot tumpukan untuk membebaskan RCX, lalu dipindahkan untuk mendaftarkan R8 ketika banyak digunakan dalam perulangan, dan kemudian dipindahkan ke slot tumpukan yang berbeda ketika kode berada di luar perulangan. Debugger Windows menggunakan rekaman lokasi yang kaya dalam file PDB dan menggunakan penunjuk instruksi saat ini untuk memilih rekaman lokasi yang sesuai untuk variabel dan parameter lokal.
Cuplikan layar jendela Lokal di Visual Studio ini menunjukkan parameter dan variabel lokal untuk fungsi dalam aplikasi 64-bit yang dioptimalkan. Fungsi ini tidak sebaris, jadi kita melihat parameter dan variabel lokal.
Anda dapat menggunakan perintah dv -v untuk melihat lokasi parameter dan variabel lokal.
Perhatikan bahwa jendela Lokal menampilkan parameter dengan benar meskipun disimpan dalam register.
Selain melacak variabel dengan jenis primitif, rekaman lokasi melacak anggota data struktur dan kelas lokal. Output debugger berikut menampilkan struktur lokal.
0:000> dt My1
Local var Type _LocalStruct
+0x000 i1 : 0n0 (edi)
+0x004 i2 : 0n1 (rsp+0x94)
+0x008 i3 : 0n2 (rsp+0x90)
+0x00c i4 : 0n3 (rsp+0x208)
+0x010 i5 : 0n4 (r10d)
+0x014 i6 : 0n7 (rsp+0x200)
0:000> dt My2
Local var @ 0xefa60 Type _IntSum
+0x000 sum1 : 0n4760 (edx)
+0x004 sum2 : 0n30772 (ecx)
+0x008 sum3 : 0n2 (r12d)
+0x00c sum4 : 0n0
Berikut adalah beberapa pengamatan tentang output debugger sebelumnya.
- Struktur lokal My1 menggambarkan bahwa kompilator dapat menyebarkan anggota data struktur lokal untuk mendaftar dan slot tumpukan yang tidak berdampingan.
- Output perintah dt My2 akan berbeda dari output perintah dt _IntSum 0xefa60. Anda tidak dapat berasumsi bahwa struktur lokal akan menempati blok memori tumpukan yang berdampingan. Dalam kasus My2, hanya
sum4
tetap di blok tumpukan asli; tiga anggota data lainnya dipindahkan ke pendaftaran. - Beberapa anggota data dapat memiliki beberapa lokasi. Misalnya, My2.sum2 memiliki dua lokasi: satu adalah register ECX (yang dipilih debugger Windows) dan yang lainnya 0xefa60+0x4 (slot tumpukan asli). Ini bisa terjadi untuk variabel lokal jenis primitif juga, dan debugger Windows memberlakukan heuristik preseden untuk menentukan lokasi mana yang akan digunakan. Misalnya, daftarkan lokasi selalu trump lokasi tumpukan.
Tampilan fungsi sebaris pada tumpukan panggilan
Selama pengoptimalan kode, beberapa fungsi ditempatkan sejalan. Artinya, isi fungsi ditempatkan langsung dalam kode seperti ekspansi makro. Tidak ada panggilan fungsi dan tidak ada kembali ke pemanggil. Untuk memfasilitasi tampilan fungsi sebaris, pengkompilasi menyimpan data dalam file PDB yang membantu mendekode potongan kode untuk fungsi sebaris (yaitu, urutan blok kode dalam fungsi pemanggil yang termasuk dalam fungsi penerima panggilan yang ditempatkan sebaris) serta variabel lokal (variabel lokal terlingkup dalam blok kode tersebut). Data ini membantu debugger menyertakan fungsi sebaris sebagai bagian dari tumpukan melepas lelah.
Misalkan Anda mengkompilasi aplikasi dan memaksa fungsi bernama func1
agar sebaris.
__forceinline int func1(int p1, int p2, int p3)
{
int num1 = 0;
int num2 = 0;
int num3 = 0;
...
}
Anda dapat menggunakan perintah bm untuk mengatur titik henti di func1
.
0:000> bm MyApp!func1
1: 000007f6`8d621088 @!"MyApp!func1" (MyApp!func1 inlined in MyApp!main+0x88)
0:000> g
Breakpoint 1 hit
MyApp!main+0x88:
000007f6`8d621088 488d0d21110000 lea rcx,[MyApp!`string' (000007f6`8d6221b0)]
Setelah Anda mengambil satu langkah ke func1
, Anda dapat menggunakan perintah k untuk melihat func1
pada tumpukan panggilan. Anda dapat menggunakan perintah dv untuk melihat variabel lokal untuk func1
. Perhatikan bahwa variabel num3
lokal ditampilkan sebagai tidak tersedia. Variabel lokal dapat tidak tersedia dalam kode yang dioptimalkan karena sejumlah alasan. Mungkin variabel tidak ada dalam kode yang dioptimalkan. Mungkin variabel belum diinisialisasi atau variabel tidak lagi digunakan.
0:000> p
MyApp!func1+0x7:
000007f6`8d62108f 8d3c33 lea edi,[rbx+rsi]
0:000> knL
# Child-SP RetAddr Call Site
00 (Inline Function) --------`-------- MyApp!func1+0x7
01 00000000`0050fc90 000007f6`8d6213f3 MyApp!main+0x8f
02 00000000`0050fcf0 000007ff`c6af0f7d MyApp!__tmainCRTStartup+0x10f
03 00000000`0050fd20 000007ff`c7063d6d KERNEL32!BaseThreadInitThunk+0xd
04 00000000`0050fd50 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
0:000> dv -v
00000000`0050fcb0 num1 = 0n0
00000000`0050fcb4 num2 = 0n0
<unavailable> num3 = <value unavailable>
Jika Anda melihat bingkai 1 di pelacakan tumpukan, Anda dapat melihat variabel lokal untuk fungsi tersebut main
. Perhatikan bahwa dua variabel disimpan dalam register.
0:000> .frame 1
01 00000000`0050fc90 000007f6`8d6213f3 MyApp!main+0x8f
0:000> dv -v
00000000`0050fd08 c = 0n7
@ebx b = 0n13
@esi a = 0n6
Debugger Windows menggabungkan data dari file PDB untuk menemukan semua tempat di mana fungsi tertentu telah ditempatkan sebaris. Anda dapat menggunakan perintah x untuk mencantumkan semua situs penelepon dari fungsi sebaris.
0:000> x simple!MoreCalculate
00000000`ff6e1455 simple!MoreCalculate = (inline caller) simple!wmain+8d
00000000`ff6e1528 simple!MoreCalculate = (inline caller) simple!wmain+160
0:000> x simple!Calculate
00000000`ff6e141b simple!Calculate = (inline caller) simple!wmain+53
Karena debugger Windows dapat menghitung semua situs penelepon dari fungsi sebaris, ini dapat mengatur titik henti di dalam fungsi sebaris dengan menghitung offset dari situs pemanggil. Anda dapat menggunakan perintah bm (yang digunakan untuk mengatur titik henti yang cocok dengan pola ekspresi reguler) untuk mengatur titik henti untuk fungsi sebaris.
Debugger Windows mengelompokkan semua titik henti yang diatur untuk fungsi sebaris tertentu ke dalam kontainer titik henti. Anda dapat memanipulasi kontainer titik henti secara keseluruhan dengan menggunakan perintah seperti ,bd, bc. Lihat contoh perintah bd 3 dan bc 3 berikut. Anda juga dapat memanipulasi titik henti individual. Lihat contoh perintah 2 berikut ini.
0:000> bm simple!MoreCalculate
2: 00000000`ff6e1455 @!"simple!MoreCalculate" (simple!MoreCalculate inlined in simple!wmain+0x8d)
4: 00000000`ff6e1528 @!"simple!MoreCalculate" (simple!MoreCalculate inlined in simple!wmain+0x160)
0:000> bl
0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52] 0001 (0001) 0:**** simple!wmain
3 e <inline function> 0001 (0001) 0:**** {simple!MoreCalculate}
2 e 00000000`ff6e1455 [n:\win7\simple\simple.cpp @ 58] 0001 (0001) 0:**** simple!wmain+0x8d (inline function simple!MoreCalculate)
4 e 00000000`ff6e1528 [n:\win7\simple\simple.cpp @ 72] 0001 (0001) 0:**** simple!wmain+0x160 (inline function simple!MoreCalculate)
0:000> bd 3
0:000> be 2
0:000> bl
0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52] 0001 (0001) 0:**** simple!wmain
3 d <inline function> 0001 (0001) 0:**** {simple!MoreCalculate}
2 e 00000000`ff6e1455 [n:\win7\simple\simple.cpp @ 58] 0001 (0001) 0:**** simple!wmain+0x8d (inline function simple!MoreCalculate)
4 d 00000000`ff6e1528 [n:\win7\simple\simple.cpp @ 72] 0001 (0001) 0:**** simple!wmain+0x160 (inline function simple!MoreCalculate)
0:000> bc 3
0:000> bl
0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52] 0001 (0001) 0:**** simple!wmain
Karena tidak ada instruksi panggilan atau pengembalian eksplisit untuk fungsi sebaris, langkah tingkat sumber sangat menantang untuk debugger. Misalnya, Anda dapat secara tidak sengaja masuk ke fungsi sebaris (jika instruksi berikutnya adalah bagian dari fungsi sebaris), atau Anda dapat melangkah masuk dan keluar dari fungsi sebaris yang sama beberapa kali (karena blok kode untuk fungsi sebaris telah dipisahkan dan dipindahkan oleh pengkompilasi). Untuk mempertahankan pengalaman melangkah yang akrab, debugger Windows mempertahankan tumpukan panggilan konseptual kecil untuk setiap alamat instruksi kode dan membangun komputer status internal untuk menjalankan operasi step-in, step-over, dan step-out. Ini memberikan perkiraan yang cukup akurat untuk pengalaman melangkah untuk fungsi non-sebaris.
Informasi Tambahan
Catatan Anda dapat menggunakan perintah .inline 0 untuk menonaktifkan debugging fungsi sebaris. Perintah .inline 1 memungkinkan penelusuran kesalahan fungsi sebaris. Teknik Debugging Standar