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

Bốn công cụ và khi nào dùng cái nào

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

  • Chọn đúng công cụ cho từng loại triệu chứng
  • Nêu chi phí tốc độ của từng công cụ
  • Biết cờ dịch tối thiểu cho mỗi công cụ
  • Biết cặp sanitizer nào không dùng chung được

Bốn công cụ, bốn việc khác nhau. Sai lầm phổ biến nhất là mở GDB ngay khi có lỗi. GDB là công cụ cuối cùng bạn nên chạm tới, vì ba công cụ kia thường chỉ thẳng ra dòng lỗi mà không cần bạn hỏi gì.

#Bốn công cụ

Công cụNó làm gìCờ dịchKhi nào chạy
AddressSanitizerBắt mọi lỗi truy cập bộ nhớ ngay tại chỗ-fsanitize=addressTrong mọi lần chạy kiểm thử
UndefinedBehaviorSanitizerBắt tràn số, dịch bit sai, chia cho không-fsanitize=undefinedTrong mọi lần chạy kiểm thử
ValgrindBắt lỗi bộ nhớ và biến chưa khởi tạo, không cần dịch lạiKhông cần cờ, chỉ cần -gKhi không dịch lại được, hoặc cần bắt biến chưa khởi tạo
GDBDừng chương trình và soi vào bên trong-g3 -O0Khi ba công cụ trên đã im lặng mà kết quả vẫn sai
Không công cụ nào phủ hết. Đó là lý do phải chạy cả ba sanitizer chứ không chọn một.

#Chọn theo triệu chứng

Triệu chứngChạy cái này trướcVì sao
Segmentation faultASanNó in ra dòng truy cập sai và dòng cấp phát, không cần bạn hỏi
Chương trình dùng bộ nhớ tăng dầnASan hoặc ValgrindCả hai đều liệt kê chỗ cấp phát chưa giải phóng
Kết quả sai ở -O2, đúng ở -O0UBSanGần như chắc chắn có UB ở đâu đó, xem Bài 33.2
Kết quả khác nhau giữa hai lần chạyValgrindBiến chưa khởi tạo, và chỉ Valgrind bắt được
Đứng im, không chạy tiếpGDB, gắn vào tiến trìnhXem nó đang kẹt ở đâu bằng backtrace
Kết quả sai một cách nhất quánGDBĐây là lỗi logic, không công cụ nào tự tìm được
Sập trên máy khách, không tái hiện đượcGDB với core dumpĐọc lại trạng thái lúc sập
Kết quả sai khi chạy nhiều luồngThreadSanitizerChỉ nó bắt được đua dữ liệu

#Chi phí và giới hạn

Công cụChậm hơnTốn thêm bộ nhớGiới hạn chính
ASanKhoảng 2 lầnKhoảng 3 lầnKhông bắt biến chưa khởi tạo, không bắt tràn số
UBSanKhoảng 1.2 lầnKhông đáng kểChỉ bắt được UB có kiểm tra lúc chạy
Valgrind20 tới 50 lầnKhoảng 2 lầnRất chậm, và không bắt tràn trên ngăn xếp
TSan5 tới 15 lần5 tới 10 lầnKhông dùng chung với ASan được
GDBKhông, khi chưa dừngKhông đáng kểCần bạn biết nên hỏi gì
Chạy ba lượt trong tích hợp liên tục
# Luot 1: ASan va UBSan, bat het loi bo nho va UB
gcc -std=c11 -g3 -O1 -fno-omit-frame-pointer \
    -fsanitize=address,undefined -fno-sanitize-recover=all \
    -o thu_san *.c
./thu_san

# Luot 2: TSan, bat dua du lieu. PHAI dich rieng.
gcc -std=c11 -g3 -O1 -fsanitize=thread -pthread -o thu_tsan *.c
./thu_tsan

# Luot 3: Valgrind, bat bien chua khoi tao
gcc -std=c11 -g -O1 -o thu_vg *.c
valgrind --leak-check=full --track-origins=yes --error-exitcode=1 ./thu_vg

# Ba luot nay chay duoc song song, nen tong thoi gian la luot cham nhat.

#Kết hợp thế nào

Quy trình chuẩn khi gặp lỗi
1. Tai hien duoc chua?
      Chua -> tim dau vao nho nhat gay loi truoc da.
      Roi  -> di tiep.

2. Chay ban ASan va UBSan.
      Co bao -> doc bao cao, sua, quay lai buoc 1.
      Im lang -> di tiep.

3. Chay Valgrind.
      Co bao -> thuong la bien chua khoi tao. Sua, quay lai buoc 1.
      Im lang -> di tiep.

4. Den day thi khong con loi BO NHO nao. Con lai la loi LOGIC.
      Mo GDB. Dat diem dung truoc cho nghi ngo.
      Neu mot bien bi doi gia tri bat thuong: dung watchpoint.

5. Sua xong: viet mot bai kiem thu tai hien dung loi do.

#Trên Windows thì sao

terminal
# MinGW-W64 8.1.0, bản dùng trong giáo trình này
gcc -fsanitize=address t.c -o t
ld.exe: cannot find -lasan
collect2.exe: error: ld returned 1 exit status
gcc -fsanitize=undefined t.c -o t
ld.exe: cannot find -lubsan
collect2.exe: error: ld returned 1 exit status
gdb --version
GNU gdb (GDB) 8.1
Công cụLinuxmacOSWindows
GDBCóCó, hoặc dùng LLDBCó, qua MinGW
ASanCóCóCó, qua MSVC hoặc clang-cl
UBSanCóCóMột phần, qua Clang
ValgrindCóBản cũ, hay hỏngKhông
TSanCóCóKhông
Dr. MemoryCóKhôngCó, thay cho Valgrind
Application VerifierKhôngKhôngCó, của Microsoft

Bốn cách để có sanitizer trên Windows

# 1. WSL2, cach de nhat: mot ban Linux day du
wsl --install -d Ubuntu
sudo apt install build-essential gdb valgrind
gcc -fsanitize=address,undefined -g prog.c -o prog

# 2. MSVC co ASan tu Visual Studio 2019 phien ban 16.9
cl /fsanitize=address /Zi prog.c

# 3. clang-cl, ban Clang dung ABI cua MSVC
clang-cl -fsanitize=address -Zi prog.c

# 4. Dr. Memory, cong cu thay Valgrind tren Windows
drmemory -- prog.exe

# Giao trinh nay dung cach 1 cho Chuong 34, va chay thang tren
# Windows cho Chuong 35.

Tự làm thử

  1. Cài WSL2 với Ubuntu và dịch một chương trình có lỗi bằng -fsanitize=address.
  2. Đo thời gian chạy của một chương trình ở bốn cấu hình: thường, ASan, UBSan, Valgrind.
  3. Thử dịch chung -fsanitize=address với -fsanitize=thread và đọc thông báo lỗi.
  4. Viết một chương trình đọc biến chưa khởi tạo và xác nhận ASan im lặng còn Valgrind thì không.
  5. Thêm ba lượt kiểm tra vào Makefile của một dự án của bạn.
  6. Thử ASAN_OPTIONS=halt_on_error=0 trên chương trình có 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

  • Thứ tự đúng là ASan và UBSan trước, Valgrind sau, GDB cuối cùng.
  • Ba công cụ đầu không cần bạn biết gì về lỗi, còn GDB chỉ trả lời câu hỏi bạn đặt.
  • ASan chậm hai lần, UBSan gần như miễn phí, Valgrind chậm hai tới năm mươi lần.
  • ASan không bắt biến chưa khởi tạo, và không dùng chung được với TSan.
  • MinGW không có libasan và libubsan, còn Valgrind không có bản Windows. Dùng WSL2 hoặc MSVC.