From 191bdc53bf6c731f384786f10baea22d6a580113 Mon Sep 17 00:00:00 2001 From: "M. Serdar Karaman" Date: Tue, 26 May 2026 08:30:10 +0300 Subject: [PATCH 1/2] =?UTF-8?q?Yeni=20yaz=C4=B1:=20Linker=20Script=20Anato?= =?UTF-8?q?misi=20=E2=80=94=20ARM=20Bare-Metal=20i=C3=A7in=20Bir=20.ld=20D?= =?UTF-8?q?osyas=C4=B1=20Sat=C4=B1r=20Sat=C4=B1r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alan: toolchain/build. Cortex-M4 STM32F4 için gerçek bir .ld iskeletini satır satır açıyor; .map ve objdump çıktılarıyla VMA/LMA, .data kopyalama, .bss sıfırlama, .init_array ve vector table mekaniklerini doğruluyor. Derinlik öğesi (Bölüm 7): gerçek .ld + .map + objdump dökümü — bellek/assembly incelemesi. ~3000 kelime, 9 ana bölüm, Mermaid bellek haritası diyagramı. agent/topics.md güncellendi: yazılanlara bandpass-sampling, açık PR'lara #88 WCET ve #89 watchdog eklendi; fikir havuzundan linker-script ve bu çalıştırmada açılan PR'larla çakışan adaylar (MC/DC, CRC, WCET, watchdog) çıkarıldı. Co-Authored-By: Claude Opus 4.7 --- ...-linker-script-anatomisi-arm-bare-metal.md | 428 ++++++++++++++++++ agent/topics.md | 65 ++- 2 files changed, 458 insertions(+), 35 deletions(-) create mode 100644 _posts/2026-05-26-linker-script-anatomisi-arm-bare-metal.md diff --git a/_posts/2026-05-26-linker-script-anatomisi-arm-bare-metal.md b/_posts/2026-05-26-linker-script-anatomisi-arm-bare-metal.md new file mode 100644 index 00000000..23bd0e86 --- /dev/null +++ b/_posts/2026-05-26-linker-script-anatomisi-arm-bare-metal.md @@ -0,0 +1,428 @@ +--- +title: "Linker Script Anatomisi: ARM Bare-Metal için Bir `.ld` Dosyası Satır Satır" +subtitle: "Anatomy of a Linker Script: Walking Through an ARM Bare-Metal `.ld` File" +background: "/img/posts/8.webp" +date: '2026-05-26 09:00:00' +layout: post +lang: tr +mermaid: true +--- + +Bare-metal bir ARM Cortex-M projesinde derleme zincirini takip ettiğinizde, kodun `main()`'e ulaşmadan önce geçtiği üç temel adım vardır: derleyici, derleyici (assembler) ve **linker**. İlk ikisi okul müfredatında genellikle kabaca anlatılır; üçüncüsü, yani linker ve onu yönlendiren `.ld` dosyası, çoğu mühendis için bir kutsal metindir — değiştirilmez, anlaşılmaz, "STM32CubeMX zaten üretiyor" denilip geçilir. + +Oysa linker script, ürününüzün belleği nasıl tükettiğini, hangi değişkenin nereye yerleştiğini, FLASH'taki sabit verilerin RAM'e ne zaman ve nasıl kopyalandığını, kesme vektör tablosunun nerede oturduğunu ve C++ kullanıyorsanız global nesnelerin constructor'larının ne zaman çağrıldığını belirleyen tek dosyadır. Onu anlamadan bare-metal hata ayıklamak, gözleri kapalı el yordamıyla yürümeye benzer. + +Bu yazıda gerçek bir Cortex-M4 (STM32F4 ailesi) için yazılmış bir `.ld` dosyasını **satır satır** açacağız. Sonra `arm-none-eabi-ld`'nin ürettiği `.map` dosyasından örnek satırlara bakacak, `objdump` ile bellek yerleşimini doğrulayacağız. Hedef, "şu satır şu işi yapar" düzeyinden öteye geçip neden bu yapıya ihtiyaç duyulduğunu anlamak. + +--- + +## Kısa Bir Tarihçe: Linker Script Nereden Geldi? + +GNU `ld`, 1988 yılında Cygnus / Free Software Foundation tarafından GNU Binutils paketinin bir parçası olarak yazıldı. O dönemde Unix platformları için tasarlanmıştı: COFF, ECOFF, a.out gibi nesne formatlarını destekliyor, hedef genellikle bir Unix işletim sistemiydi ve linker script — varsayılan olarak — kullanıcı tarafından yazılmıyordu. `ld` her hedef için yerleşik (built-in) bir linker script ile geliyordu; programcı bunu bilmek zorunda değildi. + +ELF (Executable and Linkable Format) 1989'da System V Release 4 ile geldi ve hızla Unix dünyasının standart format'ı oldu. Bugün hâlâ ARM, RISC-V, x86, PowerPC üzerinde hem hosted hem bare-metal sistemlerin neredeyse hepsi ELF üretir. ELF'in temel taşı **bölümlerdir (sections)**: kod, veri, sabitler, sembol tablosu, debug bilgisi — hepsi ayrı bölümlerde tutulur. Linker'ın görevi, birden çok nesne dosyasındaki aynı isme sahip bölümleri (örneğin tüm `.o` dosyalarının `.text` bölümlerini) birleştirip nihai bir ELF üretmektir. + +Bare-metal işin denklemini değiştirdi. Bir Cortex-M4'te ne dinamik loader var ne sayfalı bellek; FLASH ve SRAM ayrı fiziksel bloklarda, ayrı adres aralıklarında oturur. Başlangıç kodu (startup) yazmadan `main()`'e ulaşılamaz. Linker'a "şu bölümü şuraya koy, şunu FLASH'tan RAM'e kopyalamak için adres etiketleri üret" demek için varsayılan script yetmez. Bu noktada `.ld` dosyası programcının elinde olmak zorundadır. + +GNU LD'nin script dili 1990'larda olgunlaştı; bugün hâlâ aynı sözdizimi geçerlidir. LLVM'in `lld`'si büyük ölçüde aynı dili destekler, küçük farklarla. Yani bir bare-metal mühendisi için linker script okuma-yazma becerisi, otuz yıldan uzun ömürlü bir yatırımdır. + +--- + +## Bellek Modeli: FLASH ve SRAM Neden Ayrı? + +Modern bir Cortex-M tasarımı tipik olarak şu bellek bölgelerini içerir: + +- **FLASH** (NOR Flash): kod ve sabit veri için kalıcı, yazılması yavaş ve sınırlı çevrim sayısı olan, okuması hızlı bir alan. +- **SRAM** (Static RAM): okunabilen ve yazılabilen, hızlı, ama enerji kesilince içeriğini kaybeden ana iş belleği. +- **CCM/TCM** (Core-Coupled / Tightly-Coupled Memory): bazı Cortex-M4/M7 türevlerinde bulunan, CPU'ya doğrudan bağlı, daha düşük gecikmeli özel RAM. +- **Backup RAM**: VBAT pini ile beslenebilen, enerji kesilse de korunan küçük bir alan. + +Linker script'in birinci görevi, derleyiciye ve linker'a bu bellek bölgelerinin var olduğunu, hangi adres aralığında oturduklarını ve hangi izinlere (okuma / yazma / yürütme) sahip olduklarını söylemektir. + +
+graph LR + subgraph FLASH[FLASH 0x0800_0000 - 1 MB] + VTABLE[isr_vector] + TEXT[.text] + RODATA[.rodata] + DATA_LMA[.data load image] + end + subgraph SRAM[SRAM 0x2000_0000 - 128 KB] + DATA_VMA[.data runtime] + BSS[.bss] + HEAP[heap - büyür] + STACK[stack - küçülür] + end + DATA_LMA -. Reset_Handler kopyalar .-> DATA_VMA +
+ +Burada kritik nokta: `.data` bölümü hem FLASH'ta hem RAM'de **iki ayrı yerde** vardır. FLASH'taki kopya **load image**'dir; sıfırdan farklı başlangıç değerlerine sahip global değişkenlerin tohum (seed) verisini taşır. RAM'deki kopya ise çalışma zamanında erişilen "asıl" kopyadır. Linker bunu mümkün kılmak için **VMA (Virtual Memory Address)** ve **LMA (Load Memory Address)** ayrımını sunar. Bu ayrım, linker script'in en kafa karıştırıcı ama en güçlü kavramıdır. + +--- + +## MEMORY Bloğu — Bellek Bölgelerinin Tanımı + +Tipik bir STM32F4 hedefi için MEMORY bloğu şuna benzer: + +```ld +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K + CCM (rw) : ORIGIN = 0x10000000, LENGTH = 64K +} +``` + +Satır satır: + +- `FLASH (rx)`: isim ve izinler. `r` okuma, `w` yazma, `x` yürütme (executable). FLASH'a yazma izni vermemek bir hijyen tedbiridir; yanlışlıkla yazıma yönelik bir bölüm FLASH'a iliştirilirse linker uyarı verir. +- `ORIGIN`: bu bölgenin başladığı fiziksel adres. STM32F4'te FLASH 0x08000000'dan, SRAM 0x20000000'dan başlar. Bu adresler ARMv7-M bellek haritası tarafından kabaca, üretici tarafından kesin olarak tanımlanır. +- `LENGTH`: bölge uzunluğu. Linker, bir bölüm bölgeye sığmadığında **bağlama (link) zamanında** hata verir — sahada bunu sonradan keşfetmek istemezsiniz. + +`MEMORY` bloğu sadece linker'a "bu adres aralıkları var" der; içine ne konacağı `SECTIONS` ile belirlenir. + +--- + +## SECTIONS Bloğu — Bölümlerin Yerleştirilmesi + +`SECTIONS`, linker script'in kalbidir. Her **çıktı bölümünü** (output section) sırasıyla tanımlar; her çıktı bölümü, nesne dosyalarındaki bir veya daha fazla **girdi bölümünden** (input section) oluşur. + +### Vector Table — Mutlaka En Başta + +Cortex-M reset davranışını anlamak için iki adresi ezberlemek gerekir: + +- `0x00000000`: ilk Main Stack Pointer (MSP) değeri. +- `0x00000004`: Reset_Handler'ın adresi (thumb biti set, yani LSB=1). + +CPU resetlendiğinde önce bu iki kelimeyi okur, MSP'yi yükler, sonra Reset_Handler'a dallanır. Daha sonra **VTOR (Vector Table Offset Register)** üzerinden tablo başka bir adrese taşınabilir; ancak ilk anda CPU sabit bir başlangıç noktası bekler. Bu sabit adres Cortex-M0/M0+'ta donanımsal olarak 0x00000000'dır (VTOR yok). Cortex-M3/M4/M7'de varsayılan VTOR yine 0'ı işaret eder; üretici, bunu boot pini veya alias bir aralık ile (örn. STM32'de 0x00000000'ın 0x08000000'a alias olması) sağlar. + +Linker script'te bunu şöyle ifade ederiz: + +```ld +SECTIONS +{ + .isr_vector : + { + KEEP(*(.isr_vector)) + } > FLASH + ... +} +``` + +Burada dikkat edilecek üç şey var: + +1. **`KEEP(...)`**: linker'ın "ölü kod eliminasyonu" (`--gc-sections`) ile bu bölümü atmasını engeller. Vector table'a hiçbir C kodu doğrudan referans vermez; sadece donanım CPU resetinde okur. `KEEP` olmadan optimizasyon vector table'ı bütünüyle çöpe atabilir — sonra cihaz boot etmez ve siz iki gün uyumadan sebebini ararsınız. +2. **`*(.isr_vector)`**: bütün girdi nesnelerinden `.isr_vector` adlı bölümleri al. Bizim startup `.s` veya `.c` dosyamızda vector table bu isimle `__attribute__((section(".isr_vector")))` ile işaretlenmiştir. +3. **`> FLASH`**: bu çıktı bölümünün **VMA**'sı FLASH bölgesine düşsün. Vector table FLASH'ta yaşar, oradan okunur, taşınmaz. + +`.isr_vector`'un dosyanın başında yer alması zorunludur; çünkü FLASH'a yerleştirme sırasıyla yapılır ve CPU 0x08000000 / 0x00000000'da vector table bekler. + +### .text — Kod ve Salt-Okunur Veri + +```ld +.text : +{ + *(.text*) + *(.rodata*) + *(.glue_7) *(.glue_7t) + *(.eh_frame) + + KEEP(*(.init)) + KEEP(*(.fini)) + + . = ALIGN(4); + _etext = .; +} > FLASH +``` + +Önemli noktalar: + +- `*(.text*)`: tüm girdi nesnelerinden `.text` ile başlayan bütün bölümleri al. Yıldız (`*`) bir glob'tur; `.text.startup`, `.text.cold` gibi alt bölümler GCC `-ffunction-sections` ile kullanıldığında bu pattern ile yakalanır. Bu çok önemli, çünkü `-ffunction-sections` + `-Wl,--gc-sections` kombinasyonu ile çağrılmayan fonksiyonlar bağlama dışı bırakılır. +- `*(.rodata*)`: salt-okunur sabitler. `const int table[] = {...}` C kodu burada oturur. +- `*(.glue_7) *(.glue_7t)`: ARM/Thumb interworking için derleyicinin ürettiği geçiş kodu. Modern saf Thumb-2 hedeflerde (Cortex-M) genellikle boştur, ama linker tutarlılığı için yer ayırılır. +- `*(.eh_frame)`: C++ exception unwinding tabloları. Bare-metal projelerde tipik olarak `-fno-exceptions` ile devre dışı bırakılır, ama linker'a hatırlatmak iyi bir alışkanlıktır. +- `KEEP(*(.init))`, `KEEP(*(.fini))`: eski (sysv) constructor/destructor mekanizması. ARM EABI bunun yerine `.init_array` kullanır (aşağıda), ama uyumluluk için yine de tutulurlar. +- `. = ALIGN(4)`: konum sayacı (location counter, `.`) 4 byte sınırına hizalanır. ARM Cortex-M, kodun ve verinin doğal hizalı (aligned) olmasını bekler; aksi takdirde performans veya — bazı erişimlerde — UsageFault olur. +- `_etext = .`: bu satırın yarattığı `_etext` sembolü, `.text` bölümünün bittiği FLASH adresini gösterir. Bu sembol C koduna `extern uint32_t _etext;` olarak ihraç edilir ve startup'taki `.data` kopyalama döngüsünün kaynak adresi olarak kullanılır. + +### .ARM ve .init_array — Sessiz Ama Kritik + +```ld +.ARM.extab : { *(.ARM.extab*) } > FLASH +.ARM : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; +} > FLASH + +.preinit_array : +{ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array*)) + PROVIDE_HIDDEN (__preinit_array_end = .); +} > FLASH + +.init_array : +{ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); +} > FLASH + +.fini_array : +{ + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT(.fini_array.*))) + KEEP (*(.fini_array*)) + PROVIDE_HIDDEN (__fini_array_end = .); +} > FLASH +``` + +`.ARM.exidx`, exception index tablosu; ARM ABI tanımlı. Bare-metal'de C kullanıyor olsanız bile linker birkaç byte üretebilir. `__exidx_start` / `__exidx_end` sembolleri runtime'da gerekirse stack unwinding kütüphanelerinin aradığı sembollerdir. + +Asıl ilginç olan `.init_array`. ARM EABI, statik C++ constructor'larını eski `.init` mekanizması yerine `.init_array` ile çalıştırır. Newlib'in `__libc_init_array()` fonksiyonu, linker tarafından üretilen `__init_array_start` ve `__init_array_end` sembolleri arasındaki fonksiyon pointer'larını sırayla çağırır. + +`SORT(.init_array.*)` deyimi, GCC'nin `__attribute__((init_priority(N)))` ile farklı önceliklerde constructor'lar üretmesini destekler. Linker, isme göre sıralayarak deterministik bir çalışma sırası garanti eder. `KEEP` burada kritiktir; aksi halde `--gc-sections` constructor pointer dizisini "kullanılmıyor" sayar. + +C++ kullanmıyorsanız bile bu bölümlerin tanımlı olması gerekir; aksi halde `__libc_init_array()` tanımsız sembolle linker hatası verir. + +### .data — VMA ve LMA Hilesi + +İşte linker script'in en sık yanlış anlaşılan kısmı: + +```ld +.data : +{ + . = ALIGN(4); + _sdata = .; + *(.data*) + . = ALIGN(4); + _edata = .; +} > SRAM AT> FLASH + +_sidata = LOADADDR(.data); +``` + +Üç anahtar mekanizma: + +1. **`> SRAM`**: bu bölümün **VMA**'sı SRAM'dedir. `_sdata` ve `_edata` sembolleri SRAM adreslerini taşır; C kodu içinde global değişkenler bu adreslerde okunur/yazılır. +2. **`AT> FLASH`**: ama bölümün **LMA**'sı FLASH'tadır. Yani derlenmiş ELF'in load image'inde bu byte'lar FLASH bölgesine yerleştirilir; programlayıcı (flasher) onları FLASH'a yazar. +3. **`_sidata = LOADADDR(.data)`**: `LOADADDR` makrosu, bir bölümün LMA'sını döndürür. `_sidata` sembolü, FLASH'taki load image'in başlangıç adresini taşır. Startup kodu, bu adresten başlayıp `_sdata` ile `_edata` arasındaki SRAM'e byte-byte kopyalama yapar. + +Tipik startup kopyalama kodu şuna benzer: + +```c +extern uint32_t _sdata, _edata, _sidata; + +void Reset_Handler(void) { + /* .data: FLASH'tan SRAM'e kopyala */ + uint32_t *src = &_sidata; + uint32_t *dst = &_sdata; + while (dst < &_edata) { + *dst++ = *src++; + } + + /* .bss: sıfırla */ + extern uint32_t _sbss, _ebss; + dst = &_sbss; + while (dst < &_ebss) { + *dst++ = 0; + } + + SystemInit(); + __libc_init_array(); + main(); + while (1) { } +} +``` + +Bu kod, derleyicinin "her global değişkenin başlangıç değerini bilir" varsayımını koruyan görünmez sözleşmedir. C standardı şöyle der: `int counter = 42;` yazdığınızda, program `counter`'a ilk okuduğunda 42 görmek zorundadır. Bare-metal'de bunu garanti eden tek mekanizma, FLASH'tan RAM'e kopyalanan bu load image'dir. `.data` kopyalama döngüsünü startup kodundan çıkarmak, `.data` global değişkenlerinizin başlangıç değerini bozar; ve bu hata bazen aylar sonra, RAM'deki "rastgele" geçmiş değerlerden dolayı tutarsız davranış olarak ortaya çıkar. + +### .bss — Sıfır İle Başlatılan + +```ld +.bss : +{ + . = ALIGN(4); + _sbss = .; + __bss_start__ = _sbss; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; + __bss_end__ = _ebss; +} > SRAM +``` + +`.bss`, başlangıç değeri sıfır olan veya hiç başlatılmamış global / static değişkenleri tutar. ELF'te bu bölüm `NOBITS` tipindedir: load image'de **yer tutmaz**, sadece linker `_sbss`–`_ebss` boyutunu kaydeder. Startup kodu bu aralığı sıfırlar. Bu yüzden FLASH boyutu hesabına `.bss` girmez; ama RAM boyutu hesabına girer. + +`*(COMMON)` deyimi, eski K&R C ve bazı yabancı derleyicilerin `extern` tanımlamadan global değişken bildirimi yapmasından miras kalan bir konvansiyondur. Modern projede neredeyse hiç doluymaz; ama linker script'lerde alışkanlık olarak yer alır. + +### Heap ve Stack + +```ld +._user_heap_stack : +{ + . = ALIGN(8); + PROVIDE ( end = . ); + PROVIDE ( _end = . ); + . = . + _Min_Heap_Size; + . = . + _Min_Stack_Size; + . = ALIGN(8); +} > SRAM +``` + +Burada `_Min_Heap_Size` ve `_Min_Stack_Size` script'in başında veya komut satırından (`-Wl,--defsym=_Min_Stack_Size=0x800`) verilen sembollerdir. Konum sayacını bu kadar artırmak, linker'a bu kadar RAM yerinin **kullanılmış sayılmasını** söyler. Eğer `.data + .bss + heap + stack` toplamı SRAM uzunluğunu aşarsa linker bağlama hatası verir. + +`end` sembolü ise newlib'in `_sbrk()` syscall implementasyonu tarafından heap başlangıcı olarak kullanılır. + +Stack pointer ise script sonunda şöyle tanımlanır: + +```ld +_estack = ORIGIN(SRAM) + LENGTH(SRAM); +``` + +Bu sembol vector table'ın 0. girişine yazılır; CPU resetinde MSP buradan yüklenir. Stack RAM'in en üstünden başlayıp aşağı doğru büyür (full descending), heap ise aşağıdan yukarı doğru. İkisi ortada çakışırsa stack overflow yaşarsınız — ARM Cortex-M7 ve M33'te MPU + stack limit register (PSPLIM/MSPLIM) ile bunu donanım seviyesinde yakalamak mümkündür. + +--- + +## Map Dosyası Analizi — Gerçekten Ne Çıktı? + +Linker'a `-Wl,-Map=output.map` parametresi verdiğinizde, `arm-none-eabi-ld` insan tarafından okunabilir bir bağlama raporu üretir. Tipik bir map dosyasında şu bölümler vardır: + +- **Archive member included to satisfy reference**: hangi `.o` veya `.a` dosyalarının neden bağlandığı. +- **Discarded input sections**: `--gc-sections` ile elenen ölü kod. +- **Memory Configuration**: MEMORY bloğundan gelen bölgelerin özeti. +- **Linker script and memory map**: her çıktı bölümünün, girdi bölümlerinin, sembollerin adres ve boyut dökümü. + +Örnek bir parça: + +```text +.isr_vector 0x0000000008000000 0x190 + 0x0000000008000000 . = ALIGN (0x4) + *(.isr_vector) + .isr_vector 0x0000000008000000 0x190 build/startup_stm32f407xx.o + 0x0000000008000000 g_pfnVectors + +.text 0x0000000008000190 0x4ae0 + *(.text*) + .text.HAL_Init + 0x0000000008000190 0xa8 build/stm32f4xx_hal.o + .text.main + 0x0000000008000238 0x44 build/main.o + ... + 0x000000000800482a _etext = . + +.data 0x0000000020000000 0x4c load address 0x0000000008004830 + 0x0000000020000000 _sdata = . + *(.data*) + .data.SystemCoreClock + 0x0000000020000000 0x4 build/stm32f4xx_hal.o + 0x0000000020000004 SystemCoreClock + ... + 0x000000002000004c _edata = . + 0x0000000008004830 _sidata = LOADADDR (.data) + +.bss 0x000000002000004c 0x9b8 + 0x000000002000004c _sbss = . + 0x000000002000004c __bss_start__ = _sbss + ... + 0x0000000020000a04 _ebss = . +``` + +Burada doğrulanması gereken üç şey vardır: + +1. **`.isr_vector` 0x08000000'da mı?** Evet. Reset davranışı için zorunlu. +2. **`.data`'nın VMA'sı 0x20000000, LMA'sı 0x08004830 mu?** Evet — `load address 0x...` satırı LMA'yı gösteriyor. Yani 0x4c byte'lık başlangıç verisi FLASH'ta `_etext`'in hemen ardına yazılmış. +3. **`_sidata = LOADADDR(.data)` 0x08004830'a çözümlenmiş mi?** Evet. Startup kodu bu adresten okumaya başlayacak. + +Bu üç doğrulamayı her yeni bare-metal projede yapmak — özellikle bootloader / uygulama ayrımı, dual-bank FLASH veya iki ayrı SRAM bölgesi söz konusuysa — gözle görülür miktarda zaman kazandırır. Map dosyası, üretiminin ucuz olmasına rağmen genellikle yeterince incelenmez. + +`objdump` ile ek bir çapraz kontrol: + +```bash +$ arm-none-eabi-objdump -h build/firmware.elf + +Idx Name Size VMA LMA File off Algn + 0 .isr_vector 00000190 08000000 08000000 00010000 2**0 + 1 .text 00004aa0 08000190 08000190 00010190 2**3 + 2 .rodata 000003a8 08004c30 08004c30 00014c30 2**3 + 3 .ARM 00000008 08004fd8 08004fd8 00014fd8 2**2 + 4 .init_array 00000004 08004fe0 08004fe0 00014fe0 2**2 + 5 .fini_array 00000004 08004fe4 08004fe4 00014fe4 2**2 + 6 .data 0000004c 20000000 08004fe8 00020000 2**3 + 7 .bss 000009b8 2000004c 2000004c 00020004 2**2 +``` + +`.data` satırındaki VMA ≠ LMA, linker script'in işini doğru yaptığının asıl kanıtıdır. + +--- + +## Yaygın Tuzaklar + +### 1. KEEP unutulması ve `--gc-sections` + +`-Wl,--gc-sections` ile birlikte kullanıldığında, vector table veya `.init_array` `KEEP` olmadan bağlamadan tamamen silinebilir. Sonuç: cihaz reset attığında 0x0000_0004'ten okuduğu adres geçersizdir; HardFault. Bu hatayı oscilloskop ve debugger ile yakalamak günlerce sürebilir. + +### 2. `.data` LMA ile FLASH boyutu çakışması + +`firmware.bin` veya `firmware.hex` üretirken `objcopy` LMA'yı temel alır. `.text + .rodata + .data(LMA)` toplamının FLASH'a sığması gerekir. Bunu bir görüntü boyutu kontrolü ile (örn. `arm-none-eabi-size firmware.elf`) build sırasında her zaman doğrulayın. `--print-memory-usage` flag'i de linker'dan canlı bir özet verir. + +### 3. Alignment hatası + +Linker `.data` bölümünü 4 byte hizasında üretir, ama startup kopyalama döngüsü `uint32_t *` üzerinden yürür. `_sdata` ve `_edata`'nın 4'e bölünebilir adreslerde olduğunu garanti etmek için `. = ALIGN(4)` zorunludur; aksi takdirde son iterasyonda 2 byte fazladan yazıp `.bss`'in başını bozarsınız. Cortex-M0 ve M0+'ta yanlış hizalı 32-bit erişim ayrıca HardFault doğurur. + +### 4. Constructor'ların çalıştırılmaması + +Eğer startup kodu `__libc_init_array()` çağrılmıyorsa veya linker script `.init_array` bölümünü ihtiyacı olduğu sembollerle (`__init_array_start`, `__init_array_end`) ihraç etmiyorsa, C++ global nesnelerin constructor'ları çalıştırılmaz. Sonuç: `static MyClass obj;` ile yazdığınız sınıfın iç durumu (state) tamamen tanımsızdır. Çoğu zaman bu hata "obje sıfırlanmış görünüyor ama metodlar çağrılınca null pointer dereference" olarak ortaya çıkar. + +### 5. Heap büyümesi ile stack çakışması + +Klasik bir hata: dinamik tahsis (örn. `malloc`) ile heap RAM'in ortasına doğru büyürken, derin özyinelemeli bir fonksiyon stack'i aşağıya iter; ikisi ortada çakışır. Yığın taşmasını (stack overflow) erken yakalamak için: + +- Cortex-M7 / M33 / M55 için **MSPLIM** ve **PSPLIM** stack limit register'larını kullanın. +- Veya MPU ile stack alanının altına bir koruyucu (guard) bölge tanımlayın. +- Veya statik analiz (örn. `gcc -fstack-usage` + bir post-process script) ile maksimum stack derinliğini çıkarın. + +Linker script'in `_estack` ve heap/stack region tanımı, bu denetimlerin temelidir. + +--- + +## Pratik Tavsiyeler + +Çok yıllık saha deneyiminden çıkardığım bazı kısa kurallar: + +- **Linker script'i sürüm kontrolüne alın.** CubeMX gibi araçların ürettiği script'i kontrolsüz biçimde yeniden ürettirirseniz, elinizle yaptığınız özelleştirmeler — bootloader entegrasyonu, özel section'lar, ENVM kullanımı — bir buton tıklamasıyla silinir. +- **`--print-memory-usage` veya `arm-none-eabi-size` çıktısını CI'a sokun.** FLASH ve SRAM kullanım yüzdelerinin yüksek olduğunda erken uyarı veren bir kontrol, sahada "image FLASH'a sığmadı" sürprizini önler. +- **`KEEP` listesine `.isr_vector`, `.init_array`, `.fini_array`, `.preinit_array`'ı her zaman koyun.** +- **Her custom section için `__section_start__` ve `__section_end__` sembolü ihraç edin.** Sonradan bir runtime check (örn. CRC) yazmak istediğinizde bu sembollere ihtiyacınız olur. +- **Map dosyasını release notlarına ekleyin.** Bir sürümde "şu RAM'in 4 byte fazlasının nereden geldiği" sorusu sahaya çıktıktan sonra cevaplanması gereken bir soru oluyor; geçmiş map dosyaları diff almak için altın değerindedir. +- **Dual-bank FLASH'lı parçalarda iki ayrı bölge tanımlayın.** Tek bir mantıksal FLASH gibi davranmak A/B sürüm geçişlerini imkânsız hale getirir. + +--- + +## Sonuç + +Linker script, bare-metal ARM firmware'ının görünmez omurgasıdır. Reset anında CPU'nun nereden başlayacağını, global değişkenlerin değerini nereden aldığını, constructor'ların hangi sırayla çalışacağını, heap'in ne kadar büyüyebileceğini hep bu dosya belirler. Onu bir kutusu içinde anlamadan kullanmak mümkün; ama hata ayıklarken oradan dönerken, kara kutudan parlak bir el feneri çıkmasını ummak iyimserliktir. + +Bu yazıda satır satır gezdiğimiz `.ld` dosyası, gerçek bir Cortex-M4 STM32F4 projesindeki tipik yapıdan damıtıldı. CMSIS şablonlarındaki, STM32CubeIDE'nin ürettiklerindeki ve Zephyr / NuttX gibi RTOS'ların kendi linker script'lerindeki temel yapı taşları aynıdır; isim farkları, attribute kullanımı ve özel section yapıları değişir. Bir kez bu temel yapıyı sindirdikten sonra, hangi araç hangi script'i üretirse üretsin, ne yapıldığını rahatlıkla okuyabilirsiniz. + +Bir sonraki adım olarak şu üç egzersizi öneririm: (1) bir Cortex-M4 projesinde derleme sonrası `objdump -h` çıktısı ile script'inizi karşılaştırın; (2) `.bss`'i tekrar başlatma sırasını başlangıç kodunda iptal edin ve bir global int değişkenin değerini gözlemleyin (rastgele olmalı); (3) `.data` kopyalama döngüsünü kaldırın ve `int counter = 42;` ile başlattığınız bir global değişkenin ilk değerini RTT veya UART ile yazdırın — büyük olasılıkla 42 olmayacaktır. + +Linker, sessizdir; ama yaptığı işten haberdar olmadığınızda, sürpriz yapar. + +--- + +## Kaynaklar + +- [GNU LD (binutils) Documentation — Linker Scripts](https://sourceware.org/binutils/docs/ld/Scripts.html) +- [System V Application Binary Interface — Generic ABI](https://refspecs.linuxfoundation.org/elf/gabi4+/contents.html) +- [ARM Cortex-M4 Generic User Guide — Vector Table (ARM DUI 0553)](https://developer.arm.com/documentation/dui0553/latest/) +- [ARM v7-M Architecture Reference Manual (ARM DDI 0403)](https://developer.arm.com/documentation/ddi0403/latest/) +- [Newlib Source — `libc/misc/init.c` (`__libc_init_array` implementation)](https://sourceware.org/newlib/) +- [Embedded Artistry — Exploring Startup Implementations: Newlib (ARM)](https://embeddedartistry.com/blog/2019/04/17/exploring-startup-implementations-newlib-arm/) +- [Interrupt by Memfault — From Zero to main(): Demystifying Firmware Linker Scripts](https://interrupt.memfault.com/blog/how-to-write-linker-scripts-for-firmware) +- [Stargirl (Thea) Flowers — The Most Thoroughly Commented Linker Script](https://blog.thea.codes/the-most-thoroughly-commented-linker-script/) +- [ARM EABI — Run-time ABI for the ARM Architecture (IHI 0043)](https://developer.arm.com/documentation/ihi0043/latest/) +- [CMSIS-Core (M) Reference Implementation](https://arm-software.github.io/CMSIS_6/latest/Core/index.html) diff --git a/agent/topics.md b/agent/topics.md index 8a82ba14..7d59654d 100644 --- a/agent/topics.md +++ b/agent/topics.md @@ -22,11 +22,14 @@ - [x] Ölçüm Belirsizliği (GUM Annex F + NCSLI RP-12) — 2026-05-06 — alan: metroloji - [x] Kalibrasyon Zincirinin Tepesi (Birincil Standartlar) — 2026-05-07 — alan: metroloji - [x] Renode ile Zynq7000 Simülasyonu — 2026-05-14 — alan: gömülü/SoC +- [x] Bandpass Sampling: 1 GHz Sinyali 50 MHz Saatle Örneklemek — 2026-05-21 — alan: RF/DSP ## Açık PR'lar (insan inceleme bekleniyor) | PR # | Başlık | Dal | Açılış | Alan | |------|--------|-----|--------|------| +| [#89](https://github.com/mavrikant/mavrikant.github.io/pull/89) | Watchdog Timer Tasarım Desenleri | post/2026-05-24-watchdog-tasarim-desenleri | 2026-05-24 | güvenilirlik | +| [#88](https://github.com/mavrikant/mavrikant.github.io/pull/88) | WCET Analizi: Statik mi, Ölçüm mü, Hibrit mi? | post/2026-05-23-wcet-analizi-statik-olcum-hibrit | 2026-05-23 | gerçek zamanlı | | [#79](https://github.com/mavrikant/mavrikant.github.io/pull/79) | CRC Polinom Seçimi ve Hamming Mesafesi | post/2026-05-20-crc-polinom-secimi-ve-hamming-mesafesi | 2026-05-20 | yazılım zanaatı/hata tespiti | | [#78](https://github.com/mavrikant/mavrikant.github.io/pull/78) | VOR Nasıl Çalışır? 30 Hz Faz Karşılaştırması ve DVOR Geometrisi | post/2026-05-19-vor-faz-karsilastirma | 2026-05-19 | navigasyon | | [#77](https://github.com/mavrikant/mavrikant.github.io/pull/77) | MC/DC Kapsama — DO-178C DAL A | post/2026-05-18-mcdc-kapsama-do-178c-dal-a | 2026-05-17 | sertifikasyon | @@ -39,14 +42,14 @@ ## Seçildi / Devam Eden -- **Bandpass Sampling: 1 GHz Sinyali 50 MHz Saatle Örneklemek** — - dal: `post/2026-05-21-bandpass-sampling`, - dosya: `_posts/2026-05-21-bandpass-sampling.md`, - durum: PR açılacak (bu çalıştırma) — alan: RF/DSP. +- **Linker Script Anatomisi: ARM Bare-Metal için Bir `.ld` Dosyası Satır Satır** — + dal: `post/2026-05-26-linker-script-anatomisi-arm-bare-metal`, + dosya: `_posts/2026-05-26-linker-script-anatomisi-arm-bare-metal.md`, + durum: PR açılacak (bu çalıştırma) — alan: toolchain/build. ## Reddedildi (bu çalıştırma) -- _(bu çalıştırmada konu reddedilmedi; bandpass sampling havuzdan seçildi.)_ +- _(bu çalıştırmada konu reddedilmedi; linker script havuzdan seçildi.)_ ## Fikir Havuzu (aday konular — gelecek çalıştırma için) @@ -57,26 +60,14 @@ geçici olarak karşılıyor. Faz 2'de tekrar değerlendirilmesi gerekir. - [ ] **ARM Cortex-A reset vektöründen `main()`'e: gerçekten ne oluyor?** — alan: gömülü/SoC — Renode yazısının doğal devamı, somut deney imkânı -- [ ] **MC/DC kapsama: DO-178C DAL A'da neden modified condition/decision şart?** — - alan: sertifikasyon — gerçek karar tablosu örneği, decision/condition farkı -- [ ] **CRC vs checksum: neden CRC-32 değil de CRC-32C / CRC-16-CCITT seçilir?** — - alan: yazılım zanaatı — polinom seçimi, hata tespit gücü, bit-hata analizi -- [ ] **WCET analizi: statik analiz vs ölçüm tabanlı yaklaşımlar, cache etkileri** — - alan: gerçek zamanlı — somut örnek (örn. Cortex-R5 üzerinde basit görev) - [ ] **IQ örnekleme ve karmaşık sinyaller: gerçek SDR'ye giriş** — alan: RF/SDR — neden negatif frekans, neden 2 kanal - [ ] **GIC (Generic Interrupt Controller): SGI/PPI/SPI farkları ve önceliklendirme** — alan: ARM — kesme yönlendirme, multicore'da CPU affinity - [ ] **Cache coherency ve MESI: ARM'da CCI/CMN ne yapar, neden yazılım perde (barrier) gerekir?** — alan: ARM — pratik race condition örneği -- [ ] **Linker script anatomisi: ARM bare-metal için bir `.ld` dosyası satır satır** — - alan: gömülü — kendi linker script'i yazma rehberi -- [ ] **Watchdog tasarım desenleri: tek vs çoklu görev watchdog, deadman switch, - windowed watchdog** — alan: güvenilirlik — gerçek tasarım kararları - [ ] **`volatile`'ın doğru kullanımı: nerede yetmez, neden `_Atomic` gerekir?** — alan: C/eşzamanlılık — derleyici çıktı analizi -- [ ] **VOR'un çalışma prensibi: 30 Hz referans + değişken faz nasıl yön verir?** — - alan: navigasyon — faz farkı matematiği + sinyal şeması - [ ] **ILS anatomisi: localizer 90/150 Hz DDM ve glide slope** — alan: navigasyon — modülasyon derinliği farkı + örnek hesap - [ ] **Kalman filtresi tuzakları: numerik stabilite, gözlemlenebilirlik, tuning** — @@ -108,21 +99,25 @@ geçici olarak karşılıyor. Faz 2'de tekrar değerlendirilmesi gerekir. - [ ] DO-254 donanım sertifikasyonu (yazarın uzmanlığı ağırlıklı yazılım tarafında) - [ ] İzlenebilirlik matrisi (klasik konu, derinlik çıkarmak zor) -## Notlar (bu çalıştırma — 2026-05-21) - -- **Bandpass Sampling** seçildi (alan: RF/DSP). Önceki çalıştırmaların ardından - açılan PR'lar son üç alt-alanı (sertifikasyon #77, navigasyon #78, yazılım - zanaatı/CRC #79) işaretlemişti; bu yazı **bu üç alandan da** son yayınlanan 3 - posttan da (Renode gömülü/SoC, kalibrasyon ×2) farklı bir alan getiriyor. -- Yayın kapısı durumu: Bölüm 4 yalnızca "yayın PR ile olmalı" kuralı koyar; backlog - büyüklüğüne dair sert bir sınır yoktur. Açık 7 PR olmasına rağmen son yayınlanan - yazıdan (Renode, 2026-05-14) bu yana 7 gün geçti — `min_yayin_araligi_gun = 2` - şartı fazlasıyla sağlanmış durumda. Bu çalıştırmada yeni PR açıldı. -- Bandpass sampling konusunun "neden Türkçe içerikte zor bulunuyor" yanıtı: - matematik (Vaughan 1991), datasheet okuma (analog input BW), saat phase noise - ve filtre tasarımı disiplinlerinin kesişiminde bulunuyor; Türkçe kaynaklar - genellikle yalnızca tek bir cepheden ele almış oluyor (genelde Lyons özet - çevirisi). Sentez ve somut sayısal örnek boşluğu büyük. -- Açık PR'lar konusunda inceleme önceliği yorumu (gözlem): #50 ve #51 hâlâ uzun - süredir bekliyor; #50 eski yazıyı genişletiyor, #51 ise yayındaki MISRA C:2025 - ile büyük olasılıkla çakışıyor. İnceleyen kişinin dikkatine. +## Notlar (bu çalıştırma — 2026-05-26) + +- **Linker Script Anatomisi** seçildi (alan: toolchain/build). Son 3 yayınlanan + yazı (RF/DSP, gömülü/SoC, metroloji) ve tüm açık PR alt-alanları + (navigation, sertifikasyon, software-craft, real-time, reliability, + security, C/compiler, C/standard, embedded/numeric) ile çakışmıyor. Yazı, + toolchain/build perspektifinden bare-metal startup zincirini açıklıyor — + Renode yazısı simülasyon, bu yazı bağlama; konu örtüşmesi yok. +- Yayın kapısı durumu: son yayın 2026-05-21 (bandpass-sampling), bu çalıştırma + 2026-05-26. Aradan 5 gün geçti — `min_yayin_araligi_gun = 2` fazlasıyla + sağlanıyor. Bu çalıştırmada yeni PR açıldı. +- **"Neden Türkçe içerikte zor bulunuyor"** yanıtı: GNU LD manuel referans- + kılavuz tarzı ve İngilizce; ARM Cortex-M startup akışı + ELF + VMA/LMA + ayrımının üçünü birlikte sentezleyen Türkçe kaynak nadir. CMSIS şablon + `.ld` dosyaları satır-satır yorumlanmamış. Bu yazı boşluğu kapatır. +- Derinlik öğesi: gerçek bir Cortex-M4 (.ld) iskeleti satır satır + `.map` + dosyası dökümü + `arm-none-eabi-objdump -h` çıktısının analizi (bellek/ + assembly incelemesi — Bölüm 7). +- Açık PR'lar konusunda inceleme önceliği yorumu (gözlem değişmedi): #50 ve + #51 hâlâ uzun süredir bekliyor; #50 eski yazıyı genişletiyor, #51 ise + yayındaki MISRA C:2025 ile büyük olasılıkla çakışıyor. Bu çalıştırmada + ayrıca #88 (WCET) ve #89 (watchdog) tabloya eklendi. From 1da35bc94b66106d5b42f00dfdec71a746dea269 Mon Sep 17 00:00:00 2001 From: "M. Serdar Karaman" Date: Tue, 26 May 2026 12:37:23 +0300 Subject: [PATCH 2/2] =?UTF-8?q?post:=20Zynq-7000=20referans=C4=B1yla=20yen?= =?UTF-8?q?iden=20yaz=C4=B1ld=C4=B1;=20CCM=20yerine=20OCM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit İnsan geri bildirimi üzerine yazı tamamen Zynq-7000 (Cortex-A9 + OCM + DDR3 + QSPI) referansıyla yeniden yazıldı; STM32F4/Cortex-M4 örneklerinin yerini Renode yazısının doğal devamı olacak şekilde Zynq mimarisi aldı. Değişiklikler: - Bellek modeli: FLASH/SRAM/CCM yerine DDR3/OCM/QSPI. OCM_CFG remap mekaniği (BootROM 0x18 vs U-Boot 0x1F) açıklandı. - MEMORY bloğu: ps7_ddr_0, ps7_ocm_low_0, ps7_ocm_high_0, ps7_qspi_linear. - Vector table: Cortex-A9 8-entry instruction (LDR pc, =Handler), VBAR register (CP15 c12 c0 0), VINITHI semantiği. - VMA/LMA: .ocm_data örneği — OCM'de VMA, DDR'da LMA, LOADADDR() ile startup kopyalama. - Stack/heap: AAPCS 16-byte hizalama, Cortex-A9'un her CPU modu için ayrı SP kurulumu (SVC/IRQ/FIQ/Abort/Undef/System). - Pitfall'lar: OCM remap yanılgısı, çift çekirdek DDR bölme (AMP), MMU guard sayfa ile stack overflow yakalama. - Map/objdump çıktıları: Zynq adres haritasına göre güncellendi. - Kaynaklar: UG585, Cortex-A9 TRM (DDI 0388), ARMv7-A ARM (DDI 0406), CMSIS-Core (A) VBAR, Xilinx embeddedsw lscript.ld eklendi. Yazı 3000 → 3839 kelime; 9 H2 bölüm; Mermaid bellek haritası Zynq QSPI/OCM/DDR akışına uyarlandı. Co-Authored-By: Claude Opus 4.7 --- ...-linker-script-anatomisi-arm-bare-metal.md | 366 ++++++++++-------- agent/topics.md | 15 +- 2 files changed, 223 insertions(+), 158 deletions(-) diff --git a/_posts/2026-05-26-linker-script-anatomisi-arm-bare-metal.md b/_posts/2026-05-26-linker-script-anatomisi-arm-bare-metal.md index 23bd0e86..aea71bb8 100644 --- a/_posts/2026-05-26-linker-script-anatomisi-arm-bare-metal.md +++ b/_posts/2026-05-26-linker-script-anatomisi-arm-bare-metal.md @@ -1,6 +1,6 @@ --- -title: "Linker Script Anatomisi: ARM Bare-Metal için Bir `.ld` Dosyası Satır Satır" -subtitle: "Anatomy of a Linker Script: Walking Through an ARM Bare-Metal `.ld` File" +title: "Linker Script Anatomisi: Zynq-7000 Bare-Metal için Bir `.ld` Dosyası Satır Satır" +subtitle: "Anatomy of a Linker Script: Walking Through a Zynq-7000 Bare-Metal `.ld` File" background: "/img/posts/8.webp" date: '2026-05-26 09:00:00' layout: post @@ -8,11 +8,13 @@ lang: tr mermaid: true --- -Bare-metal bir ARM Cortex-M projesinde derleme zincirini takip ettiğinizde, kodun `main()`'e ulaşmadan önce geçtiği üç temel adım vardır: derleyici, derleyici (assembler) ve **linker**. İlk ikisi okul müfredatında genellikle kabaca anlatılır; üçüncüsü, yani linker ve onu yönlendiren `.ld` dosyası, çoğu mühendis için bir kutsal metindir — değiştirilmez, anlaşılmaz, "STM32CubeMX zaten üretiyor" denilip geçilir. +Bir önceki yazıda **Renode** üzerinde Zynq-7000'i simüle etmiştik. Simülatöre bir ELF verirken arka planda görünmez bir el, kodu ve veriyi DDR'a, vector table'ı OCM'e, başlangıç değerlerini doğru bölgeye yerleştiriyor. O görünmez el, **linker script**'tir. Onu anlamadığınızda Renode size yardımcı olmaz; "şu adres neden buraya geldi" sorusu cevapsız kalır. -Oysa linker script, ürününüzün belleği nasıl tükettiğini, hangi değişkenin nereye yerleştiğini, FLASH'taki sabit verilerin RAM'e ne zaman ve nasıl kopyalandığını, kesme vektör tablosunun nerede oturduğunu ve C++ kullanıyorsanız global nesnelerin constructor'larının ne zaman çağrıldığını belirleyen tek dosyadır. Onu anlamadan bare-metal hata ayıklamak, gözleri kapalı el yordamıyla yürümeye benzer. +Bare-metal Zynq-7000 (PS tarafı, çift çekirdek Cortex-A9) projesinde derleme zincirini takip ederseniz, kodun `main()`'e ulaşmadan önce geçtiği üç temel adım vardır: derleyici, derleyici (assembler) ve linker. Linker ve onu yönlendiren `.ld` dosyası, çoğu mühendis için kutsal metindir — değiştirilmez, anlaşılmaz, "Vitis zaten üretiyor" denilip geçilir. -Bu yazıda gerçek bir Cortex-M4 (STM32F4 ailesi) için yazılmış bir `.ld` dosyasını **satır satır** açacağız. Sonra `arm-none-eabi-ld`'nin ürettiği `.map` dosyasından örnek satırlara bakacak, `objdump` ile bellek yerleşimini doğrulayacağız. Hedef, "şu satır şu işi yapar" düzeyinden öteye geçip neden bu yapıya ihtiyaç duyulduğunu anlamak. +Oysa linker script, ürününüzün belleği nasıl tükettiğini, hangi değişkenin nereye yerleştiğini, hangi sabit verinin OCM'de hangisinin DDR'da oturduğunu, FSBL'in uygulamanızı nereye yüklediğini, kesme vektör tablosunun hangi adresten okunduğunu ve C++ kullanıyorsanız global nesnelerin constructor'larının ne zaman çağrıldığını belirleyen tek dosyadır. Onu anlamadan bare-metal hata ayıklamak, gözleri kapalı el yordamıyla yürümeye benzer. + +Bu yazıda Xilinx (artık AMD) Vitis'in standalone BSP'si için ürettiği tarz bir Zynq-7000 `.ld` dosyasını **satır satır** açacağız. Sonra `arm-none-eabi-ld`'nin ürettiği `.map` dosyasından örnek satırlara bakacak, `objdump` ile bellek yerleşimini doğrulayacağız. Hedef, "şu satır şu işi yapar" düzeyinden öteye geçip neden bu yapıya ihtiyaç duyulduğunu anlamak. --- @@ -20,64 +22,74 @@ Bu yazıda gerçek bir Cortex-M4 (STM32F4 ailesi) için yazılmış bir `.ld` do GNU `ld`, 1988 yılında Cygnus / Free Software Foundation tarafından GNU Binutils paketinin bir parçası olarak yazıldı. O dönemde Unix platformları için tasarlanmıştı: COFF, ECOFF, a.out gibi nesne formatlarını destekliyor, hedef genellikle bir Unix işletim sistemiydi ve linker script — varsayılan olarak — kullanıcı tarafından yazılmıyordu. `ld` her hedef için yerleşik (built-in) bir linker script ile geliyordu; programcı bunu bilmek zorunda değildi. -ELF (Executable and Linkable Format) 1989'da System V Release 4 ile geldi ve hızla Unix dünyasının standart format'ı oldu. Bugün hâlâ ARM, RISC-V, x86, PowerPC üzerinde hem hosted hem bare-metal sistemlerin neredeyse hepsi ELF üretir. ELF'in temel taşı **bölümlerdir (sections)**: kod, veri, sabitler, sembol tablosu, debug bilgisi — hepsi ayrı bölümlerde tutulur. Linker'ın görevi, birden çok nesne dosyasındaki aynı isme sahip bölümleri (örneğin tüm `.o` dosyalarının `.text` bölümlerini) birleştirip nihai bir ELF üretmektir. +ELF (Executable and Linkable Format) 1989'da System V Release 4 ile geldi ve hızla Unix dünyasının standart formatı oldu. Bugün hâlâ ARM, RISC-V, x86, PowerPC üzerinde hem hosted hem bare-metal sistemlerin neredeyse hepsi ELF üretir. ELF'in temel taşı **bölümlerdir (sections)**: kod, veri, sabitler, sembol tablosu, debug bilgisi — hepsi ayrı bölümlerde tutulur. Linker'ın görevi, birden çok nesne dosyasındaki aynı isme sahip bölümleri (örneğin tüm `.o` dosyalarının `.text` bölümlerini) birleştirip nihai bir ELF üretmektir. -Bare-metal işin denklemini değiştirdi. Bir Cortex-M4'te ne dinamik loader var ne sayfalı bellek; FLASH ve SRAM ayrı fiziksel bloklarda, ayrı adres aralıklarında oturur. Başlangıç kodu (startup) yazmadan `main()`'e ulaşılamaz. Linker'a "şu bölümü şuraya koy, şunu FLASH'tan RAM'e kopyalamak için adres etiketleri üret" demek için varsayılan script yetmez. Bu noktada `.ld` dosyası programcının elinde olmak zorundadır. +Bare-metal işin denklemini değiştirdi. Bir Zynq-7000'de ne işletim sistemi var ne dinamik loader; DDR3, OCM ve QSPI Flash ayrı fiziksel kaynaklarda, ayrı adres aralıklarında oturur. Boot zinciri ROM → FSBL → uygulama olarak ilerler. Başlangıç kodu (`_boot` ya da `Reset_Handler`) yazmadan `main()`'e ulaşılamaz. Linker'a "şu bölümü şuraya koy, şunu DDR'da yer ayır ama load image'de QSPI'a yaz" demek için varsayılan script yetmez. Bu noktada `.ld` dosyası programcının elinde olmak zorundadır. GNU LD'nin script dili 1990'larda olgunlaştı; bugün hâlâ aynı sözdizimi geçerlidir. LLVM'in `lld`'si büyük ölçüde aynı dili destekler, küçük farklarla. Yani bir bare-metal mühendisi için linker script okuma-yazma becerisi, otuz yıldan uzun ömürlü bir yatırımdır. --- -## Bellek Modeli: FLASH ve SRAM Neden Ayrı? +## Zynq-7000 Bellek Modeli: DDR, OCM ve QSPI Neden Ayrı? -Modern bir Cortex-M tasarımı tipik olarak şu bellek bölgelerini içerir: +Bir Zynq-7000 PS tarafı, linker'ın bilmesi gereken birkaç fiziksel bellek bölgesini taşır: -- **FLASH** (NOR Flash): kod ve sabit veri için kalıcı, yazılması yavaş ve sınırlı çevrim sayısı olan, okuması hızlı bir alan. -- **SRAM** (Static RAM): okunabilen ve yazılabilen, hızlı, ama enerji kesilince içeriğini kaybeden ana iş belleği. -- **CCM/TCM** (Core-Coupled / Tightly-Coupled Memory): bazı Cortex-M4/M7 türevlerinde bulunan, CPU'ya doğrudan bağlı, daha düşük gecikmeli özel RAM. -- **Backup RAM**: VBAT pini ile beslenebilen, enerji kesilse de korunan küçük bir alan. +- **DDR3**: harici DDR3 SDRAM, PS'in DDR denetleyicisi aracılığıyla erişilir. Tipik 512 MB – 1 GB boyutunda; başlangıç adresi `0x0000_0000`. Hızlı, büyük ama enerji kesilince içeriğini kaybeder. +- **OCM (On-Chip Memory)**: SoC içinde, çekirdeklere düşük gecikmeyle bağlı **256 KB** SRAM. Dört adet 64 KB banktan oluşur. Adres haritasında nereye düşeceği `slcr.OCM_CFG[RAM_HI]` 4-bit register'ı ile bank-bank ayarlanır: her bank ya düşük aralığa (`0x0000_0000`–`0x0003_FFFF`) ya da yüksek aralığa (`0xFFFC_0000`–`0xFFFF_FFFF`) eşlenir. BootROM çıkışında OCM_CFG = `0x18` olur: ilk üç bank düşükte (192 KB, `0x0000_0000`–`0x0002_FFFF`), son bank yüksekte (64 KB, `0xFFFF_0000`–`0xFFFF_FFFF`). U-Boot çoğu zaman OCM_CFG = `0x1F` yapar: dört bankı da yüksek aralığa yığar (`0xFFFC_0000`–`0xFFFF_FFFF`). +- **QSPI Flash**: linear-addressed modda `0xFC00_0000`'dan itibaren 16 MB'lık bir pencereden okunabilir. Kalıcı, ama yazılması yavaş ve sınırlı çevrim sayılı. +- **MIO/Periphery**: register'lar `0xE000_0000`'dan itibaren bir blok; linker script açısından "yer kaplamayan" ama PROVIDE edilen sembollerde geçebilir. Linker script'in birinci görevi, derleyiciye ve linker'a bu bellek bölgelerinin var olduğunu, hangi adres aralığında oturduklarını ve hangi izinlere (okuma / yazma / yürütme) sahip olduklarını söylemektir.
graph LR - subgraph FLASH[FLASH 0x0800_0000 - 1 MB] - VTABLE[isr_vector] + subgraph QSPI[QSPI Flash 0xFC00_0000 - 16 MB] + BOOTHDR[Boot Image: BOOT.bin - FSBL + bitstream + app] + end + subgraph OCM_LOW[OCM low 0x0000_0000 - 192 KB] + VECT[Vector Table - opsiyonel] + FSBL[FSBL kodu - boot sırasında] + end + subgraph OCM_HIGH[OCM high 0xFFFF_0000 - 64 KB] + VECT2[Vector Table - VINITHI=1 ise] + SMALL[Düşük-gecikmeli sabitler] + end + subgraph DDR[DDR3 0x0010_0000 - 1 GB] TEXT[.text] RODATA[.rodata] - DATA_LMA[.data load image] - end - subgraph SRAM[SRAM 0x2000_0000 - 128 KB] - DATA_VMA[.data runtime] + DATA[.data] BSS[.bss] HEAP[heap - büyür] STACK[stack - küçülür] end - DATA_LMA -. Reset_Handler kopyalar .-> DATA_VMA + BOOTHDR -. BootROM kopyalar .-> FSBL + FSBL -. uygulamayı DDR'a yükler .-> TEXT
-Burada kritik nokta: `.data` bölümü hem FLASH'ta hem RAM'de **iki ayrı yerde** vardır. FLASH'taki kopya **load image**'dir; sıfırdan farklı başlangıç değerlerine sahip global değişkenlerin tohum (seed) verisini taşır. RAM'deki kopya ise çalışma zamanında erişilen "asıl" kopyadır. Linker bunu mümkün kılmak için **VMA (Virtual Memory Address)** ve **LMA (Load Memory Address)** ayrımını sunar. Bu ayrım, linker script'in en kafa karıştırıcı ama en güçlü kavramıdır. +Burada kritik nokta: bare-metal bir Zynq uygulaması, ayağa kalkış senaryosuna göre **iki farklı LMA/VMA dengesine** sahip olabilir. Eğer ELF'i doğrudan JTAG ile DDR'a yüklüyorsanız, çoğu bölüm için LMA = VMA = DDR adresi olur. Ama gerçek üründe BOOT.bin'den boot ediyorsanız, FSBL ELF'inizi QSPI'dan okuyup DDR'a kopyalar; o senaryoda script açısından yine LMA = VMA (DDR), ama _yeniden lokasyona kaldırılan_ küçük bir tablo (örn. OCM'deki vector veya kritik fonksiyon) için LMA ≠ VMA tanımlanır. Bu ayrım, linker script'in en güçlü ama en yanlış anlaşılan kavramıdır. --- ## MEMORY Bloğu — Bellek Bölgelerinin Tanımı -Tipik bir STM32F4 hedefi için MEMORY bloğu şuna benzer: +Tipik bir Zynq-7000 standalone hedefi için MEMORY bloğu şuna benzer: ```ld MEMORY { - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K - SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K - CCM (rw) : ORIGIN = 0x10000000, LENGTH = 64K + ps7_ddr_0 (rwx) : ORIGIN = 0x00100000, LENGTH = 0x1FF00000 + ps7_ocm_low_0 (rwx) : ORIGIN = 0x00000000, LENGTH = 0x00030000 + ps7_ocm_high_0 (rwx) : ORIGIN = 0xFFFF0000, LENGTH = 0x0000FE00 + ps7_qspi_linear (rx) : ORIGIN = 0xFC000000, LENGTH = 0x01000000 } ``` Satır satır: -- `FLASH (rx)`: isim ve izinler. `r` okuma, `w` yazma, `x` yürütme (executable). FLASH'a yazma izni vermemek bir hijyen tedbiridir; yanlışlıkla yazıma yönelik bir bölüm FLASH'a iliştirilirse linker uyarı verir. -- `ORIGIN`: bu bölgenin başladığı fiziksel adres. STM32F4'te FLASH 0x08000000'dan, SRAM 0x20000000'dan başlar. Bu adresler ARMv7-M bellek haritası tarafından kabaca, üretici tarafından kesin olarak tanımlanır. -- `LENGTH`: bölge uzunluğu. Linker, bir bölüm bölgeye sığmadığında **bağlama (link) zamanında** hata verir — sahada bunu sonradan keşfetmek istemezsiniz. +- `ps7_ddr_0 (rwx)`: dış DDR3. `r` okuma, `w` yazma, `x` yürütme. Tipik 1 GB; başlangıç adresi `0x0000_0000`, fakat ilk 1 MB FSBL tarafından kullanıldığı için kullanıcı bölgesi `0x0010_0000`'dan başlatılır. Vitis BSP'sinin ürettiği script'te bu pencere `ps7_ddr_0_MEM_0` adıyla geçer; biz kısaltıyoruz. +- `ps7_ocm_low_0 (rwx)`: OCM'in düşük adres aralığına eşlenmiş 192 KB bölgesi. BootROM OCM_CFG=0x18 ayarı ile bu pencere mevcuttur. FSBL bu pencerede yaşar ve uygulamayı DDR'a yükledikten sonra kontrolü devreder. +- `ps7_ocm_high_0 (rwx)`: OCM'in yüksek aralığa eşlenmiş 64 KB bankı. `0xFFFF_0000`–`0xFFFF_FDFF`. VINITHI=1 ile bu adres alternatif reset vektör tabanı olabilir; aksi halde küçük, düşük-gecikmeli tablolar için kullanılır. +- `ps7_qspi_linear (rx)`: QSPI Flash linear-addressed pencere. Sadece okuma izniyle tanımlanır; yazma için QSPI denetleyicisi register'larıyla protokol konuşulması gerekir. `MEMORY` bloğu sadece linker'a "bu adres aralıkları var" der; içine ne konacağı `SECTIONS` ile belirlenir. @@ -87,35 +99,48 @@ Satır satır: `SECTIONS`, linker script'in kalbidir. Her **çıktı bölümünü** (output section) sırasıyla tanımlar; her çıktı bölümü, nesne dosyalarındaki bir veya daha fazla **girdi bölümünden** (input section) oluşur. -### Vector Table — Mutlaka En Başta +### Vector Table — Cortex-A9'da Talimatlar, Pointer Değil + +Cortex-A9 reset davranışını anlamak için iki noktayı ezberlemek gerekir: -Cortex-M reset davranışını anlamak için iki adresi ezberlemek gerekir: +- **VBAR (Vector Base Address Register)**: Cortex-A9'da CP15 c12 c0 0 üzerinden okunup yazılan, vector table'ın tabanını tutan register. Donanım reset anında VBAR ya `0x0000_0000` ya da `0xFFFF_0000`'a yüklenir; bu seçim, **VINITHI** konfigürasyon sinyali ile belirlenir. Zynq-7000'de varsayılan VINITHI=0; reset vektörü `0x0000_0000`'dadır ve OCM low buraya eşlendiği için ilk talimat OCM'den okunur. +- **8 girişli tablo**: Cortex-A9 (ve genel olarak ARMv7-A) vector table'ı 8 girişten oluşur: reset, undefined instruction, supervisor call (SVC/SWI), prefetch abort, data abort, reserved, IRQ, FIQ. Her giriş **bir 32-bit talimattır** — Cortex-M'deki gibi adres değil. Tipik kullanım `LDR pc, [pc, #offset]` veya kısa mesafede `B handler` formundadır. -- `0x00000000`: ilk Main Stack Pointer (MSP) değeri. -- `0x00000004`: Reset_Handler'ın adresi (thumb biti set, yani LSB=1). +Tipik bir Cortex-A9 vector table assembly'si Xilinx standalone BSP'nin `asm_vectors.S` dosyasında şuna benzer: -CPU resetlendiğinde önce bu iki kelimeyi okur, MSP'yi yükler, sonra Reset_Handler'a dallanır. Daha sonra **VTOR (Vector Table Offset Register)** üzerinden tablo başka bir adrese taşınabilir; ancak ilk anda CPU sabit bir başlangıç noktası bekler. Bu sabit adres Cortex-M0/M0+'ta donanımsal olarak 0x00000000'dır (VTOR yok). Cortex-M3/M4/M7'de varsayılan VTOR yine 0'ı işaret eder; üretici, bunu boot pini veya alias bir aralık ile (örn. STM32'de 0x00000000'ın 0x08000000'a alias olması) sağlar. +```asm +.section .vectors, "a" +_vector_table: + LDR pc, =_boot /* 0x00: Reset */ + LDR pc, =Undefined /* 0x04: Undefined instruction */ + LDR pc, =SVCHandler /* 0x08: SVC */ + LDR pc, =PrefetchAbortHandler /* 0x0C: Prefetch abort */ + LDR pc, =DataAbortHandler /* 0x10: Data abort */ + NOP /* 0x14: Reserved */ + LDR pc, =IRQHandler /* 0x18: IRQ */ + LDR pc, =FIQHandler /* 0x1C: FIQ */ +``` -Linker script'te bunu şöyle ifade ederiz: +Linker script'te bu bölümü şöyle yerleştiririz: ```ld SECTIONS { - .isr_vector : + .vectors : { - KEEP(*(.isr_vector)) - } > FLASH + KEEP(*(.vectors)) + } > ps7_ocm_low_0 ... } ``` Burada dikkat edilecek üç şey var: -1. **`KEEP(...)`**: linker'ın "ölü kod eliminasyonu" (`--gc-sections`) ile bu bölümü atmasını engeller. Vector table'a hiçbir C kodu doğrudan referans vermez; sadece donanım CPU resetinde okur. `KEEP` olmadan optimizasyon vector table'ı bütünüyle çöpe atabilir — sonra cihaz boot etmez ve siz iki gün uyumadan sebebini ararsınız. -2. **`*(.isr_vector)`**: bütün girdi nesnelerinden `.isr_vector` adlı bölümleri al. Bizim startup `.s` veya `.c` dosyamızda vector table bu isimle `__attribute__((section(".isr_vector")))` ile işaretlenmiştir. -3. **`> FLASH`**: bu çıktı bölümünün **VMA**'sı FLASH bölgesine düşsün. Vector table FLASH'ta yaşar, oradan okunur, taşınmaz. +1. **`KEEP(...)`**: linker'ın "ölü kod eliminasyonu" (`--gc-sections`) ile bu bölümü atmasını engeller. Vector table'a hiçbir C kodu doğrudan referans vermez; sadece donanım CPU exception'larında okur. `KEEP` olmadan optimizasyon vector table'ı bütünüyle çöpe atabilir — sonra cihaz boot etmez ve siz iki gün uyumadan sebebini ararsınız. +2. **`*(.vectors)`**: bütün girdi nesnelerinden `.vectors` adlı bölümleri al. `asm_vectors.S` bu isimle `.section .vectors` direktifiyle işaretlenmiştir. +3. **`> ps7_ocm_low_0`**: bu çıktı bölümünün **VMA**'sı OCM low bölgesine düşsün. Bu bölge `0x0000_0000`'dan başladığı için reset vektörü tam istediğimiz yerde, donanımın aradığı adreste oturur. VBAR'ı sonradan değiştirmiyorsak başka iş yok; değiştiriyorsak (örn. PL310 L2 cache aktif edilirken VBAR'ı DDR'daki bir kopyaya yönlendirmek), bu satırı `> ps7_ddr_0` yapıp `VBAR = &_vector_table_ddr` kodu yazarsınız. -`.isr_vector`'un dosyanın başında yer alması zorunludur; çünkü FLASH'a yerleştirme sırasıyla yapılır ve CPU 0x08000000 / 0x00000000'da vector table bekler. +Vector table'ın script'in başında yer alması zorunludur; çünkü OCM low bölgesi küçüktür ve FSBL'in geri kalanı da burada otururken aynı bölgede başka şeylere yer kalması gerekir. ### .text — Kod ve Salt-Okunur Veri @@ -132,35 +157,36 @@ Burada dikkat edilecek üç şey var: . = ALIGN(4); _etext = .; -} > FLASH +} > ps7_ddr_0 ``` Önemli noktalar: - `*(.text*)`: tüm girdi nesnelerinden `.text` ile başlayan bütün bölümleri al. Yıldız (`*`) bir glob'tur; `.text.startup`, `.text.cold` gibi alt bölümler GCC `-ffunction-sections` ile kullanıldığında bu pattern ile yakalanır. Bu çok önemli, çünkü `-ffunction-sections` + `-Wl,--gc-sections` kombinasyonu ile çağrılmayan fonksiyonlar bağlama dışı bırakılır. - `*(.rodata*)`: salt-okunur sabitler. `const int table[] = {...}` C kodu burada oturur. -- `*(.glue_7) *(.glue_7t)`: ARM/Thumb interworking için derleyicinin ürettiği geçiş kodu. Modern saf Thumb-2 hedeflerde (Cortex-M) genellikle boştur, ama linker tutarlılığı için yer ayırılır. +- `*(.glue_7) *(.glue_7t)`: ARM/Thumb interworking için derleyicinin ürettiği geçiş kodu. Zynq-7000 Cortex-A9 hem ARM hem Thumb-2 çalıştırabildiği için linker tutarlılığı açısından yer ayırılır. - `*(.eh_frame)`: C++ exception unwinding tabloları. Bare-metal projelerde tipik olarak `-fno-exceptions` ile devre dışı bırakılır, ama linker'a hatırlatmak iyi bir alışkanlıktır. - `KEEP(*(.init))`, `KEEP(*(.fini))`: eski (sysv) constructor/destructor mekanizması. ARM EABI bunun yerine `.init_array` kullanır (aşağıda), ama uyumluluk için yine de tutulurlar. -- `. = ALIGN(4)`: konum sayacı (location counter, `.`) 4 byte sınırına hizalanır. ARM Cortex-M, kodun ve verinin doğal hizalı (aligned) olmasını bekler; aksi takdirde performans veya — bazı erişimlerde — UsageFault olur. -- `_etext = .`: bu satırın yarattığı `_etext` sembolü, `.text` bölümünün bittiği FLASH adresini gösterir. Bu sembol C koduna `extern uint32_t _etext;` olarak ihraç edilir ve startup'taki `.data` kopyalama döngüsünün kaynak adresi olarak kullanılır. +- `. = ALIGN(4)`: konum sayacı (location counter, `.`) 4 byte sınırına hizalanır. ARMv7-A, kodun 4 byte hizalı olmasını bekler. +- `_etext = .`: bu satırın yarattığı `_etext` sembolü, `.text` bölümünün bittiği DDR adresini gösterir. ### .ARM ve .init_array — Sessiz Ama Kritik ```ld -.ARM.extab : { *(.ARM.extab*) } > FLASH -.ARM : { +.ARM.extab : { *(.ARM.extab*) } > ps7_ddr_0 +.ARM : +{ __exidx_start = .; *(.ARM.exidx*) __exidx_end = .; -} > FLASH +} > ps7_ddr_0 .preinit_array : { PROVIDE_HIDDEN (__preinit_array_start = .); KEEP (*(.preinit_array*)) PROVIDE_HIDDEN (__preinit_array_end = .); -} > FLASH +} > ps7_ddr_0 .init_array : { @@ -168,7 +194,7 @@ Burada dikkat edilecek üç şey var: KEEP (*(SORT(.init_array.*))) KEEP (*(.init_array*)) PROVIDE_HIDDEN (__init_array_end = .); -} > FLASH +} > ps7_ddr_0 .fini_array : { @@ -176,7 +202,7 @@ Burada dikkat edilecek üç şey var: KEEP (*(SORT(.fini_array.*))) KEEP (*(.fini_array*)) PROVIDE_HIDDEN (__fini_array_end = .); -} > FLASH +} > ps7_ddr_0 ``` `.ARM.exidx`, exception index tablosu; ARM ABI tanımlı. Bare-metal'de C kullanıyor olsanız bile linker birkaç byte üretebilir. `__exidx_start` / `__exidx_end` sembolleri runtime'da gerekirse stack unwinding kütüphanelerinin aradığı sembollerdir. @@ -187,9 +213,9 @@ Asıl ilginç olan `.init_array`. ARM EABI, statik C++ constructor'larını eski C++ kullanmıyorsanız bile bu bölümlerin tanımlı olması gerekir; aksi halde `__libc_init_array()` tanımsız sembolle linker hatası verir. -### .data — VMA ve LMA Hilesi +### .data — VMA ve LMA Ayrımının Devreye Girdiği Yer -İşte linker script'in en sık yanlış anlaşılan kısmı: +Zynq-7000 bare-metal'de en yaygın senaryo, ELF'in tamamen DDR'da koşmasıdır. Bu durumda `.data` için VMA = LMA = DDR adresi olur ve hiçbir runtime kopya gerekmez; FSBL DDR'a yüklediğinde başlangıç değerleri zaten yerindedir: ```ld .data : @@ -199,45 +225,47 @@ C++ kullanmıyorsanız bile bu bölümlerin tanımlı olması gerekir; aksi hald *(.data*) . = ALIGN(4); _edata = .; -} > SRAM AT> FLASH +} > ps7_ddr_0 +``` + +Ama VMA ≠ LMA, anlamlı olduğu iki Zynq senaryosu vardır: + +**Senaryo 1**: hot-path veriyi OCM'e taşımak. DDR erişimleri OCM'e göre çok daha yüksek gecikmeye sahiptir. Bir ISR'da kullanılan küçük bir tablo OCM'e konursa, cache'i kirletmeden, deterministik erişim alırsınız. Ama bu tablonun başlangıç değerleri ELF'in load image'inde **bir yerde** olmalı — DDR'ın LMA tarafına koyup runtime'da OCM'e kopyalamak doğal bir çözümdür: + +```ld +.ocm_data : ALIGN(4) +{ + _socm_data = .; + *(.ocm_data*) + _eocm_data = .; +} > ps7_ocm_high_0 AT> ps7_ddr_0 -_sidata = LOADADDR(.data); +_siocm_data = LOADADDR(.ocm_data); ``` Üç anahtar mekanizma: -1. **`> SRAM`**: bu bölümün **VMA**'sı SRAM'dedir. `_sdata` ve `_edata` sembolleri SRAM adreslerini taşır; C kodu içinde global değişkenler bu adreslerde okunur/yazılır. -2. **`AT> FLASH`**: ama bölümün **LMA**'sı FLASH'tadır. Yani derlenmiş ELF'in load image'inde bu byte'lar FLASH bölgesine yerleştirilir; programlayıcı (flasher) onları FLASH'a yazar. -3. **`_sidata = LOADADDR(.data)`**: `LOADADDR` makrosu, bir bölümün LMA'sını döndürür. `_sidata` sembolü, FLASH'taki load image'in başlangıç adresini taşır. Startup kodu, bu adresten başlayıp `_sdata` ile `_edata` arasındaki SRAM'e byte-byte kopyalama yapar. +1. **`> ps7_ocm_high_0`**: bu bölümün **VMA**'sı OCM high'dadır. `_socm_data` ve `_eocm_data` sembolleri OCM adreslerini taşır; C kodu içinde global değişkenler bu adreslerde okunur/yazılır. +2. **`AT> ps7_ddr_0`**: ama bölümün **LMA**'sı DDR'dadır. Yani derlenmiş ELF'in load image'inde bu byte'lar DDR bölgesine yerleştirilir; FSBL onları DDR'a yazar. +3. **`_siocm_data = LOADADDR(.ocm_data)`**: `LOADADDR` makrosu, bir bölümün LMA'sını döndürür. `_siocm_data` sembolü, DDR'daki load image'in başlangıç adresini taşır. Startup kodu, bu adresten başlayıp `_socm_data` ile `_eocm_data` arasındaki OCM'e byte-byte kopyalama yapar. Tipik startup kopyalama kodu şuna benzer: ```c -extern uint32_t _sdata, _edata, _sidata; +extern uint32_t _socm_data, _eocm_data, _siocm_data; -void Reset_Handler(void) { - /* .data: FLASH'tan SRAM'e kopyala */ - uint32_t *src = &_sidata; - uint32_t *dst = &_sdata; - while (dst < &_edata) { +void copy_ocm_data(void) { + uint32_t *src = &_siocm_data; + uint32_t *dst = &_socm_data; + while (dst < &_eocm_data) { *dst++ = *src++; } - - /* .bss: sıfırla */ - extern uint32_t _sbss, _ebss; - dst = &_sbss; - while (dst < &_ebss) { - *dst++ = 0; - } - - SystemInit(); - __libc_init_array(); - main(); - while (1) { } } ``` -Bu kod, derleyicinin "her global değişkenin başlangıç değerini bilir" varsayımını koruyan görünmez sözleşmedir. C standardı şöyle der: `int counter = 42;` yazdığınızda, program `counter`'a ilk okuduğunda 42 görmek zorundadır. Bare-metal'de bunu garanti eden tek mekanizma, FLASH'tan RAM'e kopyalanan bu load image'dir. `.data` kopyalama döngüsünü startup kodundan çıkarmak, `.data` global değişkenlerinizin başlangıç değerini bozar; ve bu hata bazen aylar sonra, RAM'deki "rastgele" geçmiş değerlerden dolayı tutarsız davranış olarak ortaya çıkar. +**Senaryo 2**: bootloader / uygulama ayrımı. Eğer kendi ikinci-aşama bootloader'ınız varsa (örn. emniyet kritik bir cihazda CRC doğrulamasından sonra uygulamaya devreden bir mini-loader), uygulama ELF'i QSPI'ya yazılır, runtime'da DDR'a kopyalanır. Bu durumda `AT> ps7_qspi_linear` kullanılır ve `_sidata`'nız QSPI adresine işaret eder. + +Bu mekanizma, derleyicinin "her global değişkenin başlangıç değerini bilir" varsayımını koruyan görünmez sözleşmedir. C standardı şöyle der: `int counter = 42;` yazdığınızda, program `counter`'a ilk okuduğunda 42 görmek zorundadır. Bare-metal'de bunu garanti eden tek mekanizma, ya FSBL'in zaten DDR'a yüklemiş olduğu image'dir ya da sizin elle yazdığınız `.data` kopyalama döngüsüdür. Hangisinin geçerli olduğunu mutlaka net biçimde belirlemelisiniz; "ortada bir yerde" bırakılan bir kopyalama akışı, sahada aylar sonra "RAM'deki rastgele geçmiş değerlerden dolayı tutarsız davranış" olarak ortaya çıkar. ### .bss — Sıfır İle Başlatılan @@ -252,46 +280,55 @@ Bu kod, derleyicinin "her global değişkenin başlangıç değerini bilir" vars . = ALIGN(4); _ebss = .; __bss_end__ = _ebss; -} > SRAM +} > ps7_ddr_0 ``` -`.bss`, başlangıç değeri sıfır olan veya hiç başlatılmamış global / static değişkenleri tutar. ELF'te bu bölüm `NOBITS` tipindedir: load image'de **yer tutmaz**, sadece linker `_sbss`–`_ebss` boyutunu kaydeder. Startup kodu bu aralığı sıfırlar. Bu yüzden FLASH boyutu hesabına `.bss` girmez; ama RAM boyutu hesabına girer. +`.bss`, başlangıç değeri sıfır olan veya hiç başlatılmamış global / static değişkenleri tutar. ELF'te bu bölüm `NOBITS` tipindedir: load image'de **yer tutmaz**, sadece linker `_sbss`–`_ebss` boyutunu kaydeder. Startup kodu bu aralığı sıfırlar. Bu yüzden QSPI boyutu hesabına `.bss` girmez; ama DDR boyutu hesabına girer. `*(COMMON)` deyimi, eski K&R C ve bazı yabancı derleyicilerin `extern` tanımlamadan global değişken bildirimi yapmasından miras kalan bir konvansiyondur. Modern projede neredeyse hiç doluymaz; ama linker script'lerde alışkanlık olarak yer alır. +Zynq'ta dikkat: çift çekirdek kullanıyorsanız (CPU0 + CPU1 ayrı standalone uygulama), her çekirdeğin kendi `.bss`'i, kendi `.data`'sı, kendi stack'i olmalıdır. İki ELF'in aynı DDR bölgesini görmesi en sık karşılaşılan AMP (Asymmetric Multi-Processing) hatasıdır. + ### Heap ve Stack ```ld -._user_heap_stack : +._heap_stack : { - . = ALIGN(8); + . = ALIGN(16); PROVIDE ( end = . ); PROVIDE ( _end = . ); - . = . + _Min_Heap_Size; - . = . + _Min_Stack_Size; - . = ALIGN(8); -} > SRAM + . = . + _HEAP_SIZE; + . = ALIGN(16); + _stack_end = .; + . = . + _STACK_SIZE; + . = ALIGN(16); + _stack = .; +} > ps7_ddr_0 ``` -Burada `_Min_Heap_Size` ve `_Min_Stack_Size` script'in başında veya komut satırından (`-Wl,--defsym=_Min_Stack_Size=0x800`) verilen sembollerdir. Konum sayacını bu kadar artırmak, linker'a bu kadar RAM yerinin **kullanılmış sayılmasını** söyler. Eğer `.data + .bss + heap + stack` toplamı SRAM uzunluğunu aşarsa linker bağlama hatası verir. - -`end` sembolü ise newlib'in `_sbrk()` syscall implementasyonu tarafından heap başlangıcı olarak kullanılır. +Burada `_HEAP_SIZE` ve `_STACK_SIZE` script'in başında veya komut satırından (`-Wl,--defsym=_STACK_SIZE=0x2000`) verilen sembollerdir. Konum sayacını bu kadar artırmak, linker'a bu kadar DDR yerinin **kullanılmış sayılmasını** söyler. -Stack pointer ise script sonunda şöyle tanımlanır: +Cortex-A9'da hizalama `8` değil `16` byte'tır. AAPCS (ARM Architecture Procedure Call Standard), public arayüzlerde SP'nin 8-byte hizalı olmasını şart koşar; pratikte ise NEON ve birçok kütüphane 16-byte hizalama bekler. `_stack_end` ve `_stack` sembolleri, başlangıç kodunun her CPU modu (SVC, IRQ, FIQ, Abort, Undef, System) için ayrı stack pointer'lar kurmasında kullanılır — Cortex-A9'da Cortex-M'den farklı olarak **her exception modunun ayrı SP'si** vardır ve bunların hepsini ayrı ayrı kurmak gerekir: -```ld -_estack = ORIGIN(SRAM) + LENGTH(SRAM); +```asm +/* Startup sırasında her mod için SP kurulumu */ +MSR CPSR_c, #(MODE_IRQ | I_BIT | F_BIT) +LDR sp, =__irq_stack_top +MSR CPSR_c, #(MODE_FIQ | I_BIT | F_BIT) +LDR sp, =__fiq_stack_top +MSR CPSR_c, #(MODE_SVC | I_BIT | F_BIT) +LDR sp, =__svc_stack_top ``` -Bu sembol vector table'ın 0. girişine yazılır; CPU resetinde MSP buradan yüklenir. Stack RAM'in en üstünden başlayıp aşağı doğru büyür (full descending), heap ise aşağıdan yukarı doğru. İkisi ortada çakışırsa stack overflow yaşarsınız — ARM Cortex-M7 ve M33'te MPU + stack limit register (PSPLIM/MSPLIM) ile bunu donanım seviyesinde yakalamak mümkündür. +`end` sembolü ise newlib'in `_sbrk()` syscall implementasyonu tarafından heap başlangıcı olarak kullanılır. --- ## Map Dosyası Analizi — Gerçekten Ne Çıktı? -Linker'a `-Wl,-Map=output.map` parametresi verdiğinizde, `arm-none-eabi-ld` insan tarafından okunabilir bir bağlama raporu üretir. Tipik bir map dosyasında şu bölümler vardır: +Linker'a `-Wl,-Map=output.map` parametresi verdiğinizde, `arm-none-eabi-ld` insan tarafından okunabilir bir bağlama raporu üretir. Tipik bir Zynq-7000 map dosyasında şu bölümler vardır: -- **Archive member included to satisfy reference**: hangi `.o` veya `.a` dosyalarının neden bağlandığı. +- **Archive member included to satisfy reference**: hangi `.o` veya `.a` dosyalarının (örn. `libxil.a` Xilinx standalone BSP) neden bağlandığı. - **Discarded input sections**: `--gc-sections` ile elenen ölü kod. - **Memory Configuration**: MEMORY bloğundan gelen bölgelerin özeti. - **Linker script and memory map**: her çıktı bölümünün, girdi bölümlerinin, sembollerin adres ve boyut dökümü. @@ -299,63 +336,68 @@ Linker'a `-Wl,-Map=output.map` parametresi verdiğinizde, `arm-none-eabi-ld` ins Örnek bir parça: ```text -.isr_vector 0x0000000008000000 0x190 - 0x0000000008000000 . = ALIGN (0x4) - *(.isr_vector) - .isr_vector 0x0000000008000000 0x190 build/startup_stm32f407xx.o - 0x0000000008000000 g_pfnVectors - -.text 0x0000000008000190 0x4ae0 +.vectors 0x0000000000000000 0x40 + 0x0000000000000000 . = ALIGN (0x4) + *(.vectors) + .vectors 0x0000000000000000 0x40 build/asm_vectors.o + 0x0000000000000000 _vector_table + +.text 0x0000000000100000 0xb2c0 + 0x0000000000100000 _ftext = . *(.text*) + .text.boot 0x0000000000100000 0x1c8 build/boot.o + 0x0000000000100000 _boot .text.HAL_Init - 0x0000000008000190 0xa8 build/stm32f4xx_hal.o - .text.main - 0x0000000008000238 0x44 build/main.o + 0x00000000001001c8 0xa8 build/platform.o + .text.main 0x0000000000100270 0x44 build/main.o ... - 0x000000000800482a _etext = . - -.data 0x0000000020000000 0x4c load address 0x0000000008004830 - 0x0000000020000000 _sdata = . - *(.data*) - .data.SystemCoreClock - 0x0000000020000000 0x4 build/stm32f4xx_hal.o - 0x0000000020000004 SystemCoreClock + 0x000000000010b2c0 _etext = . + +.ocm_data 0x00000000ffff0000 0x80 load address 0x000000000010c000 + 0x00000000ffff0000 _socm_data = . + *(.ocm_data*) + .ocm_data.lut 0x00000000ffff0000 0x80 build/dsp_kernel.o + ... + 0x00000000ffff0080 _eocm_data = . + 0x000000000010c000 _siocm_data = LOADADDR (.ocm_data) + +.data 0x000000000010c080 0x4c + 0x000000000010c080 _sdata = . ... - 0x000000002000004c _edata = . - 0x0000000008004830 _sidata = LOADADDR (.data) -.bss 0x000000002000004c 0x9b8 - 0x000000002000004c _sbss = . - 0x000000002000004c __bss_start__ = _sbss +.bss 0x000000000010c0cc 0x9b8 + 0x000000000010c0cc _sbss = . + 0x000000000010c0cc __bss_start__ = _sbss ... - 0x0000000020000a04 _ebss = . + 0x000000000010ca84 _ebss = . ``` Burada doğrulanması gereken üç şey vardır: -1. **`.isr_vector` 0x08000000'da mı?** Evet. Reset davranışı için zorunlu. -2. **`.data`'nın VMA'sı 0x20000000, LMA'sı 0x08004830 mu?** Evet — `load address 0x...` satırı LMA'yı gösteriyor. Yani 0x4c byte'lık başlangıç verisi FLASH'ta `_etext`'in hemen ardına yazılmış. -3. **`_sidata = LOADADDR(.data)` 0x08004830'a çözümlenmiş mi?** Evet. Startup kodu bu adresten okumaya başlayacak. +1. **`.vectors` `0x0000_0000`'da mı?** Evet — OCM low'a düştüğü için VBAR=0 reset davranışı için zorunlu. +2. **`.ocm_data`'nın VMA'sı `0xFFFF_0000`, LMA'sı `0x0010_C000` mu?** Evet — `load address 0x...` satırı LMA'yı gösteriyor. Yani 128 byte'lık başlangıç verisi DDR'da `_etext`'in hemen ardına yazılmış. FSBL bu byte'ları DDR'a yükleyecek; startup kodumuz oradan OCM'e kopyalayacak. +3. **`_siocm_data = LOADADDR(.ocm_data)` `0x0010_C000`'a çözümlenmiş mi?** Evet. Startup kodu bu adresten okumaya başlayacak. -Bu üç doğrulamayı her yeni bare-metal projede yapmak — özellikle bootloader / uygulama ayrımı, dual-bank FLASH veya iki ayrı SRAM bölgesi söz konusuysa — gözle görülür miktarda zaman kazandırır. Map dosyası, üretiminin ucuz olmasına rağmen genellikle yeterince incelenmez. +Bu üç doğrulamayı her yeni Zynq projesinde yapmak — özellikle bootloader / uygulama ayrımı, AMP konfigürasyonu veya OCM remap kullanımı söz konusuysa — gözle görülür miktarda zaman kazandırır. `objdump` ile ek bir çapraz kontrol: ```bash $ arm-none-eabi-objdump -h build/firmware.elf -Idx Name Size VMA LMA File off Algn - 0 .isr_vector 00000190 08000000 08000000 00010000 2**0 - 1 .text 00004aa0 08000190 08000190 00010190 2**3 - 2 .rodata 000003a8 08004c30 08004c30 00014c30 2**3 - 3 .ARM 00000008 08004fd8 08004fd8 00014fd8 2**2 - 4 .init_array 00000004 08004fe0 08004fe0 00014fe0 2**2 - 5 .fini_array 00000004 08004fe4 08004fe4 00014fe4 2**2 - 6 .data 0000004c 20000000 08004fe8 00020000 2**3 - 7 .bss 000009b8 2000004c 2000004c 00020004 2**2 +Idx Name Size VMA LMA File off Algn + 0 .vectors 00000040 00000000 00000000 00008000 2**2 + 1 .text 0000b2c0 00100000 00100000 00010000 2**3 + 2 .rodata 000003a8 0010b2c0 0010b2c0 0001b2c0 2**3 + 3 .ARM 00000008 0010b668 0010b668 0001b668 2**2 + 4 .init_array 00000010 0010b670 0010b670 0001b670 2**2 + 5 .fini_array 00000004 0010b680 0010b680 0001b680 2**2 + 6 .ocm_data 00000080 ffff0000 0010c000 00020000 2**3 + 7 .data 0000004c 0010c080 0010c080 0002c080 2**3 + 8 .bss 000009b8 0010c0cc 0010c0cc 0002c0cc 2**3 ``` -`.data` satırındaki VMA ≠ LMA, linker script'in işini doğru yaptığının asıl kanıtıdır. +`.ocm_data` satırındaki VMA ≠ LMA, linker script'in işini doğru yaptığının asıl kanıtıdır. `.vectors`'ın `0x0000_0000`'da görünmesi, OCM low'un BootROM tarafından doğru remap edildiğini varsayar — eğer cihaz boot ediyor ama vector'ler çalışmıyorsa, OCM_CFG'ye bakın. --- @@ -363,29 +405,37 @@ Idx Name Size VMA LMA File off Algn ### 1. KEEP unutulması ve `--gc-sections` -`-Wl,--gc-sections` ile birlikte kullanıldığında, vector table veya `.init_array` `KEEP` olmadan bağlamadan tamamen silinebilir. Sonuç: cihaz reset attığında 0x0000_0004'ten okuduğu adres geçersizdir; HardFault. Bu hatayı oscilloskop ve debugger ile yakalamak günlerce sürebilir. +`-Wl,--gc-sections` ile birlikte kullanıldığında, vector table veya `.init_array` `KEEP` olmadan bağlamadan tamamen silinebilir. Sonuç: cihaz reset attığında VBAR'ın işaret ettiği yerden okuduğu talimat geçersizdir; cihaz "ölü" görünür. Bu hatayı oscilloskop ve debugger ile yakalamak günlerce sürebilir. + +### 2. OCM remap yanılgısı + +OCM_CFG register'ı SLCR (System Level Control Registers) bloğundadır ve runtime'da değiştirilebilir. Eğer linker script'iniz vector table'ı `0x0000_0000`'a koyuyorsa ama uygulamanız boot sonrası OCM_CFG'yi `0x1F` yapıp tüm bankları yüksek aralığa yığarsa, bir sonraki exception VBAR=0'dan okumaya çalışacak ve **bu bölgeye DDR3'ten okuyacaktır** — DDR'ın o adresinde ne varsa onu yürütür. Klasik bir "neden cihazım hardfault üretiyor" hatası. Çözüm: OCM remap'i değiştirmeden önce VBAR'ı yeni adrese yönlendirin. + +### 3. `.data` LMA ile DDR boyutu çakışması + +`firmware.bin` veya `BOOT.bin` üretirken `bootgen` veya `objcopy` LMA'yı temel alır. `.text + .rodata + .data(LMA) + .ocm_data(LMA)` toplamının DDR kullanıcı bölgesine sığması gerekir. Bunu bir görüntü boyutu kontrolü ile (örn. `arm-none-eabi-size firmware.elf`) build sırasında her zaman doğrulayın. `--print-memory-usage` flag'i de linker'dan canlı bir özet verir. -### 2. `.data` LMA ile FLASH boyutu çakışması +### 4. Alignment hatası -`firmware.bin` veya `firmware.hex` üretirken `objcopy` LMA'yı temel alır. `.text + .rodata + .data(LMA)` toplamının FLASH'a sığması gerekir. Bunu bir görüntü boyutu kontrolü ile (örn. `arm-none-eabi-size firmware.elf`) build sırasında her zaman doğrulayın. `--print-memory-usage` flag'i de linker'dan canlı bir özet verir. +Linker `.data` bölümünü 4 byte hizasında üretir, ama startup kopyalama döngüsü `uint32_t *` üzerinden yürür. `_sdata` ve `_edata`'nın 4'e bölünebilir adreslerde olduğunu garanti etmek için `. = ALIGN(4)` zorunludur; aksi takdirde son iterasyonda 2 byte fazladan yazıp `.bss`'in başını bozarsınız. Cortex-A9 MMU açıkken ve özellikle Strongly-Ordered veya Device tipi sayfa tabloları kullanılıyorsa yanlış hizalı erişim Data Abort doğurur. -### 3. Alignment hatası +### 5. Constructor'ların çalıştırılmaması -Linker `.data` bölümünü 4 byte hizasında üretir, ama startup kopyalama döngüsü `uint32_t *` üzerinden yürür. `_sdata` ve `_edata`'nın 4'e bölünebilir adreslerde olduğunu garanti etmek için `. = ALIGN(4)` zorunludur; aksi takdirde son iterasyonda 2 byte fazladan yazıp `.bss`'in başını bozarsınız. Cortex-M0 ve M0+'ta yanlış hizalı 32-bit erişim ayrıca HardFault doğurur. +Eğer startup kodu `__libc_init_array()` çağırmıyorsa veya linker script `.init_array` bölümünü ihtiyacı olduğu sembollerle (`__init_array_start`, `__init_array_end`) ihraç etmiyorsa, C++ global nesnelerin constructor'ları çalıştırılmaz. Sonuç: `static MyClass obj;` ile yazdığınız sınıfın iç durumu tamamen tanımsızdır. Çoğu zaman bu hata "obje sıfırlanmış görünüyor ama metodlar çağrılınca null pointer dereference" olarak ortaya çıkar. -### 4. Constructor'ların çalıştırılmaması +### 6. Çift çekirdekte aynı bölgeyi paylaşmak -Eğer startup kodu `__libc_init_array()` çağrılmıyorsa veya linker script `.init_array` bölümünü ihtiyacı olduğu sembollerle (`__init_array_start`, `__init_array_end`) ihraç etmiyorsa, C++ global nesnelerin constructor'ları çalıştırılmaz. Sonuç: `static MyClass obj;` ile yazdığınız sınıfın iç durumu (state) tamamen tanımsızdır. Çoğu zaman bu hata "obje sıfırlanmış görünüyor ama metodlar çağrılınca null pointer dereference" olarak ortaya çıkar. +Zynq'ın iki Cortex-A9'u tek bir DDR görür. AMP konfigürasyonda her ELF'in linker script'ini düzenleyip `ps7_ddr_0` bölgesini bölmeden bağlarsanız, CPU0 ve CPU1 birbirlerinin `.bss`'lerini sıfırlayarak başlatır — hangisi son sıfırlarsa diğeri çöker. Vitis tarafında bu, BSP konfigürasyonunda iki uygulamanın DDR pencerelerini ayrı ayrı tanımlamayı gerektirir. -### 5. Heap büyümesi ile stack çakışması +### 7. Heap büyümesi ile stack çakışması -Klasik bir hata: dinamik tahsis (örn. `malloc`) ile heap RAM'in ortasına doğru büyürken, derin özyinelemeli bir fonksiyon stack'i aşağıya iter; ikisi ortada çakışır. Yığın taşmasını (stack overflow) erken yakalamak için: +Klasik bir hata: dinamik tahsis (örn. `malloc`) ile heap DDR'ın ortasına doğru büyürken, derin özyinelemeli bir fonksiyon stack'i aşağıya iter; ikisi ortada çakışır. Zynq-7000 Cortex-A9'da donanım stack-limit register'ı yoktur (Cortex-M7/M33'teki PSPLIM/MSPLIM gibi). Yığın taşmasını erken yakalamak için: -- Cortex-M7 / M33 / M55 için **MSPLIM** ve **PSPLIM** stack limit register'larını kullanın. -- Veya MPU ile stack alanının altına bir koruyucu (guard) bölge tanımlayın. +- MMU ile stack alanının altına bir koruyucu (guard) sayfa tanımlayın (Strongly-Ordered + No Access). - Veya statik analiz (örn. `gcc -fstack-usage` + bir post-process script) ile maksimum stack derinliğini çıkarın. +- Veya çalışma anında stack pattern kontrolü (canary) yapın. -Linker script'in `_estack` ve heap/stack region tanımı, bu denetimlerin temelidir. +Linker script'in `_stack_end` / `_stack` ve heap region tanımı, bu denetimlerin temelidir. --- @@ -393,22 +443,23 @@ Linker script'in `_estack` ve heap/stack region tanımı, bu denetimlerin temeli Çok yıllık saha deneyiminden çıkardığım bazı kısa kurallar: -- **Linker script'i sürüm kontrolüne alın.** CubeMX gibi araçların ürettiği script'i kontrolsüz biçimde yeniden ürettirirseniz, elinizle yaptığınız özelleştirmeler — bootloader entegrasyonu, özel section'lar, ENVM kullanımı — bir buton tıklamasıyla silinir. -- **`--print-memory-usage` veya `arm-none-eabi-size` çıktısını CI'a sokun.** FLASH ve SRAM kullanım yüzdelerinin yüksek olduğunda erken uyarı veren bir kontrol, sahada "image FLASH'a sığmadı" sürprizini önler. -- **`KEEP` listesine `.isr_vector`, `.init_array`, `.fini_array`, `.preinit_array`'ı her zaman koyun.** +- **Linker script'i sürüm kontrolüne alın.** Vitis'in ürettiği `lscript.ld`'yi kontrolsüz biçimde yeniden ürettirirseniz, elinizle yaptığınız özelleştirmeler — bootloader entegrasyonu, OCM placement, AMP DDR bölme — bir buton tıklamasıyla silinir. +- **`--print-memory-usage` veya `arm-none-eabi-size` çıktısını CI'a sokun.** DDR ve OCM kullanım yüzdelerinin yüksek olduğunda erken uyarı veren bir kontrol, sahada "image sığmadı" sürprizini önler. +- **`KEEP` listesine `.vectors`, `.init_array`, `.fini_array`, `.preinit_array`'ı her zaman koyun.** - **Her custom section için `__section_start__` ve `__section_end__` sembolü ihraç edin.** Sonradan bir runtime check (örn. CRC) yazmak istediğinizde bu sembollere ihtiyacınız olur. -- **Map dosyasını release notlarına ekleyin.** Bir sürümde "şu RAM'in 4 byte fazlasının nereden geldiği" sorusu sahaya çıktıktan sonra cevaplanması gereken bir soru oluyor; geçmiş map dosyaları diff almak için altın değerindedir. -- **Dual-bank FLASH'lı parçalarda iki ayrı bölge tanımlayın.** Tek bir mantıksal FLASH gibi davranmak A/B sürüm geçişlerini imkânsız hale getirir. +- **Map dosyasını release notlarına ekleyin.** Bir sürümde "şu DDR adresinin 4 byte fazlasının nereden geldiği" sorusu sahaya çıktıktan sonra cevaplanması gereken bir soru oluyor; geçmiş map dosyaları diff almak için altın değerindedir. +- **Çift çekirdek senaryolarında DDR'ı kesin sınırlarla bölün.** CPU0 için tipik `0x0010_0000`–`0x0FFF_FFFF`, CPU1 için `0x1000_0000`–`0x1FFF_FFFF`. Paylaşılan veri için ayrı bir bölge ayırın ve cache coherency kurallarını (PL310 + ACP veya non-cacheable mapping) bilinçli planlayın. +- **OCM'i sadece gerçekten gereken yerlerde kullanın.** 256 KB'lık küçük bir kaynaktır; vector table, FSBL artıkları, kritik ISR kodu ve düşük-gecikme tabloları için saklayın. --- ## Sonuç -Linker script, bare-metal ARM firmware'ının görünmez omurgasıdır. Reset anında CPU'nun nereden başlayacağını, global değişkenlerin değerini nereden aldığını, constructor'ların hangi sırayla çalışacağını, heap'in ne kadar büyüyebileceğini hep bu dosya belirler. Onu bir kutusu içinde anlamadan kullanmak mümkün; ama hata ayıklarken oradan dönerken, kara kutudan parlak bir el feneri çıkmasını ummak iyimserliktir. +Linker script, bare-metal Zynq-7000 firmware'ının görünmez omurgasıdır. Reset anında CPU'nun nereden başlayacağını, global değişkenlerin değerini nereden aldığını, vector table'ın OCM'de mi DDR'de mi oturduğunu, constructor'ların hangi sırayla çalışacağını, heap'in ne kadar büyüyebileceğini hep bu dosya belirler. Onu bir kara kutu olarak kullanmak mümkün; ama hata ayıklarken oradan dönerken, kutudan parlak bir el feneri çıkmasını ummak iyimserliktir. -Bu yazıda satır satır gezdiğimiz `.ld` dosyası, gerçek bir Cortex-M4 STM32F4 projesindeki tipik yapıdan damıtıldı. CMSIS şablonlarındaki, STM32CubeIDE'nin ürettiklerindeki ve Zephyr / NuttX gibi RTOS'ların kendi linker script'lerindeki temel yapı taşları aynıdır; isim farkları, attribute kullanımı ve özel section yapıları değişir. Bir kez bu temel yapıyı sindirdikten sonra, hangi araç hangi script'i üretirse üretsin, ne yapıldığını rahatlıkla okuyabilirsiniz. +Bu yazıda satır satır gezdiğimiz `.ld` dosyası, gerçek bir Zynq-7000 standalone projesindeki tipik yapıdan damıtıldı. Xilinx Vitis'in ürettiği `lscript.ld`'lerin, OpenAMP referans tasarımlarının, FreeRTOS-Zynq portunun ve hatta Linux'un öncesinde koşan U-Boot SPL'nin linker script'lerindeki temel yapı taşları aynıdır; isim farkları, attribute kullanımı ve özel section yapıları değişir. Bir kez bu temel yapıyı sindirdikten sonra, hangi araç hangi script'i üretirse üretsin, ne yapıldığını rahatlıkla okuyabilirsiniz. -Bir sonraki adım olarak şu üç egzersizi öneririm: (1) bir Cortex-M4 projesinde derleme sonrası `objdump -h` çıktısı ile script'inizi karşılaştırın; (2) `.bss`'i tekrar başlatma sırasını başlangıç kodunda iptal edin ve bir global int değişkenin değerini gözlemleyin (rastgele olmalı); (3) `.data` kopyalama döngüsünü kaldırın ve `int counter = 42;` ile başlattığınız bir global değişkenin ilk değerini RTT veya UART ile yazdırın — büyük olasılıkla 42 olmayacaktır. +Bir sonraki adım olarak şu üç egzersizi öneririm: (1) bir Zynq-7000 standalone projesinde derleme sonrası `objdump -h` çıktısı ile script'inizi karşılaştırın; (2) `.bss`'i tekrar başlatma sırasını başlangıç kodunda iptal edin ve bir global int değişkenin değerini gözlemleyin (DDR refresh sonrası rastgele olmalı); (3) bir tabloyu `__attribute__((section(".ocm_data")))` ile işaretleyip OCM'e yerleştirin ve `objdump -h` çıktısında VMA `0xFFFF_xxxx`, LMA `0x0010_xxxx` çiftini doğrulayın — büyük olasılıkla ilk denemede LMA ile VMA'yı karıştıracaksınız; ikinci denemede `LOADADDR()` mantığını içselleştirmiş olacaksınız. Linker, sessizdir; ama yaptığı işten haberdar olmadığınızda, sürpriz yapar. @@ -418,11 +469,14 @@ Linker, sessizdir; ama yaptığı işten haberdar olmadığınızda, sürpriz ya - [GNU LD (binutils) Documentation — Linker Scripts](https://sourceware.org/binutils/docs/ld/Scripts.html) - [System V Application Binary Interface — Generic ABI](https://refspecs.linuxfoundation.org/elf/gabi4+/contents.html) -- [ARM Cortex-M4 Generic User Guide — Vector Table (ARM DUI 0553)](https://developer.arm.com/documentation/dui0553/latest/) -- [ARM v7-M Architecture Reference Manual (ARM DDI 0403)](https://developer.arm.com/documentation/ddi0403/latest/) +- [Zynq-7000 SoC Technical Reference Manual (UG585) — On-Chip Memory (OCM)](https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM/On-Chip-Memory-OCM) +- [Zynq-7000 Booting and Running Without External Memory (Xilinx Wiki)](https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842377/Zynq-7000) +- [ARM Cortex-A9 Technical Reference Manual (ARM DDI 0388)](https://developer.arm.com/documentation/ddi0388/latest/) +- [ARMv7-A Architecture Reference Manual (ARM DDI 0406)](https://developer.arm.com/documentation/ddi0406/latest/) +- [CMSIS-Core (Cortex-A) — Vector Base Address Register (VBAR)](https://arm-software.github.io/CMSIS_6/latest/Core_A/group__CMSIS__VBAR.html) - [Newlib Source — `libc/misc/init.c` (`__libc_init_array` implementation)](https://sourceware.org/newlib/) -- [Embedded Artistry — Exploring Startup Implementations: Newlib (ARM)](https://embeddedartistry.com/blog/2019/04/17/exploring-startup-implementations-newlib-arm/) +- [Xilinx `embeddedsw` — Standalone BSP `lscript.ld` örnekleri (GitHub)](https://github.com/Xilinx/embeddedsw) +- [AMD/Xilinx — Vitis Linker Scripts](https://www.xilinx.com/html_docs/xilinx2018_1/SDK_Doc/SDK_concepts/concept_sdk_linkerscripts.html) - [Interrupt by Memfault — From Zero to main(): Demystifying Firmware Linker Scripts](https://interrupt.memfault.com/blog/how-to-write-linker-scripts-for-firmware) - [Stargirl (Thea) Flowers — The Most Thoroughly Commented Linker Script](https://blog.thea.codes/the-most-thoroughly-commented-linker-script/) - [ARM EABI — Run-time ABI for the ARM Architecture (IHI 0043)](https://developer.arm.com/documentation/ihi0043/latest/) -- [CMSIS-Core (M) Reference Implementation](https://arm-software.github.io/CMSIS_6/latest/Core/index.html) diff --git a/agent/topics.md b/agent/topics.md index 7d59654d..6df57171 100644 --- a/agent/topics.md +++ b/agent/topics.md @@ -42,10 +42,11 @@ ## Seçildi / Devam Eden -- **Linker Script Anatomisi: ARM Bare-Metal için Bir `.ld` Dosyası Satır Satır** — +- **Linker Script Anatomisi: Zynq-7000 Bare-Metal için Bir `.ld` Dosyası Satır Satır** — dal: `post/2026-05-26-linker-script-anatomisi-arm-bare-metal`, dosya: `_posts/2026-05-26-linker-script-anatomisi-arm-bare-metal.md`, - durum: PR açılacak (bu çalıştırma) — alan: toolchain/build. + durum: PR #90 açıldı; insan geri bildirimi ile Zynq-7000 (Cortex-A9 + OCM + DDR3 + QSPI) + referansıyla yeniden yazıldı — Renode yazısının doğal devamı. alan: toolchain/build. ## Reddedildi (bu çalıştırma) @@ -107,6 +108,16 @@ geçici olarak karşılıyor. Faz 2'de tekrar değerlendirilmesi gerekir. security, C/compiler, C/standard, embedded/numeric) ile çakışmıyor. Yazı, toolchain/build perspektifinden bare-metal startup zincirini açıklıyor — Renode yazısı simülasyon, bu yazı bağlama; konu örtüşmesi yok. +- **İnsan geri bildirimi (PR #90 üzerinde):** ilk taslak STM32F4/Cortex-M4 + üzerinden yazılmıştı. Geri bildirim: Renode yazısının doğal devamı olacak + şekilde Zynq-7000 (Cortex-A9 + OCM + DDR3 + QSPI) referans alınsın; CCM + yerine OCM anlatılsın. Yazı tamamen Zynq-7000'e uyarlandı: vector table + Cortex-A9 (VBAR, 8 instruction entry, AAPCS 16-byte stack alignment, CPU + mode başına ayrı SP), bellek bölgeleri `ps7_ddr_0` / `ps7_ocm_low_0` / + `ps7_ocm_high_0` / `ps7_qspi_linear`, OCM_CFG remap mekaniği, AMP çift + çekirdek DDR bölme, VMA/LMA örneği olarak `.ocm_data` (DDR'da LMA, OCM'de + VMA). Yazı 3000 → 3839 kelimeye çıktı (hedef üst sınırı 3500'ün biraz + üstünde; Zynq mimari detayı bunu hak ediyor). - Yayın kapısı durumu: son yayın 2026-05-21 (bandpass-sampling), bu çalıştırma 2026-05-26. Aradan 5 gün geçti — `min_yayin_araligi_gun = 2` fazlasıyla sağlanıyor. Bu çalıştırmada yeni PR açıldı.