Breakpoint menghentikan program berdasarkan lokasi (baris atau fungsi). Watchpoint menghentikan program berdasarkan data—berhenti setiap kali nilai sebuah ekspresi berubah. Mekanisme ini sangat berguna ketika sebuah variabel berubah nilai tanpa kita ketahui siapa yang mengubahnya, terutama bila variabel tersebut di-share lintas banyak fungsi atau diubah lewat pointer. Pada chapter ini kita akan mempelajari tiga jenis watchpoint gdb: watch (berhenti saat ekspresi ditulis), rwatch (berhenti saat ekspresi dibaca), dan awatch (berhenti saat dibaca atau ditulis). Format output gdb pada chapter ini mengikuti dokumentasi resmi di sourceware.org/gdb/current/onlinedocs/gdb/Set-Watchpoints.html.

Program Contoh #

Kita akan memakai program yang memanggil fungsi ubah_nilai tiga kali. Setiap pemanggilan mengubah nilai nilai ke step * 10. Tanpa watchpoint, kita harus memasang breakpoint pada setiap baris pemanggilan; dengan watchpoint, gdb otomatis berhenti pada baris manapun nilai diubah.

c
/* File: /tmp/test_watch1.c */
#include <stdio.h>
static void ubah_nilai(int *x, int step) {
    *x = step * 10;
}
int main(void) {
    int nilai = 0;
    printf("awal: %d\n", nilai);
    ubah_nilai(&nilai, 1);
    printf("setelah step 1: %d\n", nilai);
    ubah_nilai(&nilai, 2);
    printf("setelah step 2: %d\n", nilai);
    ubah_nilai(&nilai, 3);
    printf("setelah step 3: %d\n", nilai);
    return 0;
}
bash
gcc -g -O0 -std=c11 -Wall -Wextra /tmp/test_watch1.c -o /tmp/test_watch1
/tmp/test_watch1

Output yang Dihasilkan:

text
awal: 0
setelah step 1: 10
setelah step 2: 20
setelah step 3: 30

watch: Berhenti Saat Nilai Berubah #

Perintah watch <ekspresi> memasang watchpoint yang berhenti setiap kali nilai ekspresi tersebut berubah. gdb membandingkan nilai sebelum dan sesudah setiap instruksi mesin; bila berbeda, gdb berhenti dan menampilkan nilai lama serta nilai baru.

bash
gdb --batch \
    -ex 'break main' \
    -ex 'run' \
    -ex 'watch nilai' \
    -ex 'continue' \
    -ex 'continue' \
    -ex 'continue' \
    /tmp/test_watch1

Output yang Dihasilkan (format resmi gdb):

text
Breakpoint 1 at 0x...: file /tmp/test_watch1.c, line 8.

Breakpoint 1, main () at /tmp/test_watch1.c:8
8           int nilai = 0;
Hardware watchpoint 2: nilai

Hardware watchpoint 2: nilai

Old value = 0
New value = 10
0x0000... in ubah_nilai (x=0x..., step=1) at /tmp/test_watch1.c:4
4           *x = step * 10;

Hardware watchpoint 2: nilai

Old value = 10
New value = 20
0x0000... in ubah_nilai (x=0x..., step=2) at /tmp/test_watch1.c:4
4           *x = step * 10;

Hardware watchpoint 2: nilai

Old value = 20
New value = 30
0x0000... in ubah_nilai (x=0x..., step=3) at /tmp/test_watch1.c:4
4           *x = step * 10;

Mari kita bedah output watchpoint di atas:

  1. break main lalu run: kita memasang breakpoint pada main terlebih dahulu karena watchpoint memerlukan variabel nilai sudah ada di scope. Tanpa breakpoint ini, gdb tidak bisa memasang watchpoint karena program belum memasuki main.
  2. watch nilai: gdb membuat watchpoint bernomor 2. Kata Hardware watchpoint menandakan gdb memakai dukungan hardware debugging dari CPU (debug register) sehingga watchpoint tidak memperlambat eksekusi. Tanpa hardware support, gdb akan jatuh kembali ke software watchpoint yang jauh lebih lambat.
  3. Old value = 0 / New value = 10: gdb menampilkan nilai nilai sebelum dan sesudah perubahan. Watchpoint berhenti pada baris yang menyebabkan perubahan—yaitu *x = step * 10; di dalam ubah_nilai. Backtrace pada titik berhenti menunjukkan fungsi mana yang melakukan perubahan.
  4. continue dua kali berikutnya: setiap kali nilai berubah (menjadi 20, lalu 30), watchpoint berhenti kembali dengan pola yang sama.

rwatch dan awatch #

Selain watch yang hanya berhenti saat nilai ditulis, gdb menyediakan dua varian lain:

PerintahBerhenti saat
watch exprnilai expr ditulis (berubah)
rwatch exprnilai expr dibaca
awatch exprnilai expr dibaca atau ditulis

rwatch berguna ketika kita ingin tahu siapa yang membaca sebuah variabel—misalnya memeriksa apakah password yang seharusnya terenkripsi tetap dibaca dalam bentuk plain text oleh kode lain. awatch adalah kombinasi keduanya.

bash
gdb --batch \
    -ex 'break main' \
    -ex 'run' \
    -ex 'awatch nilai' \
    -ex 'continue' \
    /tmp/test_watch1

Output yang Dihasilkan (format resmi gdb):

text
Breakpoint 1 at 0x...: file /tmp/test_watch1.c, line 8.

Breakpoint 1, main () at /tmp/test_watch1.c:8
8           int nilai = 0;
Hardware access (read/write) watchpoint 2: nilai

Hardware access (read/write) watchpoint 2: nilai

Value = 0
0x0000... in main () at /tmp/test_watch1.c:9
9           printf("awal: %d\n", nilai);

Watchpoint berhenti pada baris printf("awal: %d\n", nilai); karena printf membaca nilai nilai (akses baca). Baris Value = 0 menampilkan nilai yang sedang dibaca, tanpa menyebutkan nilai baru karena tidak ada penulisan.

Watchpoint pada Ekspresi #

Watchpoint tidak terbatas pada nama variabel sederhana. Kita bisa memasang watchpoint pada ekspresi seperti arr[i], node->next->data, atau total % 100. gdb mengevaluasi ekspresi pada setiap langkah mesin dan berhenti bila hasilnya berubah.

bash
gdb --batch \
    -ex 'break main' \
    -ex 'run' \
    -ex 'watch nilai > 15' \
    -ex 'continue' \
    -ex 'print nilai' \
    /tmp/test_watch1

Output yang Dihasilkan (format resmi gdb):

text
Breakpoint 1 at 0x...: file /tmp/test_watch1.c, line 8.

Breakpoint 1, main () at /tmp/test_watch1.c:8
8           int nilai = 0;
Hardware watchpoint 2: nilai > 15

Hardware watchpoint 2: nilai > 15

Old value = false
New value = true
0x0000... in ubah_nilai (x=0x..., step=2) at /tmp/test_watch1.c:4
4           *x = step * 10;
$1 = 20

Mari kita uraikan elemen kunci pada output watchpoint ekspresi di atas:

  1. watch nilai > 15: watchpoint dipasang pada ekspresi boolean nilai > 15. Awalnya nilai = 0, sehingga ekspresi bernilai false.
  2. Old value = false / New value = true: setelah ubah_nilai(&nilai, 2), nilai menjadi 20. Ekspresi nilai > 15 sekarang bernilai true—transisi yang memicu watchpoint.
  3. 0x0000... in ubah_nilai ... at /tmp/test_watch1.c:4: baris yang menyebabkan transisi nilai adalah *x = step * 10; di dalam ubah_nilai. Kita bisa langsung tahu fungsi mana yang membuat nilai melewati ambang batas 15.
  4. $1 = 20: hasil print nilai pada prompt berikutnya menunjukkan nilai nilai saat ini adalah 20. Watchpoint tidak berhenti pada ubah_nilai(&nilai, 1) (yang menghasilkan nilai = 10) karena 10 > 15 masih false.

Watchpoint Software vs Hardware #

gdb mendukung dua mode watchpoint:

  • Hardware watchpoint: memakai debug register CPU (x86 memiliki 4 register DR0-DR3). Sangat cepat (tidak memperlambat eksekusi) tetapi jumlahnya terbatas.
  • Software watchpoint: gdb mengevaluasi ulang ekspresi setelah setiap instruksi mesin. Bisa menangani ekspresi kompleks dan tidak terbatas jumlahnya, tetapi memperlambat eksekusi hingga 1000x lebih lambat.

gdb otomatis memilih hardware watchpoint bila memungkinkan. Pesan Hardware watchpoint N: expr menandakan mode hardware aktif. Bila gdb menjatuhkan ke software, pesannya menjadi Watchpoint N: expr tanpa awalan Hardware.

Manajemen Watchpoint #

Watchpoint dikelola dengan perintah yang sama dengan breakpoint—keduanya disimpan di daftar bernomor yang sama dan bisa dimanipulasi dengan info breakpoints, delete N, disable N, dan enable N.

bash
gdb --batch \
    -ex 'break main' \
    -ex 'run' \
    -ex 'watch nilai' \
    -ex 'info breakpoints' \
    /tmp/test_watch1

Output yang Dihasilkan (format resmi gdb):

text
Breakpoint 1 at 0x...: file /tmp/test_watch1.c, line 8.

Breakpoint 1, main () at /tmp/test_watch1.c:8
8           int nilai = 0;
Hardware watchpoint 2: nilai
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x...   in main at /tmp/test_watch1.c:8
        breakpoint already hit 1 time
2       hw watchpoint  keep y              nilai

Kolom Type membedakan breakpoint (breakpoint) dari watchpoint (hw watchpoint). Kolom What menampilkan ekspresi yang dipantau. Watchpoint bisa dihapus dengan delete 2 atau dinonaktifkan dengan disable 2.