스타트업 코드 분석 2


전원을 인가하면 ARM core는 최초 Reset 신호(PORESETn)가 인가 되어 아래와 같이 Reset_Handler가 호출된다.

RESET Handler 호출 부분
memory_map

RESET Handler 호출 후 레지시터
memory_map

위 그림을 보면 아래 레지스터가 링스 스크립터와 스타트업 코드에 정의된 지시어 대로 설정된 것을 확인할 수 있다.
SP : 0x20005000
PC : 0x08000384 (Reset_Handler)
LP : 0xffffffff
MSP: 0x20005000

SP: Stack Pointer, PC: Program Counter, LP: Link Register 아래 링크를 확인하면 리셋이 된 후 SP, PC, LP 값이 어떤 값으로 설정 되는지 확인할 수 있다.

참고 링크: https://developer.arm.com/documentation/dui0552/a/the-cortex-m3-processor/programmers-model/core-registers?lang=en

참고 링크: https://developer.arm.com/documentation/ddi0403/d/Application-Level-Architecture/Application-Level-Programmers–Model/Registers-and-execution-state/ARM-core-registers?lang=en

  .section .text.Reset_Handler
  .weak Reset_Handler
  .type Reset_Handler, %function
Reset_Handler:

아래와 같이 스타트업 코드에 정의된 지시자를 분석해 보았다.

.section .text.Reset_Handler

.text 섹션에서 Reset_Handler를 정의한다.

참고 링크: https://damduc.tistory.com/398

  .weak Reset_Handler

.weak Reset_Handler Reset_Handler를 정의한 함수를 호출할 것이다. (인터럽트 함수에서 많이 사용 좀더 스터디가 필요함)

참고 링크: https://tigershin-shinhyeonkyu.tistory.com/8

.type Reset_Handler, %function

.type type은 function 타입니다.

https://www.youtube.com/watch?v=5kr2uteLkrs&t=109s

Reset_Handler를 진입하면 아래와 같은 명령어를 확인할 수 있다. 명령어 참고 PDF는 아래 링크에서 다운 받을 수 있다.

참고 PDF : https://www.st.com/resource/en/programming_manual/pm0056-stm32f10xxx20xxx21xxxl1xxxx-cortexm3-programming-manual-stmicroelectronics.pdf

Reset_Handler:

/* Copy the data segment initializers from flash to SRAM */
  ldr r0, =_sdata
  ldr r1, =_edata
  ldr r2, =_sidata
  movs r3, #0
  b LoopCopyDataInit
ldr r0, =_sdata // r0 = _sdata;

아래 설명과 같이 ldr 명령어는 특정 메모리 주소에 있는 값을 로드한다.

ldr 명령어 설명
load_register

Rt: 로드할 레지스터 label: PC-relative expression 으로 address라고 생각하면 된다.

PC-relative expression 관한 참고 링크: https://developer.arm.com/documentation/dui0473/c/Cacdbfji

r0 레지스터 값에 _sdata의 값을 로드한다.
*.map 파일을 확인하면 아래와 같이 _sdata의 값을 확인할 수 있다.

_sdata = 0x20000000
sdata_value

아래 그림과 같이 r0 레지스터 값이 0x20000000 인 것을 알 수 있다.
r0_value

ldr r1, =_edata // r1 = _edata;

r1 레지스터에 _edata 값을 로드한다. .map 파일을 확인하면 아래와 같이 _edata의 값을 확인할 수 있다.

_edata = 0x2000000c
edata_value

아래 그림과 같이 r1 레지스터 값이 0x2000000c 인 것을 알 수 있다.
r0_value

ldr r2, =_sidata // r2 = _sidata;

.map 파일을 확인하면 아래와 같이 _edata의 값을 확인할 수 있다.

_sidata = 0x08001274
sidata_value

아래 그림을 확인하면 data 영역이 정의 된 것을 확인할 수 있다.
linker_script_loadaddr

_sdata, _edata 는 메모리의 data 주소를 나타내며 RAM 영역에 위치한다. data 영역의 역할은 초기화 된 전역 변수들이 저장되는 위치이다. _edata 의 주소는 전역 변수의 개수에 따라 주소가 달라진다.

_sidata란 전역 변수에 저장할 초기 값들을 저장된 위치를 가르키며 _sidata의 주소는 FLASH(EEPROM) 영역을 가르킨다. _sidata의 메모리 주소는 코드에 정의된 주소를 가르키기 때문에 코드의 내용에 따라서 주소가 달라진다. 아래 MCU 메모리 영역에 대한 그림을 참고하면 이해하기 쉽니다.


program_memory_map

  /* Used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections into "RAM" Ram type memory */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    *(.RamFunc)        /* .RamFunc sections */
    *(.RamFunc*)       /* .RamFunc* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */

  } >RAM AT> FLASH

위 코드는 링커 스크립트 파일에서 data 영역을 정의한 코드이다.

 >RAM AT> FLASH

’>’RAM 이란 .data 세션을 RAM 주소에 할당한다는 것이다. AT > FLASH란 이 세선의 로드 주소를 FLASH 주소로 정의한 것이다.

_sidata = LOADADDR(.data);

LOADADDR(.data) 란 .data의 FLASH 주소를 리턴한다.

참고 링크:http://korea.gnu.org/manual/release/ld/ld-sjp/ld-ko_3.html#SEC21

movs r3, #0

상수 0 값을 r3로 복사한다. mov뒤에 있는 s는 condition flag를 업데이트한 다는 내용이다.

stm32 프로그램 가이드에 나와 있는 mov 내용
mov_command

b LoopCopyDataInit

LoopCopyDataInit로 분기한다는 내용이며 goto 와 비슷하다.

어셈블리의 기본적인 문법은 아래 링크를 참고하면 도움이 된다.

참고 링크: https://kyuhyuk.kr/article/raspberry-pi/2019/05/15/ARM-Assembly

참고 링크: https://etst.tistory.com/37

프로그램 상태 관련 Flag는 아래 내용을 참고

참고 링크: https://developer.arm.com/documentation/dui0552/a/the-cortex-m3-processor/programmers-model/core-registers?lang=en

LoopCopyDataInit으로 분기 되면 아래와 같은 코드를 볼 수 있다. 어셈블리 문법을 참고하여 아래와 같이 C 코드로 작성해 보았다.

PC 레지스터가 변화는 것을 확인 결과 어셈블리 산술 명령어로 넘어갈 때는 주소에서 2씩 증가했는데 분기 명령어 LoopCopyDataInit로 넘어갈 때는 4가 증가되는 것을 확인하였다. 이 부분은 스터디가 필요하다.

CopyDataInit:
  ldr r4, [r2, r3] // r4 = *(r2 + r3);
  str r4, [r0, r3] // *(r0 + r3) = r4;
  adds r3, r3, #4  // r3 = r3 + 4

LoopCopyDataInit:
  adds r4, r0, r3  // r4 = r0 + r3
  cmp r4, r1
  bcc CopyDataInit // if (r4 < r1) goto CopyDataInit

아래와 같이 전역 변수를 설정 후 main 함수에 전역 변수를 변경하는 코드를 추가하였다.
global_value_code

아래와 같이 메모리 맵 확인 시 _sidata 주소에 전역 변수가 설정된 것을 확인할 수 있다.
memory_sidata_value

아래와 같이 메모리 맵 확인 시 _sdata 주소 부터 전역 변수가 설정된 것을 확인할 수 있다.
memory_data_value

아래 그림은 FLASH 에 정의된 전역 변수 값이 RAM에 저장되는 에셈블리 코드를 쉽게 이해하기 위해 c코드와 그림으로 설명한 것이다.
data_copy_flow

아래 그림은 전역 변수의 주소 값을 확인한 그림이다.
global_value_address