섹션이 실행 시 있어야 할 주소(VMA) 와 파일에 저장된 주소(LMA).
VMA (Virtual Memory Address) — 실행 중에 이 주소에 있다고 가정하고 코드가 생성된다
- LMA (Load Memory Address) — — 펌웨어 이미지에서 이 주소에 기록된다
대부분은 같다
- .text — : VMA = LMA = 0x08000000 (플래시에서 그대로 실행)
- .rodata: VMA = LMA = 플래시
- .bss — : 초기 데이터가 없으므로 LMA가 무의미
다른 경우 — .data
.data
LMA = 0x08010000 (플래시) ← 초기값이 저장된 곳
VMA = 0x20000000 (RAM) ← 실행 중에 있어야 할 곳
startup 코드가 LMA에서 VMA로 복사한다
"어디에 저장하고 어디서 실행하는가"가 다를 때 이 개념이 필요하다.
링커 스크립트 표기
.data : {
_sdata = .; /* VMA 시작 */
*(.data*)
_edata = .;
} > RAM AT> FLASH
↑ ↑
VMA LMA
> RAM이 VMA, AT> FLASH가 LMA를 지정한다.
- _sidata = LOADADDR(.data); — /* LMA 값을 심볼로 */
LOADADDR()로 LMA를 얻어 startup 코드에 전달한다.
왜 이 구분이 필요한가
① .data 복사 (가장 흔함)
초기값은 비휘발성 저장소에 있어야 하고, 실행 중에는 쓰기 가능한 곳에 있어야 한다.
② RAM에서 실행하는 함수
.RamFunc
LMA = 플래시 (펌웨어에 포함)
VMA = RAM (여기서 실행)
플래시 쓰기 루틴처럼 자기가 있는 플래시를 지우면 안 되는 코드에 필요하다.
③ 부트로더 + 애플리케이션
앱을 다른 주소에 링크하고, 부트로더가 그 위치로 복사하거나 점프한다.
④ 압축 이미지
LMA = 압축된 데이터가 저장된 위치 VMA = 압축 해제 후 실행할 위치 startup 코드가 압축을 풀며 복사 플래시 공간이 부족할 때 쓴다.
확인 방법
arm-none-eabi-objdump -h firmware.elf
Sections:
Idx Name Size VMA LMA File off
1 .text 0000b0d0 08000000 08000000 00010000
2 .data 0000019c 20000000 0800b0d0 00020000
↑ ↑
VMA LMA ← 다르다!
3 .bss 00002000 2000019c 2000019c 0002019c
.data만 VMA와 LMA가 다른 것이 정상적인 모습이다.
잘못됐을 때의 증상
LMA를 지정하지 않으면 (> RAM 만 쓰고 AT> FLASH 를 빠뜨림)
→ 링커가 .data 를 RAM 주소에 "저장"하려 한다
→ 바이너리 파일이 이상하게 커지거나
→ 초기값이 펌웨어에 포함되지 않아 전역 변수가 쓰레기 값
"전역 변수 초기값이 이상하다" 면 링커 스크립트와 startup 복사 루프를 함께 확인해야 한다.
bin 파일과 hex 파일
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin
arm-none-eabi-objcopy -O ihex firmware.elf firmware.hex
.bin은 주소 정보가 없는 순수 바이트열이다.
LMA 순서대로 배치되며, 중간에 빈 공간이 있으면 0xFF로 채워진다.
어느 주소에 써야 하는지는 사람이 알아야 한다.
.hex는 주소 정보를 포함한다. 그래서 여러 영역에 흩어진 데이터도
하나의 파일로 다룰 수 있고, 프로그래머가 알아서 올바른 위치에 쓴다.
부트로더가 있는 시스템에서 .bin을 잘못된 주소에 쓰는 실수가 흔하므로
.hex를 쓰는 편이 안전하다.