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

c
/* 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;
}
bash
gcc -g -O0 -std=c11 -Wall -Wextra /tmp/test_step1.c -o /tmp/test_step1
/tmp/test_step1

Output yang Dihasilkan:

text
hasil = 32

next: 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.

bash
gdb --batch \
    -ex 'break proses' \
    -ex 'run' \
    -ex 'next' \
    -ex 'next' \
    -ex 'next' \
    -ex 'info locals' \
    -ex 'continue' \
    /tmp/test_step1

Output yang Dihasilkan (format resmi gdb):

text
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:

  1. break proses: breakpoint dipasang pada baris pertama fungsi proses (baris 6: int a = nilai + 1;). Program berhenti sebelum baris itu dieksekusi.
  2. next pertama: baris int a = nilai + 1; dieksekusi. gdb berhenti pada baris 7: int b = kuadrat(a);. Variabel a sekarang bernilai 4 (3 + 1).
  3. next kedua: baris pemanggilan kuadrat(a) dieksekusi secara penuh tanpa masuk ke body kuadrat. gdb berhenti pada baris 8: int c = b * 2;. Variabel b sekarang bernilai 16 (4 * 4).
  4. next ketiga: baris int c = b * 2; dieksekusi. gdb berhenti pada baris 9: return c;. Variabel c sekarang bernilai 32.
  5. info locals: pada titik ini, seluruh variabel lokal sudah memiliki nilai akhir. gdb menampilkan a = 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.

bash
gdb --batch \
    -ex 'break proses' \
    -ex 'run' \
    -ex 'next' \
    -ex 'step' \
    -ex 'info args' \
    -ex 'info locals' \
    -ex 'finish' \
    /tmp/test_step1

Output yang Dihasilkan (format resmi gdb):

text
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:

  1. break proses lalu run: program berhenti di baris 6 (int a = nilai + 1;) sebelum eksekusi.
  2. next: baris 6 dieksekusi, gdb berhenti pada baris 7: int b = kuadrat(a);. Sampai di titik ini, perilaku step dan next sama.
  3. step: alih-alih melompati kuadrat, gdb masuk ke body kuadrat dan berhenti pada baris 3: return x * x;. Indikator x = 4 (dari info args) membuktikan bahwa nilai argumen a sudah diterima fungsi kuadrat dengan benar.
  4. info locals pada body kuadrat: output No locals. karena kuadrat tidak memiliki variabel lokal selain parameter x (yang sudah ditampilkan info args).
  5. finish: perintah ini menjalankan sisa fungsi kuadrat sampai ia kembali ke pemanggil proses. 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 menekan next berkali-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:

PerintahBerhenti berikutnya padaMasuk fungsi?
stepbaris berikutnya yang dieksekusiya, masuk body fungsi
nextbaris berikutnya di frame saat initidak, fungsi dipanggil penuh
finishbaris setelah pemanggil fungsi saat initidak, fungsi saat ini dijalankan penuh
untilbaris setelah loop saat ini selesaitidak, 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.

bash
gdb --batch \
    -ex 'break proses' \
    -ex 'run' \
    -ex 'next' \
    -ex 'print a' \
    -ex 'print a * 10' \
    -ex 'print $1 + $2' \
    /tmp/test_step1

Output yang Dihasilkan (format resmi gdb):

text
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 = 44

Mari kita uraikan tiga perintah print di atas:

  1. print a: menampilkan nilai variabel a, yaitu 4 (3 + 1 setelah next pertama). Hasil disimpan ke variabel history $1.
  2. print a * 10: gdb mengevaluasi ekspresi a * 10 dengan nilai a saat ini (4) dan menyimpan hasilnya (40) ke variabel history $2.
  3. print $1 + $2: kita bisa memakai variabel history pada ekspresi berikutnya. $1 adalah 4, $2 adalah 40, sehingga $1 + $2 = 44. Variabel history $3 menyimpan 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.

bash
gdb --batch \
    -ex 'break proses' \
    -ex 'run' \
    -ex 'next' \
    -ex 'next' \
    -ex 'info locals' \
    -ex 'info args' \
    /tmp/test_step1

Output yang Dihasilkan (format resmi gdb):

text
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 = 3

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

bash
gdb --batch \
    -ex 'break proses' \
    -ex 'run' \
    -ex 'next' \
    -ex 'set variable a = 100' \
    -ex 'print a' \
    -ex 'continue' \
    /tmp/test_step1

Output yang Dihasilkan (format resmi gdb):

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