Quy trình sáu bước và tệp .gdbinit
Sau bài này bạn sẽ làm được
- Áp dụng quy trình sáu bước cho một lỗi thật
- Thu hẹp phạm vi bằng cách chia đôi
- Viết tệp .gdbinit với các lệnh tự định nghĩa
- Kết thúc mọi lần sửa lỗi bằng một bài kiểm thử hồi quy
Gỡ lỗi giỏi không phải là biết nhiều lệnh GDB. Đó là có một quy trình, và bám vào quy trình đó cả khi đang bực. Hai bước hay bị bỏ nhất là bước đầu và bước cuối, và đó cũng là hai bước quan trọng nhất.
#Sáu bước
Tái hiện lỗi một cách ổn định
Tìm đầu vào nhỏ nhất gây lỗi. Nếu lỗi chỉ xuất hiện một lần trong mười lần chạy thì bạn không biết mình đã sửa hay chưa.
Thu hẹp phạm vi
Chia đôi: xóa dần mã, dữ liệu vào, hoặc lịch sử phiên bản cho tới khi lỗi biến mất. Mỗi lần chia đôi giảm một nửa vùng phải tìm.
Chạy sanitizer
ASan và UBSan trước, Valgrind sau. Chúng không cần bạn có giả thuyết và thường chỉ thẳng ra dòng lỗi.
Mở GDB
Đặt điểm dừng trước chỗ nghi ngờ, không phải tại chỗ sập. Đọc
btvàinfo locals.Dùng watchpoint nếu một biến đổi bất thường
Đây là bước riêng vì nó giải quyết một loại câu hỏi mà bốn bước trên không giải quyết được.
Viết kiểm thử hồi quy rồi mới sửa
Bài kiểm thử phải thất bại trên bản chưa sửa. Nếu nó đạt thì nó không kiểm thử đúng thứ bạn nghĩ.
#Bước một: tái hiện
# Co dinh cac yeu to ngau nhien de loi tai hien duoc
# 1. So ngau nhien: dat hat co dinh
srand(12345); /* thay cho srand(time(NULL)) */
# 2. Dia chi cap phat: tat ngau nhien hoa khong gian dia chi
setarch -R ./prog # Linux
(gdb) set disable-randomization on # GDB tat san, mac dinh
# 3. Bo nho chua khoi tao: bat cho no mot mau co dinh
MALLOC_PERTURB_=42 ./prog # glibc: dien 42 vao bo nho moi cap
# va 213 vao bo nho vua free
# 4. Da luong: giam so luong
./prog --so-luong=1 # neu chuong trinh cho phep
# 5. Moi truong: chay trong moi truong toi thieu
env -i ./prog#Bước hai: thu hẹp
Chia đôi dữ liệu vào
# Tep dau vao 10000 dong lam chuong trinh sap.
head -5000 vao.txt > a.txt && ./prog a.txt # sap? -> loi o nua dau
tail -5000 vao.txt > b.txt && ./prog b.txt # sap? -> loi o nua sau
# Lap lai. Sau 14 lan chia doi tu 10000 dong, ban con 1 dong.
# Cong cu tu dong hoa viec nay:
# ddmin, delta debugging
# creduce, cvise: rut gon MA NGUON C giu nguyen mot trieu chungChia đôi lịch sử phiên bản
# "Thang truoc con chay dung, gio thi sap." Git tim ho ban:
git bisect start
git bisect bad # ban hien tai co loi
git bisect good v1.2.0 # ban nay chay dung
# Git checkout mot ban o giua. Ban chay thu roi bao:
git bisect good # hoac
git bisect bad
# Lap lai. Voi 1000 ban ghi, chi can 10 lan thu.
git bisect reset # xong, quay ve nhanh cu
# Tu dong hoan toan, neu ban co mot lenh kiem tra:
git bisect run ./kiem_tra.sh # tra ve 0 la tot, khac 0 la loiChia đôi mã nguồn
/* Xoa dan cho toi khi loi bien mat. Moi buoc phai kiem tra lai. */
/* Vong 1: bo mot nua so ham goi trong main */
/* Vong 2: bo mot nua con lai */
/* ...
Cach nay tho nhung hieu qua. Khi ban con hai muoi dong ma van
sap, viec doc ma tro nen de dang.
Meo: dung "#if 0 ... #endif" thay vi xoa that, de con khoi phuc.
Bai 19.6 da noi. */
/* Va mot bien the tinh te hon: chia doi CO DICH */
gcc -O2 ... # sap
gcc -O1 ... # chay dung -> loi lien quan toi toi uu o -O2
gcc -O2 -fno-strict-aliasing ... # chay dung -> tim ra thu pham#Bước sáu: kiểm thử hồi quy
/* Dung khung kiem thu ba macro cua Bai 32.4 */
#include "test.h"
#include "sinhvien.h"
static void thu_loi_ghi_de(void) {
NHOM("loi #142: ten qua dai ghi de len diem");
SinhVien sv = { "", 100 };
dat_ten(&sv, "NguyenVanA"); /* 10 ky tu vao mang 8 byte */
KIEM_BANG(sv.diem, 100); /* diem KHONG duoc doi */
KIEM_BANG(sv.ten[7], 0); /* co ky tu ket thuc chuoi */
KIEM_CHUOI(sv.ten, "NguyenV"); /* bi cat dung bay ky tu */
}[loi #142: ten qua dai ghi de len diem] HONG thu.c:12: sv.diem == 100 (16750 vs 100) HONG thu.c:13: sv.ten[7] == 0 (110 vs 0) HONG thu.c:14: "NguyenVanA" vs "NguyenV" 3 kiem tra, 3 hong
[loi #142: ten qua dai ghi de len diem] 3 kiem tra, 0 hong
#Tệp .gdbinit
# --- hien thi ---
set print pretty on # struct xuong dong, thut le
set print array on # mang moi phan tu mot dong
set print array-indexes on # kem chi so: [0] = 5
set print elements 200 # so phan tu toi da in ra
set print repeats 10 # gom gia tri lap lai
set print null-stop on # chuoi dung o ky tu ket thuc
set print union on # in ca thanh vien union
# --- tien lam viec ---
set pagination off # khong hoi "Press enter to continue"
set confirm off # khong hoi lai khi quit hay delete
set history save on # nho lenh giua cac phien
set history size 10000
set history filename ~/.gdb_history
# --- hop ngu ---
set disassembly-flavor intel # cu phap Intel, de doc hon AT&T
# --- go loi ---
set disable-randomization on # mac dinh da bat, ghi ro cho chac
set backtrace past-main off # khong hien ruot cua libc truoc main
# --- cho phep nap .gdbinit trong thu muc du an ---
add-auto-load-safe-path ~/duan#Lệnh tự định nghĩa
# --- khoi dong nhanh ---
define bm
break main
run
end
document bm
Dat diem dung o main va chay ngay.
end
# --- in danh sach lien ket ---
define in_ds
set $p = $arg0
set $i = 0
while $p != 0
printf "[%d] ma=%-4d ten=%s\n", $i, $p->ma, $p->ten
set $p = $p->sau
set $i = $i + 1
end
printf "tong cong %d nut\n", $i
end
document in_ds
In toan bo danh sach lien ket. Vi du: in_ds dau
end
# --- in mot sinh vien cho de doc ---
define in_sv
printf "SinhVien{ ma=%d ten=%s diem=%d }\n", \
$arg0->ma, $arg0->ten, $arg0->diem
end
# --- do bo nho mot khoi ---
define do_khoi
printf "=== %d byte tu %p ===\n", $arg1, $arg0
x/$arg1xb $arg0
end
document do_khoi
Do n byte tu mot dia chi. Vi du: do_khoi p 64
end
# --- ngu canh day du khi dung ---
define nc
printf "=== vet goi ===\n"
bt
printf "=== bien cuc bo ===\n"
info locals
printf "=== tham so ===\n"
info args
end
document nc
In vet goi, bien cuc bo va tham so cua khung hien tai.
end# Trong .gdbinit hoac mot tep .py rieng
python
import gdb
class InDanhSach(gdb.Command):
"""In danh sach lien ket. Cu phap: in_ds <nut_dau>"""
def __init__(self):
super().__init__("in_ds", gdb.COMMAND_USER)
def invoke(self, doi_so, tu_man_hinh):
p = gdb.parse_and_eval(doi_so)
i = 0
while p != 0:
print(f"[{i}] ma={int(p['ma']):<4} ten={p['ten'].string()}")
p = p["sau"]
i += 1
if i > 10000:
print("qua 10000 nut, co the co vong")
break
print(f"tong cong {i} nut")
InDanhSach()
end
# Python trong GDB con lam duoc:
# - pretty printer: doi cach GDB hien mot kieu do ban dinh nghia
# - doc va ghi file, goi ra ngoai
# - phan tich cau truc du lieu phuc tap
#
# Moi ban GDB hien dai deu co san Python. Kiem tra:
# (gdb) python print(gdb.VERSION)Tự làm thử
- Chạy một chương trình với
MALLOC_PERTURB_=42và xem có lỗi mới lộ ra không. - Dùng
git bisectđể tìm bản ghi làm hỏng một bài kiểm thử. - Viết một bài kiểm thử hồi quy và xác nhận nó thất bại trên bản chưa sửa.
- Viết tệp
~/.gdbinittheo mẫu trong bài. - Viết lệnh
in_dsvàncrồi dùng chúng trên một chương trình thật. - Viết một pretty printer bằng Python cho một struct của bạn.
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
- Sáu bước: tái hiện, thu hẹp, sanitizer, GDB, watchpoint, kiểm thử hồi quy.
MALLOC_PERTURB_=42biến nhiều lỗi thỉnh thoảng thành lỗi luôn xảy ra, chỉ bằng một biến môi trường.git bisect runtự động tìm bản ghi gây lỗi trong mười lần thử với một nghìn bản ghi.- Bài kiểm thử hồi quy phải thất bại trên bản chưa sửa, nếu không nó không chứng minh được gì.
- Lệnh tự định nghĩa trong
.gdbinitcủa dự án nên đưa vào kho mã để cả nhóm dùng chung.