Linker Script STM32: MEMORY, sections và tại sao .data cần hai địa chỉ
Giải thích linker script STM32 căn bản: MEMORY region, .text/.data/.bss, LMA vs VMA, startup copy.
Linker script (.ld) là file quyết định firmware của bạn nằm ở đâu trong bộ nhớ. Không phải compiler, không phải IDE - linker script mới là thứ nói cho toolchain biết địa chỉ nào chứa code, địa chỉ nào chứa data, vùng nào là RAM, vùng nào là Flash.
Khi bạn flash firmware lên STM32 và nó chạy đúng, linker script đang làm việc đằng sau lặng lẽ. Khi firmware chạy được trên board nhưng một số biến global bị sai giá trị - rất có thể linker script hoặc startup code đang có vấn đề.
Cấu trúc cơ bản
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 36K
}
SECTIONS
{
.text : { ... } > FLASH
.data : { ... } > RAM AT> FLASH
.bss : { ... } > RAM
}
Hai khối chính: MEMORY khai báo vùng nhớ vật lý, SECTIONS map từng loại dữ liệu vào vùng nhớ tương ứng.
MEMORY - khai báo địa chỉ thật của chip
ORIGIN là địa chỉ bắt đầu, LENGTH là kích thước. Với STM32G0B1:
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 144K
}
Attribute rx = readable + executable. xrw = executable + readable + writable.
Giá trị này không tùy tiện - phải khớp với datasheet chip. Nếu khai báo sai LENGTH, linker sẽ không báo lỗi nếu code vẫn nhỏ hơn, nhưng firmware có thể ghi đè vùng nhớ của chip khác trên PCB nếu dùng external bus.
Các section quan trọng
| Mục | Giá trị | Ghi chú |
|---|---|---|
| .text | Code và hằng số read-only | Nằm trong Flash. CPU fetch instruction từ đây. Bao gồm vector table, tất cả function, const global. |
| .rodata | Read-only data | Thường merge vào .text. String literal, const array, lookup table. |
| .data | Biến global/static có giá trị khởi tạo | Lưu trong Flash (LMA), copy sang RAM (VMA) khi startup. Ví dụ: `int x = 5;` |
| .bss | Biến global/static không khởi tạo hoặc = 0 | Không chiếm space trong Flash. Startup code zero-fill vùng này trong RAM. Ví dụ: `int y;` hoặc `int y = 0;` |
| .stack | Call stack của chương trình | Nằm trong RAM. Địa chỉ đầu stack thường là giá trị đầu tiên trong vector table (MSP). |
| .heap | Dynamic allocation (malloc) | Nằm trong RAM. Embedded thường tránh dùng hoặc giới hạn kỹ. |
LMA vs VMA - tại sao .data cần hai địa chỉ
Đây là điểm dễ gây nhầm nhất.
LMA (Load Memory Address) - địa chỉ lưu trữ của data trong file firmware, nằm trong Flash.
VMA (Virtual Memory Address) - địa chỉ thực thi khi runtime, nằm trong RAM.
Tại sao .data cần hai địa chỉ? Vì biến global có giá trị khởi tạo phải:
- Được lưu trong Flash khi bạn flash firmware (LMA) - vì Flash là non-volatile
- Được copy sang RAM khi startup (VMA) - vì CPU chỉ có thể ghi/đọc RAM, không ghi được Flash trực tiếp
.data :
{
_sdata = .; /* ký hiệu = địa chỉ đầu RAM */
*(.data .data.*)
_edata = .; /* ký hiệu = địa chỉ cuối RAM */
} >RAM AT> FLASH
/* ^VMA ^LMA */
/* Linker tự tính _sidata = địa chỉ LMA trong Flash */
>RAM AT> FLASH có nghĩa: VMA trong RAM, LMA trong Flash.
Sau đó, startup code chịu trách nhiệm copy:
/* Trong startup_stm32g0xx.s hoặc main trước khi gọi main() */
extern uint32_t _sdata, _edata, _sidata;
uint32_t *src = &_sidata; /* LMA - nguồn trong Flash */
uint32_t *dst = &_sdata; /* VMA - đích trong RAM */
while (dst < &_edata) {
*dst++ = *src++;
}
Nếu startup code không chạy (ví dụ: reset handler bị lỗi trước khi copy), biến global có giá trị khởi tạo sẽ chứa rác.
.bss - tiết kiệm Flash bằng cách không lưu zero
Biến int counter = 0; không cần lưu giá trị 0 trong Flash vì startup code sẽ zero-fill toàn bộ .bss:
extern uint32_t _sbss, _ebss;
uint32_t *p = &_sbss;
while (p < &_ebss) {
*p++ = 0;
}
Kết quả: .bss không chiếm byte nào trong file .bin flash, nhưng chiếm RAM khi chạy. Với firmware có nhiều buffer lớn khởi tạo zero, đây là tiết kiệm Flash đáng kể.
Chia Flash thành nhiều vùng
Khi cần dành một phần Flash để lưu data (config, EEPROM emulation), khai báo thêm MEMORY region:
MEMORY
{
/* STM32G0B1: 512K Flash, dual bank - mỗi bank 256K */
FLASH_APP (rx) : ORIGIN = 0x08000000, LENGTH = 240K /* Program */
FLASH_DATA (r) : ORIGIN = 0x0803C000, LENGTH = 16K /* EEPROM emu */
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 144K
}
SECTIONS
{
.text : { ... } > FLASH_APP
.data : { ... } > RAM AT> FLASH_APP
.bss : { ... } > RAM
/* Section riêng cho EEPROM emulation - linker không đụng vào */
.eeprom_data (NOLOAD) :
{
. = ALIGN(2048); /* Align theo page size */
KEEP(*(.eeprom_data))
} > FLASH_DATA
}
(NOLOAD) nói linker không load section này vào RAM - nó sống trong Flash.
KEEP(...) ngăn linker optimize bỏ section này dù không có code reference tới.
Sau đó trong C:
/* Đặt biến vào section cụ thể */
__attribute__((section(".eeprom_data")))
const uint8_t EepromPage[2048] = { [0 ... 2047] = 0xFF };
Kiểm tra kết quả linker
# Xem size từng section
arm-none-eabi-size build/firmware.elf
# Xem map file - nơi từng symbol đặt vào
arm-none-eabi-nm -S --size-sort build/firmware.elf | tail -20
# Xem toàn bộ map (nếu build tạo .map file)
cat build/firmware.map | grep -A3 ".data"
Checklist khi sửa linker script
Bài liên quan
- Flash, EEPROM và RAM khác nhau như thế nào?
- Startup Code STM32: reset handler và vector table
- STM32G0 Flash: linker script, EEPROM emulation và dual bank
Tài liệu tham khảo
- STM32G0B1 Reference Manual RM0444
- GNU LD Linker Scripts - sourceware.org
- Mastering the GNU Make - linker section, Embedded Artistry
Thấy nội dung này hữu ích?
Lưu lại hoặc chia sẻ cho người cũng đang học firmware, BIOS/UEFI và embedded systems.
Nội dung liên quan
Một số bài viết, ghi chú hoặc project có liên quan đến nội dung bạn vừa đọc.
Flash, EEPROM và RAM khác nhau như thế nào?
Giải thích nguyên lý hoạt động của Flash, EEPROM và RAM.
Flash Operations STM32: Erase, Write, Polling và tại sao program đứng
Cách thao tác Flash STM32 qua HAL: unlock, erase, write, polling vs interrupt, và nguyên nhân program freeze khi xóa Flash.
Reset Vector và Bootloader trên Cortex-M: VTOR, MSP và jump sequence
Reset vector là gì, VTOR hoạt động ra sao, và chuỗi bước cần thiết để bootloader jump đúng cách vào application - tránh crash sau khi có interrupt đầu tiên.
Biến note thành bài viết hoàn chỉnh
Notes là nơi ghi nhanh khái niệm.