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
quitterminal
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ệnh | Dừng khi | Dùng khi |
|---|---|---|
| watch x | x bị GHI và giá trị thay đổi | Tìm ai sửa một biến |
| rwatch x | x bị ĐỌC | Tìm ai dùng một biến |
| awatch x | x bị đọc HOẶC ghi | Theo 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ết | Dùng lệnh |
|---|---|
| Ai gán lại con trỏ p | watch p |
| Ai sửa dữ liệu p trỏ tới | watch -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ộ đệm | watch *(char *)(bo_dem + 5) |
| Ai sửa một biến toàn cục | watch g_dem, không cần -l |
| Ai đọc một biến sắp bị xóa | rwatch 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ứng | Phần mềm | |
|---|---|---|
| Số lượng tối đa trên x86 | 4 | Không giới hạn |
| Kích thước vùng tối đa | 8 byte | Bất kỳ |
| Chậm hơn | Không đáng kể | Hàng trăm tới hàng nghìn lần |
| Cách nhận biết | Có chữ Hardware | Không có |
| Dùng được trên mọi nền tảng | Không, tùy CPU và hệ điều hành | Có |
#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ử
- Chạy
ghi_de.cvà xác nhậndiemđổi từ 100 thành 16750. - Đặt watchpoint trên
sv.diemvà đọc backtrace tìm thủ phạm. - Giải thích vì sao
New valuelà 110 chứ không phải một số khác. - Thử
watchtrên một struct lớn và xác nhận GDB không dùng phần cứng. - So
watch pvớiwatch -l *ptrên cùng một chương trình. - 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ừ
strcpyquadat_tentới dòng gọi trongmain. watch ptheo dõi con trỏ, cònwatch -l *ptheo 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.