00 Valgrind çerçevesi — shadow memory nasıl çalışır
Valgrind, programınızı kendi sanal makine (JIT çevirici) üzerinde çalıştırır. Her bellek hücresini bir "shadow byte" ile izler. Bu yaklaşım son derece güçlüdür ama ciddi bir yavaşlamaya yol açar.
Valgrind'in çekirdek mekanizması shadow memory'dir: her gerçek bellek baytı için bir ya da iki "gölge" bayt tutulur. Bu gölge baytlar, ilgili belleğin durumunu kodlar — tahsis edilmiş mi? başlatılmış mı? hâlâ sahiplenilmiş mi? Program her bellek okuma/yazma işleminde valgrind bu gölge değeri kontrol eder.
# Debian/Ubuntu
sudo apt install valgrind
# ARM native (hedef üzerinde)
sudo apt install valgrind # ARM paket deposundan
# Sürüm kontrolü
valgrind --version
# valgrind-3.19.0
# Debug sembolleriyle derle (ZORUNLU — daha anlamlı çıktı için)
gcc -g -O0 -o myapp sensor.c main.c
Performans etkisi
01 Memcheck — memory leak ve use-after-free
Memcheck, Valgrind'in varsayılan aracıdır. Dört ana hata türünü tespit eder: memory leak, use-after-free, buffer overflow ve uninitialized memory read.
# Varsayılan araç memcheck'tir
valgrind ./myapp
# Açıkça belirt
valgrind --tool=memcheck ./myapp
# Argüman geçir
valgrind ./myapp --port /dev/ttyS0 --baud 115200
Use-after-free tespiti
/* sensor.c */
SensorHandle *s = sensor_create();
sensor_init(s);
sensor_destroy(s); /* s free edildi */
int val = sensor_read(s); /* BUG: freed pointer kullanımı */
==1234== Invalid read of size 4
==1234== at 0x10498: sensor_read (sensor.c:45)
==1234== at 0x104e8: main (main.c:23)
==1234== Address 0x5204e80 is 0 bytes inside a block of size 48 free'd
==1234== at 0x4848899: free (in /usr/lib/valgrind/vgpreload_memcheck.so)
==1234== at 0x104d2: sensor_destroy (sensor.c:60)
==1234== at 0x104e0: main (main.c:22)
==1234== Block was alloc'd at
==1234== at 0x4848A28: malloc (in /usr/lib/valgrind/vgpreload_memcheck.so)
==1234== at 0x10454: sensor_create (sensor.c:15)
Buffer overflow tespiti
==1234== Invalid write of size 1
==1234== at 0x109A4: fill_buffer (sensor.c:88)
==1234== by 0x10A10: main (main.c:45)
==1234== Address 0x5204eb0 is 0 bytes after a block of size 256 alloc'd
==1234== at 0x4848A28: malloc (...)
==1234== by 0x10980: fill_buffer (sensor.c:82)
# 256 baytlık buffer'a 257 bayt yazılmaya çalışıldı
02 Memcheck — --leak-check=full, --track-origins, --suppressions
Gelişmiş Memcheck seçenekleri: tam leak raporu, başlatılmamış değerlerin kaynağını izleme ve bilinen false positive'leri bastırma.
# Kapsamlı leak analizi
valgrind \
--leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
./myapp 2>&1 | tee valgrind_report.txt
==1234== LEAK SUMMARY:
==1234== definitely lost: 48 bytes in 1 blocks
==1234== indirectly lost: 256 bytes in 3 blocks
==1234== possibly lost: 0 bytes in 0 blocks
==1234== still reachable: 1,024 bytes in 4 blocks
==1234== suppressed: 0 bytes in 0 blocks
==1234==
==1234== 48 bytes in 1 blocks are definitely lost in loss record 1 of 2
==1234== at 0x4848A28: malloc (vgpreload_memcheck.so)
==1234== at 0x10454: sensor_create (sensor.c:15)
==1234== at 0x104d0: main (main.c:18)
# "definitely lost": pointer hiçbir değişkende yok → gerçek leak
# "still reachable": program sonunda pointer hâlâ erişilebilir
--track-origins ile başlatılmamış okuma
==1234== Conditional jump or move depends on uninitialised value(s)
==1234== at 0x10510: process_data (data.c:34)
==1234== by 0x104e8: main (main.c:27)
==1234== Uninitialised value was created by a heap allocation
==1234== at 0x4848A28: malloc (vgpreload_memcheck.so)
==1234== at 0x10454: sensor_create (sensor.c:15)
# malloc ile tahsis edilmiş ama memset/başlatma yapılmamış
Suppressions — false positive bastırma
# Suppression dosyası oluştur (genellikle glibc/openssl hataları için)
valgrind --gen-suppressions=all ./myapp 2>&1 | \
grep -A 20 "^{" > myapp.supp
# Suppression dosyasını kullan
valgrind --suppressions=myapp.supp ./myapp
# Valgrind çıkış kodu: hata varsa 0 dışı döner
valgrind --error-exitcode=1 ./myapp
echo "Valgrind çıkış kodu: $?"
# CI/CD pipeline'da kullanışlı: hata varsa build başarısız olur
03 Callgrind — CPU profiling
Callgrind, her fonksiyonun kaç talimat çalıştırdığını ve çağrı ağacını kaydeder. gerçek zaman ölçümü değil talimat sayısıdır — bu nedenle deterministtir.
# Callgrind ile çalıştır
valgrind --tool=callgrind ./myapp
# callgrind.out.1234 dosyası oluşur
# callgrind_annotate ile konsola rapor
callgrind_annotate callgrind.out.1234
# Belirli bir fonksiyon
callgrind_annotate --auto=yes callgrind.out.1234 | grep -A 20 "sensor_read"
# Özet — en pahalı fonksiyonlar
callgrind_annotate callgrind.out.1234 | head -50
--------------------------------------------------------------------------------
Profile data file 'callgrind.out.1234' (creator: callgrind-3.19.0)
--------------------------------------------------------------------------------
I1 cache:
D1 cache:
LL cache:
--------------------------------------------------------------------------------
Ir
--------------------------------------------------------------------------------
1,234,567 PROGRAM TOTALS
--------------------------------------------------------------------------------
Ir file:function
--------------------------------------------------------------------------------
876,543 sensor.c:json_parse
234,567 libc.so.6:(below main)
123,456 sensor.c:sensor_read
45,678 data.c:process_data
# Ir = Instruction References (talimat sayısı)
# json_parse: toplam talimatların %71'i — optimize edilmeli!
--instr-atstart=no ile seçici profilleme
# Başlangıçta enstrümantasyonu kapat
valgrind --tool=callgrind --instr-atstart=no ./myapp
# Sadece istenen bölümde enstrümantasyonu aç
# C kodunda seçici enstrümantasyon:
# #include <valgrind/callgrind.h>
# CALLGRIND_START_INSTRUMENTATION;
# sensor_read(...);
# CALLGRIND_STOP_INSTRUMENTATION;
# CALLGRIND_DUMP_STATS;
# Çalışan prosese sinyal gönder (interactive)
callgrind_control -i on # enstrümantasyonu aç
callgrind_control -i off # enstrümantasyonu kapat
callgrind_control -d # stats dump et
04 kcachegrind — Callgrind görselleştirme
kcachegrind, callgrind.out dosyasını görsel çağrı ağacı ve kaynak annotasyonu olarak sunar. Profiling iş akışının en etkili parçasıdır.
# kcachegrind kurulumu
sudo apt install kcachegrind
# Callgrind çıktısını aç
kcachegrind callgrind.out.1234 &
# Terminal tabanlı alternatif: callgrind_annotate
callgrind_annotate --tree=both callgrind.out.1234 | less
# Pyprof2calltree: Python profiler çıktısını callgrind formatına çevir
# (Python uygulamaları için)
python3 -m cProfile -o output.pstats myapp.py
pyprof2calltree -i output.pstats -o callgrind.python
kcachegrind callgrind.python
kcachegrind üç ana görünüm sunar: Flat Profile (fonksiyon başına maliyet), Call Graph (çağrı ilişkileri ağacı) ve Source Annotated (satır bazında maliyet). Embedded geliştirmede en sık kullanılan, hotspot fonksiyonu bulmak için Flat Profile'dır.
05 Massif — heap profiler
Massif, heap bellek kullanımını zaman içinde izler. Peak kullanımı ve en fazla bellek tahsis eden kod yollarını tespit eder. Uzun süre çalışan daemon'larda bellek büyümesini izlemek için idealdir.
# Massif ile çalıştır
valgrind --tool=massif ./myapp
# massif.out.1234 dosyası oluşur
# ms_print ile metin raporu
ms_print massif.out.1234 | less
MB
4.543^ #
| @@@##
| @@@@@####
| @@@@@@@######
| :@@@@@@@@@########
| @@@:::@@@@@@@@@##########
| @@@@@@::::@@@@@@@@@@##########
| @@@@@@@@@@::::@@@@@@@@@@@##########
| @@@@@@@@@@@@@@::::@@@@@@@@@@@@##########
| @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:::@@@@@@@@@@@@@##########
| @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:::@@@@@@@@@@@@@@##########
| @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:::@@@@@@@@@@@@@@@#########
0 +----------------------------------------------------------------------->Gi
0 4.21
# # = heap @ = stack : = diğer
# Peak: 4.543 MB — bu noktadaki stack trace:
--------------------------------------------------------------------------------
n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B)
--------------------------------------------------------------------------------
10 4,234,567 4,766,208 4,763,904 2,304 0
98.95% (4,763,904B) (heap allocation functions) ...
99.91% (4,759,040B) 0x10490: sensor_buffer_alloc (sensor.c:34)
99.91% (4,759,040B) 0x104d0: sensor_loop (main.c:67)
# Stack belleği de izle
valgrind --tool=massif --stacks=yes ./myapp
# Snapshot sıklığı
valgrind --tool=massif --detailed-freq=10 ./myapp
# Sayfalara göre (mmap dahil)
valgrind --tool=massif --pages-as-heap=yes ./myapp
# massif-visualizer (GUI alternatif)
massif-visualizer massif.out.1234
06 Helgrind — thread race condition dedektörü
Helgrind, çok iş parçacıklı programlarda veri yarışı (data race), mutex deadlock ve POSIX iş parçacığı hatalarını tespit eder. Embedded gerçek zamanlı sistemlerde kritik bir güvenlik aracıdır.
# Helgrind ile çalıştır
valgrind --tool=helgrind ./myapp_threaded 2>&1 | tee helgrind_report.txt
/* İki thread, mutex olmadan g_sensor_value'ya erişiyor */
int g_sensor_value = 0; /* global, korumasız */
void *reader_thread(void *arg) {
while (1) {
printf("Value: %d\n", g_sensor_value); /* RACE! */
usleep(100000);
}
}
void *writer_thread(void *arg) {
while (1) {
g_sensor_value = adc_read(); /* RACE! */
usleep(50000);
}
}
==1234== Possible data race during read of size 4 at 0x60104C
==1234== at 0x10510: reader_thread (threaded.c:12)
==1234== by 0x4C38A85: mythread_wrapper (hg_intercepts.c:406)
==1234== This conflicts with a previous write of size 4 by thread #2
==1234== at 0x10590: writer_thread (threaded.c:22)
==1234== by 0x4C38A85: mythread_wrapper (hg_intercepts.c:406)
==1234== Lock at 0x60102C was first observed by thread #1, not held
# DRD: Helgrind'e alternatif, daha az bellek kullanır
valgrind --tool=drd ./myapp_threaded
# DRD + mutex kontrolü devre dışı
valgrind --tool=drd --check-stack-var=yes ./myapp_threaded
07 Pratik: C embedded programda leak bul
Gerçek bir embedded C uygulamasında Valgrind ile memory leak bulma ve düzeltme senaryosu.
Örnek program — sensor_daemon.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int fd;
int channel;
char *config_path; /* heap tahsis edilecek */
} SensorHandle;
SensorHandle *sensor_create(const char *config) {
SensorHandle *s = malloc(sizeof(SensorHandle));
s->config_path = strdup(config); /* heap tahsis */
s->fd = -1;
s->channel = 0;
return s;
}
void sensor_destroy(SensorHandle *s) {
/* BUG: config_path free edilmiyor! */
free(s);
}
int main(void) {
for (int i = 0; i < 100; i++) {
SensorHandle *s = sensor_create("/etc/sensor.conf");
/* ... kullan ... */
sensor_destroy(s);
}
return 0;
}
# Derle (-g zorunlu, -O0 önerilir)
gcc -g -O0 -o sensor_daemon sensor_daemon.c
# Valgrind ile çalıştır
valgrind --leak-check=full --show-leak-kinds=all \
--track-origins=yes ./sensor_daemon
==1234== HEAP SUMMARY:
==1234== in use at exit: 1,900 bytes in 100 blocks
==1234== total heap usage: 200 allocs, 100 frees, 6,900 bytes allocated
==1234== 1,900 bytes in 100 blocks are definitely lost in loss record 1 of 1
==1234== at 0x4848899: malloc (vgpreload_memcheck.so)
==1234== at 0x401260: strdup (strdup.c:42)
==1234== at 0x10490: sensor_create (sensor_daemon.c:13)
==1234== at 0x104e8: main (sensor_daemon.c:25)
==1234== LEAK SUMMARY:
==1234== definitely lost: 1,900 bytes in 100 blocks
==1234== total heap usage: 200 allocs, 100 frees
Düzeltme
void sensor_destroy(SensorHandle *s) {
if (!s) return;
free(s->config_path); /* önce iç heap'i serbest bırak */
s->config_path = NULL; /* dangling pointer güvenliği */
free(s);
}
/* Valgrind çalıştır — temiz çıktı:
LEAK SUMMARY: definitely lost: 0 bytes in 0 blocks
ERROR SUMMARY: 0 errors from 0 contexts */
08 Cross-ARM Valgrind build notu
Valgrind ARM hedefte native olarak çalışır — cross-derleme yapıp hedef üzerinde çalıştırmak gerekir. x86 geliştirici makinesinde ARM binary'yi doğrudan analiz etmek mümkün değildir.
# Seçenek 1: QEMU user-mode emulation ile x86 üzerinde ARM çalıştır
# (Valgrind QEMU içinde çalışmaz doğrudan, ama test için faydalı)
qemu-arm-static -L /opt/sysroot ./sensor_daemon_arm
# Seçenek 2: ARM board/VM üzerinde native valgrind
# Buildroot: BR2_PACKAGE_VALGRIND=y (ARM destekli)
# Yocto: IMAGE_INSTALL:append = " valgrind"
# Seçenek 3: Docker ARM emulation (çok yavaş ama erişilebilir)
docker run --rm --platform linux/arm/v7 \
-v $(pwd):/work ubuntu:22.04 \
bash -c "apt-get install -y valgrind gcc && \
cd /work && gcc -g -o sensor sensor.c && \
valgrind --leak-check=full ./sensor"
ARM'da Valgrind kısıtlamaları
# Valgrind ARM desteği: ARMv7 ve AArch64 desteklenir
# ARMv5 ve öncesi: desteklenmez
# ARM hard-float ile derleme
arm-linux-gnueabihf-gcc -g -O0 \
-mfloat-abi=hard -mfpu=neon-vfpv4 \
-o sensor_armhf sensor.c
# Valgrind ARM özel sorunları:
# - Thumb-2 talimatlarının tamamı desteklenmeyebilir
# - NEON intrinsic'ler bazen yanlış analiz edilir
# → Sorun çıkarsa: -O0 ve -mfpu=vfpv3 ile dene
# AArch64 (64-bit ARM — daha iyi destek)
aarch64-linux-gnu-gcc -g -O0 -o sensor_a64 sensor.c
# AArch64'te Valgrind desteği ARMv7'den çok daha iyidir
AddressSanitizer — Valgrind alternatifi
# Valgrind çalışmazsa: ASan derleme zamanı çözümü
arm-linux-gnueabihf-gcc -g -O1 \
-fsanitize=address \
-fno-omit-frame-pointer \
-o sensor_asan sensor.c
# Çalıştır (hedef üzerinde)
./sensor_asan
# Yalnızca bellek sızıntısı (LeakSanitizer)
arm-linux-gnueabihf-gcc -g -O1 -fsanitize=leak -o sensor_lsan sensor.c
# ASan avantajları: ~2x yavaşlama (Valgrind'in 10x'ine karşı)
# ASan dezavantajı: derleme zamanı enstrümantasyon gerekir
# binary boyutu büyür (~2x)
Geliştirme aşamasında AddressSanitizer tercih et — çok daha hızlı ve cross-compile ile doğrudan çalışır. Valgrind Memcheck'i, ASan'ın bulamadığı karmaşık heap hatalarında veya üçüncü parti kütüphane analizinde kullan. Callgrind'i performans profilleme için, Massif'i ise daemon'larda bellek büyümesi için kullan.