들어가며
2편에서 .text/.data/.bss 섹션과 링커 스크립트를 다뤘다. .data > RAM AT > FLASH 구문이 LMA와 VMA를 분리한다는 것까지 정리했다.
그런데 이 링커 스크립트는 누가, 언제, 어떻게 처리하는 걸까. main.c 하나를 작성하고 빌드 버튼을 누르면 어떤 일이 일어나는지 정확히 알지 못한 채로 쓰고 있었다.
ARM Cortex-M 툴체인
ARM Cortex-M을 타겟으로 빌드할 때 쓰는 컴파일러는 arm-none-eabi-gcc다.
이름을 뜯어보면:
arm: 타겟 아키텍처none: 운영체제 없음 (bare-metal)eabi: Embedded ABI — 함수 호출 규약, 데이터 정렬 방식 표준gcc: GNU Compiler Collection
STM32CubeIDE나 PlatformIO 같은 IDE를 쓰면 이 툴체인이 자동으로 설정된다. 직접 Makefile이나 CMake를 구성할 때는 직접 지정해야 한다.
툴체인에 포함된 주요 도구들:
| 도구 | 역할 |
|---|---|
arm-none-eabi-gcc | C/C++ 컴파일러 |
arm-none-eabi-as | 어셈블러 |
arm-none-eabi-ld | 링커 |
arm-none-eabi-objcopy | 포맷 변환 (.elf → .hex/.bin) |
arm-none-eabi-size | 섹션별 크기 출력 |
arm-none-eabi-objdump | 역어셈블, 섹션 정보 확인 |
main.c → 전처리 → 컴파일 → 어셈블 → 링크 → firmware.hex/.bin 전체 파이프라인
빌드 4단계
.c 파일 하나를 MCU에 올릴 수 있는 형태로 만들기까지 네 단계를 거친다.
1단계. 전처리 (Preprocessing)
#include, #define, #ifdef 같은 전처리 지시문을 처리한다. 헤더 파일을 소스에 붙여넣고, 매크로를 치환한다. 결과물은 .i 파일이다.
| |
전처리 결과를 직접 보면 헤더 파일이 통째로 붙어서 수천 줄짜리 파일이 되는 걸 확인할 수 있다. 컴파일러가 실제로 보는 것은 이 상태다.
2단계. 컴파일 (Compilation)
전처리된 C 코드를 어셈블리(.s)로 변환한다. 이 단계에서 컴파일러 최적화가 적용된다.
| |
-mcpu=cortex-m4 -mthumb가 중요하다. ARM Cortex-M은 Thumb-2 명령셋만 사용하기 때문에 -mthumb 없이 빌드하면 A32(ARM) 명령어가 생성되고 MCU에서 실행되지 않는다.
-O0(최적화 없음)과 -O2(최적화 켜짐)로 컴파일한 어셈블리를 비교하면 코드 양 차이가 꽤 크다. 최적화가 어떻게 동작하는지는 5편에서 따로 다룬다.
3단계. 어셈블 (Assembly)
어셈블리(.s)를 기계어로 변환한다. 결과물은 오브젝트 파일(.o)이다.
| |
오브젝트 파일은 아직 완전한 실행 파일이 아니다. 다른 파일에 정의된 함수나 변수의 주소가 미확정 상태로 남아 있다. 예를 들어 printf()를 호출하면 printf의 실제 주소는 이 단계에서 모른다.
4단계. 링킹 (Linking)
여러 .o 파일과 라이브러리를 하나로 합쳐 실행 파일을 만든다.
| |
이 단계에서 세 가지 일이 일어난다:
- 섹션 병합: 여러
.o의.text,.data,.bss를 하나로 합친다 - 주소 확정: 미완성이던 함수/변수 주소를 실제 주소로 채운다
- 메모리 배치: 링커 스크립트에 따라 각 섹션을 메모리 주소에 배치한다
결과물은 .elf 파일이다.
ELF 포맷
링킹 결과물인 .elf는 Executable and Linkable Format의 약자다. 리눅스 실행 파일과 동일한 포맷이다.
ELF 파일 안에는:
.text,.data,.bss섹션과 각각의 LMA/VMA 주소- 심볼 테이블 (함수/변수 이름과 주소 매핑)
- 디버그 정보 (소스 파일 이름, 줄 번호)
가 들어 있다. 디버거(GDB)가 ELF를 읽으면 소스 코드의 어느 줄이 어느 주소에 해당하는지 알 수 있는 이유다.
arm-none-eabi-size로 섹션 크기를 확인할 수 있다:
text + data= 플래시 사용량data + bss= RAM 사용량
빌드 후 이 숫자를 보는 습관이 있으면 메모리 초과를 미리 잡을 수 있다.
.hex / .bin 변환
플래시에 올리려면 ELF를 .hex 또는 .bin 형식으로 변환해야 한다. objcopy가 이 역할을 한다.
.hex: Intel HEX 포맷. 주소 정보와 데이터가 텍스트로 저장된다. ST-Link, J-Link 같은 디버거가 이 포맷을 주로 쓴다..bin: 순수 바이너리. 주소 정보 없이 데이터만 있다. DFU나 부트로더를 통해 올릴 때 자주 쓴다.
전체 흐름 정리
