Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 35.526 phút đọc

Watchpoint, vũ khí mạnh nhất của GDB

Sau bài này bạn sẽ làm được

  • Đặt watch, rwatch, awatch và biết khác nhau
  • Tìm thủ phạm ghi đè một biến bằng watchpoint
  • Dùng watch -l khi biến ra khỏi phạm vi
  • Biết giới hạn số watchpoint phần cứng và cái giá của bản mềm

Điểm dừng trả lời câu hỏi "khi chạy tới đây thì trạng thái thế nào". Watchpoint trả lời câu hỏi khó hơn nhiều: "ai đã sửa biến này". Đó là câu hỏi mà không cách nào khác trả lời được trong thời gian hợp lý.

#Một ví dụ thật, từ đầu tới cuối

ghi_de.c
#include <stdio.h>
#include <string.h>

typedef struct {
    char ten[8];
    int  diem;
} SinhVien;

static void dat_ten(SinhVien *sv, const char *ten) {
    strcpy(sv->ten, ten);          /* khong kiem tra do dai */
}

int main(void) {
    SinhVien sv;

    sv.diem = 100;
    printf("diem truoc = %d\n", sv.diem);

    dat_ten(&sv, "NguyenVanA");    /* 10 ky tu vao mang 8 byte */

    printf("diem sau   = %d\n", sv.diem);
    printf("ten        = %s\n", sv.ten);

    return 0;
}
terminal
gcc -std=c11 -g3 -O0 -Wall -Wextra -o ghi_de.exe ghi_de.c && ./ghi_de.exe
diem truoc = 100
diem sau   = 16750
ten        = NguyenVanA
g3.gdb
set pagination off
set confirm off
break main
run
next
watch sv.diem
continue
bt
info locals
quit
terminal
gdb -q -batch -x g3.gdb ./ghi_de.exe
Breakpoint 1 at 0x401584: file ghi_de.c, line 16.

Thread 1 hit Breakpoint 1, main () at ghi_de.c:16
16	    sv.diem = 100;
17	    printf("diem truoc = %d\n", sv.diem);
Hardware watchpoint 2: sv.diem

Thread 1 hit Hardware watchpoint 2: sv.diem

Old value = 100
New value = 110
0x00007ffef8f6afd2 in strcpy () from C:\WINDOWS\System32\msvcrt.dll
#0  0x00007ffef8f6afd2 in strcpy () from C:\WINDOWS\System32\msvcrt.dll
#1  0x0000000000401570 in dat_ten (sv=0x61fe44, ten=0x404011 "NguyenVanA") at ghi_de.c:10
#2  0x00000000004015af in main () at ghi_de.c:19

#Ba loại watchpoint

LệnhDừng khiDùng khi
watch xx bị GHI và giá trị thay đổiTìm ai sửa một biến
rwatch xx bị ĐỌCTìm ai dùng một biến
awatch xx bị đọc HOẶC ghiTheo dõi mọi truy cập
watch sv.diem              # bien hoac truong
watch *p                   # thu con tro tro toi
watch p                    # chinh con tro
watch a[3]                 # mot phan tu mang
watch *(int *)0x61fe44     # mot dia chi cu the
watch g_dem if g_dem > 100 # co dieu kien, nhu diem dung

info watchpoints           # liet ke
delete 2                   # xoa, dung chung danh sach voi diem dung

#Vấn đề phạm vi và watch -l

(gdb) watch x
Hardware watchpoint 2: x

(gdb) continue
...
Watchpoint 2 deleted because the program has left the block
in which its expression is valid.

/* Xay ra khi x la bien CUC BO va ham chua no da tra ve.
   GDB tu xoa watchpoint vi bieu thuc "x" khong con nghia. */
Bạn muốn biếtDùng lệnh
Ai gán lại con trỏ pwatch p
Ai sửa dữ liệu p trỏ tớiwatch -l *p
Ai sửa trường diem của một sinh viên cụ thểwatch -l sv->diem
Ai ghi vào byte thứ 5 của một bộ đệmwatch *(char *)(bo_dem + 5)
Ai sửa một biến toàn cụcwatch g_dem, không cần -l
Ai đọc một biến sắp bị xóarwatch g_cu

#Phần cứng hay phần mềm

Watchpoint phần cứng
CPU x86 có bốn thanh ghi gỡ lỗi DR0 tới DR3. Mỗi thanh ghi theo dõi được một tới tám byte, và CPU tự sinh ngắt khi vùng đó bị chạm. Chi phí gần bằng không.
(gdb) watch sv.diem
Hardware watchpoint 2: sv.diem            <- tot: dung thanh ghi phan cung

(gdb) watch bo_dem
Watchpoint 2: bo_dem                      <- KHONG co chu "Hardware":
                                             ban PHAN MEM, rat cham
Phần cứngPhần mềm
Số lượng tối đa trên x864Không giới hạn
Kích thước vùng tối đa8 byteBất kỳ
Chậm hơnKhông đáng kểHàng trăm tới hàng nghìn lần
Cách nhận biếtCó chữ HardwareKhông có
Dùng được trên mọi nền tảngKhông, tùy CPU và hệ điều hànhCó

#Bốn tình huống nên dùng

Một: biến đổi giá trị mà không ai gán

Đúng ví dụ đầu bài. Đây là tình huống kinh điển, và nguyên nhân gần như luôn là một lần ghi tràn ở chỗ khác.

Hai: tìm nơi một tài nguyên bị giải phóng

/* Trieu chung: dung sau khi giai phong, nhung khong biet ai free */

(gdb) break malloc
(gdb) run
(gdb) finish                    /* lay dia chi vua cap phat */
Value returned is $1 = (void *) 0x602010

(gdb) watch -l *(int *)0x602010
(gdb) continue

/* Khi bo cap phat ghi so sach vao khoi do luc free, watchpoint dung,
   va backtrace chi thang vao lan free.

   Cach nay cham va thu cong. Neu dung duoc ASan thi dung ASan:
   no in ca ba cho trong mot bao cao. Nhung khi khong dich lai duoc
   thi day la cach duy nhat. */

Ba: tìm lỗi trong một cấu trúc dữ liệu

/* Trieu chung: cay do den mat can bang sau vai nghin thao tac,
   nhung khong biet thao tac nao lam hong. */

(gdb) watch -l nut->mau
(gdb) commands
 >silent
 >printf "mau doi tai: "
 >bt 3
 >continue
 >end

/* Ket hop watchpoint voi lenh tu dong o Bai 35.2: ban duoc mot
   nhat ky moi lan truong do doi, kem ba khung goi, ma khong dung
   chuong trinh lan nao. */

Bốn: xác nhận một biến KHÔNG bị sửa

/* Doi khi ban muon chung minh dieu nguoc lai: */

(gdb) watch g_cau_hinh
(gdb) continue
...
[Inferior 1 (process 1234) exited normally]

/* Chay het chuong trinh ma watchpoint khong dung lan nao
   -> bien do that su khong bi sua.

   Rat huu ich khi ban dang loai tru gia thuyet. */

Tự làm thử

  1. Chạy ghi_de.c và xác nhận diem đổi từ 100 thành 16750.
  2. Đặt watchpoint trên sv.diem và đọc backtrace tìm thủ phạm.
  3. Giải thích vì sao New value là 110 chứ không phải một số khác.
  4. Thử watch trên một struct lớn và xác nhận GDB không dùng phần cứng.
  5. So watch p với watch -l *p trên cùng một chương trình.
  6. Kết hợp watchpoint với khối commands để ghi nhật ký mọi lần một trường đổi giá trị.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • Watchpoint trả lời câu hỏi "ai đã sửa biến này", kèm giá trị cũ, giá trị mới và cả chuỗi gọi hàm.
  • Trong ví dụ ở bài này, ba khung backtrace chỉ thẳng từ strcpy qua dat_ten tới dòng gọi trong main.
  • watch p theo dõi con trỏ, còn watch -l *p theo dõi ô nhớ nó trỏ tới. Hai việc hoàn toàn khác nhau.
  • x86 chỉ có bốn watchpoint phần cứng, mỗi cái tối đa tám byte. Vượt quá là bản phần mềm, chậm hàng nghìn lần.
  • Watchpoint bắt được ghi tràn giữa hai trường của cùng một struct, chỗ mà ASan bỏ qua.