Project: báo cáo săn lỗi
Sau bài này bạn sẽ làm được
- Lập báo cáo đầy đủ cho từng lỗi tìm được
- Giải thích nguyên nhân chứ không chỉ cách sửa
- Chứng minh cả ba công cụ đều sạch sau khi vá
- Viết bộ kiểm thử hồi quy cho mọi lỗi đã sửa
Một dự án C từ năm trăm tới một nghìn dòng, có ít nhất mười lăm lỗi thuộc đủ các loại đã học. Bạn nộp bốn thứ: báo cáo, bản vá, bằng chứng, và bộ kiểm thử hồi quy. Đây là bài tập gói lại toàn bộ Phần 13.
#Đề bài
quanly/
include/
sinhvien.h khai bao kieu va ham
danhsach.h danh sach dong
tepdulieu.h doc ghi tep
chuoi.h tien ich chuoi
src/
sinhvien.c
danhsach.c
tepdulieu.c
chuoi.c
main.c
test/
test.h khung kiem thu ba macro, tu Bai 32.4
test.c
test_hoi_quy.c ban se viet tep nay
du_lieu/
hop_le.csv
hong.csv du lieu vao co van de, de kich hoat cac loi
Makefile| Nhóm lỗi | Số lượng tối thiểu | Bài đã học |
|---|---|---|
| Truy cập ngoài biên, heap và ngăn xếp | 3 | 33.3, 34.2 |
| Rò rỉ bộ nhớ | 2 | 34.4 |
| Dùng sau khi giải phóng hoặc giải phóng hai lần | 2 | 33.3, 34.2 |
| Biến chưa khởi tạo | 1 | 34.4 |
| Tràn số hoặc dịch bit sai | 2 | 33.4, 34.3 |
| Vi phạm strict aliasing | 1 | 33.6 |
| Lỗi logic thuần túy, không công cụ nào bắt | 2 | 35.4 |
| Giả định implementation-defined | 1 | 33.7 |
| Không kiểm tra giá trị trả về của malloc hoặc fopen | 1 | 18.2 |
#Quy trình bắt buộc
Dịch sạch trước đã
Chạy với đủ cờ cảnh báo và sửa hết. Ghi lại số cảnh báo ban đầu và số lỗi mà chúng bắt được. Ở Bài 34.6 con số đó là hai trên mười.
Chạy ba lượt sanitizer
ASan cộng UBSan, rồi Valgrind. Ghi lại từng báo cáo nguyên văn trước khi sửa bất cứ thứ gì.
Sửa một lỗi, chạy lại toàn bộ
Không sửa nhiều lỗi cùng lúc. Một số lỗi che lấp lỗi khác, và sửa lỗi đầu có thể làm lộ ra lỗi thứ hai ở cùng chỗ.
Với mỗi lỗi, viết kiểm thử trước khi vá
Chạy kiểm thử trên bản chưa vá và chép lại dòng
HONG. Đó là bằng chứng kiểm thử của bạn thật sự bắt được lỗi.Hai lỗi cuối tìm bằng cách đọc mã
Sau khi ba công cụ đã im lặng mà kết quả vẫn sai, bạn còn lại lỗi logic. Dùng GDB và watchpoint, và cuối cùng là đọc mã.
#Bộ công cụ và kịch bản
CC = gcc
CHUNG = -std=c11 -Iinclude \
-Wall -Wextra -Wpedantic -Wshadow -Wconversion \
-Wcast-qual -Wstrict-prototypes -Wmissing-prototypes \
-Wnull-dereference -Wdouble-promotion -Wwrite-strings \
-Wformat=2 -Wstack-usage=8192
NGUON = $(wildcard src/*.c)
NGUON_THU = $(wildcard test/*.c)
# --- ban thuong, chi de xem chuong trinh chay the nao ---
thuong:
$(CC) $(CHUNG) -g -O1 -o quanly $(NGUON)
# --- ban sanitizer: ASan va UBSan ---
san:
$(CC) $(CHUNG) -g3 -O1 -fno-omit-frame-pointer \
-fsanitize=address,undefined \
-fsanitize-address-use-after-scope \
-fno-sanitize-recover=all \
-o quanly_san $(NGUON)
# --- ban cho Valgrind ---
vg:
$(CC) $(CHUNG) -g -O1 -o quanly_vg $(NGUON)
valgrind --leak-check=full --show-leak-kinds=all \
--track-origins=yes --error-exitcode=1 \
./quanly_vg du_lieu/hong.csv
# --- ban kiem thu hoi quy ---
thu:
$(CC) $(CHUNG) -g3 -O1 -fsanitize=address,undefined \
-fno-sanitize-recover=all \
-o chay_thu $(filter-out src/main.c,$(NGUON)) $(NGUON_THU)
ASAN_OPTIONS=detect_stack_use_after_return=1 ./chay_thu
# --- chay het ---
kiem: san thu vg
ASAN_OPTIONS=detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 \
MALLOC_PERTURB_=42 \
./quanly_san du_lieu/hong.csv
sach:
rm -f quanly quanly_san quanly_vg chay_thu *.o
.PHONY: thuong san vg thu kiem sach#!/bin/sh
# Tra ve 0 neu MOI THU sach, khac 0 neu co loi.
# Dung voi: git bisect run ./kiem_tra.sh
set -e
make sach > /dev/null
make san > /dev/null 2>&1 || exit 125 # 125 = khong dich duoc, bo qua
ASAN_OPTIONS=detect_leaks=1 \
UBSAN_OPTIONS=halt_on_error=1 \
MALLOC_PERTURB_=42 \
./quanly_san du_lieu/hong.csv > /dev/null 2>&1 || exit 1
make thu > /dev/null 2>&1 || exit 1
exit 0#Mẫu báo cáo
# Loi 07: ro ri bo nho khi doc dong CSV hong
| | |
|---|---|
| **Tep va dong** | src/tepdulieu.c:64 |
| **Loai** | Ro ri bo nho, definitely lost |
| **Cong cu** | Valgrind, va LeakSanitizer xac nhan |
| **Muc do** | Cao: ro ri moi dong hong, tep lon lam het bo nho |
| **Trang thai** | Da va, co kiem thu hoi quy |
## Ket xuat goc
==9871== 5,600 bytes in 100 blocks are definitely lost in loss record 4 of 4
==9871== at 0x4C2FB0F: malloc (vg_replace_malloc.c:299)
==9871== by 0x4009A1: sv_tao (sinhvien.c:18)
==9871== by 0x400B23: doc_dong (tepdulieu.c:58)
==9871== by 0x400C05: doc_tep (tepdulieu.c:81)
==9871== by 0x400E12: main (main.c:34)
==9871==
==9871== LEAK SUMMARY:
==9871== definitely lost: 5,600 bytes in 100 blocks
## Nguyen nhan
doc_dong cap phat mot SinhVien bang sv_tao o dong 58, roi phan tich
cac truong. Neu mot truong khong hop le thi ham tra ve NULL o dong 64
ma KHONG giai phong doi tuong da cap.
Tep du_lieu/hong.csv co 100 dong hong, moi dong ro ri 56 byte,
tong 5600 byte, dung nhu Valgrind bao.
Day la mau loi kinh dien: duong THANH CONG co giai phong, duong LOI
thi khong. Loi loai nay khong bao gio lo ra trong kiem thu chi dung
du lieu hop le.
## Ban va
@@ -60,7 +60,10 @@ SinhVien *doc_dong(const char *dong) {
if (phan_tich_diem(truong[2], &sv->diem) != 0) {
- return NULL;
+ sv_huy(sv);
+ return NULL;
}
## Bang chung sau khi va
$ make vg
==9903== LEAK SUMMARY:
==9903== definitely lost: 0 bytes in 0 blocks
==9903== indirectly lost: 0 bytes in 0 blocks
==9903== ERROR SUMMARY: 0 errors from 0 contexts
$ make san
$ ./quanly_san du_lieu/hong.csv
doc 900 dong hop le, bo qua 100 dong hong
(khong co bao cao nao cua ASan)
## Kiem thu hoi quy
Xem test/test_hoi_quy.c ham thu_loi_07.
Truoc khi va: 1 kiem tra, 1 hong
Sau khi va: 1 kiem tra, 0 hong#Bộ kiểm thử hồi quy
#include <stdlib.h>
#include <string.h>
#include "test.h"
#include "sinhvien.h"
#include "tepdulieu.h"
#include "danhsach.h"
#include "chuoi.h"
/* Loi 01: ten qua dai ghi de len truong ke ben */
static void thu_loi_01(void) {
NHOM("loi 01: tran bo dem ten");
SinhVien sv = { 0 };
sv.diem = 100;
sv_dat_ten(&sv, "MotCaiTenRatDaiVuotQuaGioiHan");
KIEM_BANG(sv.diem, 100);
KIEM_BANG(sv.ten[sizeof sv.ten - 1], 0);
}
/* Loi 07: ro ri tren duong loi cua doc_dong */
static void thu_loi_07(void) {
NHOM("loi 07: ro ri khi dong CSV hong");
/* Doc mot dong hong 1000 lan. Neu con ro ri thi ASan bao
khi chuong trinh thoat. */
for (int i = 0; i < 1000; ++i) {
SinhVien *sv = doc_dong("1,Nguyen Van A,KHONG_PHAI_SO");
KIEM(sv == NULL);
}
}
/* Loi 09: tran so khi tinh kich thuoc mang */
static void thu_loi_09(void) {
NHOM("loi 09: tran khi tinh kich thuoc");
DanhSach *ds = ds_tao();
KIEM(ds != NULL);
/* So phan tu vo ly phai bi tu choi, khong duoc cap phat sai */
KIEM_BANG(ds_dat_suc_chua(ds, (size_t)-1), -1);
KIEM_BANG(ds_dat_suc_chua(ds, 1000000000000u), -1);
ds_huy(ds);
}
/* Loi 12: dung sau khi giai phong trong ds_xoa */
static void thu_loi_12(void) {
NHOM("loi 12: dung sau khi giai phong");
DanhSach *ds = ds_tao();
for (int i = 0; i < 10; ++i) ds_them(ds, sv_tao(i, "x", 5));
ds_xoa(ds, 5);
KIEM_BANG(ds_so(ds), 9);
KIEM(ds_tai(ds, 5) != NULL);
KIEM_BANG(ds_tai(ds, 5)->ma, 6); /* phan tu sau da don len */
ds_huy(ds);
}
/* Loi 14: doi_cho hong khi hai con tro trung nhau */
static void thu_loi_14(void) {
NHOM("loi 14: hoan doi voi hai con tro trung");
SinhVien *a = sv_tao(1, "A", 10);
ds_doi_cho(a, a); /* truong hop bien */
KIEM_BANG(a->ma, 1);
KIEM_BANG(a->diem, 10);
KIEM_CHUOI(a->ten, "A");
sv_huy(a);
}
int main(void) {
thu_loi_01();
thu_loi_07();
thu_loi_09();
thu_loi_12();
thu_loi_14();
/* ... du 15 ham ... */
printf("\n%d kiem tra, %d hong\n", test_so_kiem, test_so_hong);
return test_so_hong == 0 ? 0 : 1;
}#Danh sách nghiệm thu
Chương 33, hành vi không xác định
- Phân loại đúng mỗi lỗi thành undefined, unspecified hay implementation-defined.
- Giải thích được vì sao ít nhất một lỗi chỉ lộ ra ở
-O2. - Không dùng
-fwrapvhay-fno-strict-aliasinglàm bản vá. - Mọi kiểm tra tràn đặt trước phép tính, không đặt sau.
Chương 34, gỡ lỗi bộ nhớ
- Chạy đủ ba lượt: ASan cộng UBSan, TSan nếu có luồng, Valgrind.
- Mọi báo cáo chép nguyên văn vào tài liệu, kể cả phần vết gọi.
- Dùng
--error-exitcode=1và-fno-sanitize-recover=all, để tích hợp liên tục phát hiện được. - Nêu được ít nhất một lỗi mà chỉ Valgrind bắt, và một lỗi mà chỉ ASan bắt.
Chương 35, GDB
- Dùng ít nhất một watchpoint để tìm ra một lỗi ghi đè, kèm backtrace.
- Dùng ít nhất một điểm dừng có điều kiện trên một vòng lặp dài.
- Viết ít nhất một lệnh tự định nghĩa trong
.gdbinitcủa dự án. - Với hai lỗi logic, ghi lại quá trình thu hẹp phạm vi từng bước.
Chất lượng chung
- Dịch sạch với bộ cờ nghiêm ngặt đầy đủ, không một cảnh báo nào.
- Cả ba công cụ im lặng sau khi vá.
- Đủ mười lăm bài kiểm thử hồi quy, và mỗi bài đều thất bại trên bản chưa vá.
make sach && make kiemchạy từ đầu và trả về mã thoát 0.- Mỗi báo cáo có đủ sáu phần: vị trí, kết xuất gốc, nguyên nhân, bản vá, bằng chứng, kiểm thử.
- Ghi lại tổng kết: bao nhiêu lỗi mỗi công cụ bắt được, và bao nhiêu lỗi phải đọc mã mới ra.
Tự làm thử
- Cài mười lăm lỗi vào một dự án và đổi mã nguồn với một bạn học.
- Chạy đủ ba lượt công cụ và ghi lại kết xuất gốc trước khi sửa gì.
- Viết mười lăm báo cáo theo mẫu sáu phần.
- Viết mười lăm bài kiểm thử hồi quy và chứng minh từng bài thất bại trên bản chưa vá.
- Viết
kiem_tra.shvà chạygit bisect runtrên lịch sử phiên bản của bạn. - Lập bảng tổng kết công cụ nào bắt được bao nhiêu lỗi.
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
- Bốn thứ phải nộp: báo cáo, bản vá, bằng chứng sau khi vá, và bộ kiểm thử hồi quy.
- Ghi lại kết xuất gốc trước khi sửa, vì sau khi vá thì bằng chứng biến mất.
- Phần nguyên nhân viết cho tử tế thường cho bạn một mẫu để tìm ra những lỗi cùng loại ở chỗ khác.
- Bộ kiểm thử hồi quy phải dịch với sanitizer, nếu không nó bỏ sót cả nhóm lỗi rò rỉ và dùng sau khi giải phóng.
- Sanitizer giải quyết khoảng tám mươi phần trăm số lỗi với chi phí gần bằng không, phần còn lại vẫn đòi đọc mã.