스타트업 코드 분석 2
전원을 인가하면 ARM core는 최초 Reset 신호(PORESETn)가 인가 되어 아래와 같이 Reset_Handler가 호출된다.
RESET Handler 호출 부분
RESET Handler 호출 후 레지시터
위 그림을 보면 아래 레지스터가 링스 스크립터와 스타트업 코드에 정의된 지시어 대로 설정된 것을 확인할 수 있다.
SP : 0x20005000
PC : 0x08000384 (Reset_Handler)
LP : 0xffffffff
MSP: 0x20005000
SP: Stack Pointer, PC: Program Counter, LP: Link Register 아래 링크를 확인하면 리셋이 된 후 SP, PC, LP 값이 어떤 값으로 설정 되는지 확인할 수 있다.
.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는 아래 링크에서 다운 받을 수 있다.
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 명령어 설명
Rt: 로드할 레지스터 label: PC-relative expression 으로 address라고 생각하면 된다.
PC-relative expression 관한 참고 링크: https://developer.arm.com/documentation/dui0473/c/Cacdbfji
r0 레지스터 값에 _sdata의 값을 로드한다.
*.map 파일을 확인하면 아래와 같이 _sdata의 값을 확인할 수 있다.
_sdata = 0x20000000
아래 그림과 같이 r0 레지스터 값이 0x20000000 인 것을 알 수 있다.
ldr r1, =_edata // r1 = _edata;
r1 레지스터에 _edata 값을 로드한다. .map 파일을 확인하면 아래와 같이 _edata의 값을 확인할 수 있다.
_edata = 0x2000000c
아래 그림과 같이 r1 레지스터 값이 0x2000000c 인 것을 알 수 있다.
ldr r2, =_sidata // r2 = _sidata;
.map 파일을 확인하면 아래와 같이 _edata의 값을 확인할 수 있다.
_sidata = 0x08001274
아래 그림을 확인하면 data 영역이 정의 된 것을 확인할 수 있다.
_sdata, _edata 는 메모리의 data 주소를 나타내며 RAM 영역에 위치한다. data 영역의 역할은 초기화 된 전역 변수들이 저장되는 위치이다. _edata 의 주소는 전역 변수의 개수에 따라 주소가 달라진다.
_sidata란 전역 변수에 저장할 초기 값들을 저장된 위치를 가르키며 _sidata의 주소는 FLASH(EEPROM) 영역을 가르킨다. _sidata의 메모리 주소는 코드에 정의된 주소를 가르키기 때문에 코드의 내용에 따라서 주소가 달라진다. 아래 MCU 메모리 영역에 대한 그림을 참고하면 이해하기 쉽니다.
/* 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 내용
b LoopCopyDataInit
LoopCopyDataInit로 분기한다는 내용이며 goto 와 비슷하다.
어셈블리의 기본적인 문법은 아래 링크를 참고하면 도움이 된다.
참고 링크: https://kyuhyuk.kr/article/raspberry-pi/2019/05/15/ARM-Assembly
참고 링크: https://etst.tistory.com/37
프로그램 상태 관련 Flag는 아래 내용을 참고
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 함수에 전역 변수를 변경하는 코드를 추가하였다.
아래와 같이 메모리 맵 확인 시 _sidata 주소에 전역 변수가 설정된 것을 확인할 수 있다.
아래와 같이 메모리 맵 확인 시 _sdata 주소 부터 전역 변수가 설정된 것을 확인할 수 있다.
아래 그림은 FLASH 에 정의된 전역 변수 값이 RAM에 저장되는 에셈블리 코드를 쉽게 이해하기 위해 c코드와 그림으로 설명한 것이다.
아래 그림은 전역 변수의 주소 값을 확인한 그림이다.