00 nm nedir — sembol tablosu temelleri
nm, ELF object dosyalarının, shared library'lerin ve çalıştırılabilir dosyaların sembol tablolarını listeler. GNU binutils ailesinin en çok kullanılan araçlarından biridir.
Bir sembol, kaynak koddaki her fonksiyon, global değişken ve extern bildirimdir. Derleyici her .o dosyasına bir sembol tablosu ekler; linker bu tabloları birleştirerek referansları çözümler. nm bu sürecin her aşamasında çalışır.
# Temel kullanım
nm myapp
nm main.o
nm libsensor.so
# ARM cross:
arm-linux-gnueabihf-nm myapp
# C++ binary (demangling ile)
nm -C mycppapp
# Format: [adres] [tür] [sembol_adı]
00010454 T main
00010490 T sensor_read
00020000 D g_sensor_value
U printf
U __libc_start_main
0001049c t helper_static
00020004 B uninit_global
Adres sütunu büyük harf semboller için onaltılık bellek adresi, küçük harfler yerel (local) sembolleri gösterir. U (Undefined) sembollerin adresi yoktur — başka nesne dosyalarından gelecekler.
01 Sembol tür kodları: T U W w B D ve diğerleri
nm'nin tek harfli tür kodları, sembolün hangi section'da yaşadığını ve bağlama kurallarını özetler. Büyük harf = global, küçük harf = local.
# Sadece global fonksiyonlar (T tipli)
nm myapp | grep ' T '
# Tanımsız semboller (U tipli) — link bağımlılıkları
nm myapp | grep ' U '
# Weak semboller
nm myapp | grep -E ' [Ww] '
# BSS semboller (sıfır başlangıçlı global'lar)
nm myapp | grep ' B ' | sort -k1
02 -a, -u, -D bayrakları
nm'nin en sık kullanılan bayrakları: tüm sembolleri görme, sadece tanımsızları listeleme ve dinamik sembol tablosunu okuma.
# -a: hata ayıklama sembollerini de dahil et (genellikle önemsizler)
nm -a myapp | head -20
# -u / --undefined-only: sadece tanımsız semboller
nm -u myapp
# Hangi fonksiyonları dışarıdan aldığını öğrenmek için ideal
# -D / --dynamic: dinamik sembol tablosunu oku (.dynsym)
# Strip edilmiş binary'lerde tek çalışan seçenek
nm -D /usr/lib/libpthread.so.0 | head -20
# -D ile bir kütüphanenin dışa aktardığı API'yi gör
nm -D libmysensor.so | grep ' T '
U __cxa_atexit@@GLIBC_2.4
U __libc_start_main@@GLIBC_2.4
U malloc@@GLIBC_2.4
U free@@GLIBC_2.4
U printf@@GLIBC_2.4
U pthread_create@@GLIBC_2.4
U sensor_driver_init
# @@ sonrası versiyon bağımlılığını gösterir
# sensor_driver_init: libsensor.so'dan gelmeli — bulunamamışsa link hatası
03 -C — C++ demangling
C++ derleyicileri, fonksiyon aşırı yüklemesini (overloading) ve namespace'leri kodlamak için sembol adlarını bozar (name mangling). -C bu bozulmayı çözer.
# C++ binary'de mangled semboller
nm mycppapp | grep "_Z"
# 00010490 T _ZN6Sensor4readEi
# 000104d0 T _ZN6Sensor5resetEv
# 00010510 T _ZN6SensorC1Eii
# -C ile demangled
nm -C mycppapp | grep -v "^[0-9a-f]* [a-z] "
# 00010490 T Sensor::read(int)
# 000104d0 T Sensor::reset()
# 00010510 T Sensor::Sensor(int, int)
# c++filt ile bağımsız demangling
echo "_ZN6Sensor4readEi" | c++filt
# Sensor::read(int)
C++ kodundan C kütüphaneleriyle bağlantı kurmak için extern "C" blokları kullanılır. Bu blok içindeki fonksiyonlar mangling uygulanmadan düz isimle sembol tablosuna girer. nm -C çıktısında extern "C" fonksiyonları C gibi görünür.
04 --defined-only ve --undefined-only
Bu iki bayrak, nesne dosyaları ve kütüphaneler arasındaki API sınırlarını netleştirmek için kullanılır.
# Bu kütüphanenin dışa aktardığı (defined) semboller
nm --defined-only libsensor.so | grep ' T '
# 00001234 T sensor_init
# 00001290 T sensor_read
# 000012d0 T sensor_close
# Bu binary'nin dışarıya bağımlı olduğu semboller
nm --undefined-only myapp
# Bağlama sırasında çözülmesi gereken her şey
# İki kütüphanenin ortak dışa aktardığı semboller (çakışma kontrolü)
comm -12 \
<(nm --defined-only liba.so | awk '{print $3}' | sort) \
<(nm --defined-only libb.so | awk '{print $3}' | sort)
# Çıktı: her iki kütüphanede de tanımlı semboller — potansiyel çakışma!
05 Sembol boyutu ve sıralama
nm ile sembolleri boyutlarına göre sıralayarak en büyük fonksiyonları veya değişkenleri tespit edebilirsiniz. Flash/RAM kullanım optimizasyonu için çok faydalıdır.
# -S: boyut sütununu göster
nm -S myapp | head -20
# 00010454 0000003c T main
# 00010490 000000a8 T sensor_read <-- 168 bayt
# 00010538 00000024 T sensor_close
# Büyükten küçüğe sırala (en büyük fonksiyon en üstte)
nm -S --size-sort myapp | grep ' T ' | tail -20
# Özellikle firmware boyut optimizasyonunda kritik:
arm-linux-gnueabihf-nm -S --size-sort firmware.elf | \
grep ' T ' | tail -30
# En büyük 30 fonksiyonu gösterir — bunları küçültmek büyük kazanç sağlar
# BSS kullanımı: en büyük global değişkenler
nm -S --size-sort myapp | grep ' B ' | tail -20
nm'nin boyut sütunu (-S) her zaman güvenilir değildir: bazı derleyici sürümleri fonksiyon boyutlarını hatalı raporlar. Kesin boyut için readelf -s veya size komutunu da kontrol edin.
06 nm vs readelf -s farkı
nm ve readelf -s aynı sembol tablosunu farklı biçimlerde sunar. Hangisini ne zaman kullanmalısınız?
# nm çıktısı — kısa, insan dostu
nm myapp | head -5
# 00010454 T main
# 00010490 T sensor_read
# 00020000 D g_value
# U printf
# readelf -s çıktısı — ayrıntılı, tüm alanlar
readelf -s myapp | head -10
# Num: Value Size Type Bind Vis Ndx Name
# 48: 00010454 60 FUNC LOCAL DEFAULT 3 main
# 49: 00010490 168 FUNC GLOBAL DEFAULT 3 sensor_read
07 Pratik: link hataları debug + ABI uyumluluk kontrolü
Gerçek dünya senaryoları: "undefined reference" link hatalarını nm ile bulmak ve iki kütüphane versiyonu arasında ABI kırılıp kırılmadığını kontrol etmek.
Senaryo 1: undefined reference hatasını debug etmek
# Link hatası:
# main.o: undefined reference to `sensor_driver_init'
# 1. Hatayı veren object dosyasının hangi sembolleri istediğini gör
nm -u main.o
# U sensor_driver_init <-- bunu arıyoruz
# 2. Hangi kütüphane/object bu sembolü sağlıyor?
nm --defined-only libsensor.so | grep "sensor_driver_init"
# 00001234 T sensor_driver_init <-- burada!
# 3. Ama belki imla farklıdır?
nm --defined-only libsensor.so | grep -i "sensor"
# 00001200 T Sensor_driver_Init <-- büyük harf farkı!
# 4. Kütüphane link satırına eklendi mi?
gcc main.o -lsensor -o myapp # doğru
gcc main.o -o myapp -lsensor # yanlış sıra — GNU ld'de sorun çıkabilir
# 5. Birden fazla .a dosyasında ara
for lib in /usr/local/lib/*.a; do
result=$(nm --defined-only "$lib" | grep "sensor_driver_init")
[ -n "$result" ] && echo "$lib: $result"
done
Senaryo 2: ABI uyumluluk kontrolü
# libsensor.so.1 ile libsensor.so.2 arasında hangi semboller kayboldu?
# v1 dışa aktarımları
nm --defined-only -D libsensor.so.1 | \
awk '{print $3}' | sort > /tmp/symbols_v1.txt
# v2 dışa aktarımları
nm --defined-only -D libsensor.so.2 | \
awk '{print $3}' | sort > /tmp/symbols_v2.txt
# v1'de olan ama v2'de olmayan semboller (kırılan API!)
comm -23 /tmp/symbols_v1.txt /tmp/symbols_v2.txt
# sensor_legacy_read <-- v2'de kaldırıldı → ABI kırıldı!
# v2'de eklenen yeni semboller (ekleme sorun değil)
comm -13 /tmp/symbols_v1.txt /tmp/symbols_v2.txt
# sensor_read_async
# sensor_get_metadata
Senaryo 3: Embedded — firmware'de hangi fonksiyonlar kullanılıyor?
# Firmware.elf boyut raporu
arm-none-eabi-nm --size-sort -S firmware.elf | \
grep ' T ' | sort -rn | head -20
# RAM kullanımı: .data + .bss
arm-none-eabi-nm -S firmware.elf | grep -E ' [BD] ' | \
awk '{sum += strtonum("0x"$2)} END {printf "RAM: %d bytes\n", sum}'
# Flash kullanımı: .text + .rodata
arm-none-eabi-nm -S firmware.elf | grep -E ' [TR] ' | \
awk '{sum += strtonum("0x"$2)} END {printf "Flash: %d bytes\n", sum}'