개발하는 컴퓨터(호스트)와 실행할 기기(타깃)의 아키텍처가 다를 때 호스트에서 타깃용 바이너리를 만드는 것.
호스트: x86-64 리눅스/윈도우/맥
- 타깃 — : ARM Cortex-M
x86 컴파일러로는 ARM 명령어를 만들 수 없다 → 크로스 컴파일러 필요
툴체인 이름 읽는 법
arm-none-eabi-gcc
│ │ │ └── 도구 이름
│ │ └─────── ABI (Embedded ABI)
│ └───────────── OS (none = 베어메탈)
└────────────────── 아키텍처
arm-linux-gnueabihf-gcc
└── OS가 리눅스, hf = 하드웨어 부동소수점
none과 linux의 차이가 중요하다.
- arm-none-eabi — : OS 없음. newlib. MCU 펌웨어용 arm-linux-gnueabihf : 리눅스 있음. glibc. 임베디드 리눅스 애플리케이션용 잘못 고르면 시스템 콜을 기대하는 libc가 링크되어 동작하지 않는다.
구성 요소
- gcc — 컴파일러
- as — 어셈블러
- ld — 링커
- objcopy — 포맷 변환 (elf → bin/hex)
- objdump — 역어셈블, 섹션 정보
- size — 섹션 크기
- nm — 심볼 목록
- gdb — 디버거
- newlib — C 표준 라이브러리 (임베디드용)
필수 컴파일 옵션
arm-none-eabi-gcc \
-mcpu=cortex-m4 \ # 코어
-mthumb \ # Thumb 명령어 (Cortex-M 필수)
-mfpu=fpv4-sp-d16 -mfloat-abi=hard \ # FPU
-DSTM32F407xx \ # 칩 정의
-Os -g3 \
-ffunction-sections -fdata-sections \
-Wl,--gc-sections \
-T STM32F407.ld \ # 링커 스크립트
-Wl,-Map=out.map \
--specs=nano.specs \ # 경량 libc
main.c -o firmware.elf
-mfloat-abi 는 반드시 일치시켜야 한다.
- soft — : FPU 없음. 소프트웨어 에뮬레이션
- softfp — : FPU 사용하지만 함수 인자는 정수 레지스터로 (호환성)
- hard — : FPU 레지스터로 인자 전달. 가장 빠름
라이브러리와 ABI가 다르면 링크 에러가 난다.
error: uses VFP register arguments, output does not 가 그것이다.
반호스팅(semihosting) — 주의할 함정
--specs=rdimon.specs # printf 가 디버거를 통해 PC 콘솔로 출력
--specs=nosys.specs # 시스템 콜을 스텁으로 (아무것도 안 함)
반호스팅은 디버거가 연결되어 있어야 동작한다.
디버거 없이 실행하면 printf에서 멈춘다 — 브레이크포인트 명령을
실행하는데 응답할 디버거가 없기 때문이다.
양산 펌웨어에서 반드시 제거해야 하며, "디버거를 빼면 동작하지 않는다"의 대표 원인이다.
_write 재구현 — printf를 UART로
int _write(int fd, char *ptr, int len) {
HAL_UART_Transmit(&huart2, (uint8_t *)ptr, len, HAL_MAX_DELAY);
return len;
}
nosys.specs와 함께 쓰면 printf가 UART로 나간다.
임베디드에서 가장 흔한 로그 출력 방법이다.
빌드 시스템
- Makefile — — 전통적. 세밀한 제어
- CMake — — 크로스 플랫폼. 툴체인 파일로 크로스 컴파일 설정
- PlatformIO — — 라이브러리 관리 포함
- 벤더 IDE — — STM32CubeIDE, MCUXpresso (Eclipse 기반)
# CMake 툴체인 파일
set(CMAKE_SYSTEM_NAME Generic) # ★ 중요
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) # ★ 링크 테스트 회피
★ CMAKE_TRY_COMPILE_TARGET_TYPE 을 설정하지 않으면
CMake가 컴파일러 검사를 위해 실행 파일을 만들려다 실패한다.
링커 스크립트 없이는 링크가 안 되기 때문이다. 자주 겪는 문제다.
결과물 변환
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin
arm-none-eabi-objcopy -O ihex firmware.elf firmware.hex
arm-none-eabi-size firmware.elf
재현 가능한 빌드
같은 소스가 항상 같은 바이너리를 만들어야
-
배포된 펌웨어와 소스의 대응을 검증할 수 있다
-
인증·감사에서 요구된다
-
방해 요소: DATE, TIME 매크로, 절대 경로, 빌드 순서
-
-ffile-prefix-map, SOURCE_DATE_EPOCH 등으로 제거
도커 컨테이너로 툴체인 버전까지 고정하는 것이 현대적 관행이다. 컴파일러 버전이 다르면 바이너리가 달라지기 때문이다.