00 objdump nedir — ELF anatomy
objdump, GNU binutils paketinin bir parçasıdır ve ELF (Executable and Linkable Format) ikili dosyalarını çok çeşitli formatlarda incelemenizi sağlar. Embedded geliştirmede vazgeçilmezdir.
Bir ELF dosyası üç ana katmandan oluşur: ELF header (dosya tipi, hedef mimari, entry point), section header tablosu (linker'ın kullandığı bölümler: .text, .data, .bss...) ve program header tablosu (loader'ın kullandığı segment tanımları). objdump bu katmanları ayrı ayrı ya da hep birlikte gösterebilir.
Kurulum
# Debian/Ubuntu — native (x86)
sudo apt install binutils
# ARM cross-toolchain ile birlikte gelir:
sudo apt install gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf
# Kurulumu doğrula
arm-linux-gnueabihf-objdump --version
# GNU objdump (GNU Binutils for Debian) 2.38
# Yocto/Buildroot: SDK içinde aarch64-poky-linux-objdump vb.
which aarch64-poky-linux-objdump
Temel sözdizimi
objdump [SEÇENEKLER] dosya
# Cross-compile edilmiş ARM ELF için hedef mimariye uygun araç kullan:
arm-linux-gnueabihf-objdump -d myprogram
Native objdump, ARM ELF dosyalarını da okuyabilir; ancak arm-linux-gnueabihf-objdump gibi hedef mimariye özgü araçlar daha doğru disassemble çıktısı üretir. Özellikle Thumb/Thumb-2 geçişlerinde bunu göreceksiniz.
01 -d — disassemble (ARM Thumb & x86-64)
-d bayrağı, çalıştırılabilir kod içeren tüm section'ları (genellikle .text) disassemble eder. ARM Thumb ve x86-64 çıktıları birbirinden oldukça farklıdır.
# Temel disassemble
objdump -d /bin/ls | head -40
# ARM cross: Thumb-2 kodu
arm-linux-gnueabihf-objdump -d hello_arm
# Belirli bir fonksiyonu filtrele (grep ile)
objdump -d myapp | grep -A 30 '<main>:'
# Intel syntax (x86) — AT&T yerine Intel sözdizimi
objdump -d -M intel /bin/ls | head -20
x86-64 örnek çıktı
0000000000001149 <main>:
1149: f3 0f 1e fa endbr64
114d: 55 push %rbp
114e: 48 89 e5 mov %rsp,%rbp
1151: 48 83 ec 10 sub $0x10,%rsp
1155: 89 7d fc mov %edi,-0x4(%rbp)
1158: 48 89 75 f0 mov %rsi,-0x10(%rbp)
115c: bf 00 00 00 00 mov $0x0,%edi
1161: e8 ea fe ff ff call 1050 <puts@plt>
1166: b8 00 00 00 00 mov $0x0,%eax
116b: c9 leave
116c: c3 ret
ARM Thumb-2 örnek çıktı
00010454 <main>:
10454: b580 push {r7, lr}
10456: af00 add r7, sp, #0
10458: f7ff effe blx 10458 <puts@plt>
1045c: 4603 mov r3, r0
1045e: 4618 mov r0, r3
10460: bd80 pop {r7, pc}
# Thumb talimatları 2 veya 4 bayttır (Thumb-2 karışık genişlik)
# 'blx' = Branch with Link and Exchange — Thumb/ARM modunu değiştirir
Tüm section'ları disassemble et
# -D: tüm section'lar (data section'ları da dahil)
objdump -D firmware.elf | less
# Belirli bir section:
objdump -d --section=.text.startup myapp
02 -h — section headers
Section header tablosu, ELF dosyasındaki her bölümün adını, tipini, boyutunu ve bellek adresini listeler. Linker script sorunlarını debug etmek için temel araçtır.
objdump -h myapp
myapp: file format elf32-littlearm
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00003a4c 00010000 00010000 00010000 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 000004f8 00013a4c 00013a4c 00013a4c 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .data 00000124 00020000 00013f44 00013f44 2**2
CONTENTS, ALLOC, LOAD, DATA
3 .bss 000008a0 00020124 00014068 00014068 2**3
ALLOC
4 .debug_info 00012345 00000000 00000000 00014068 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
Flag anlamları
Embedded sistemlerde LMA (Load Memory Address) flash'taki adresi, VMA (Virtual Memory Address) RAM'deki çalışma adresini gösterir. Önyükleyici başlangıçta .data section'ını LMA'dan VMA'ya kopyalar. İkisi aynıysa normal hosted uygulama.
03 -t — symbol table
-t, ELF dosyasındaki tüm sembolleri (fonksiyonlar, global değişkenler, external referanslar) listeler.
# Sembol tablosunu göster
objdump -t myapp
# Sırala ve filtrele
objdump -t myapp | sort | grep -i "func_name"
# Sadece fonksiyonları göster (F tipi)
objdump -t myapp | grep ' F '
myapp: file format elf32-littlearm
SYMBOL TABLE:
00010454 l F .text 0000003c main
00010490 g F .text 000000a8 sensor_read
00020000 g .data 00000004 g_sensor_value
00000000 *UND* 00000000 printf
00000000 *UND* 00000000 __libc_start_main
Sütun anlamları
04 -r — relocation entries
Relocation tablosu, linker'ın veya dinamik yükleyicinin adresleri çözümlemek için kullanacağı yer tutucuları listeler. Statik bağlama sorunlarını anlamak için kritiktir.
# .o (object) dosyasındaki relocation'ları göster
objdump -r main.o
# Tam ELF'de dinamik relocation'lar için -R kullan
objdump -R myapp
main.o: file format elf32-littlearm
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000008 R_ARM_THM_CALL sensor_read
00000014 R_ARM_THM_CALL printf
00000020 R_ARM_ABS32 g_sensor_value
RELOCATION RECORDS FOR [.rodata]:
00000000 R_ARM_ABS32 .LC0
Her kayıt; offset (section içindeki konum), relocation tipi (mimariye özgü: R_ARM_THM_CALL, R_X86_64_PLT32 vb.) ve hedef sembol adını içerir. Linker bu bilgiyi kullanarak gerçek adresleri yerleştir.
05 -x — tüm header'lar
-x, tüm başlık bilgilerini tek komutla görüntüler: ELF header, section headers, symbol table ve dynamic section.
# -x = --all-headers
objdump -x myapp | less
# -f = sadece dosya header özeti
objdump -f myapp
myapp: file format elf32-littlearm
architecture: arm, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x00010454
# EXEC_P: çalıştırılabilir
# HAS_SYMS: sembol tablosu mevcut (strip edilmemiş)
# D_PAGED: sayfa hizalı yükleme
Dynamic Section:
NEEDED libpthread.so.0
NEEDED libc.so.6
SONAME libmysensor.so.1
RPATH /opt/sysroot/lib
06 -S — kaynak + assembly (interleaved)
-S bayrağı, DWARF debug bilgisi varsa assembly satırlarının arasına kaynak kodu satırlarını ekler. Optimizasyon sonrası kodun ne hale geldiğini görmek için idealdir.
# -g ile derlenmesi gerekir (debug semboller)
arm-linux-gnueabihf-gcc -g -O1 -o sensor sensor.c
# Interleaved kaynak + assembly
arm-linux-gnueabihf-objdump -S sensor | less
# Belirli bir fonksiyon
arm-linux-gnueabihf-objdump -S sensor | \
awk '/^[0-9a-f]+ <read_temperature>:/,/^[0-9a-f]+ <[^>]+>:/'
00010490 <read_temperature>:
int read_temperature(int channel) {
10490: b580 push {r7, lr}
10492: af00 add r7, sp, #0
int raw = adc_read(channel);
10494: f7ff ffd8 bl 10448 <adc_read>
return (raw * 330) / 4096 - 40;
10498: f44f 7454 mov.w r4, #848 @ 0x350
1049c: fb04 f000 mul.w r0, r4, r0
104a0: 0c00 lsrs r0, r0, #16
104a2: 3fd8 subs r7, #216 @ -40
104a4: 4438 add r0, r7
104a6: bd80 pop {r7, pc}
Yüksek optimizasyon düzeylerinde (-O2, -O3) kaynak satırları ile assembly arasındaki eşleşme bozulabilir — döngüler vektörize edilir, satır içi fonksiyonlar açılır. Güvenilir eşleşme için -O0 -g ya da -Og -g kullanın.
07 --dwarf — debug bilgisi
DWARF, ELF dosyalarına gömülen standart debug formatıdır. objdump --dwarf ile debug section'larını ham olarak inceleyebilirsiniz.
# Tüm DWARF section'larını göster
objdump --dwarf myapp | less
# Sadece satır numarası tablosu (.debug_line)
objdump --dwarf=decodedline myapp
# Sadece kaynak dosya isimleri
objdump --dwarf=info myapp | grep -i "DW_AT_name"
# Satır numarasından adres bul
objdump --dwarf=decodedline myapp | grep "sensor.c"
CU: sensor.c:
File name Line number Starting address
sensor.c 1 0x10454
sensor.c 5 0x10458
sensor.c 8 0x10490
sensor.c 12 0x10498
sensor.c 15 0x104a6
Bu tablo, her kaynak satırının hangi bellek adresine karşılık geldiğini gösterir. addr2line aracı da arka planda bu tabloyu kullanır.
08 Pratik: crash adresi → assembly satırı
Embedded sistemlerde en yaygın senaryo: seri port üzerinden gelen "Segmentation fault at 0x000104b2" gibi bir mesajı kaynak koda ve assembly satırına çevirmek.
Senaryo 1: crash adresi → kaynak satırı
# Hedef: crash adresinin hangi fonksiyon ve satır olduğunu bul
# Crash mesajı: "pc : [<000104b2>]"
# 1. addr2line ile kaynak satırını bul (debug binary gerekir)
arm-linux-gnueabihf-addr2line -e myapp -f 0x104b2
# read_temperature
# /home/user/project/sensor.c:12
# 2. objdump ile o adresi disassemble et
arm-linux-gnueabihf-objdump -d myapp | \
awk '/^[[:space:]]*104[0-9a-f]+:/{print}' | \
grep -B5 -A5 "104b2:"
104ac: 6800 ldr r0, [r0, #0] <-- r0 = NULL buraya geldi
104ae: fb04 f000 mul.w r0, r4, r0
104b2: 0c00 lsrs r0, r0, #16 <-- CRASH BU SATIRDA
104b4: 3fd8 subs r7, #216
# r0 = NULL pointer dereference → ldr r0, [r0, #0] çöktü
Senaryo 2: stripped binary analizi
# Üretim binary'si genellikle strip edilmiştir
file myapp_stripped
# myapp_stripped: ELF 32-bit LSB executable, ARM, stripped
# Sembol tablosu boş olacak
objdump -t myapp_stripped
# SYMBOL TABLE: no symbols
# Ancak kod hâlâ disassemble edilebilir
objdump -d myapp_stripped | head -50
# Çözüm: aynı kaynak kodu debug ile derle, adresleri eşleştir
arm-linux-gnueabihf-gcc -g -O2 -o myapp_debug sensor.c
objdump -S myapp_debug | grep -A 20 "104b0:"
# Yocto: debug paketini yükle
# IMAGE_INSTALL += "myapp-dbg" veya PACKAGE_DEBUG_SPLIT_STYLE
Toplu crash decode scripti
#!/bin/bash
# Kullanım: ./crash_decode.sh myapp 0x104b2 0x10498 0x10454
BINARY=$1
shift
TOOLPREFIX=${TOOLPREFIX:-arm-linux-gnueabihf-}
for ADDR in "$@"; do
echo "=== $ADDR ==="
${TOOLPREFIX}addr2line -e "$BINARY" -f -i "$ADDR"
${TOOLPREFIX}objdump -d "$BINARY" | \
grep -A 3 "^[[:space:]]*${ADDR#0x}:"
echo
done
Yocto projelerinde, üretim imajındaki stripped binary ile birlikte aynı build'den üretilen -dbg paketi genellikle /usr/lib/debug/ altında .debug dosyasını içerir. objdump ve addr2line bu dosyayı otomatik olarak bulabilir: objdump -S /usr/lib/debug/usr/bin/myapp.debug