Valgrind adalah framework instrumentasi dinamis yang menjalankan binary di atas virtual machine internal, mengintercept setiap instruksi mesin untuk melacak penggunaan memori, threading, dan cache. Berbeda dengan sanitizers yang menambahkan instrumentasi pada saat kompilasi, Valgrind bekerja pada binary yang sudah jadi—tidak perlu kompilasi ulang. Valgrind paling terkenal lewat tool memcheck yang mendeteksi memory leak, uninitialized read, dan invalid access. Pada chapter ini kita akan mempelajari tool memcheck dan callgrind, membaca laporan valgrind, serta membandingkan Valgrind dengan AddressSanitizer.
Menjalankan Valgrind #
Cara memakai valgrind sangat sederhana: prefix perintah program dengan valgrind. Valgrind memuat binary ke dalam VM-nya, menjalankan instruksi satu per satu dengan instrumentasi, lalu mencetak laporan di akhir. Tidak perlu kompilasi ulang—binary biasa bisa langsung dipakai.
gcc -g -O0 -std=c11 program.c -o program
valgrind --leak-check=full ./programOutput yang Dihasilkan (format resmi valgrind memcheck):
==12345== Memcheck, a memory error detector
==12345== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==12345== Using Valgrind-3.23.0 and LibVEX
==12345== Command: ./program
==12345==
(program output here)
==12345==
==12345== HEAP SUMMARY:
==12345== in use at exit: 5 bytes in 1 blocks
==12345== total heap usage: 2 allocs, 1 frees, 21 bytes allocated
==12345==
==12345== 5 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x4848898: malloc (vg_replace_malloc.c:431)
==12345== by 0x401142: salin_nama (program.c:6)
==12345== by 0x401166: main (program.c:12)
==12345==
==12345== LEAK SUMMARY:
==12345== definitely lost: 5 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: 1 errors from 1 contexts (suppressed: 0 from 0)Mari kita bedah elemen kunci pada laporan valgrind di atas:
==12345== Memcheck, a memory error detector: nomor dalam==...==adalah PID proses yang dijalankan valgrind. Tool yang aktif adalahMemcheck—tool default yang fokus pada bug memori.HEAP SUMMARY: in use at exit: 5 bytes in 1 blocks: ringkasan memori heap saat program keluar. Ada 5 byte dalam 1 alokasi yang belum dibebaskan.total heap usage: 2 allocs, 1 frees, 21 bytes allocated: selama eksekusi, program melakukan 2 alokasi dan 1 free (total 21 byte dialokasikan). Selisih 2 - 1 = 1 blok yang bocor.5 bytes in 1 blocks are definitely lost in loss record 1 of 1: klasifikasi leakdefinitely lost. Valgrind membedakan beberapa kategori leak:definitely lost: blok yang tidak direferensikan pointer manapun (analog direct leak di LSan).indirectly lost: blok yang hanya direferensikan oleh blok yang juga lost.possibly lost: blok yang pointer-nya mungkin hilang, valgrind tidak yakin.still reachable: blok yang masih punya pointer global, biasanya tidak dianggap leak.
at 0x4848898: malloc (vg_replace_malloc.c:431) ... by 0x401142: salin_nama (program.c:6): backtrace alokasi. Valgrind menggantimalloclibc dengan versi sendiri (vg_replace_malloc.c) agar bisa melacak alokasi. Backtrace kemudian menunjukkansalin_namapada baris 6 sebagai pemanggilmalloc.ERROR SUMMARY: 1 errors from 1 contexts: ringkasan akhir—1 error dalam 1 context (lokasi source unik). Exit code program menjadi non-zero.
Program Contoh dengan Leak #
Mari kita lihat program yang menghasilkan laporan di atas. Program mengalokasikan buffer string, lalu lupa membebaskan salah satu.
/* File: /tmp/test_valgrind1.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char *salin_nama(const char *src) {
size_t n = strlen(src) + 1;
char *out = (char *)malloc(n);
if (out) memcpy(out, src, n);
return out;
}
int main(void) {
char *nama1 = salin_nama("Budi");
char *nama2 = salin_nama("Siti");
printf("nama1 = %s\n", nama1);
free(nama1);
/* nama2 sengaja di-leak untuk demo */
return 0;
}gcc -g -O0 -std=c11 -Wall -Wextra /tmp/test_valgrind1.c -o /tmp/test_valgrind1
valgrind --leak-check=full /tmp/test_valgrind1Valgrind akan menampilkan laporan dengan struktur seperti di atas, menunjukkan bahwa nama2 (5 byte) bocor di salin_nama pada baris 6, dipanggil dari main baris 12.
Deteksi Uninitialized Read #
Selain leak, memcheck mendeteksi uninitialized read—pembacaan variabel yang belum diberi nilai. Bug ini sering menyebabkan hasil program tidak deterministik (berbeda setiap run).
/* File: /tmp/test_valgrind2.c */
#include <stdio.h>
int main(void) {
int x;
if (x > 10) { /* bug: x belum diinisialisasi */
printf("x besar: %d\n", x);
} else {
printf("x kecil\n");
}
return 0;
}gcc -g -O0 -std=c11 /tmp/test_valgrind2.c -o /tmp/test_valgrind2
valgrind /tmp/test_valgrind2Output yang Dihasilkan (format resmi valgrind):
==12345== Conditional jump or move depends on uninitialised value(s)
==12345== at 0x401135: main (test_valgrind2.c:4)
==12345== Uninitialised value was created by a stack allocation
==12345== at 0x401126: main (test_valgrind2.c:2)Mari kita uraikan dua bagian laporan valgrind untuk uninitialized read:
Conditional jump or move depends on uninitialised value(s): valgrind menandai bahwa percabangan kondisional (if (x > 10)) bergantung pada nilai yang belum diinisialisasi. Valgrind tidak bisa memastikan cabang mana yang akan dipilih—hasilnya acak.at 0x401135: main (test_valgrind2.c:4): baris 4 adalahif (x > 10). Alamat instruksi (0x401135) menunjuk ke instruksi percabangan dalam binary.Uninitialised value was created by a stack allocation ... at 0x401126: main (test_valgrind2.c:2): valgrind menelusuri kapan variabel uninitialized dibuat. Baris 2 adalah awal fungsimaintempat stack frame (yang berisix) dialokasikan. Informasi ini membantu menemukan deklarasi variabel yang lupa diberi nilai awal.
Flag Penting Valgrind #
| Flag | Fungsi |
|---|---|
--leak-check=full | laporan leak lengkap dengan backtrace (default: summary) |
--show-leak-kinds=all | tampilkan semua kategori leak (definite, indirect, possible, reachable) |
--track-origins=yes | telusuri asal-usul uninitialized value (lebih lambat tapi informatif) |
--verbose | cetak info detail tentang eksekusi valgrind |
--trace-children=yes | ikuti proses anak hasil fork/exec |
--error-exitcode=42 | exit code program bila ada error (default: 0 bila program sukses) |
--tool=callgrind | ganti tool dari memcheck ke callgrind (profiling) |
--tool=helgrind | ganti tool ke helgrind (deteksi data race) |
Tool Selain memcheck #
Valgrind adalah framework dengan beberapa tool selain memcheck:
memcheck(default): deteksi bug memori (leak, uninitialized read, invalid access).callgrind: profil call graph dan jumlah eksekusi tiap fungsi. Output bisa dilihat dengancallgrind_annotateataukcachegrind.cachegrind: profil cache hit/miss untuk identifikasi bottleneck memori.helgrind: deteksi data race pada program multithreaded.massif: profil heap usage dari waktu ke waktu—berguna untuk menemukan titik konsumsi memori tertinggi.drddanexp-dhat: alternatif untuk thread error dan heap block profiling.
Contoh penggunaan callgrind:
valgrind --tool=callgrind --callgrind-out-file=/tmp/callgrind.out ./program
callgrind_annotate /tmp/callgrind.outPerbandingan Valgrind vs AddressSanitizer #
Karena sanitizers (ASan, UBSan, LSan) dan Valgrind sering dipakai untuk tujuan yang sama, berikut perbandingan praktis:
| Aspek | Valgrind | ASan/LSan |
|---|---|---|
| Cara kerja | VM-based, instrumentasi binary saat runtime | Compiler-based, instrumentasi saat kompilasi |
| Memerlukan kompilasi ulang | tidak | ya (perlu -fsanitize=...) |
| Perlambatan | 10-50x | 2x |
| Overhead memori | 2-3x | 2-3x |
| Deteksi uninitialized read | ya (memcheck) | tidak (perlu MemorySanitizer, hanya clang) |
| Deteksi leak sementara | ya | tidak (LSan hanya periksa saat exit) |
| Deteksi bug thread (helgrind) | ya | terpisah (ThreadSanitizer) |
| Profil performa (callgrind) | ya | tidak |
| Support platform | Linux, macOS | Linux, macOS, Windows, Android |