Saya terus bertanya-tanya bagaimana cara kerja debugger? Khususnya yang bisa 'dilampirkan' agar sudah bisa dieksekusi. Saya mengerti bahwa kompiler menerjemahkan kode ke bahasa mesin, tetapi kemudian bagaimana debugger 'tahu' apa yang dilampirkan?
Saya terus bertanya-tanya bagaimana cara kerja debugger? Khususnya yang bisa 'dilampirkan' agar sudah bisa dieksekusi. Saya mengerti bahwa kompiler menerjemahkan kode ke bahasa mesin, tetapi kemudian bagaimana debugger 'tahu' apa yang dilampirkan?
Jawaban:
Detail tentang cara kerja debugger akan tergantung pada apa yang Anda debug, dan apa OS-nya. Untuk debugging asli di Windows Anda dapat menemukan beberapa detail di MSDN: Win32 Debugging API .
Pengguna memberi tahu debugger proses mana yang harus dilampirkan, baik dengan nama atau ID proses. Jika itu adalah nama maka debugger akan mencari proses ID, dan memulai sesi debug melalui panggilan sistem; di bawah Windows ini akan menjadi DebugActiveProcess .
Setelah dilampirkan, debugger akan memasuki loop peristiwa seperti pada UI apa pun, tetapi alih-alih peristiwa yang berasal dari sistem windowing, OS akan menghasilkan peristiwa berdasarkan apa yang terjadi dalam proses debugged - misalnya pengecualian yang terjadi. Lihat WaitForDebugEvent .
Debugger dapat membaca dan menulis memori virtual proses target, dan bahkan menyesuaikan nilai registernya melalui API yang disediakan oleh OS. Lihat daftar fungsi debugging untuk Windows.
Debugger dapat menggunakan informasi dari file simbol untuk menerjemahkan dari alamat ke nama variabel dan lokasi dalam kode sumber. Informasi file simbol adalah kumpulan API yang terpisah dan bukan merupakan bagian inti dari OS. Pada Windows ini melalui SDK Akses Antarmuka Debug .
Jika Anda men-debug lingkungan yang dikelola (.NET, Java, dll.) Proses biasanya akan terlihat serupa, tetapi detailnya berbeda, karena lingkungan mesin virtual menyediakan API debug daripada OS yang mendasarinya.
Seperti yang saya pahami:
Untuk breakpoint perangkat lunak pada x86, debugger menggantikan byte pertama dari instruksi dengan CC
( int3
). Ini dilakukan dengan WriteProcessMemory
di Windows. Ketika CPU sampai ke instruksi itu, dan mengeksekusi int3
, ini menyebabkan CPU untuk menghasilkan pengecualian debug. OS menerima interupsi ini, menyadari bahwa proses sedang di-debug, dan memberi tahu proses debugger bahwa breakpoint terkena.
Setelah breakpoint dipukul dan proses dihentikan, debugger melihat daftar breakpointnya, dan menggantikannya CC
dengan byte yang awalnya ada di sana. Set debugger TF
, Bendera Perangkap dalam EFLAGS
(dengan memodifikasi CONTEXT
), dan melanjutkan proses. Bendera Perangkap menyebabkan CPU secara otomatis menghasilkan pengecualian satu langkah ( INT 1
) pada instruksi berikutnya.
Ketika proses yang sedang di-debug berhenti pada waktu berikutnya, debugger akan kembali menggantikan byte pertama dari instruksi breakpoint CC
, dan proses berlanjut.
Saya tidak yakin apakah ini persis seperti yang diterapkan oleh semua debugger, tapi saya telah menulis program Win32 yang berhasil men-debug sendiri menggunakan mekanisme ini. Sama sekali tidak berguna, tetapi mendidik.
Di Linux, proses debugging dimulai dengan panggilan sistem ptrace (2) . Artikel ini memiliki tutorial yang bagus tentang cara menggunakan ptrace
untuk menerapkan beberapa konstruksi debugging sederhana.
(2)
memberi tahu kami sesuatu yang lebih (atau kurang) dari "ptrace adalah panggilan sistem"?
(2)
adalah nomor bagian manual. Lihat en.wikipedia.org/wiki/Man_page#Manual_sections untuk deskripsi bagian manual.
ptrace
adalah panggilan sistem.
(2)
memberitahu kita bahwa kita dapat mengetik man 2 ptrace
dan mendapatkan halaman buku yang tepat - tidak penting di sini karena tidak ada yang lain ptrace
untuk disatukan, tetapi untuk dibandingkan man printf
dengan man 3 printf
di Linux.
Jika Anda menggunakan OS Windows, sumber yang bagus untuk ini adalah "Aplikasi Debugging untuk Microsoft .NET dan Microsoft Windows" oleh John Robbins:
(atau bahkan edisi yang lebih lama: "Aplikasi Debugging" )
Buku ini memiliki bab tentang cara kerja debugger yang menyertakan kode untuk beberapa debugger sederhana (tetapi berfungsi).
Karena saya tidak terbiasa dengan rincian debugging Unix / Linux, hal ini mungkin tidak berlaku sama sekali untuk OS lain. Tapi saya kira itu sebagai pengantar untuk subjek yang sangat kompleks konsep - jika bukan detail dan API - harus 'port' ke sebagian besar OS.
Sumber berharga lainnya untuk memahami debugging adalah manual CPU Intel (Manual Pengembang Perangkat Lunak IntelĀ® 64 dan IA-32). Dalam volume 3A, bab 16, ia memperkenalkan dukungan perangkat keras untuk debugging, seperti pengecualian khusus dan register debugging perangkat keras. Berikut ini adalah dari bab itu:
Bendera T (perangkap), TSS - Menghasilkan pengecualian debug (#DB) ketika upaya dilakukan untuk beralih ke tugas dengan bendera T yang diatur di TSS-nya.
Saya tidak yakin apakah Window atau Linux menggunakan flag ini atau tidak, tetapi sangat menarik untuk membaca bab itu.
Semoga ini bisa membantu seseorang.
Saya pikir ada dua pertanyaan utama untuk dijawab di sini:
1. Bagaimana debugger mengetahui bahwa pengecualian terjadi?
Ketika pengecualian terjadi dalam proses yang sedang di-debug, debugger akan diberitahukan oleh OS sebelum penangan pengecualian pengguna yang didefinisikan dalam proses target diberi kesempatan untuk merespons pengecualian tersebut. Jika debugger memilih untuk tidak menangani pemberitahuan pengecualian (kesempatan pertama) ini, urutan pengiriman pengecualian berlanjut lebih jauh dan utas target kemudian diberi kesempatan untuk menangani pengecualian jika ingin melakukannya. Jika pengecualian SEH tidak ditangani oleh proses target, debugger kemudian dikirim acara debug lain, disebut pemberitahuan kesempatan kedua, untuk menginformasikan bahwa pengecualian tidak tertangani terjadi dalam proses target. Sumber
2. Bagaimana debugger tahu cara berhenti di breakpoint?
Jawaban yang disederhanakan adalah: Ketika Anda memasukkan break-point ke dalam program, debugger mengganti kode Anda pada saat itu dengan instruksi int3 yang merupakan interupsi perangkat lunak . Akibatnya, program ditangguhkan dan debugger dipanggil.
Pemahaman saya adalah bahwa ketika Anda mengkompilasi aplikasi atau file DLL, apa pun yang dikompilasi berisi simbol yang mewakili fungsi dan variabel.
Ketika Anda memiliki build debug, simbol-simbol ini jauh lebih rinci daripada ketika build rilis, sehingga memungkinkan debugger memberi Anda lebih banyak informasi. Saat Anda melampirkan debugger ke suatu proses, ia melihat fungsi mana yang saat ini sedang diakses dan menyelesaikan semua simbol debug yang tersedia dari sini (karena ia tahu seperti apa internal dari file yang dikompilasi, ia dapat memastikan apa yang mungkin ada dalam memori. , dengan isi int, float, string, dll.). Seperti yang dikatakan poster pertama, informasi ini dan cara kerja simbol-simbol ini sangat tergantung pada lingkungan dan bahasa.