본문 바로가기
Robot/MCU

Cortex-M3 Start Up Code 분석 2부

by lifeseed 2013. 9. 4.

※ 아래의 내용은 직접 작성한 내용이며, 경어를 사용하지 않았습니다.

읽으시는동안 불편하시더라도 이해 부탁드립니다.

그리고 테스트를 위한 보드는 http://oroca.org 에서 공동구매한 stm32 smart robot board를 사용하였습니다.

 

cf) 관련 글 목록 

0. STM32F103CB 개발을 위한 ST Micro Resource 활용 

http://lifeseed.tistory.com/57 

1. Cortex-M3 Link Script 

 http://lifeseed.tistory.com/55

2. Cortex-M3 Start Up Code 분석 1부 

 http://lifeseed.tistory.com/56

 

 

0. 시작하기 전에

Cortex-M3 Link Script 의 0. 시작하기전에.. chapter에 설명한 것과 동일하게 CMSIS에서 제공하는 startup_ARMCM3.S 에 stm과 관련된 사항을 포팅하였다.

vector table에 정의된 handler함수들을 제외하고는 startup_ARMCM3.S와 동일한 내용으로 구성되어 있다.

 

여기서는 지난 시간에 이어 startup_ARMCM3.S 코드의 reset_handler 코드를 분석하고자 한다.

 

 

1. Flash Map 과 Memory Map 비교

 본격적으로 코드를 분석하기 이전에 실제 빌드된 binary의  Map과 Memory에 상주되는 Map의 차이를 알아보는 것도 의미가 있을 듯하다.

무엇보다 내가 작성한 코드가 어디에 저장되어 있으며, 동작할 때는 어디서 돌고 있는지 정도는 알아 두도록 하자. 특히 Start Up Code에서 FLASH영역에서 RAM영역으로 Copy동작이 일어나는데, 여기에 대한 이해를 도울 수 있다.

 

 

Flash Map

Memory Map

ld section name

 

FLASH_ORIGIN
.text    
   
.ARM.extab

code area

code area

.ARM.exidx    
   
  __etext  
.data  

data area

 
RAM_ORIGIN __data_start__
 

data area

  __data_end__
 

bss area

 
 

heap area

 
 

stack area

 

 

 

 

1) Flash Map

 코드를 빌드하면 elf가 생성되고 objcopy 명령을 이용하여 binary 를 생성할 수 있다. 이 때 생성된 binary가 flash에 저장된다.

binary에 저당되는 내용은 link script에서 선언된 FLASH 영역과 RAM 영역중 .data section이 저장되는데, 앞서 언급한 바와 같이 .data영역에는 0이 아닌 초기 값을 가지는 변수의 데이터들이 저장되어 있다.

그외 bss, heap, stack등은 코드 실행 중 생성할 수 있는 값들이기 때문에 따로 flash에 저장되는 binary에 포함 시키지 않는다.

 

2) Memory Map

 Link Script에 선언된 주소 값에 의해 실제 메모리상에 배치되는 각 영역들의 주소 값을 가진다. Link Script에 의해 동작하는 Program Code 들은 FLASH_ORIGIN (본 예제에서는 0x80000) 부터 배치되며, read & writable한 영역들은 모두 RAM영역에 배치된다. 단 .data 영역은 0이 아닌 초기 값을 가져야 하므로 코드 시작시 Flash로 부터 복사해 와야 하며, bss영역은 0으로 초기화 되어야 하는데, 이런 일련의 과정들이 초기화 코드에 포함되어야 한다.

 

2. Reset Handler 분석

 

    .text
    .thumb
    .thumb_func
    .align 2
    .globl    Reset_Handler
    .type    Reset_Handler, %function
Reset_Handler:

/*     Loop to copy data from read only memory to RAM. The ranges
 *      of copy from/to are specified by following symbols evaluated in
 *      linker script.
 *      __etext: End of code section, i.e., begin of data sections to copy from.
 *      __data_start__/__data_end__: RAM address range that data should be
 *      copied to. Both must be aligned to 4 bytes boundary.  */
    ldr    r1, =__etext
    ldr    r2, =__data_start__
    ldr    r3, =__data_end__

#if 1
/* Here are two copies of loop implemenations. First one favors code size
 * and the second one favors performance. Default uses the first one.
 * Change to "#if 0" to use the second one */
.flash_to_ram_loop:
    cmp     r2, r3
    ittt    lt
    ldrlt   r0, [r1], #4
    strlt   r0, [r2], #4
    blt    .flash_to_ram_loop
#else
    subs    r3, r2
    ble    .flash_to_ram_loop_end
.flash_to_ram_loop:
    subs    r3, #4
    ldr    r0, [r1, r3]
    str    r0, [r2, r3]
    bgt    .flash_to_ram_loop
.flash_to_ram_loop_end:
#endif

 

/*     Loop to fill zero to the bss segment.. The ranges
 *      of copy from/to are specified by following symbols evaluated in
 *      linker script.
 *      __bss_start__/__bss_end__: RAM address range that data should be
 *      filled to 0. Both must be aligned to 4 bytes boundary.  */
    movs    r1, #0x0
    ldr    r2, =__bss_start__
    ldr    r3, =__bss_end__
.clear_bss_loop:
    str    r1, [r2], #0x4
    cmp     r2, r3
    blt    .clear_bss_loop

 

 

#ifndef __NO_SYSTEM_INIT  // call SystemInit in main()
    ldr    r0, =SystemInit
    blx    r0
#endif

    ldr    r0, =main
    bx    r0

 

    .pool
    .size Reset_Handler, . - Reset_Handler

 

전체적인 구성은 지시자, 함수 선언, 함수 body, 그리고 함수의 코드 크기를 알려주는 지시자로 구성된다.

앞서 언급 한 것 처럼 함수 body는 크기 data 영역 복사, bss 초기화, 그리고 main함수 호출 순으로 구성되어 있다.

 

1) data 영역 복사

    ldr    r1, =__etext                             //  Flash에 위치한 data의 시작 주소
    ldr    r2, =__data_start__                   // RAM에 위치할 data의 시작 주소
    ldr    r3, =__data_end__                    // RAM에 위치할 data의 마지막 주소

#if 1

.flash_to_ram_loop: 

    cmp     r2, r3            // RAM의 시작 주소와 마지막 주소가 같은지 비교
    ittt    lt                     // r2 가 r3보다 작으면 if then 명령 수행
    ldrlt   r0, [r1], #4      // r2가 r3보다 작으면 r1에 저장된 주소값의 데이터를 r0로 복사하고

                                  //           r1에 저장된 주소를 4 만큼 증가
    strlt   r0, [r2], #4      // r2가 r3보다 작으면 r0에 저장된 값을 r2에 저장된 주소값에 복사하고

                                  //            r2에 저장된 주소값을 4 만큼 증가
    blt    .flash_to_ram_loop  // r2가 r3보다 작으면 .flash_to_ram_loop로 jump

#else
    subs    r3, r2                            // r3에서 r2를 빼면서 condition flag 설정
    ble    .flash_to_ram_loop_end     // r3에서 r2를 뺀 값이 0보다 작으면 .flash_to_ram_loop_end 로 jump
.flash_to_ram_loop:
    subs    r3, #4                        // r3에서 4를 뺌
    ldr    r0, [r1, r3]                     // r1+r3 주소값의 데이터를 읽어서 r0에 저장
    str    r0, [r2, r3]                     // r0의 데이터를 r2+r3 주소값에 저장
    bgt    .flash_to_ram_loop         // r3가 0보다 클 경우 .flash_to_ram_loop로 jump
.flash_to_ram_loop_end:
#endif

 

__etext는 flash map에서 code의 끝 부분 즉 data의 시작부분을 의미하며, __data_start__ 및 __data_end__는 memory map에서 RAM영역의 data 시작 및 끝 부분을 나타낸다.

즉 위 asm 코드는 data 의 크기 만큼 __etext 에서 __data_start__로 복사하는 루틴이된다.

C로 나타내면 다음과 같다.

 

volatile unsigned int *bin_addr, *data_addr;

bin_addr = (unsigned int *) &__etext;

data_addr = (unsigned int) &__data_start__;

 

for(idx = 0 ; idx < (&__data_end__ - &__data_start__) ; idx++)

{

     data_addr[idx] = bin_addr[idx];

}

 

2) bss 영역 초기화

bss란 코드에서 0으로 초기화 되는 영역들을 말하며, start up에서 0으로 초기화 해 주지 않으면 RAM에 남아있는 찌꺼기 값이 초기 값으로 설정되어 프로그램을 오동작 시킬 수 있다.

    movs    r1, #0x0                      //  r1에 0 대입
    ldr    r2, =__bss_start__            // r2에 bss의 시작 주소값 대입
    ldr    r3, =__bss_end__            // r3에 bss의 마지막 주소값 대입
.clear_bss_loop:
    str    r1, [r2], #0x4                   // r2에 저장된 주소값에 r1의 값을 저장 후 r2의 주소값을 4 만큼 증가
    cmp     r2, r3                           // r2 와 r3 비교
    blt    .clear_bss_loop                // r2가 r3보다 작으면 .clear_bss_loop 로 jump

 

 

link script에서 predefine symbol인 __bss_start__에서 __bss_end__까지 0으로 채우는 루틴이다.

역시 C로 나타내면 다음과 같다.

 

volatile unsigned int *bss_addr;

 

for(idx = 0 ; idx < (&__bss_end__ -  &__bss_start__) ; idx++)

{

bss_addr[idx] = 0;

}

 

 

3) main 함수로 jump

#ifndef __NO_SYSTEM_INIT  // call SystemInit in main()
    ldr    r0, =SystemInit                  // r0에 Systeminit 함수 시작 주소 저장
    blx    r0                                   // r0 로 branch (즉 함수 호출)
#endif

    ldr    r0, =main                         // r0에 main 함수의 시작 주소 저장
    bx    r0                                   // r0 로 program count 이동. (main 함수 실행)

 

CMSIS에서 제공하는 start up코드에서는 main으로 jump하기 전에 Systemini 함수를 호출 하는 부분이 포함되어 있다.

cmsis에 포함된 sample 코드는 system_ARMCM3.c, st에서 제공하는 코드는 system_stm32f10x.c 이며, Clock 초기화 관련 코드들이 들어간다.

그러나 SystemInit 가 C로 구현된 함수 이므로, 여기서 호출하지 않고 main 함수의 시작 부분에서 호출해도 상관 없다.

즉... ST에서 제공하는 sample 코드분석시 main함수가 호출 되기 전에 system_stm32f10x.c 에 있는 SystemInit 함수가 호출 된다는 사실만 알고 있으면 된다.

 

여기까지가 시스템 시작 부터 main함수가 호출 되기 까지의 과정의 link script, startup_ARMCM3.S 를 통하여 살펴 보았다.

 

다음 포스팅에선 ST에서 제공하는 CM3 칩에서 Cortex-M3 가 담당하는 부분이 어디까지인지 살펴 보고 Cortex-M3 Core를 담당하는 CMSIS 라이브러리를 살펴보고자 한다.