Belajar Firmware Engineer - Memory & Linker
Episode 8 of 28

Belajar Firmware Engineer - Memory & Linker

Menjadi penguasa memori firmware: memahami pembagian flash dan RAM ke section .text, .rodata, .data, dan .bss, membaca dan mengubah linker script MEMORY/SECTIONS, penempatan stack dan heap, serta menganalisis file .map untuk mendeteksi pemborosan memori.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 7 firmware kita berjalan multitasking dengan FreeRTOS, pada episode ini kita turun ke lapisan yang paling menentukan kestabilan: memori. RTOS yang benar pun akan ambruk jika stack task menimpa data, atau heap penuh tanpa diketahui.

Mengapa topik ini penting? Karena di firmware, memori adalah sumber daya yang tidak bisa di-query di runtime — kalian harus tahu sejak build time di mana program, data, stack, dan heap berada. Kesalahan di sini muncul sebagai reboot loop di lapangan yang sangat sulit direproduksi. Linker script dan file .map adalah peta lengkapnya.

Pembagian Memori: Flash vs RAM

Program firmware terbagi menjadi beberapa section yang disimpan ke dua jenis memori:

SectionIsiLokasiContoh
.textKode programFlashFungsi, ISR
.rodataData konstanFlashString, lookup table
.dataVariabel global terinisialisasiRAM (disalin dari flash saat boot)int counter = 5;
.bssVariabel global nol/uninitializedRAM (dizero-kan saat boot)int buffer[1024];
.stackStack CPURAM (atas)Konteks fungsi & ISR
.heapAlokasi dinamisRAMmalloc / pvPortMalloc

Kunci pemahaman: .data dan .bss tinggal di RAM, tapi nilainya harus disiapkan saat boot — itulah tugas startup code (episode 2): menyalin .data dari flash dan men-zero-kan .bss.

Linker Script

Linker script memberitahu linker ke mana tiap section ditaruh. Untuk STM32F103 (64 KB flash, 20 KB RAM) biasanya terlihat seperti ini:

STM32F103C8.ld - MEMORY (inti)
MEMORY
{
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 64K
    RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}

Dan penempatan section di bagian SECTIONS:

SECTIONS - pola penempatan (inti)
SECTIONS
{
    .isr_vector : { KEEP(*(.isr_vector)) } > FLASH
    .text       : { *(.text*) }           > FLASH
    .rodata     : { *(.rodata*) }         > FLASH
 
    _sidata = LOADADDR(.data);
    .data :
    {
        _sdata = .;
        *(.data*);
        _edata = .;
    } > RAM AT > FLASH
 
    .bss :
    {
        _sbss = .;
        *(.bss*);
        _ebss = .;
    } > RAM
 
    _estack = ORIGIN(RAM) + LENGTH(RAM);
}

Simbol seperti _sdata, _sidata, _estack dipakai oleh startup code untuk menyalin dan nol-kan section. Mengubah LENGTH RAM/FLASH tanpa tahu sebab-akibatnya adalah cara tercepat merusak firmware — selalu sesuaikan dengan angka di datasheet chip.

Note

Istilah yang wajib dipahami: AT > FLASH artinya data awalnya "dilahirkan" di flash (di sinilah _sidata menunjuk), lalu dijalankan dari RAM (> RAM). Tanpa perbedaan load address vs runtime address ini, variabel terinisialisasi tidak akan pernah berisi nilai yang benar.

Stack vs Heap

Stack: dipakai setiap pemanggilan fungsi — variabel lokal, konteks ISR, dan konteks task RTOS. Cepat, otomatis, tapi terbatas. Overflow (stack tumbuh menimpa data) = korupsi memori diam-diam. Pada Cortex-M, stack tumbuh ke bawah dari _estack (puncak RAM) — lebih rendah alamat = tumbuh mendekati data.

Heap: dipakai malloc/pvPortMalloc (FreeRTOS). Di firmware, alokasi dinamis harus dibatasi — fragmentasi dan alokasi gagal di tengah operasi bisa membahayakan sistem real-time. Banyak project produksi memakai alokasi statis penuh (semua buffer dideklarasikan di compile time).

Menganalisis File .map

File .map adalah laporan lengkap linker — alat detektif pemborosan memori:

Buat dan cek file .map
arm-none-eabi-gcc -Wl,-Map=firmware.map ...
arm-none-eabi-size firmware.elf
Contoh output size
   text    data     bss     dec     hex filename
  18344     388    1416   20148    4eb4 firmware.elf

Bacaan: text = flash untuk kode+rodata, data = bagian .data (di flash + RAM), bss = RAM untuk variabel. Bandingkan dengan LENGTH di linker script — kalau text mendekati flash, atau data+bss mendekati RAM, kalian tahu sisa ruang sebelum mengoptimasi.

Praktik: Konfigurasi Linker

Latihan nyata: sesuaikan linker script dengan chip yang kalian pakai. Untuk chip 256 KB flash / 64 KB RAM:

Sesuaikan MEMORY (contoh STM32F103ZE)
MEMORY
{
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 512K
    RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}

Langkah verifikasi setelah mengubah linker script:

Cek apakah konfigurasi benar
1. Build; cek tidak ada error "region FLASH overflowed"
2. baca output `arm-none-eabi-size` - bandingkan dengan LENGTH
3. flash + boot; jika langsung HardFault -> cek _estack & vector table
4. (lanjut) analisis detail dengan `arm-none-eabi-nm -n firmware.elf`

Untuk FreeRTOS, ingat episode 7: setiap task butuh stack sendiri dari heap. Ukur kebutuhan dengan uxTaskGetStackHighWaterMark saat debug.

Warning

Aturan firmware produksi: minimalkan alokasi dinamis. Alokasikan buffer sekali di setup (atau statis), jangan malloc di loop. FreeRTOS punya xTaskCreateStatic untuk task statis, dan heap_4/heap_5 menyatukan fragmentasi — tapi desain terbaik adalah tidak memerlukan alokasi dinamis sama sekali.

Kesalahan Umum Memory

  1. Mengubah LENGTH tanpa membaca datasheet — alamat register menimpa flash/RAM lain.
  2. Buffer statis raksasa di .bss — RAM penuh sebelum runtime; gunakan size untuk audit.
  3. Stack overflow tanpa deteksi — gunakan MPU/stack canary (episode 18) dan ukur free stack task di FreeRTOS.
  4. malloc di loop — fragmentasi dan crash acak.
  5. Mengabaikan alignment — struct pakai __attribute__((packed)) hanya untuk register; misalignment = fault + performa buruk.

Penutup

Inti yang harus dibawa pulang:

  • Memori dibagi .text/.rodata (flash) dan .data/.bss (RAM), disiapkan oleh startup code saat boot.
  • Linker script (MEMORY + SECTIONS) menentukan penempatan; simbol _sdata, _sidata, _estack dipakai startup code.
  • Stack otomatis tapi terbatas (tumbuh ke bawah dari puncak RAM); heap dinamis sebaiknya dihindari di produksi.
  • Audit memori dengan file .map dan arm-none-eabi-size.
  • MPU, stack canary, dan free-stack check menyelamatkan produksi.

Di episode 9 selanjutnya kita menghadapi momen paling penting seorang firmware engineer: Debugging & Tracing — GDB, semihosting, RTT, dan fault handlers untuk membedah firmware yang tidak berperilaku seperti harapan. Karena di dunia nyata, firmware hampir selalu salah pada percobaan pertama!

Belajar Firmware Engineer - Memory & Linker | Belajar Firmware Engineer