Valgrind adalah framework instrumentasi binary yang dapat mendeteksi memory leak, akses ke memori belum diinisialisasi, double-free, dan race condition. Berbeda dari AddressSanitizer (ASan), Valgrind tidak perlu compile ulang—cukup jalankan binary biasa lewat valgrind. Bab ini menuntun kita memasang Valgrind, menjalankan program dengan memcheck (tool default), dan membaca laporannya.
1. Pemasangan Valgrind #
Valgrind tersedia di package manager sebagian besar distro Linux. Pemasangan tidak tersedia di macOS atau Windows secara native (memerlukan WSL2 atau VM Linux).
# Debian/Ubuntu
sudo apt install -y valgrind
# Fedora
sudo dnf install -y valgrind
# Arch
sudo pacman -S --needed valgrindVerifikasi:
valgrind --versionOutput yang Dihasilkan:
valgrind-3.24.02. Program Demo dengan Bug #
Buat program berikut yang berisi tiga bug memori: leak, write out-of-bound, dan use-of-uninitialized-value.
/* File: valgrind_demo.c - Demo bug yang ditangkap valgrind */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void leak_memory(void) {
char *buf = malloc(100);
strcpy(buf, "data yang tidak pernah di-free");
printf("[leak] buf = %s\n", buf);
/* lupa free(buf) */
}
static void write_out_of_bound(void) {
char small[8];
strcpy(small, "data terlalu panjang untuk buffer 8 byte");
printf("[oob] small = %s\n", small);
}
static void use_uninitialized(void) {
int x; /* belum diisi */
printf("[uninit] x = %d\n", x);
}
int main(void) {
leak_memory();
write_out_of_bound();
use_uninitialized();
return 0;
}Compile dengan -g agar Valgrind dapat menampilkan nomor baris:
gcc -std=c11 -Wall -Wextra -g -O0 -o valgrind_demo valgrind_demo.c3. Menjalankan Valgrind Memcheck #
Jalankan binary via Valgrind:
valgrind --leak-check=full --show-leak-kinds=all \
--track-origins=yes --verbose \
./valgrind_demoPenjelasan flag:
| Flag | Kegunaan |
|---|---|
--leak-check=full | Laporkan leak dengan detail lokasi malloc |
--show-leak-kinds=all | Tampilkan semua jenis leak (definite, indirect, possible) |
--track-origins=yes | Lacak asal nilai uninitialized (lebih lambat tapi informatif) |
--verbose | Tampilkan informasi tambahan saat startup |
--tool=memcheck | Default—pilih tool Valgrind (memcheck, helgrind, callgrind, massif) |
Output yang Dihasilkan (potongan, dengan --track-origins=yes):
==12345== Memcheck, a memory error detector
==12345== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==12345== Using Valgrind-3.24.0 and LibVEX
==12345== Command: ./valgrind_demo
==12345==
[leak] buf = data yang tidak pernah di-free
==12345== Invalid write of size 1
==12345== at 0x10917A: strcpy (vg_replace_strmem.c:524)
==12345== by 0x1091AC: write_out_of_bound (valgrind_demo.c:14)
==12345== by 0x109244: main (valgrind_demo.c:25)
==12345== Address 0x4fff0c0 is 0 bytes after a block of size 8 alloc'd
==12345== at 0x484A8DC: ... (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345== by 0x10917A: strcpy ...
==12345==
[oob] small = data terlalu panjang untuk buffer 8 byte
==12345== Conditional jump or move depends on uninitialised value(s)
==12345== at 0x484E3D4: __printf_chk ...
==12345== by 0x1091C9: use_uninitialized (valgrind_demo.c:20)
==12345== by 0x109244: main (valgrind_demo.c:26)
==12345== Uninitialised value was created by a stack allocation
==12345== at 0x109193: use_uninitialized (valgrind_demo.c:18)
==12345==
[uninit] x = 0
==12345==
==12345== HEAP SUMMARY:
==12345== in use at exit: 100 bytes in 1 blocks
==12345== total heap usage: 2 allocs, 1 frees, 124 bytes allocated
==12345==
==12345== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x484A8DC: malloc (vg_replace_malloc.c:446)
==12345== by 0x109152: leak_memory (valgrind_demo.c:8)
==12345== by 0x109234: main (valgrind_demo.c:24)
==12345==
==12345== LEAK SUMMARY:
==12345== definitely lost: 100 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== ERROR SUMMARY: 3 errors from 3 contextsMari kita telaah baris-baris output di atas:
Invalid write of size 1: Valgrind mendeteksistrcpymenulis 1 byte di luar buffersmall[8]. Lokasi tepat ditampilkan:valgrind_demo.c:14. PesanAddress ... is 0 bytes after a block of size 8 alloc'dmenunjukkan write terjadi tepat setelah akhir buffer stack.Conditional jump or move depends on uninitialised value(s): Variabelxdiprintfbelum diisi. Valgrind melacak asal nilai via--track-origins=yes:Uninitialised value was created by a stack allocation at line 18. Tanpa flag ini, Valgrind hanya menampilkan lokasi pemakaian, bukan lokasi deklarasi.HEAP SUMMARY: Statistik alokasi heap.2 allocs, 1 freesmenunjukkan satu malloc tanpa free—yaitu leak dileak_memory.100 bytes in 1 blocks are definitely lost: Detail leak. Memanggilmalloc(100)dileak_memorybaris 8, tanpafree. Lokasi tepat:leak_memory (valgrind_demo.c:8)dipanggil darimain (valgrind_demo.c:24).ERROR SUMMARY: 3 errors from 3 contexts: Total 3 error—1 invalid write, 1 uninit, 1 leak. Saat tidak ada error, Valgrind mencetakERROR SUMMARY: 0 errors.
4. Tool Lain di Valgrind #
Selain memcheck (default), Valgrind menyyalurkan beberapa tool lain:
| Tool | Kegunaan | Command |
|---|---|---|
memcheck | Memory leak, invalid access, uninit | valgrind ./app (default) |
helgrind | Race condition, deadlock | valgrind --tool=helgrind ./app |
drd | Data race detector alternatif | valgrind --tool=drd ./app |
callgrind | Profiling call graph | valgrind --tool=callgrind ./app |
massif | Heap profiling (snapshot alokasi) | valgrind --tool=massif ./app |
cachegrind | Cache & branch prediction | valgrind --tool=cachegrind ./app |
lackey | Counting instructions dasar | valgrind --tool=lackey ./app |
Contoh callgrind #
valgrind --tool=callgrind --collect-atstart=no ./app
# Hasil: callgrind.out.<pid>
callgrind_annotate callgrind.out.12345 | head -40Contoh massif #
valgrind --tool=massif --stacks=yes ./app
ms_print massif.out.12345 | head -405. Suppression File #
Beberapa library (glibc, GTK, Qt) memiliki leak “false positive” yang sebenarnya intentional. Valgrind dapat mengabaikan leak tersebut via suppression file.
# Generate suppression otomatis dari run pertama
valgrind --gen-suppressions=all --leak-check=full ./app 2>&1 > /dev/nullOutput berisi blok { ... } yang dapat kita pilih dan simpan ke file myapp.supp. Lalu jalankan dengan suppression:
valgrind --suppressions=myapp.supp --leak-check=full ./app6. Integrasi dengan Editor dan CI #
VS Code #
Pasang ekstensi Valgrind dari extension marketplace. Konfigurasi .vscode/tasks.json:
{
"label": "Valgrind Run",
"type": "shell",
"command": "valgrind",
"args": [
"--leak-check=full",
"--show-leak-kinds=all",
"--track-origins=yes",
"./build/${fileBasenameNoExtension}"
],
"group": "test"
}CI/CD (GitHub Actions) #
Tambah step ke workflow:
- name: Valgrind check
run: |
sudo apt install -y valgrind
valgrind --leak-check=full --error-exitcode=1 ./build/test_suiteFlag --error-exitcode=1 menyebabkan Valgrind mengembalikan exit code 1 jika ada error—CI dapat mendeteksi failure.
7. Troubleshooting #
Error: valgrind: failed to start tool 'memcheck' #
Valgrind tidak dapat menjalankan binary—biasanya karena binary memakai instruksi yang belum didukung Valgrind. Update Valgrind ke versi terbaru. Pada CPU sangat baru (RISC-V, AVX-512), Valgrind mungkin belum mendukung instruksi tertentu.
Error: difficult to debug, need recompile without -fomit-frame-pointer #
Beberapa kompilator default menghapus frame pointer yang dipakai Valgrind untuk unwind stack. Compile ulang dengan:
gcc -std=c11 -g -O0 -fno-omit-frame-pointer ...Lambat sekali (50× slowdown) #
Valgrind memerlukan instrumentasi setiap instruksi—perlambatan 10-50× adalah normal. Untuk program besar, batasi runtime via --trace-children=yes dan --run-libc-freeres=no. Atau pertimbangkan ASan yang overhead-nya hanya 2×.
Leak di glibc startup (false positive) #
Beberapa leak terlihat dari dlopen atau inisialisasi glibc. Pakai suppression file bawaan:
valgrind --suppressions=/usr/share/doc/valgrind/examples/glibc-2.X.supp ./app