컴파일된 오브젝트들을 메모리의 어느 주소에 어떻게 배치할지 지시하는 파일
(.ld).
기본 구조
- ENTRY(Reset_Handler) — /* 진입점 */
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
CCM (rw) : ORIGIN = 0x10000000, LENGTH = 64K
}
SECTIONS {
.isr_vector : { KEEP(*(.isr_vector)) } > FLASH
.text : { *(.text*) *(.rodata*) } > FLASH
.data : { … } > RAM AT> FLASH
.bss : { … } > RAM
}
MEMORY — 물리적 메모리 선언
- FLASH (rx) — r=읽기, x=실행
- RAM — (rwx) w=쓰기까지
속성은 정보 제공용이라 실제 보호를 하지는 않는다(그것은 MPU의 일). 링커가 경고를 내는 데 쓰인다.
SECTIONS — 배치 규칙
.text : {
. = ALIGN(4); /* 현재 위치를 4바이트 정렬 */
KEEP(*(.isr_vector)) /* gc-sections 로부터 보호 */
*(.text) /* 모든 오브젝트의 .text */
*(.text*) /* .text.func 같은 하위 섹션도 */
*(.rodata*)
. = ALIGN(4);
_etext = .; /* 현재 주소를 심볼로 정의 */
} > FLASH
.(점)은 현재 위치 카운터다. 이것을 조작해 정렬하거나 공간을 띄운다.
심볼 정의 — C에서 쓰는 법
_sdata = .;
extern uint32_t _sdata;
uint32_t *p = &_sdata; // ★ 반드시 & 를 붙인다
★ 링커 심볼에는 값이 없고 주소만 있다.
_sdata 자체를 읽으면 그 주소에 있는 데이터를 읽게 되어 엉뚱한 값이 나온다.
주소 자체가 우리가 원하는 값이므로 &가 필요하다.
초심자가 가장 많이 틀리는 부분이다.
실전 활용 ① — 부트로더와 앱 분리
/* 부트로더 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K
/* 애플리케이션 (별도 프로젝트) */ FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 480K
앱의 벡터 테이블도 0x08008000에 놓이고, 앱은 시작 시
SCB->VTOR = 0x08008000 을 설정한다.
실전 활용 ② — 특정 RAM에 배치
.ccmram : {
- _sccm = .;
- (.ccmram)
- _eccm = .; } > CCM AT> FLASH
__attribute__((section(".ccmram"))) uint32_t fast_buf[256];
주의 — CCM은 DMA가 접근하지 못하는 경우가 있다. DMA 버퍼를 여기 두면 전송이 안 된다.
실전 활용 ③ — 크기 검사
- _Min_Heap_Size — = 0x200; _Min_Stack_Size = 0x800;
._user_heap_stack : {
- . = . + _Min_Heap_Size;
- . = . + _Min_Stack_Size; } > RAM
ASSERT(_ebss + _Min_Heap_Size + _Min_Stack_Size <= ORIGIN(RAM) + LENGTH(RAM),
- "RAM 부족: 스택·힙 공간이 확보되지 않음")
빌드 시점에 실패하므로 런타임에 스택 오버플로로 죽는 것보다 훨씬 낫다.
실전 활용 ④ — 펌웨어 정보 심기
.fw_info 0x08007F00 : {
- KEEP(*(.fw_info)) } > FLASH
__attribute__((section(".fw_info"), used))
const FwInfo info = { .magic = 0xA5A5, .version = 0x010203, .crc = 0 };
고정된 주소에 버전 정보를 두면 부트로더가 앱을 실행하기 전에 유효성과 버전을 확인할 수 있다. OTA 구현의 기본 요소다.
맵 파일 — 문제 진단의 핵심 도구
-Wl,-Map=firmware.map
맵 파일에서 확인할 것
- 각 섹션의 실제 시작 주소와 크기
- 어느 오브젝트 파일이 얼마나 기여했는지
- 심볼별 주소 (HardFault의 PC 값을 여기서 찾는다)
- Discarded input sections (gc-sections 로 제거된 것)
HardFault 분석에서 PC 값을 맵 파일과 대조하면 어느 함수에서 죽었는지 알 수 있다. 맵 파일을 읽을 줄 아는 것이 임베디드 디버깅의 기본기다.
흔한 오류
-
"region FLASH overflowed by N bytes"
-
코드가 너무 크다. -Os, gc-sections, nano.specs 검토
-
"undefined reference to _sdata"
-
링커 스크립트에 심볼 정의가 없거나 이름이 다르다
-
"section .data VMA overlaps section .bss"
-
배치가 겹쳤다. ALIGN 또는 순서 확인