gdb pada assembly

Breakpoint pada label, inspeksi register dan memori, serta langkah per instruksi di gdb.

Daftar isi

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.

nasm jumlah-array.asm
; 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
    syscall

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

bash
nasm -f elf64 jumlah-array.asm -o jumlah-array.o
ld jumlah-array.o -o jumlah-array
./jumlah-array; echo "exit=$?"
text Keluaran
exit=90

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

bash
gdb -batch -ex 'break ulang' -ex 'run' -ex 'info registers rax rcx' ./jumlah-array
text Keluaran
Breakpoint 1 at 0x401006

Breakpoint 1, 0x0000000000401006 in ulang ()
rax            0x0                 0
rcx            0x0                 0

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

bash
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-array
text Keluaran
Breakpoint 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                 10

Tanda => 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.

bash
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-array
text Keluaran
Dump 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                30

Bongkaran 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, sehingga info registers hanya menghasilkan pesan bahwa program belum berjalan. Setiap sesi dimulai dengan run setelah 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. Perintah set disassembly-flavor intel menyelaraskan 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 break menerima label program maupun alamat dalam bentuk break *alamat, dan -batch -ex menjalankan rangkaian perintah gdb secara terurut tanpa sesi interaktif.
  • x/i $pc menampilkan instruksi yang akan dijalankan berikutnya, info registers melaporkan isi register, x/5gx &label membaca lima elemen memori 8 byte pada alamat label, dan disassemble menampilkan bongkaran satu fungsi.
  • stepi menjalankan satu instruksi sehingga perubahan register terlihat per langkah, sedangkan nexti berperilaku sama tetapi melewati pemanggilan fungsi.
  • Pada program latihan, kombinasi breakpoint, langkah per instruksi, dan pembacaan memori menunjukkan rcx melompat dari 0 ke 2 sehingga elemen indeks 1 dan 3 tidak pernah dijumlahkan.

Referensi#