← C Thực Chiến · Toolchain & Kiểm soát chất lượng

Bài 7 · Nâng cao · 24 phút· Cập nhật 11/06/2026

Sanitizers: ASan, UBSan, TSan

Biên soạn bởi Nguyễn Anh Tuấn

AddressSanitizer, UBSan, TSan: bắt lỗi bộ nhớ (use-after-free, tràn bộ đệm) và undefined behavior lúc chạy với -fsanitize; so sánh với valgrind.

Sanitizer là những “cảm biến” mà trình biên dịch cài vào chương trình: bạn biên dịch với một cờ, chạy bình thường, và nếu code chạm phải lỗi bộ nhớ hay hành vi không xác định, nó dừng lại ngay và in báo cáo kèm stack trace chính xác:

bat-asan

gcc -std=c17 -fsanitize=address -g -fno-omit-frame-pointer main.c -o app
./app    # chay binh thuong; ASan bao loi NGAY khi xay ra

# -g                      : co so dong & ten ham trong bao cao
# -fno-omit-frame-pointer : stack trace day du, doc duoc
  • Instrument lúc biên dịch (-fsanitize=…), bắt lỗi lúc CHẠY.
  • Báo cáo chính xác: loại lỗi + nơi cấp phát/free/truy cập.
  • Gần như không false positive - khác phân tích tĩnh (đoán).

Chọn một lỗi để xem sanitizer nào bắt nó và báo cáo lúc chạy trông ra sao:

Chọn một lỗi - xem sanitizer nào bắt và báo cáo lúc chạy:
main.c
int *p = malloc(sizeof(int));
free(p);
*p = 5;            // <-- dung vung da free
AddressSanitizer -fsanitize=address
Bắt: tràn bộ đệm, use-after-free, double-free
→ heap-use-after-free
Báo cáo lúc chạy (sau khi biên dịch với -fsanitize=address và chạy ./app)
==1==ERROR: AddressSanitizer: heap-use-after-free on address 0x602...
WRITE of size 4 at 0x602... thread T0
    #0 0x... in main main.c:8
freed by thread T0 here:
    #1 0x... in main main.c:7
previously allocated by thread T0 here:
    #1 0x... in main main.c:6

Khác phân tích tĩnh (đoán từ mã), sanitizer chạy chương trình đã được “gắn cảm biến” và báo lỗi đúng lúc nó xảy ra, kèm stack trace chính xác nơi cấp phát / free / truy cập.

ASan bắt gần như mọi lỗi truy cập bộ nhớ: tràn bộ đệm (heap/stack/ global), use-after-free, double-free. Báo cáo chỉ rõ nơi cấp phát và nơi giải phóng:

asan-report (heap-use-after-free)

==1==ERROR: AddressSanitizer: heap-use-after-free on address 0x602...
WRITE of size 4 at 0x602... thread T0
    #0 main.c:8 in main           <- noi TRUY CAP sai
freed by thread T0 here:
    #1 main.c:7 in main           <- noi da FREE
previously allocated by thread T0 here:
    #1 main.c:6 in main           <- noi da MALLOC
  • -fsanitize=address bắt: overflow, use-after-free, double-free, use-after-return.
  • Kèm LeakSanitizer: liệt kê khối chưa free khi thoát.
  • Đọc báo cáo từ trên xuống: lỗi gì → ở đâu → free ở đâu → cấp ở đâu.

Mỗi sanitizer chuyên một mảng lỗi:

cac-co

# UBSan: hanh vi khong xac dinh (tran so co dau, deref NULL, shift qua bit...)
gcc -std=c17 -fsanitize=undefined -g main.c -o app

# TSan: data race (KHONG ket hop voi address)
gcc -std=c17 -fsanitize=thread -g race.c -o race

# Ket hop duoc: ASan + UBSan
gcc -std=c17 -fsanitize=address,undefined -g main.c -o app

Không kết hợp ASan + TSan

ASan và TSan kiểm soát bộ nhớ/luồng theo cách xung đột → phải tạo hai bản build riêng. Nhưng -fsanitize=address,undefined (ASan + UBSan) thì chạy chung được, rất nên dùng khi phát triển.

Bật sanitizer trong build Debug/CI, không bật Release. Chạy bộ test dưới sanitizer để bắt lỗi bộ nhớ tự động:

thuc-te

# CMake: mot build rieng co sanitizer
cmake -S . -B build-asan -DCMAKE_C_FLAGS="-fsanitize=address,undefined -g"
cmake --build build-asan
ctest --test-dir build-asan        # chay test duoi sanitizer

# valgrind: khong can recompile, cham hon (~20x)
valgrind --leak-check=full ./app
  • Chạy TEST dưới sanitizer trong CI → bắt lỗi bộ nhớ trước khi merge.
  • ASan ~2× (nhanh, cần recompile); valgrind ~20× (chậm, không recompile).
  • ASAN_OPTIONS / UBSAN_OPTIONS tinh chỉnh hành vi (vd halt_on_error=1).
Chạy trên máy bạn - kỹ năng thật cần gõ lệnh thật

Biên dịch các file lỗi với ASan và UBSan, chạy, và đọc báo cáo runtime thật (use-after-free, tràn bộ đệm, tràn số).

$ unzip 07-sanitizers.zip && cd 07-sanitizers   # tai zip bang nut o tren
$ gcc -fsanitize=address -g uaf.c -o uaf && ./uaf      # heap-use-after-free
$ gcc -fsanitize=address -g overflow.c -o ov && ./ov   # heap-buffer-overflow
$ gcc -fsanitize=undefined -g ub.c -o ub && ./ub       # signed overflow
$ gcc -fsanitize=address -g leak.c -o lk && ./lk        # memory leak (Linux)
Xem từng file (5) ↡

Câu hỏi thường gặp

Phân tích tĩnh đoán lỗi từ MÃ mà không chạy (có thể false positive, bỏ sót lỗi phụ thuộc dữ liệu chạy). Sanitizer CHẠY chương trình đã “gắn cảm biến” và báo lỗi THẬT ngay khi xảy ra - gần như không false positive, kèm stack trace chính xác. Nhược: chỉ thấy lỗi trên đường code thực sự chạy → cần test/đầu vào tốt. Hai cái bổ sung nhau.

Bài Phân tích tĩnh (clang-tidy) →

Khoảng 2× thời gian chạy và tăng đáng kể RAM (nó giữ “shadow memory” mô tả vùng nào hợp lệ). Đủ nhanh cho phát triển và CI, nhưng KHÔNG bật cho bản Release giao cho người dùng.

-g nhúng thông tin gỡ lỗi để báo cáo có TÊN HÀM và SỐ DÒNG. -fno-omit-frame-pointer giữ con trỏ khung để stack trace đầy đủ, đọc được. Thiếu chúng, báo cáo chỉ có địa chỉ thô.

Không - cả hai đều cần kiểm soát bộ nhớ/luồng theo cách xung đột nhau. Tạo hai bản build riêng (một -fsanitize=address, một -fsanitize=thread). ASan + UBSan thì kết hợp được (-fsanitize=address,undefined).

valgrind không cần biên dịch lại (chạy trên binary thường), bắt được vài thứ ASan không, nhưng chậm hơn nhiều (~20×). ASan nhanh hơn (~2×), tích hợp build/CI dễ, báo cáo đẹp - nhưng phải recompile. Nhiều dự án dùng CẢ hai.

Phần phát hiện RÒ RỈ, thường đi kèm ASan (hoặc bật riêng -fsanitize=leak). Khi chương trình thoát, nó liệt kê các khối heap chưa được free, kèm nơi cấp phát - để bạn lần ra chỗ quên free.

Bài Cấp phát động (malloc/free) - khoá C nâng cao →

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

Sanitizer khác phân tích tĩnh ở điểm nào?

  1. 1

    Sanitizer nào?

    Trong công cụ ở Bước 2: lỗi “Dùng sau khi free” do sanitizer nào bắt, cờ gì?

    Hoàn thành khi: AddressSanitizer (ASan), cờ -fsanitize=address.

  2. 2

    Tràn số nguyên

    Lỗi tràn số nguyên có dấu (INT_MAX + 1) do sanitizer nào bắt?

    Hoàn thành khi: UndefinedBehaviorSanitizer (UBSan), -fsanitize=undefined.

  3. 3

    Vì sao không Release

    Vì sao KHÔNG bật ASan cho bản phát hành (Release) giao người dùng?

    Hoàn thành khi: Chậm ~2× và tốn RAM. Sanitizer chỉ để dev/test/CI, không cho sản phẩm cuối.

  4. 4

    Kết hợp

    Có thể dùng ASan và TSan cùng một bản build không? Còn ASan + UBSan?

    Hoàn thành khi: ASan + TSan: KHÔNG (chạy riêng). ASan + UBSan: ĐƯỢC (-fsanitize=address,undefined).

  5. 5

    Đọc báo cáo

    Báo cáo ASan có “freed by …” và “previously allocated by …” để làm gì?

    Hoàn thành khi: Chỉ chính xác nơi free và nơi cấp phát khối → lần ra nguyên nhân use-after-free/double-free rất nhanh.

  6. 6

    Sanitizer hay test

    Sanitizer có thay thế unit test không? Vì sao?

    Hoàn thành khi: Không. Sanitizer chỉ bắt lỗi trên đường code THỰC SỰ CHẠY - cần test/đầu vào tốt để kích hoạt. Test xác nhận hành vi đúng; sanitizer bắt lỗi bộ nhớ/UB khi test chạy.