Setelah breakpoint menghentikan eksekusi, kita perlu menggerakkan program baris demi baris untuk melihat aliran data dan menemukan titik di mana nilai variabel mulai salah. gdb menyediakan empat perintah utama untuk stepping: step (masuk ke fungsi), next (lompati fungsi), finish (jalankan sampai fungsi kembali), dan continue (lanjut sampai breakpoint berikutnya). Pada chapter ini kita akan memakai keempat perintah tersebut, menginspeksi variabel lokal dengan info locals, dan menyimpan hasil ekspresi dalam variabel history lewat print. Output gdb pada chapter ini mengikuti format yang didokumentasikan di sourceware.org/gdb/current/onlinedocs/gdb/Stepping.html.
Program Contoh #
Kita akan memakai program berikut yang memanggil fungsi kuadrat di dalam proses. Struktur pemanggilan bertingkat ini memungkinkan kita melihat perbedaan antara step (yang masuk ke kuadrat) dan next (yang melompati kuadrat).
/* File: /tmp/test_step1.c */
#include <stdio.h>
static int kuadrat(int x) {
return x * x;
}
static int proses(int nilai) {
int a = nilai + 1;
int b = kuadrat(a);
int c = b * 2;
return c;
}
int main(void) {
int hasil = proses(3);
printf("hasil = %d\n", hasil);
return 0;
}gcc -g -O0 -std=c11 -Wall -Wextra /tmp/test_step1.c -o /tmp/test_step1
/tmp/test_step1Output yang Dihasilkan:
hasil = 32next: Lompati Fungsi #
Perintah next mengeksekusi satu baris source. Bila baris tersebut berisi panggilan fungsi, next menjalankan seluruh fungsi tersebut tanpa berhenti di dalamnya, lalu berhenti kembali pada baris berikutnya di pemanggil. Perintah ini cocok untuk verifikasi alur tingkat tinggi tanpa terjebak di detail implementasi fungsi pustaka.
gdb --batch \
-ex 'break proses' \
-ex 'run' \
-ex 'next' \
-ex 'next' \
-ex 'next' \
-ex 'info locals' \
-ex 'continue' \
/tmp/test_step1Output yang Dihasilkan (format resmi gdb):
Breakpoint 1 at 0x...: file /tmp/test_step1.c, line 6.
Breakpoint 1, proses (nilai=3) at /tmp/test_step1.c:6
6 int a = nilai + 1;
7 int b = kuadrat(a);
8 int c = b * 2;
9 return c;
a = 4
b = 16
c = 32
hasil = 32
[Inferior 1 (process ...) exited normally]Mari kita bedah urutan perintah next di atas:
break proses: breakpoint dipasang pada baris pertama fungsiproses(baris 6:int a = nilai + 1;). Program berhenti sebelum baris itu dieksekusi.nextpertama: barisint a = nilai + 1;dieksekusi. gdb berhenti pada baris 7:int b = kuadrat(a);. Variabelasekarang bernilai4(3 + 1).nextkedua: baris pemanggilankuadrat(a)dieksekusi secara penuh tanpa masuk ke bodykuadrat. gdb berhenti pada baris 8:int c = b * 2;. Variabelbsekarang bernilai16(4 * 4).nextketiga: barisint c = b * 2;dieksekusi. gdb berhenti pada baris 9:return c;. Variabelcsekarang bernilai32.info locals: pada titik ini, seluruh variabel lokal sudah memiliki nilai akhir. gdb menampilkana = 4,b = 16,c = 32.
step: Masuk ke Fungsi #
Perintah step juga mengeksekusi satu baris source, tetapi bila baris berisi panggilan fungsi, step masuk ke body fungsi yang dipanggil dan berhenti pada baris pertamanya. Perintah ini berguna ketika kita mencurigai bug berada di dalam fungsi yang dipanggil, bukan di pemanggil.
gdb --batch \
-ex 'break proses' \
-ex 'run' \
-ex 'next' \
-ex 'step' \
-ex 'info args' \
-ex 'info locals' \
-ex 'finish' \
/tmp/test_step1Output yang Dihasilkan (format resmi gdb):
Breakpoint 1 at 0x...: file /tmp/test_step1.c, line 6.
Breakpoint 1, proses (nilai=3) at /tmp/test_step1.c:6
6 int a = nilai + 1;
7 int b = kuadrat(a);
3 return x * x;
x = 4
No locals.
Run till exit from #0 kuadrat (x=4) at /tmp/test_step1.c:3
0x0000... in proses (nilai=3) at /tmp/test_step1.c:8
8 int c = b * 2;
hasil = 32
[Inferior 1 (process ...) exited normally]Mari kita uraikan elemen kunci pada output sesi step di atas:
break proseslalurun: program berhenti di baris 6 (int a = nilai + 1;) sebelum eksekusi.next: baris 6 dieksekusi, gdb berhenti pada baris 7:int b = kuadrat(a);. Sampai di titik ini, perilakustepdannextsama.step: alih-alih melompatikuadrat, gdb masuk ke bodykuadratdan berhenti pada baris 3:return x * x;. Indikatorx = 4(dariinfo args) membuktikan bahwa nilai argumenasudah diterima fungsikuadratdengan benar.info localspada bodykuadrat: outputNo locals.karenakuadrattidak memiliki variabel lokal selain parameterx(yang sudah ditampilkaninfo args).finish: perintah ini menjalankan sisa fungsikuadratsampai ia kembali ke pemanggilproses. gdb menampilkan baris berikutnya yang akan dieksekusi:8 int c = b * 2;. Hal ini berguna untuk keluar dari fungsi yang sedang di-step tanpa harus menekannextberkali-kali.
finish dan until #
finish menjalankan fungsi saat ini sampai return, lalu berhenti pada baris pemanggil. Sementara until mirip dengan next tetapi digunakan untuk maju sampai baris melebihi baris saat ini—berguna untuk keluar dari loop yang sedang berjalan tanpa melewati breakpoint di luar loop.
Perbedaan perilaku tiga perintah stepping utama dapat diringkas sebagai berikut:
| Perintah | Berhenti berikutnya pada | Masuk fungsi? |
|---|---|---|
step | baris berikutnya yang dieksekusi | ya, masuk body fungsi |
next | baris berikutnya di frame saat ini | tidak, fungsi dipanggil penuh |
finish | baris setelah pemanggil fungsi saat ini | tidak, fungsi saat ini dijalankan penuh |
until | baris setelah loop saat ini selesai | tidak, loop dijalankan penuh |
Inspeksi Variabel #
gdb menyediakan beberapa perintah untuk membaca nilai variabel dan ekspresi. Perintah print adalah yang paling umum; hasilnya disimpan dalam variabel history bernama $1, $2, dan seterusnya, sehingga bisa dirujuk pada perintah berikutnya.
gdb --batch \
-ex 'break proses' \
-ex 'run' \
-ex 'next' \
-ex 'print a' \
-ex 'print a * 10' \
-ex 'print $1 + $2' \
/tmp/test_step1Output yang Dihasilkan (format resmi gdb):
Breakpoint 1 at 0x...: file /tmp/test_step1.c, line 6.
Breakpoint 1, proses (nilai=3) at /tmp/test_step1.c:6
6 int a = nilai + 1;
7 int b = kuadrat(a);
$1 = 4
$2 = 40
$3 = 44Mari kita uraikan tiga perintah print di atas:
print a: menampilkan nilai variabela, yaitu4(3 + 1 setelahnextpertama). Hasil disimpan ke variabel history$1.print a * 10: gdb mengevaluasi ekspresia * 10dengan nilaiasaat ini (4) dan menyimpan hasilnya (40) ke variabel history$2.print $1 + $2: kita bisa memakai variabel history pada ekspresi berikutnya.$1adalah4,$2adalah40, sehingga$1 + $2=44. Variabel history$3menyimpan hasil ini.
info locals dan info args #
Perintah info locals mencetak seluruh variabel lokal pada frame saat ini; info args mencetak seluruh argumen fungsi saat ini. Keduanya adalah cara tercepat untuk memeriksa state tanpa harus menyebut nama satu per satu.
gdb --batch \
-ex 'break proses' \
-ex 'run' \
-ex 'next' \
-ex 'next' \
-ex 'info locals' \
-ex 'info args' \
/tmp/test_step1Output yang Dihasilkan (format resmi gdb):
Breakpoint 1 at 0x...: file /tmp/test_step1.c, line 6.
Breakpoint 1, proses (nilai=3) at /tmp/test_step1.c:6
6 int a = nilai + 1;
7 int b = kuadrat(a);
8 int c = b * 2;
a = 4
b = 16
nilai = 3Pada titik berhenti setelah dua kali next, variabel a sudah bernilai 4 dan b sudah bernilai 16. Variabel c belum dideklarasikan pada saat itu sehingga tidak muncul di info locals. Perintah info args menampilkan nilai = 3—argumen fungsi proses yang masih aktif di seluruh body fungsi.
Memodifikasi Nilai Saat Debugging #
gdb memungkinkan kita mengubah nilai variabel saat debugging dengan perintah set variable <nama> = <nilai>. Fitur ini sangat berguna untuk menguji cabang kode yang sulit dipicu dari input normal.
gdb --batch \
-ex 'break proses' \
-ex 'run' \
-ex 'next' \
-ex 'set variable a = 100' \
-ex 'print a' \
-ex 'continue' \
/tmp/test_step1Output yang Dihasilkan (format resmi gdb):
Breakpoint 1 at 0x...: file /tmp/test_step1.c, line 6.
Breakpoint 1, proses (nilai=3) at /tmp/test_step1.c:6
6 int a = nilai + 1;
7 int b = kuadrat(a);
$1 = 100
hasil = 20000
[Inferior 1 (process ...) exited normally]Hasil hasil = 20000 sangat berbeda dari eksekusi normal (32) karena kita mengubah a dari 4 menjadi 100. b = kuadrat(100) = 10000, lalu c = b * 2 = 20000, dan printf mencetak hasil = 20000. Modifikasi nilai saat debugging adalah cara cepat untuk menguji apakah cabang tertentu menangani kasus ekstrim.