Kesalahan pada program assembly sering tidak menyertakan pesan yang menjelaskan penyebabnya, sehingga pencarian penyebab membutuhkan alat yang mampu mengamati program dari dalam. Topik ini membahas penggunaan gdb untuk keperluan tersebut pada program assembly, yaitu menaruh breakpoint pada label maupun alamat, menginspeksi register dan memori ketika program berhenti, melangkah satu instruksi demi satu instruksi, dan membaca keluaran disassembly gdb. Seluruh sesi dijalankan dalam mode batch agar setiap langkahnya dapat direproduksi pada terminal, dan semuanya diperagakan pada satu program latihan yang sengaja mengandung kesalahan.
Program latihan dengan kesalahan#
Program berikut diharapkan menjumlahkan lima elemen array lalu mengakhiri proses dengan hasil penjumlahannya. Programnya sengaja diberi satu kesalahan pada perulangannya agar proses pencarian penyebabnya dapat diperagakan dari awal sampai akhir.
; jumlah-array.asm
; Menjumlahkan lima elemen array dq.
; Terdapat bug: indeks bertambah dua sehingga elemen 1 dan 3 terlewat.
default rel
section .rodata
data: dq 10, 20, 30, 40, 50
section .text
global _start
_start:
xor rax, rax ; penjumlah
xor rcx, rcx ; indeks elemen
ulang:
cmp rcx, 5
jae selesai
mov rdx, [data + rcx*8]
add rax, rdx
add rcx, 2 ; BUG: seharusnya bertambah satu
jmp ulang
selesai:
mov edi, eax
mov eax, 60 ; nomor syscall exit
syscallSetelah dirakit, ditautkan, dan dijalankan, program berakhir dengan kode keluar 90, sedangkan penjumlahan kelima elemen tersebut, yaitu 10, 20, 30, 40, dan 50, seharusnya menghasilkan 150. Selisih 60 itu berarti sebagian elemen tidak pernah dijumlahkan atau dijumlahkan dengan nilai yang keliru. gdb digunakan untuk menentukan kemungkinan mana yang benar-benar terjadi.
nasm -f elf64 jumlah-array.asm -o jumlah-array.o
ld jumlah-array.o -o jumlah-array
./jumlah-array; echo "exit=$?"exit=90Breakpoint pada label#
gdb berhenti pada titik yang ditetapkan melalui perintah break, dan titik itu dapat berupa label dari program assembly. Sesi batch berikut menaruh breakpoint pada label ulang, menjalankan program, lalu menampilkan dua register kunci tepat ketika eksekusi berhenti.
gdb -batch -ex 'break ulang' -ex 'run' -ex 'info registers rax rcx' ./jumlah-arrayBreakpoint 1 at 0x401006
Breakpoint 1, 0x0000000000401006 in ulang ()
rax 0x0 0
rcx 0x0 0Opsi -batch memerintahkan gdb menjalankan seluruh perintah yang diberikan melalui -ex secara berurutan, kemudian keluar tanpa membuka sesi interaktif. Perintah break ulang menetapkan breakpoint pada instruksi pertama setelah label, yang alamatnya dilaporkan sebagai 0x401006, sedangkan run menjalankan program sampai breakpoint pertama tercapai. Dua baris terakhir dari info registers menunjukkan rax dan rcx masih nol karena program baru saja memasuki perulangan pertamanya.
Selain label, gdb menerima alamat langsung dalam bentuk break *0x40100c, yang digunakan pada bagian akhir topik ini. gdb juga menyediakan watchpoint, yaitu titik henti yang terpicu ketika nilai sebuah ekspresi berubah, misalnya watch rax, sehingga perubahan nilai dapat dilacak tanpa menetapkan alamat instruksi tertentu.
Melangkah per instruksi#
Perintah stepi menjalankan tepat satu instruksi lalu kembali ke gdb, sehingga perubahan register dapat dibaca pada setiap langkah. Sesi berikut juga memanggil set disassembly-flavor intel agar bongkaran gdb tampil dalam notasi Intel, kemudian menampilkan instruksi yang akan dijalankan berikutnya melalui x/i $pc, yaitu perintah pemeriksaan memori yang membaca satu instruksi pada alamat yang tersimpan di register pc.
gdb -batch -ex 'set disassembly-flavor intel' -ex 'break ulang' -ex 'run' -ex 'x/i $pc' -ex 'stepi 3' -ex 'x/i $pc' -ex 'info registers rcx rdx' -ex 'stepi 2' -ex 'info registers rcx rax' ./jumlah-arrayBreakpoint 1 at 0x401006
Breakpoint 1, 0x0000000000401006 in ulang ()
=> 0x401006 <ulang>: cmp rcx,0x5
0x0000000000401014 in ulang ()
=> 0x401014 <ulang+14>: add rax,rdx
rcx 0x0 0
rdx 0xa 10
0x000000000040101b in ulang ()
rcx 0x2 2
rax 0xa 10Tanda => menandai instruksi yang akan dijalankan berikutnya. Setelah tiga langkah, eksekusi berhenti pada add rax,rdx dan rdx berisi 10, yaitu nilai elemen indeks 0 yang dibaca dengan benar. Dua langkah berikutnya menjalankan penjumlahan dan penambahan indeks, dan penyebab kesalahan terlihat pada langkah itu, yaitu rcx berpindah dari 0 langsung ke 2 karena instruksinya adalah add rcx,2. Elemen indeks 1 tidak pernah dibaca, dan pola yang sama berulang pada elemen indeks 3, sehingga dua elemen bernilai 20 dan 40 tidak pernah masuk ke penjumlah.
Pasangan dari stepi adalah nexti, yang juga menjalankan satu instruksi tetapi melewati pemanggilan fungsi sekaligus ketika instruksinya adalah call. Untuk program tanpa fungsi seperti program latihan ini, keduanya berperilaku sama. Apabila penyelidikan membutuhkan tampilan yang terus-menerus, gdb menyediakan antarmuka teks melalui perintah gdb -tui beserta layout asm, yang menampilkan bongkaran berdampingan dengan jendela register. Mode itu membutuhkan terminal interaktif sehingga tidak diperagakan di sini.
Membaca disassembly dan memori#
Perintah disassemble menampilkan bongkaran satu fungsi secara utuh, sedangkan x/5gx &data membaca lima elemen memori berukuran 8 byte pada alamat label data dan menampilkannya dalam heksadesimal. Sesi berikut menggabungkan keduanya dengan breakpoint pada alamat instruksi pembacaan array.
gdb -batch -ex 'disassemble ulang' -ex 'break *0x40100c' -ex 'run' -ex 'x/5gx &data' -ex 'continue' -ex 'info registers rcx rdx' -ex 'continue' -ex 'info registers rcx rdx' ./jumlah-arrayDump of assembler code for function ulang:
0x0000000000401006 <+0>: cmp $0x5,%rcx
0x000000000040100a <+4>: jae 0x40101d <selesai>
0x000000000040100c <+6>: mov 0x402000(,%rcx,8),%rdx
0x0000000000401014 <+14>: add %rdx,%rax
0x0000000000401017 <+17>: add $0x2,%rcx
0x000000000040101b <+21>: jmp 0x401006 <ulang>
End of assembler dump.
Breakpoint 1 at 0x40100c
Breakpoint 1, 0x000000000040100c in ulang ()
0x402000: 0x000000000000000a 0x0000000000000014
0x402010: 0x000000000000001e 0x0000000000000028
0x402020: 0x0000000000000032
Breakpoint 1, 0x000000000040100c in ulang ()
rcx 0x2 2
rdx 0xa 10
Breakpoint 1, 0x000000000040100c in ulang ()
rcx 0x4 4
rdx 0x1e 30Bongkaran pada bagian awal keluaran memperlihatkan urutan instruksi lengkap dengan alamat dan offsetnya, dan alamat 0x40100c pada instruksi mov itulah yang digunakan sebagai breakpoint alamat. Lima nilai pada x/5gx &data, yaitu 0xa, 0x14, 0x1e, 0x28, dan 0x32, merupakan nilai 10 sampai 50, sehingga isi array terbukti benar. Dua pemberhentian berikutnya memperlihatkan rcx berubah dari 0 ke 2 lalu ke 4, sedangkan rdx berisi 10 kemudian 30, yang berarti elemen indeks 1 tidak pernah diambil. Kesimpulan kedua jalur penyelidikan ini sama dengan temuan langkah per instruksi, yaitu perbaikan berupa penggantian add rcx,2 dengan penambahan satu, misalnya inc rcx.
Kesalahan umum#
- Sesi gdb dijalankan tanpa perintah
run. Breakpoint yang sudah ditetapkan tidak berpengaruh sebelum program dijalankan, sehinggainfo registershanya menghasilkan pesan bahwa program belum berjalan. Setiap sesi dimulai denganrunsetelah breakpoint ditetapkan. - Keluaran AT&T dibaca dengan kebiasaan Intel. Bongkaran gdb menggunakan notasi AT&T secara default, misalnya
cmp $0x5,%rcx, sehingga operand terbaca terbalik apabila kebiasaan notasi Intel langsung diterapkan. Perintahset disassembly-flavor intelmenyelaraskan tampilannya dengan notasi yang digunakan sepanjang jalur ini. - Program diedit tanpa dirakit ulang. gdb mengamati berkas executable yang ada saat sesi dimulai, sehingga breakpoint yang mengacu pada label versi lama dapat berhenti di tempat yang sudah tidak sesuai dengan sumber terbaru. Rakit ulang program sebelum sesi gdb dimulai.
Ringkasan#
- Perintah
breakmenerima label program maupun alamat dalam bentukbreak *alamat, dan-batch -exmenjalankan rangkaian perintah gdb secara terurut tanpa sesi interaktif. x/i $pcmenampilkan instruksi yang akan dijalankan berikutnya,info registersmelaporkan isi register,x/5gx &labelmembaca lima elemen memori 8 byte pada alamat label, dandisassemblemenampilkan bongkaran satu fungsi.stepimenjalankan satu instruksi sehingga perubahan register terlihat per langkah, sedangkannextiberperilaku sama tetapi melewati pemanggilan fungsi.- Pada program latihan, kombinasi breakpoint, langkah per instruksi, dan pembacaan memori menunjukkan
rcxmelompat dari 0 ke 2 sehingga elemen indeks 1 dan 3 tidak pernah dijumlahkan.
Referensi#
- GDB Documentation — Sourceware — Manual gdb yang mencakup breakpoint, langkah per instruksi, dan pemeriksaan memori.
- gdb(1) — Linux man-pages
— Ringkasan opsi baris perintah gdb, termasuk
-batchdan-ex.