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).

bash
# Debian/Ubuntu
sudo apt install -y valgrind

# Fedora
sudo dnf install -y valgrind

# Arch
sudo pacman -S --needed valgrind

Verifikasi:

bash
valgrind --version

Output yang Dihasilkan:

text
valgrind-3.24.0

2. Program Demo dengan Bug #

Buat program berikut yang berisi tiga bug memori: leak, write out-of-bound, dan use-of-uninitialized-value.

c
/* 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:

bash
gcc -std=c11 -Wall -Wextra -g -O0 -o valgrind_demo valgrind_demo.c

3. Menjalankan Valgrind Memcheck #

Jalankan binary via Valgrind:

bash
valgrind --leak-check=full --show-leak-kinds=all \
         --track-origins=yes --verbose \
         ./valgrind_demo

Penjelasan flag:

FlagKegunaan
--leak-check=fullLaporkan leak dengan detail lokasi malloc
--show-leak-kinds=allTampilkan semua jenis leak (definite, indirect, possible)
--track-origins=yesLacak asal nilai uninitialized (lebih lambat tapi informatif)
--verboseTampilkan informasi tambahan saat startup
--tool=memcheckDefault—pilih tool Valgrind (memcheck, helgrind, callgrind, massif)

Output yang Dihasilkan (potongan, dengan --track-origins=yes):

text
==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 contexts

Mari kita telaah baris-baris output di atas:

  1. Invalid write of size 1: Valgrind mendeteksi strcpy menulis 1 byte di luar buffer small[8]. Lokasi tepat ditampilkan: valgrind_demo.c:14. Pesan Address ... is 0 bytes after a block of size 8 alloc'd menunjukkan write terjadi tepat setelah akhir buffer stack.
  2. Conditional jump or move depends on uninitialised value(s): Variabel x di printf belum 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.
  3. HEAP SUMMARY: Statistik alokasi heap. 2 allocs, 1 frees menunjukkan satu malloc tanpa free—yaitu leak di leak_memory.
  4. 100 bytes in 1 blocks are definitely lost: Detail leak. Memanggil malloc(100) di leak_memory baris 8, tanpa free. Lokasi tepat: leak_memory (valgrind_demo.c:8) dipanggil dari main (valgrind_demo.c:24).
  5. ERROR SUMMARY: 3 errors from 3 contexts: Total 3 error—1 invalid write, 1 uninit, 1 leak. Saat tidak ada error, Valgrind mencetak ERROR SUMMARY: 0 errors.

4. Tool Lain di Valgrind #

Selain memcheck (default), Valgrind menyyalurkan beberapa tool lain:

ToolKegunaanCommand
memcheckMemory leak, invalid access, uninitvalgrind ./app (default)
helgrindRace condition, deadlockvalgrind --tool=helgrind ./app
drdData race detector alternatifvalgrind --tool=drd ./app
callgrindProfiling call graphvalgrind --tool=callgrind ./app
massifHeap profiling (snapshot alokasi)valgrind --tool=massif ./app
cachegrindCache & branch predictionvalgrind --tool=cachegrind ./app
lackeyCounting instructions dasarvalgrind --tool=lackey ./app

Contoh callgrind #

bash
valgrind --tool=callgrind --collect-atstart=no ./app
# Hasil: callgrind.out.<pid>
callgrind_annotate callgrind.out.12345 | head -40

Contoh massif #

bash
valgrind --tool=massif --stacks=yes ./app
ms_print massif.out.12345 | head -40

5. Suppression File #

Beberapa library (glibc, GTK, Qt) memiliki leak “false positive” yang sebenarnya intentional. Valgrind dapat mengabaikan leak tersebut via suppression file.

bash
# Generate suppression otomatis dari run pertama
valgrind --gen-suppressions=all --leak-check=full ./app 2>&1 > /dev/null

Output berisi blok { ... } yang dapat kita pilih dan simpan ke file myapp.supp. Lalu jalankan dengan suppression:

bash
valgrind --suppressions=myapp.supp --leak-check=full ./app

6. Integrasi dengan Editor dan CI #

VS Code #

Pasang ekstensi Valgrind dari extension marketplace. Konfigurasi .vscode/tasks.json:

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:

yaml
- name: Valgrind check
  run: |
    sudo apt install -y valgrind
    valgrind --leak-check=full --error-exitcode=1 ./build/test_suite

Flag --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:

bash
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:

bash
valgrind --suppressions=/usr/share/doc/valgrind/examples/glibc-2.X.supp ./app