Skip to content

Chapter 04

Hongjayeong-02 edited this page Oct 5, 2026 · 3 revisions

04. Linker Script & Startup Code

Goal

STM32F439ZI가 Reset된 뒤 main()에 도달하기까지의 과정을 이해하고, Linker Script와 Startup Code가 .data, .bss, Stack, Vector Table을 어떻게 준비하는지 확인한다.

Reset
  ↓
Vector Table
  ↓
Initial MSP / Reset_Handler
  ↓
.data Copy
  ↓
.bss Clear
  ↓
SystemInit()
  ↓
main()

Ch02~03에서는 임시 linker/startup 파일을 사용했다.

Ch04부터는 이후 모든 chapter에서 사용할:

common/linker/stm32f439zi.ld
common/startup/startup_stm32f439zi.s

를 기준으로 build한다.


1. Theory

STM32F439ZI Memory

이번 project에서 사용하는 주요 memory:

FLASH
Start : 0x08000000
Size  : 2048 KB

SRAM
Start : 0x20000000
Size  : 128 KB

CCM RAM
Start : 0x10000000
Size  : 64 KB

CCM RAM은 이번 실습에서는 사용하지 않는다.

STM32F439ZI의 128KB SRAM 끝 주소:

0x20000000 + 0x00020000
=
0x20020000

따라서 initial stack pointer로 사용하는 _estack은:

0x20020000

이다.


2. Linker Script

Linker Script는 프로그램의 section을 어느 memory address에 배치할지 결정한다.

대표 section:

.isr_vector → FLASH
.text       → FLASH
.rodata     → FLASH
.data       → RAM, initial value는 FLASH
.bss        → RAM

개념적인 memory layout:

FLASH
0x08000000
┌──────────────────┐
│ .isr_vector      │
├──────────────────┤
│ .text            │
├──────────────────┤
│ .rodata          │
├──────────────────┤
│ .data init value │
└──────────────────┘


RAM
0x20000000
┌──────────────────┐
│ .data            │
├──────────────────┤
│ .bss             │
├──────────────────┤
│ heap / stack     │
└──────────────────┘
0x20020000

.data

초기값이 있는 global/static variable.

Example:

unsigned int g_blink_count = 5;

실행 중에는 RAM에 있어야 하지만, 전원이 꺼져도 초기값 5는 firmware에 남아 있어야 한다.

따라서:

FLASH
initial value = 5
      ↓
Reset_Handler copy
      ↓
RAM
g_blink_count = 5

가 된다.


.bss

초기값이 없는 global/static variable.

Example:

unsigned int g_toggle_flag;

C에서는 program 시작 시:

g_toggle_flag = 0

이어야 한다.

따라서 Reset_Handler가 .bss 영역을 RAM에서 0으로 채운다.


3. LMA and VMA

.data를 이해할 때 중요한 개념이다.

LMA

Load Memory Address

Firmware image에서 초기값이 저장된 위치.

.data의 경우:

FLASH

VMA

Virtual / Run Memory Address

실행 중 CPU가 실제 variable을 접근하는 위치.

.data의 경우:

RAM

따라서:

.data

LMA → FLASH
VMA → RAM

이다.


4. Startup Code

Cortex-M4는 Reset 직후 main()으로 바로 가지 않는다.

Vector Table의 첫 두 word를 hardware가 읽는다.

Vector[0]
→ Initial MSP

Vector[1]
→ Reset_Handler

즉 conceptually:

MSP ← Vector[0]
PC  ← Vector[1]

가 된다.


Reset_Handler

Startup code의 핵심 순서:

Reset_Handler
     ↓
Initial Stack
     ↓
Copy .data
FLASH → RAM
     ↓
Clear .bss
RAM → 0
     ↓
SystemInit()
     ↓
main()

이 작업이 끝나야 C program이 기대하는 runtime environment가 만들어진다.


5. Vector Table & Weak Handler

STM32F439ZI는 Cortex exception뿐 아니라 많은 peripheral IRQ를 가진다.

이 project의 startup file에는 STM32F439ZI에 맞는 IRQ vector table을 구성했다.

사용하지 않는 handler는 보통:

weak symbol
     ↓
Default_Handler

로 연결한다.

나중에 application에서:

void EXTI15_10_IRQHandler(void)
{
    ...
}

처럼 같은 이름의 handler를 정의하면 strong symbol이 weak symbol을 대신한다.

즉 startup file을 매번 수정할 필요가 없다.


6. Practice Files

이 chapter의 실제 실습 구성:

Ch04/
├── main.c
├── stm32f439zi.ld
└── startup_stm32f439zi.s

이후 chapter에서는 공용화해서:

common/
├── linker/
│   └── stm32f439zi.ld
└── startup/
    └── startup_stm32f439zi.s

형태로 사용한다.

stm32f439zi.ld와 startup_stm32f439zi.s의 전체 원본은 현재 Notion 본문에는 포함되어 있지 않다. 재실습 시 archived project의 실제 파일을 이 Wiki에 추가한다.


7. Practice Code

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)

/* .data section verification */
unsigned int g_blink_count = 5;

/* .bss section verification */
unsigned int g_toggle_flag;

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));

    /*
     * .bss initialization check
     *
     * g_toggle_flag should be 0 after Reset_Handler.
     * If not, use fast blinking as an error indication.
     */
    if (g_toggle_flag != 0)
    {
        while (1)
        {
            GPIOB_ODR ^= LD1_PIN;
            delay(50000);
        }
    }

    while (1)
    {
        for (unsigned int i = 0;
             i < g_blink_count;
             i++)
        {
            GPIOB_ODR ^= LD1_PIN;
            delay(800000);
        }

        delay(3200000);

        g_toggle_flag++;
    }
}

8. Why These Two Variables?

g_blink_count

unsigned int g_blink_count = 5;

초기값이 있으므로:

.data

section에 위치한다.

Startup이 정상이라면:

FLASH의 5
   ↓
Reset_Handler
   ↓
RAM g_blink_count = 5

가 되어야 한다.


g_toggle_flag

unsigned int g_toggle_flag;

초기값이 없으므로:

.bss

section에 위치한다.

Startup이 정상이라면 main() 진입 시:

g_toggle_flag = 0

이어야 한다.

그래서:

if (g_toggle_flag != 0)

이면 startup 문제가 있다고 판단하고 LED를 빠르게 깜빡이게 한다.

즉 이 chapter의 LED 동작은 단순 blink가 아니라:

.data / .bss Startup Verification

용이다.


9. Linker Symbols

Startup code와 linker script는 symbol을 통해 주소를 공유한다.

대표적으로:

_estack
_sidata
_sdata
_edata
_sbss
_ebss

의 의미는 다음과 같다.

_estack
→ initial stack address

_sidata
→ FLASH에 저장된 .data 초기값 시작

_sdata
→ RAM의 .data 시작

_edata
→ RAM의 .data 끝

_sbss
→ .bss 시작

_ebss
→ .bss 끝

Startup은 이를 이용해:

_sidata → _sdata ~ _edata

범위로 data를 복사하고:

_sbss ~ _ebss

범위를 0으로 초기화한다.


10. Execution Flow

Power / Reset
      ↓
Vector Table
      ↓
MSP = _estack
      ↓
PC = Reset_Handler
      ↓
.data Copy
      ↓
.bss Clear
      ↓
SystemInit()
      ↓
main()
      ↓
g_blink_count = 5
g_toggle_flag = 0

11. Practice Commands

Build:

make

Symbol 확인:

arm-none-eabi-nm \
    ch04_linker_startup.elf \
    | grep -E "_estack|_sdata|_edata|_sbss|_ebss"

Section 확인:

arm-none-eabi-objdump \
    -h ch04_linker_startup.elf

Program size:

arm-none-eabi-size \
    ch04_linker_startup.elf

12. Recorded Result

실습에서 확인한 symbol:

20000008 B _ebss
20000004 D _edata
20020000 R _estack
20000004 B _sbss
20000000 D _sdata

_estack

RAM Start = 0x20000000
RAM Size  = 128 KB = 0x20000

0x20000000
+
0x00020000
=
0x20020000

실제 결과:

_estack = 0x20020000

정확히 일치한다.


.data

_sdata = 0x20000000
_edata = 0x20000004

크기:

4 bytes

이는:

unsigned int g_blink_count = 5;

하나와 일치한다.


.bss

_sbss = 0x20000004
_ebss = 0x20000008

실제 global variable 영역:

4 bytes

로:

unsigned int g_toggle_flag;

와 일치한다.

Build 결과에서는 heap/stack reservation까지 포함해 .bss 관련 RAM 사용량이 더 크게 표시될 수 있다.


13. Verification

재실습할 때는 build 성공만 보는 것보다 runtime variable까지 직접 확인하는 것이 좋다.

1. Build

make clean
make

2. Symbol

arm-none-eabi-nm \
    ch04_linker_startup.elf \
    | grep -E "_estack|_sdata|_edata|_sbss|_ebss"

확인:

_estack = 0x20020000

3. ELF Sections

arm-none-eabi-objdump \
    -h ch04_linker_startup.elf

확인:

.isr_vector
.text
.data
.bss

4. Debugger

main() 진입 직후:

g_blink_count = 5
g_toggle_flag = 0

인지 확인한다.

이 화면이:

.data copy PASS
.bss clear PASS

를 가장 직접적으로 보여준다.


5. Board

정상 startup:

Normal blink pattern

.bss 초기화 이상:

Fast continuous blink

14. Screenshots to Add

images/ch04_build.png
images/ch04_symbols.png
images/ch04_sections.png
images/ch04_debug_variables.png
images/ch04_led.gif

이 chapter에서는 특히:

nm symbol result
+
Debugger variable result

두 장이 가장 중요하다.


15. Key Points

Linker Script
→ Where?

Startup Code
→ What happens after reset?
  • Linker Script는 Flash/RAM과 section 배치를 결정한다.
  • Vector Table의 첫 word는 initial MSP, 두 번째 word는 Reset_Handler다.
  • .data는 초기값이 Flash에 있고 runtime에는 RAM에 존재한다.
  • .bss는 RAM 공간만 필요하며 startup에서 0으로 초기화한다.
  • .data는 LMA와 VMA가 다르다.
  • Reset_Handler가 C runtime을 준비한 뒤 main()을 호출한다.
  • weak interrupt handler를 사용하면 application에서 필요한 IRQ만 override할 수 있다.
  • nm, objdump, debugger를 통해 linker/startup 동작을 검증할 수 있다.

Clone this wiki locally