00 addr2line nedir — DWARF mekanizması
addr2line, bir bellek adresini kaynak kodu konumuna çeviren bir araçtır. Embedded geliştirmede en pratik crash analizi yöntemidir: hedefte gelen adres, geliştirici makinasında kaynak satırına dönüşür.
addr2line, ELF dosyasındaki DWARF debug bilgisini okur. DWARF, her assembly talimatını bir kaynak dosyası ve satır numarasıyla ilişkilendiren .debug_line section'ını içerir. Bu section, -g bayrağıyla derleme yapıldığında oluşturulur.
# binutils paketinin bir parçası — ayrı kurulum gerekmez
which addr2line
# /usr/bin/addr2line
# ARM cross:
arm-linux-gnueabihf-addr2line --version
# GNU addr2line (GNU Binutils for Debian) 2.38
# Temel kullanım
addr2line -e myapp 0x104b2
# /home/user/project/sensor.c:42
01 -e, -f, -i bayrakları
addr2line'ın üç temel bayrağı: ikili dosya belirtme, fonksiyon adı gösterme ve inline fonksiyon zincirini açma.
# -e: hangi ELF dosyası kullanılacak
addr2line -e myapp 0x104b2
# /home/user/project/sensor.c:42
# -f: fonksiyon adını da göster
addr2line -e myapp -f 0x104b2
# read_temperature
# /home/user/project/sensor.c:42
# -i: inline fonksiyon zincirini göster
# Derleyici inline ettiğinde gerçek kaynak farklı dosyada olabilir
addr2line -e myapp -f -i 0x104b2
# convert_adc_to_celsius <-- inline edilmiş iç fonksiyon
# /home/user/project/adc.h:15
# read_temperature <-- asıl çağıran
# /home/user/project/sensor.c:42
# -p: insan dostu çıktı formatı (pretty-print)
addr2line -e myapp -f -p 0x104b2
# read_temperature at /home/user/project/sensor.c:42
02 Crash log adresini kaynak satırına çevirme
ARM tabanlı embedded sistemlerde seri konsoldan gelen tipik bir crash mesajı: "Segmentation fault (core dumped)" yerine doğrudan register dump gelir.
ARM sistem panic çıktısı
Unhandled fault: page fault (0x82f) at 0x00000000
pgd = dc3a4000
[00000000] *pgd=9c3a8031, *pte=00000000, *ppte=00000000
Internal error: Oops: 82f [#1] SMP ARM
Modules linked in: sensor_drv
CPU: 0 PID: 134 Comm: sensor_daemon Not tainted 5.15.0
Hardware name: Generic DT based system
PC is at read_temperature+0x22/0x60
LR is at sensor_loop+0x4e/0xa0
pc : [<00010494>] lr : [<000104de>]
sp : dea8bd80 ip : 00000000 fp : dea8bda4
# PC (program counter) adresini decode et
arm-linux-gnueabihf-addr2line -e myapp -f -i 0x00010494
# read_temperature
# /home/user/project/sensor.c:38
# LR (link register) adresini decode et — çağıran yer
arm-linux-gnueabihf-addr2line -e myapp -f -i 0x000104de
# sensor_loop
# /home/user/project/main.c:92
# Stack backtrace'i toplu decode et
arm-linux-gnueabihf-addr2line -e myapp -f -i \
0x00010494 0x000104de 0x00010600 0x00010700
Crash log'dan adresleri otomatik çıkarma
# Crash log'dan hex adresleri çıkar ve decode et
grep -oE '\[<[0-9a-f]+>\]' crash.log | \
grep -oE '[0-9a-f]+' | \
while read addr; do
echo "=== 0x$addr ==="
arm-linux-gnueabihf-addr2line -e myapp -f -p "0x$addr"
done
03 Core dump + backtrace analizi
Core dump, bir süreç çöktüğünde bellek görüntüsünü kaydeder. gdb ile backtrace alınır, addr2line ile kaynak satırları decode edilir.
# Core dump'ı etkinleştir (hedef sistemde)
ulimit -c unlimited
echo "/tmp/core.%e.%p" > /proc/sys/kernel/core_pattern
# Programı çalıştır, crash bekle
./myapp
# Segmentation fault (core dumped)
# gdb ile backtrace al
arm-linux-gnueabihf-gdb myapp /tmp/core.myapp.1234 \
-batch -ex "bt" -ex "quit"
#0 0x00010494 in read_temperature ()
#1 0x000104de in sensor_loop ()
#2 0x00010600 in main ()
#3 0x76f4e3c4 in __libc_start_main () from /lib/libc.so.6
# gdb backtrace'indeki adresleri addr2line ile decode et
for addr in 0x00010494 0x000104de 0x00010600; do
echo -n "#? $addr: "
arm-linux-gnueabihf-addr2line -e myapp -f -p "$addr"
done
# Çıktı:
# #? 0x00010494: read_temperature at sensor.c:38
# #? 0x000104de: sensor_loop at main.c:92
# #? 0x00010600: main at main.c:15
04 DWARF debug info gereksinimi
addr2line çalışabilmek için DWARF debug bilgisine ihtiyaç duyar. Üretim binary'leri genellikle strip edilmiş olduğundan bu bilgi ayrı tutulmalıdır.
# -g ile derle: DWARF bilgisi ELF içine gömülür
arm-linux-gnueabihf-gcc -g -O2 -o myapp sensor.c main.c
# Üretim: debug bilgisini ayrı .debug dosyasına çıkar
arm-linux-gnueabihf-objcopy --only-keep-debug myapp myapp.debug
arm-linux-gnueabihf-strip --strip-debug myapp
# Debug link ekle (gdb ve addr2line .debug dosyasını bulur)
arm-linux-gnueabihf-objcopy \
--add-gnu-debuglink=myapp.debug myapp
# Debug dosyası ile addr2line kullan
arm-linux-gnueabihf-addr2line -e myapp.debug -f 0x10494
# DWARF var mı kontrol et
readelf -S myapp | grep debug
# [18] .debug_info PROGBITS ... (DWARF mevcut)
# Çıktı yoksa: strip edilmiş, .debug dosyasını kullan
Yocto'da IMAGE_FEATURES += "dbg-pkgs" veya -dbg paketleri kurulunca debug sembolleri /usr/lib/debug/ altına yerleştirilir. addr2line bu dizini otomatik arar. Cross geliştirmede: addr2line -e deploy/images/.../myapp.debug
05 PIE / -fpie ile adres düzeltme
PIE (Position Independent Executable) ve ASLR (Address Space Layout Randomization) ile derlenen programlarda crash adresleri her çalıştırmada farklı olur. Ham adresi kullanmak yerine base address ile offset hesabı yapılır.
# PIE binary mi kontrol et
readelf -h myapp | grep Type
# Type: DYN (Position-Independent Executable) <-- PIE
# Type: EXEC (Executable file) <-- non-PIE
# PIE binary'de crash: /proc/PID/maps ile base address bul
# Crash çıktısından:
# PC: 0x5648a3b2 (runtime adresi)
# /proc/maps'ten base address bul (programı çalıştır, PID not al)
cat /proc/1234/maps | head -3
# 56489000-5648f000 r-xp 00000000 08:01 ... myapp <-- base: 0x56489000
# Offset hesapla: runtime_addr - base_addr
# 0x5648a3b2 - 0x56489000 = 0x13b2
# addr2line'a offset ver
addr2line -e myapp -f 0x13b2
# read_temperature
# /home/user/project/sensor.c:38
Embedded: ASLR genellikle kapalıdır
# Embedded Linux'ta ASLR durumu
cat /proc/sys/kernel/randomize_va_space
# 0 = ASLR kapalı (pek çok embedded sistemde varsayılan)
# 1 = stack/mmap randomize
# 2 = tam ASLR
# ASLR kapalıysa non-PIE binary'de adresler sabittir
# → addr2line'a doğrudan ham adresi ver
# Test sırasında ASLR geçici kapatma
echo 0 > /proc/sys/kernel/randomize_va_space
06 Kernel oops adresi decode
Kernel modülü çöktüğünde dmesg'de görünen oops mesajı, modül base address ve offset bilgisi içerir. addr2line ile modül kaynak kodu bulunabilir.
[ 234.567890] BUG: unable to handle kernel NULL pointer dereference at 00000000
[ 234.567891] IP: [<bf001234>] sensor_drv_read+0x34/0x80 [sensor_drv]
[ 234.567892] *pde = 00000000
[ 234.567893] Oops: 0002 [#1] SMP
[ 234.567894] CPU: 0 PID: 152 Comm: cat Tainted: G O 5.15.0
[ 234.567895] Call Trace:
[ 234.567896] [<bf001290>] sensor_drv_ioctl+0x50/0xc0 [sensor_drv]
[ 234.567897] [<c012abc0>] vfs_ioctl+0x30/0x60
# Kernel modülü için: .ko dosyasının debug versiyonu gerekir
# IP: sensor_drv_read+0x34 → fonksiyon + offset
# addr2line ile .ko debug dosyasından decode
addr2line -e sensor_drv.ko.debug -f 0x34
# sensor_drv_read
# /home/user/kernel/drivers/sensor/sensor_drv.c:87
# Alternatif: scripts/faddr2line (kernel kaynak ağacında)
./scripts/faddr2line sensor_drv.ko sensor_drv_read+0x34
# sensor_drv_read+0x34/0x80:
# sensor_drv.c:87
Kernel modüllerini debug sembollerle derlemek için CONFIG_DEBUG_INFO=y gerekir. Yocto'da: KERNEL_EXTRA_FEATURES += "debug". Üretim kerneli genellikle strip edilmiştir; geliştirme amacıyla build sunucusundaki vmlinux dosyasını saklayın.
07 Pratik: gdbserver crash decode + otomatik script
gdbserver üzerinden uzaktan hata ayıklama ve otomatik crash decode scripti.
gdbserver ile uzaktan crash decode
# gdbserver başlat
gdbserver :2345 ./myapp
# ya da çalışan bir sürece bağlan
gdbserver --attach :2345 1234
# arm-linux-gnueabihf-gdb ile uzaktan bağlan
arm-linux-gnueabihf-gdb myapp_debug
# gdb içinde:
(gdb) target remote 192.168.1.100:2345
(gdb) set sysroot /opt/sysroot
(gdb) break main
(gdb) continue
# ... crash gerçekleşir ...
(gdb) bt
# #0 0x00010494 in read_temperature (ch=0) at sensor.c:38
# #1 0x000104de in sensor_loop () at main.c:92
# gdb çıktısından adresleri al, addr2line ile çapraz doğrula
(gdb) info frame
(gdb) quit
Otomatik crash decode scripti
#!/bin/bash
# Kullanım: ./auto_decode.sh <binary> <crash_log>
# Örnek: ./auto_decode.sh myapp.debug /tmp/crash.log
BINARY=${1:?"İkili dosya belirtin"}
LOGFILE=${2:-/dev/stdin}
TOOLPREFIX=${TOOLPREFIX:-arm-linux-gnueabihf-}
ADDR2LINE="${TOOLPREFIX}addr2line"
echo "=== CRASH DECODE: $BINARY ==="
echo
# Hex adreslerini çıkar (0x ile başlayan ya da köşeli parantezli)
grep -oE '(0x[0-9a-fA-F]+|\[<[0-9a-fA-F]+>\])' "$LOGFILE" | \
grep -oE '[0-9a-fA-F]{6,}' | \
sort -u | \
while read addr; do
result=$("$ADDR2LINE" -e "$BINARY" -f -p "0x$addr" 2>/dev/null)
if [ -n "$result" ] && ! echo "$result" | grep -q "?"; then
echo "0x$addr → $result"
fi
done
echo
echo "=== BİTTİ ==="
chmod +x auto_decode.sh
./auto_decode.sh myapp.debug crash.log
# Çıktı:
# === CRASH DECODE: myapp.debug ===
#
# 0x00010494 → read_temperature at sensor.c:38
# 0x000104de → sensor_loop at main.c:92
# 0x00010600 → main at main.c:15
#
# === BİTTİ ===