Bare metal C Ch03 빌드과정과 GNU툴체인 - Hongjayeong-02/Basic_SoC_implementation GitHub Wiki
Ch03. 빌드 과정과 GNU ARM Toolchain
목표
STM32CubeIDE의Build버튼 뒤에서 실제로 일어나는 과정을
전처리 → 컴파일 → 어셈블 → 링킹 → 이미지 생성 순서로 직접 이해한다.
현재 자료 기준 안내
Notion에는 Ch03을build.sh로 단계별 실행한 학습 기록이 정리되어 있다.
다만 현재 첨부한bare-metal 실습코드.zip에는 별도의Ch03폴더나build.sh파일은 포함되어 있지 않다.
따라서 이 페이지의 명령어와 결과는 Notion 학습 기록을 기준으로 정리하고, 실습 소스는 Ch02의 실제main.c를 사용한다.
1. 왜 Build 과정을 나눠서 봐야 하는가
IDE에서 Build 버튼을 누르면 한 번에 끝나는 것처럼 보이지만, 실제 내부 과정은 여러 단계로 나뉜다.
main.c
│
├─ 1. Preprocessing
▼
main.i
│
├─ 2. Compilation
▼
main.s
│
├─ 3. Assembly
▼
main.o
│
├─ 4. Linking
▼
firmware.elf
│
├─ 5. Image conversion
▼
firmware.hex / firmware.bin
이 흐름을 알면 다음과 같은 오류가 어느 단계의 문제인지 구분하기 쉬워진다.
#define,#include관련 문제 → 전처리 단계- C 문법/타입 오류 → 컴파일 단계
- 어셈블리 문법 오류 → 어셈블 단계
undefined reference→ 링킹 단계region ... overflowed→ 메모리 배치/링커스크립트 문제
2. 단계별 정리
| 단계 | 입력 | 출력 | 대표 명령 | 역할 |
|---|---|---|---|---|
| Preprocessing | .c |
.i |
gcc -E |
#include, #define 전개 |
| Compilation | .i |
.s |
gcc -S |
C → ARM Assembly |
| Assembly | .s |
.o |
gcc -c |
Assembly → Object code |
| Linking | .o |
.elf |
gcc -T linker.ld |
Object 파일 결합 + 주소 배치 |
| Image conversion | .elf |
.hex, .bin |
objcopy |
Flash에 쓸 이미지 추출 |
3. 전처리 — main.c → main.i
arm-none-eabi-gcc \
-mcpu=cortex-m4 \
-mthumb \
-mfloat-abi=soft \
-E main.c \
-o main.i
-E 옵션은 전처리만 수행한다.
대표적으로 다음이 처리된다.
#define LD1_PIN (1UL << 0)
컴파일러가 C 문법으로 분석하기 전에 전처리기가 단순 치환한다.
즉 전처리기는:
#define
#include
#if / #ifdef
같은 전처리 지시문을 처리하지만, C 프로그램의 의미를 이해하는 단계는 아니다.
4. 컴파일 — main.i → main.s
arm-none-eabi-gcc \
-mcpu=cortex-m4 \
-mthumb \
-mfloat-abi=soft \
-S main.i \
-o main.s
-S는 C 코드를 ARM Assembly로 변환하고 멈춘다.
예를 들어 C의:
while (count--)
{
__asm("nop");
}
같은 구문은 실제로는 조건 비교, 감소, 분기 명령들로 바뀐다.
이 단계에서 중요한 점은:
C 언어의
if,while, 함수 호출도 CPU가 실행할 때는 결국 명령어들의 조합으로 바뀐다는 것.
5. 어셈블 — main.s → main.o
arm-none-eabi-gcc \
-mcpu=cortex-m4 \
-mthumb \
-mfloat-abi=soft \
-c main.s \
-o main.o
Assembly를 기계어가 포함된 Object file로 변환한다.
main.o는 아직 완성된 firmware가 아니다.
이 상태에서는:
- 함수/변수의 최종 주소가 확정되지 않을 수 있고
- startup object와 아직 결합되지 않았고
- Flash/RAM 배치도 최종 결정되지 않았다.
즉 .o는 링크 전 중간 산출물이다.
6. 링킹 — .o → .elf
Notion 학습 기록의 명령은 다음과 같다.
arm-none-eabi-gcc \
-mcpu=cortex-m4 \
-mthumb \
-mfloat-abi=soft \
-T linker.ld \
main.o startup.o \
-o ch03_build.elf
링커는 여러 Object file을 하나로 합친다.
main.o
startup.o
│
├─ Linker Script
▼
ch03_build.elf
링커스크립트는 다음과 같은 질문에 답한다.
Vector Table은 어디에 둘 것인가?
.text는 Flash 어디에 둘 것인가?
.data는 Flash에 어떻게 저장하고 RAM 어디에서 실행할 것인가?
.bss는 RAM 어디에 둘 것인가?
Stack의 시작 주소는 어디인가?
이 내용은 Ch04에서 직접 다룬다.
7. ELF → HEX
arm-none-eabi-objcopy \
-O ihex \
ch03_build.elf \
ch03_build.hex
.elf
ELF에는 실행 코드뿐 아니라 다음 정보도 포함될 수 있다.
- Symbol
- Section
- 함수/변수 정보
- Debugging information
따라서 GDB, nm, objdump, size 같은 도구가 분석하기 좋다.
.hex / .bin
실제 Flash programming에 사용하기 쉬운 메모리 이미지다.
ELF
├─ Code
├─ Data
├─ Symbols
└─ Debug information
↓ objcopy
HEX / BIN
└─ Flash에 기록할 실제 이미지
8. objdump로 Object file 확인
arm-none-eabi-objdump -d main.o
-d는 Object file의 기계어를 다시 Assembly 형태로 보여준다.
이를 통해:
C source
↓ compiler
Assembly
↓ assembler
Machine code
관계를 직접 확인할 수 있다.
Notion 학습 기록에서는 delay() 루프가 subs, cmp, bne 계열의 저수준 명령으로 변환되는 것을 확인했다.
9. Compiler option
-mcpu=cortex-m4
Target CPU를 Cortex-M4로 지정한다.
-mcpu=cortex-m4
STM32F439ZI는 Cortex-M4F core를 사용한다.
-mthumb
Cortex-M 계열이 사용하는 Thumb instruction set을 대상으로 코드를 생성한다.
-mthumb
-mfloat-abi=soft
이 Ch03 실습은 GPIO register를 조작하는 정수 연산만 사용하므로 Notion 기록에서는:
-mfloat-abi=soft
를 사용했다.
후속 CMSIS 실습에서는 F439ZI의 FPv4-SP FPU를 사용하기 위해:
-mfloat-abi=hard
-mfpu=fpv4-sp-d16
옵션을 사용하는 흐름으로 정리되어 있다.
10. 실습에 사용한 실제 main.c
현재 첨부 ZIP에서 확인한 Ch02_register/Src/main.c이다.
#define PERIPH_BASE (0x40000000UL)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000UL)
#define RCC_BASE (AHB1PERIPH_BASE + 0x3800UL)
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400UL)
#define RCC_AHB1ENR (*(volatile unsigned int *)(RCC_BASE + 0x30UL))
#define GPIOB_MODER (*(volatile unsigned int *)(GPIOB_BASE + 0x00UL))
#define GPIOB_ODR (*(volatile unsigned int *)(GPIOB_BASE + 0x14UL))
#define GPIOBEN (1UL << 1)
#define LD1_PIN (1UL << 0)
static void delay(volatile unsigned int count)
{
while (count--) { __asm("nop"); }
}
int main(void)
{
RCC_AHB1ENR |= GPIOBEN;
GPIOB_MODER &= ~(0x3UL << (0 * 2));
GPIOB_MODER |= (0x1UL << (0 * 2));
while (1)
{
GPIOB_ODR ^= LD1_PIN;
delay(800000);
}
}
Ch03에서는 이 소스의 기능 자체를 바꾸는 것이 목적이 아니라, 이 코드가 어떤 단계를 거쳐 ELF/HEX로 변환되는지 확인하는 것이 핵심이다.
11. Notion에 기록된 실습 결과
Notion 기록 기준:
text = 252 bytes
data = 0 bytes
bss = 0 bytes
Ch02에서 동일한 소스와 동일한 링크 조건을 사용했기 때문에, Make를 사용한 결과와 직접 명령을 실행한 결과가 같았다고 기록되어 있다.
핵심은:
Build 도구가 달라도 같은 source + 같은 compiler option + 같은 linker script를 사용하면 결과는 동일해야 한다.
12. 자주 사용하는 GNU ARM 도구
arm-none-eabi-gcc
arm-none-eabi-objcopy
arm-none-eabi-objdump
arm-none-eabi-size
arm-none-eabi-nm
| Tool | 역할 |
|---|---|
gcc |
Compile / Assemble / Link driver |
objcopy |
ELF → HEX/BIN 변환 |
objdump |
Object/ELF disassembly 및 정보 확인 |
size |
.text, .data, .bss 크기 확인 |
nm |
ELF symbol 확인 |
13. 핵심 정리
Build pipeline
.c
↓ Preprocess
.k
↓ Compile
.s
↓ Assemble
.o
↓ Link
.elf
↓ objcopy
.hex / .bin
파일별 의미
| 확장자 | 의미 |
|---|---|
.c |
C source |
.i |
전처리 완료 C source |
.s |
Assembly source |
.o |
Relocatable object |
.elf |
최종 linked executable |
.hex/.bin |
Flash programming image |
한 줄 요약
IDE의 Build 버튼은 하나의 마법 버튼이 아니라 GNU Toolchain의 여러 단계를 연속으로 실행한 결과다.
14. 이 챕터에서 배운 점
- Build 과정이 단계별로 어떤 파일을 만드는지 이해했다.
- C 코드가 Assembly와 machine code로 변환되는 흐름을 확인했다.
.o,.elf,.hex의 역할 차이를 구분했다.- Linker가 단순 파일 결합이 아니라 실제 메모리 주소 배치까지 담당한다는 것을 확인했다.
- 다음 Ch04에서 Linker Script와 Startup code를 직접 작성하기 위한 기반을 만들었다.
네비게이션: 이전: Ch02. 메모리 주소로 레지스터 직접 정의 · 전체 목차 · 다음: Ch04. Linker Script와 Startup File