00 ltrace vs strace — fark ve çalışma prensibi
strace kernel ile uygulama arasındaki syscall sınırını izlerken ltrace, shared library ile uygulama arasındaki PLT (Procedure Linkage Table) geçişlerini yakalar.
PLT mekanizması
Dinamik bağlamada her shared library çağrısı, PLT (Procedure Linkage Table) üzerinden geçer. ltrace bu tablodaki girişleri runtime'da değiştirerek her çağrıyı kendi kod parçasından geçirir, argümanları ve dönüş değerini kaydeder, ardından gerçek fonksiyona yönlendirir.
# Debian/Ubuntu
sudo apt install ltrace
# ARM embedded: Buildroot'ta
# BR2_PACKAGE_LTRACE=y
# Yocto:
# IMAGE_INSTALL:append = " ltrace"
# ltrace statik binary'lerde çalışmaz!
file myapp | grep -q "statically linked" && echo "ltrace çalışmaz"
01 Temel kullanım — shared lib çağrılarını izle
ltrace çıktısı, her kütüphane fonksiyon çağrısını argümanları ve dönüş değeriyle birlikte gösterir.
# Temel kullanım
ltrace ./myapp
# Çalışan sürece bağlan
ltrace -p 1234
# Kısa çıktı (sadece kütüphane adlarını göster)
ltrace -l '*' ./myapp 2>&1 | head -30
__libc_start_main(0x10454, 1, 0xbefff6c4, 0x10580 <unfinished ...>
fopen("/etc/sensor.conf", "r") = 0x20010
fgets(0xbefff540, 256, 0x20010) = 0xbefff540
strcmp("port", "port") = 0
fgets(0xbefff540, 256, 0x20010) = 0xbefff540
strcmp("baud", "baud") = 0
fclose(0x20010) = 0
malloc(48) = 0x200a0
memset(0x200a0, '\0', 48) = 0x200a0
printf("Sensor initialized on %s\n", "/dev/ttyS0") = 30
sleep(1) = 0
...
+++ exited (status 0) +++
Format: fonksiyon_adı(argümanlar) = dönüş_değeri. String argümanlar otomatik olarak tırnak içinde gösterilir (ilk 32 karakter). <unfinished ...>, çok katmanlı çağrılarda görülür.
02 -e — çağrı filtreleme
ltrace çıktısı da strace gibi gürültülü olabilir. -e ile belirli fonksiyonları filtreleyin.
# Sadece malloc ve free
ltrace -e malloc,free myapp
# Belirli kütüphaneyi filtrele
ltrace -l libpthread.so.0 myapp
# Birden fazla kütüphane
ltrace -l 'libpthread*' -l 'libssl*' myapp
# Joker karakter ile fonksiyon filtrele
ltrace -e 'str*' myapp
# strcmp, strlen, strncpy, strdup... — tüm str* fonksiyonları
# Belirli fonksiyonları hariç tut
ltrace -e '!memcpy,memset' myapp
# Özet istatistik (-c)
ltrace -c myapp
% time seconds usecs/call calls function
------ ----------- ----------- --------- --------------------
45.23 0.023456 1234 19 read
22.11 0.011478 574 20 fgets
18.34 0.009512 475 20 strcmp
8.12 0.004213 421 10 malloc
4.20 0.002180 218 10 free
1.00 0.000519 519 1 fopen
0.62 0.000322 45 7 strlen
0.38 0.000197 197 1 fclose
------ ----------- ----------- --------- --------------------
100.00 0.051877 88 total
03 -C — C++ demangling
C++ uygulamalarında kütüphane çağrıları mangled isimlerle görünür. -C bayrağı bunları okunabilir hale getirir.
# -C olmadan (C++ binary)
ltrace ./mycppapp 2>&1 | head -10
# _ZNSt8ios_base4InitC1Ev(0x404120, 1, 0x7ffc...) = 0
# _ZNSt6localeC1Ev(0x404130, ...) = 0x404130
# _ZN6SensorC1Eii(0x7ffd..., 1, 115200) = 0x7ffd...
# -C ile demangled
ltrace -C ./mycppapp 2>&1 | head -10
# std::ios_base::Init::Init()(0x404120, ...) = 0
# std::locale::locale()(0x404130, ...) = 0x404130
# Sensor::Sensor(int, int)(0x7ffd..., 1, 115200) = 0x7ffd...
C++ STL container'ları (vector, map, string) sayısız iç fonksiyon çağrısı üretir. ltrace çıktısı çok gürültülü olabilir. ltrace -e malloc,free -C mycppapp kombinasyonu, yalnızca bellek işlemlerini görmek için çok kullanışlıdır.
04 -n — indent ile çağrı derinliği
-n N ile her iç içe çağrı N boşluk girintili gösterilir. Çağrı ağacını anlamayı kolaylaştırır.
# -n 4: her seviye 4 boşluk girinti
ltrace -n 4 ./myapp 2>&1 | head -20
__libc_start_main(...)
sensor_init(...) <unfinished ...>
fopen("/etc/sensor.conf", "r") = 0x20010
fgets(0xbefff540, 256, 0x20010) = 0xbefff540
atoi("115200") = 115200
fclose(0x20010) = 0
malloc(sizeof_SensorHandle) = 0x200a0
<... sensor_init resumed> = 0x200a0
sensor_read(0x200a0, ...) <unfinished ...>
read(fd=5, buf, 128) [system call]
05 malloc / free takibi — basit memory leak tespiti
ltrace ile malloc/free çağrılarını sayarak basit bellek sızıntısı tespiti yapılabilir. Valgrind kadar ayrıntılı değildir ama çok daha hızlıdır.
# malloc ve free çağrılarını izle
ltrace -e malloc,free,calloc,realloc myapp 2>&1 | \
grep -E "^(malloc|free|calloc|realloc)"
malloc(48) = 0x200a0
malloc(1024) = 0x20120
free(0x200a0)
malloc(72) = 0x200a0
malloc(256) = 0x20520
free(0x20120)
# malloc(256) = 0x20520 için free yok → potansiyel leak!
# malloc ve free çağrılarını say
ltrace -e malloc,free,calloc myapp 2>&1 | \
awk '
/^malloc|^calloc/ { mallocs++ }
/^free/ { frees++ }
END {
printf "malloc+calloc: %d\nfree: %d\n", mallocs, frees
if (mallocs > frees)
printf "UYARI: %d adet serbest bırakılmamış tahsis!\n", mallocs - frees
else
printf "OK: tahsis/serbest bırakma dengeli\n"
}
'
ltrace ile malloc/free sayımı yüzeysel bir kontroldür: aynı sayıda malloc ve free olsa bile farklı pointer'larla çağrılmış olabilir (double-free veya yanlış pointer). Gerçek memory leak analizi için Valgrind Memcheck kullanın.
06 Dinamik linker debug — LD_PRELOAD ile fark
ltrace ve LD_PRELOAD farklı mekanizmalardır. ltrace non-intrusive izleme yaparken LD_PRELOAD gerçek bir fonksiyon değiştirir.
# malloc_hook.c: tüm malloc çağrılarını logla
cat > /tmp/malloc_hook.c <<'EOF'
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
static void *(*real_malloc)(size_t) = NULL;
void *malloc(size_t size) {
if (!real_malloc)
real_malloc = dlsym(RTLD_NEXT, "malloc");
void *ptr = real_malloc(size);
fprintf(stderr, "[MALLOC] size=%zu ptr=%p\n", size, ptr);
return ptr;
}
EOF
# Derle
gcc -shared -fPIC -o /tmp/malloc_hook.so /tmp/malloc_hook.c -ldl
# Çalıştır
LD_PRELOAD=/tmp/malloc_hook.so myapp 2>&1 | head -20
# [MALLOC] size=48 ptr=0x200a0
# [MALLOC] size=1024 ptr=0x20120
ltrace vs LD_PRELOAD karşılaştırması
# Hangi kütüphanelerin nasıl yüklendiğini göster
LD_DEBUG=libs myapp 2>&1 | head -20
# 1234: find library=libc.so.6 [0]: found at /lib/arm-linux-gnueabihf/libc.so.6
# 1234: find library=libsensor.so.1 [0]: trying /usr/local/lib...
# 1234: find library=libsensor.so.1 [0]: NOT FOUND
# LD_DEBUG seçenekleri
# LD_DEBUG=libs : kütüphane arama
# LD_DEBUG=symbols : sembol çözümleme
# LD_DEBUG=bindings: bağlama bilgileri
# LD_DEBUG=all : her şey (çok ayrıntılı)
07 Pratik: yavaş kütüphane fonksiyonu tespiti
Bir embedded uygulaması beklenenden yavaş çalışıyorsa, ltrace ile hangi kütüphane çağrısının zaman aldığı bulunabilir.
Senaryo: JSON parse kütüphanesi profilleme
# -T: her çağrının süresini göster
ltrace -T -C myapp 2>&1 | grep -v "= 0 <0\." | head -30
# Sadece 0.0xx saniyeden uzun süren çağrıları göster
fopen("/var/lib/sensor/data.json", "r") = 0x20010 <0.000234>
fread(0x20040, 1, 65536, 0x20010) = 4096 <0.000089>
json_parse(0x20040, 4096) = 0x21000 <1.234567>
# json_parse 1.23 saniye sürdü! CPU-bound işlem
json_get(0x21000, "temperature") = 0x21100 <0.000012>
json_free(0x21000) = NULL <0.000345>
# ltrace -c ile toplam süre istatistikleri
ltrace -c myapp 2>&1
# % time seconds usecs/call calls function
# 94.23 1.234567 1234567 1 json_parse <-- açık kazanan
# 3.11 0.040234 2012 20 fread
# 1.22 0.015998 1599 10 pthread_mutex_lock
# Çözüm: json_parse'ı daha hızlı kütüphane ile değiştir
# ya da sonucu önbelleğe al (dosya değişmemişse tekrar parse etme)
ARM embedded: ltrace sınırlamaları
# ltrace bazı ARMv7 Thumb-2 binary'lerinde sorun çıkarabilir
# Alternatif: uftrace (daha modern, ARM desteği daha iyi)
uftrace record ./myapp
uftrace replay | head -30
# Ya da gprof ile profil (derleme zamanı enstrümantasyon)
arm-linux-gnueabihf-gcc -pg -o myapp_prof sensor.c main.c
./myapp_prof
arm-linux-gnueabihf-gprof myapp_prof gmon.out | head -30