Bài 34.428 phút đọc
Valgrind và bốn loại rò rỉ
Sau bài này bạn sẽ làm được
- Chạy valgrind với bộ cờ đầy đủ
- Phân biệt bốn loại rò rỉ và biết loại nào phải sửa
- Đọc vết gọi để tìm dòng cấp phát
- So sánh Valgrind với ASan để chọn đúng việc
Valgrind chậm hơn ASan hai mươi lần và không bắt được tràn trên ngăn xếp. Nhưng nó có hai thứ ASan không có: không cần dịch lại, và bắt được biến chưa khởi tạo. Riêng điều thứ hai đủ để giữ nó trong bộ công cụ.
#Chạy thử
# Bo co day du, nen thuoc long
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--error-exitcode=1 \
./prog
# Giai thich tung co:
# --leak-check=full liet ke tung cho ro ri, khong chi tong so
# --show-leak-kinds=all hien ca bon loai, khong chi "definitely"
# --track-origins=yes voi bien chua khoi tao: chi ra NOI SINH RA no.
# Cham hon nhung gan nhu luon dang gia
# --error-exitcode=1 tra ve 1 khi co loi, de CI phat hien duocterminal
gcc -std=c11 -g -O1 -o rr ro_ri.c && valgrind --leak-check=full ./rr
==12345== Memcheck, a memory error detector ==12345== Command: ./rr ==12345== ==12345== ==12345== HEAP SUMMARY: ==12345== in use at exit: 11 bytes in 1 blocks ==12345== total heap usage: 2 allocs, 1 frees, 24 bytes allocated ==12345== ==12345== 11 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==12345== at 0x4C2FB0F: malloc (vg_replace_malloc.c:299) ==12345== by 0x400597: tao_ten (ro_ri.c:5) ==12345== by 0x4005F8: main (ro_ri.c:12) ==12345== ==12345== LEAK SUMMARY: ==12345== definitely lost: 11 bytes in 1 blocks ==12345== indirectly lost: 0 bytes in 0 blocks ==12345== possibly lost: 0 bytes in 0 blocks ==12345== still reachable: 0 bytes in 0 blocks ==12345== suppressed: 0 bytes in 0 blocks ==12345== ==12345== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
#Bốn loại rò rỉ
| Loại | Phải sửa | Nghĩa là |
|---|---|---|
| definitely lost | Có | Không còn con trỏ nào tới khối đó. Rò rỉ thật |
| indirectly lost | Có, bằng cách sửa cái ở trên | Khối bị mất vì cấu trúc cha đã mất |
| possibly lost | Xem xét | Chỉ còn con trỏ trỏ vào giữa khối, không vào đầu |
| still reachable | Thường không | Chưa giải phóng nhưng vẫn còn con trỏ tới |
Bốn loại trong một chương trình
typedef struct Nut {
int gia_tri;
struct Nut *sau;
} Nut;
static char *g_bo_dem; /* bien toan cuc */
int main(void) {
/* 1. definitely lost: mat con tro */
char *a = malloc(10);
a = malloc(20); /* khoi 10 byte khong ai giu nua */
/* 2. indirectly lost: danh sach mat ca chuoi */
Nut *dau = malloc(sizeof *dau);
dau->sau = malloc(sizeof *dau); /* nut thu hai */
dau->sau->sau = NULL;
dau = NULL; /* nut 1 definitely, nut 2 indirectly */
/* 3. possibly lost: con tro tro vao giua khoi */
char *b = malloc(100);
b += 50; /* khong con tro vao dau khoi */
/* 4. still reachable: van co con tro toan cuc tro toi */
g_bo_dem = malloc(30);
free(a);
return 0;
}terminal
valgrind --leak-check=full --show-leak-kinds=all ./bl
==12345== LEAK SUMMARY: ==12345== definitely lost: 26 bytes in 2 blocks ==12345== indirectly lost: 16 bytes in 1 blocks ==12345== possibly lost: 100 bytes in 1 blocks ==12345== still reachable: 30 bytes in 1 blocks ==12345== suppressed: 0 bytes in 0 blocks
#Việc chỉ Valgrind làm được
Memcheck
Công cụ mặc định của Valgrind. Nó theo dõi từng bit của bộ nhớ với hai lá cờ: bit này có hợp lệ để truy cập không, và bit này đã được ghi giá trị xác định chưa. Lá cờ thứ hai là thứ ASan không có.
chua_kt.c
#include <stdio.h>
#include <stdlib.h>
static int tinh(int *a, int n) {
int t; /* quen khoi tao */
for (int i = 0; i < n; ++i) t += a[i];
return t;
}
int main(void) {
int a[3] = { 1, 2, 3 };
printf("tong = %d\n", tinh(a, 3));
return 0;
}terminal
# ASan hoàn toàn im lặng với lỗi này
gcc -std=c11 -g -O1 -fsanitize=address,undefined -o ck chua_kt.c && ./ck
tong = 6
gcc -std=c11 -g -O1 -o ck chua_kt.c && valgrind --track-origins=yes ./ck
==12345== Conditional jump or move depends on uninitialised value(s) ==12345== at 0x4E88B00: vfprintf (vfprintf.c:1642) ==12345== by 0x4E8F898: printf (printf.c:33) ==12345== by 0x400605: main (chua_kt.c:15) ==12345== Uninitialised value was created by a stack allocation ==12345== at 0x400566: tinh (chua_kt.c:4) ==12345== ==12345== Use of uninitialised value of size 8 ==12345== at 0x4E86F0B: _itoa_word (_itoa.c:179) ==12345== by 0x4E8A517: vfprintf (vfprintf.c:1642) ==12345== by 0x400605: main (chua_kt.c:15)
#Đọc vết gọi
| Thông báo | Nghĩa |
|---|---|
| Invalid read of size N | Đọc vùng không được phép |
| Invalid write of size N | Ghi vùng không được phép |
| Invalid free() | Giải phóng con trỏ không phải từ malloc, hoặc lần hai |
| Mismatched free() / delete | Cấp bằng malloc mà giải bằng delete |
| Conditional jump depends on uninitialised value | Rẽ nhánh trên giá trị chưa xác định |
| Use of uninitialised value of size N | Dùng giá trị chưa xác định làm địa chỉ |
| Source and destination overlap | memcpy với vùng chồng nhau |
| Syscall param points to uninitialised byte | Truyền vùng chưa khởi tạo vào lời gọi hệ thống |
==12345== Invalid write of size 4
==12345== at 0x4005A1: main (tbd.c:8) <- DONG GHI SAI
==12345== Address 0x5204054 is 0 bytes after a block of size 20 alloc'd
==12345== at 0x4C2FB0F: malloc (vg_replace_malloc.c:299)
==12345== by 0x400591: main (tbd.c:5) <- DONG CAP PHAT
/* Doc tu tren xuong:
1. Loai loi va kich thuoc: ghi 4 byte
2. Vet goi toi cho ghi, dong tren cung la dong cua ban
3. Vi tri tuong doi: "0 bytes after a block of size 20"
4. Vet goi toi cho cap phat
Cau truc giong het bao cao cua ASan o Bai 34.2. Hai cong cu
khac nhau ve co che nhung giong nhau ve nhung gi ban can biet. */#So với ASan
| Valgrind | ASan | |
|---|---|---|
| Cần dịch lại | Không | Có |
| Chậm hơn | 20 tới 50 lần | Khoảng 2 lần |
| Tràn trên heap | Bắt được | Bắt được |
| Tràn trên ngăn xếp | Không | Bắt được |
| Tràn trên biến toàn cục | Không | Bắt được |
| Dùng sau khi giải phóng | Bắt được | Bắt được |
| Rò rỉ bộ nhớ | Bắt được, phân bốn loại | Bắt được |
| Biến chưa khởi tạo | Bắt được | Không |
| Chạy trên Windows | Không | Có, qua MSVC |
| Cần mã nguồn | Không, chỉ cần -g cho tên hàm | Có |
#Các công cụ khác của Valgrind
# Memcheck: mac dinh, phat hien loi bo nho
valgrind ./prog
valgrind --tool=memcheck ./prog
# Callgrind: dem so lenh moi ham, de tim diem nong
valgrind --tool=callgrind ./prog
callgrind_annotate callgrind.out.12345 | head -30
kcachegrind callgrind.out.12345 # giao dien do hoa
# Massif: theo doi bo nho heap theo thoi gian
valgrind --tool=massif ./prog
ms_print massif.out.12345
# Helgrind: phat hien dua du lieu va khoa cheo
valgrind --tool=helgrind ./prog
# DRD: cung muc dich voi Helgrind, thuat toan khac
valgrind --tool=drd ./prog
# Cachegrind: mo phong bo nho dem CPU
valgrind --tool=cachegrind ./progTự làm thử
- Chạy Valgrind trên chương trình rò rỉ và xác định lời gọi nào bị rò rỉ.
- Viết chương trình sinh đủ bốn loại rò rỉ và đối chiếu với bảng tổng kết.
- Viết chương trình đọc biến chưa khởi tạo, xác nhận ASan im lặng còn Valgrind thì không.
- Chạy với và không có
--track-origins=yesvà so hai báo cáo. - Dùng Massif trên một chương trình cấp phát nhiều và đọc biểu đồ.
- Sinh tệp bỏ qua bằng
--gen-suppressions=allcho một chương trình dùng thư viện đồ họa.
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ộ cờ đầy đủ là
--leak-check=full --show-leak-kinds=all --track-origins=yes --error-exitcode=1. - Thiếu
--error-exitcode=1thì Valgrind trả về mã thoát của chương trình, nên CI không phát hiện được lỗi. - Bốn loại rò rỉ: definitely và indirectly phải sửa, possibly cần xem xét, still reachable thường bỏ qua được.
- Chỉ Valgrind bắt được biến chưa khởi tạo, và
--track-originslà thứ biến báo cáo đó thành có ích. - Valgrind không bắt được tràn trên ngăn xếp và trên biến toàn cục, đó là chỗ ASan mạnh hơn hẳn.