뭘 알아야 이해를 하지
6개의 모드마다 모두 R13과 R14 레지스터는 공통적으로 있는데, 그 이유가 무엇일까요? 분명 이유가 있겠죠? 왜 R13과 R14일까요?
ARM core에서는 R13과 R14 레지스터는 특별한 일을 한답니다. 이제부터 소개하죠.
R13은 스택 포인터
R13은 스택 포인터 레지스터라고 불린답니다.
스택이라... 어디선가 말이 들어본 말이지 않나요? 갑자기 Push, Pop이라는 용어가 떠오르면서 자료 구조가 생각나죠? ㅋㅋㅋ
모두들 오래 전에 배운 내용이라 기억이 가물가물 하실겁니다.
스택이란 데이타를 잠시 보관하는 메모리 공간이랍니다. 스택에 잠시 보관할 때는 Push, 보관된 데이타를 꺼낼 때는 Pop이라는 명령어가 사용됩니다. C 언어와 스택은 아주 각별하답니다. C 언어는 스택 메모리 공간이 없으면 프로그램 자체가 동작하지 않습니다. 어떤 CPU이든 C 언어로 만든 프로그램이 동작한다면 반드시 스택 공간을 사용하게 되어 있답니다. 이 공간은 언제든지 읽고, 쓰기가 가능한 SDRAM 이여야 한답니다. 왜?? 데이타를 잠시 보관하는 공간이라서요. ㅋㅋ
임베디드 시스템을 만들 때 반드시 필요한 하드웨어를 뽑아야 한다면, CPU와 SDRAM이랍니다. MCU에 따라 CPU와 SDRAM이 내장된 형태가 있는 반면, CPU와 SDRAM이 따로 있는 경우가 있죠. USN이라고 8bit MCU가 있는데 아주 작은 RAM이 내장되어 있답니다. 32bit 프로세서에도 내장된 SDRAM이 있어요. 주로 인터널에스램(Internal SRAM,I-RAM)이라고 부르죠. I-RAM은 주로 부트로더를 올리거나 인터럽트 벡터 테이블 코드 등 프로그램이 동작한답니다.
이제 스택이 어떻게 동작하는지 동작원리를 알아보고, C 언어로 만든 프로그램이 ARM core에서 동작할 때 스택이 어떻게 동작되는지를 알아보아요.
스택에 데이타를 저장될 때는 Push, 저장된 데이타를 다시 꺼낼 때는 Pop이랍니다. 스택은 김치와의 인연도 있답니다. 한국사람이라면 누구나 김치를 좋아하죠. 이 김치를 장독대에 보관하면 맛이 정말 일품이죠. (갑자기 무진장 배가 고파지는군요. ㅋㅋ)
장독대에 김치 한 포기씩 정성껏 넣을 때 스택이 동작하는 것처럼 Push를 한답니다. 이렇게 쌓아 두었던 김치를 꺼낼 때 Pop을 하죠. 김치가 저장 되는 순서는 ① -> ② -> ③ -> ④, 꺼내는 순서는 반대로 되죠.
그럼 ARM core에서는 Push와 Pop이 어떻게 동작되는지 알아보죠.
Push① 명령어가 실행되면 SDRAM의 상위 주소②에서부터 데이타가 저장된답니다. 그리고 스택 포인터④는 0x30010000 - 4번지로 이동하죠. 다음 Push① 명령어가 실행되면 스택 포인터 위치 주소③ 에다 데이타를 저장한답니다. Push 명령어가 모두 끝나고, 이제 Pop명령어가 실행되면 스택 포인터 위치 주소에 있는 데이타를 꺼내 오고 스택 포인터 위치는 Stack point +4 주소로 이동된답니다. 그래서 ARM core에서는 스택 이동 방향이 Push 경우는 상위 주소에서 하위 주소로, Pop경우는 하위 주소에서 상위 주소로 이동한답니다.
방금 설명드린 내용이 도대체 왜...왜...R13하고 어떤 연관성이 있을까요?
개발자가 C 언어로 프로그램을 했다면 분명히 Push, Pop 명령어가 있답니다. 언제 명령어를 만들었냐고요? 아니 발뺌하실 생각은 아니죠? ㅋㅋ 과학 수사로 DNA 분석하면 다 나온 다니깐요. ㅋㅋ
제가 분석을 해 보니 바로 함수 콜 할 때 있더라구요.
사실 개발자는 직접 Push와 Pop명령어를 사용하지는 않구요, 컴파일을 하고 나면 Push, Pop 같은 명령어를 볼 수가 있답니다. Push, Pop 명령어는 32bit ARM 명령어와 16bit THUMB 명령어로 나누어 진답니다. 먼저 32bit ARM 명령어에서 Push는 stmdb이고 Pop은 ldmia 이랍니다. 16bit THUMB 명령어에서는 Push는 push이고 Pop은 pop 명령어랍니다.
32bit ARM Instruction 16bit THUMB Instruction Push stmdb Push Pop ldmia Pop
그럼 직접 소스 코드를 보죠. 먼저 32bit ARM 명령어랍니다.
main함수에서 func2c 함수① 호출을 했어요. func2c함수의 시작 위치에서 보면 r13을 사용한 어셈블리 명령어②가 실행된답니다. 스택에 데이타 값들을 push 한 후 func2c 함수의 프로그램들이 실행된답니다. func2c함수의 마지막에는 스택에 저장된 데이타를 pop하는 어셈블리 명령어③가 실행되고 main 함수로 돌아간답니다.
다음으로는 16bit THUMB 명령어랍니다.
main함수에서 func2c 함수① 호출을 했어요. func2c함수의 시작 위치에서 보면 r13을 사용한 어셈블리 명령어②가 실행된답니다. 스택에 데이타 값들을 push 한 후 func2c 함수의 프로그램들이 실행된답니다. func2c함수의 마지막에는 스택에 저장된 데이타를 pop하는 어셈블리 명령어③가 실행되고 main 함수로 돌아간답니다.
아래와 같은 예제를 통해 Push와 Pop 동작 될 때, R13 레지스터와 메모리 값들 변화에 대해 알아보죠.
Push 할 때 R13과 스택의 변화
main 함수에서 func2 함수로 진입해서 Push 명령어인 stmdb가 실행①될 때 R13의 레지스터 값④이 변한답니다.
1. PC값이 0x0288일 때 R13은 0x30FFD7E8 주소를 가리키고 있고,
2. 명령어가 실행①되면 R13 주소②에서 부터 하위 주소로 r3, r4, r14값③들을 Push⑤ 하고,
3. R13은 0x30FFD7DC 주소④ 를 가리키게 됩니다.
Pop 할때 R13과 스택의 변화
func2 함수의 마지막 주소에 Pop 명령어인 ldmia가 실행①될 때 R13의 레지스터 값④이 변한답니다.
1. PC값이 0x02C4일 때 R13은 0x30FFD7DC 주소를 가리키고 있고,
2. 명령어가 실행①되면 R13 주소에서부터 상위 주소로 r3, r4, r14값②들을 pop③ 하고,
3. PC값은 R14 주소를 저장한 ④ 주소로 점프를 한 다음,
4. R13은 0x30FFD7E8 주소를 가리키게 됩니다.
지금까지 R13의 역할과 동작 원리에 대해 살펴봤어요.
이처럼 R13은 함수 콜을 할 때 스택이 사용된다는 것을 알게 되었는데, ARM에는 6개 모드가 있다고 했잖아요. 각 모드 별로 R13이 있기 때문에 스택 위치가 각각 다른 주소를 가리키게 된답니다. 각 모드 별로 스택 위치를 언제 지정할까요? 그건 Startup.s 파일에서 지정하도록 되어 있답니다. Startup 파일에서 스택 영역 할당이라는 부분이 있지요.
Startup.s 소스 코드로 살펴보면,
▶코드 Startup.s STACK_BASEADDR EQU 0x31000000
UserStack EQU (STACK_BASEADDR-0x3800)
SVCStack EQU (STACK_BASEADDR-0x2800)
UndefStack EQU (STACK_BASEADDR-0x2400)
AbortStack EQU (STACK_BASEADDR-0x2000)
IRQStack EQU (STACK_BASEADDR-0x1000)
FIQStack EQU (STACK_BASEADDR-0x0)
;----------------------
InitStacks
;----------------------
MRS r0,cpsr
BIC r0,r0,#MODEMASK
ORR r1,r0,#USRMODE|NOINT
MSR cpsr_cxsf,r1
LDR sp,=UserStack
ORR r1,r0,#UNDEFMODE|NOINT
MSR cpsr_cxsf,r1
LDR sp,=UndefStack
ORR r1,r0,#ABORTMODE|NOINT
MSR cpsr_cxsf,r1
LDR sp,=AbortStack
ORR r1,r0,#IRQMODE|NOINT
MSR cpsr_cxsf,r1
LDR sp,=IRQStack
ORR r1,r0,#FIQMODE|NOINT
MSR cpsr_cxsf,r1
LDR sp,=FIQStack
BIC r0,r0,#MODEMASK|NOINT
ORR r1,r0,#SVCMODE
MSR cpsr_cxsf,r1
LDR sp,=SVCStack
MOV pc,lr
LTORG
스택 포인터가 SDRAM의 상위 주소라는 걸 명심하세요. 자세한 내용은 링커 스크립트(303) Unit에 자세히 설명을 해 놓았답니다.
마지막으로 왜 C 언어는 반드시 스택을 사용해야 하는지 그 이유를 말씀 드리자면, Startup.s 파일에서 Main.c의 main 함수로 진입할 때, 반드시 스택을 사용하게 되어 있답니다.
Stack에 대한 ARM 명령어와 THUMB 명령어를 비교해 보도록 하죠.
ARM 명령어 THUMB 명령어 PUSH STMDB R13!,{r1,r2,(R14) } PUSH {r1,r2,r14} POP LDMIA R13!,{r1,r2,(R15) } POP {r1,r2,r15}
R14는 링크 레지스터
C 언어로 프로그램을 만들다 보면 자주 서브함수를 호출하곤 합니다. 스택에는 주로 변수의 값이나 서브함수 실행이 끝나고 나면 복귀해야 될 주소가 저장되어 있답니다.
하지만 서브함수라고 해서 무조건 스택을 사용하는 것은 아니랍니다. 그렇다면 서브함수 중 스택을 사용하지 않을 경우, 서브함수 실행이 끝나고 복귀해야 될 주소를 ARM core는 어떻게 알게 될까요? 지금부터 그 궁금증을 풀어보아요.
R14는 링크 레지스터(Link Register)라고 해서 스택을 사용하지 않는 서브함수 에서 복귀해야 될 주소를 저장하는 역할을 한답니다. 예제 프로그램을 보면서 설명 드리죠.
main 함수에서 func2d 함수를 호출했답니다. func2d 함수 진입 시점①에 Stack 명령인 Push 명령어가 없답니다. 이와 같은 경우는 func2d 함수 실행이 끝나고 나면 다시 main함수의 어느 주소로 돌아가야 할지 모른답니다.
main 함수에서 func2d 함수로 진입할 때 ARM core에서는 어떤 일이 일어나는지 알아보죠.func2d 함수로 진입①했을 때, R14②는 func2d 함수를 호출한 다음의 주소 값(0x20C4)을 저장한답니다.
R14에는 복귀할 주소가 저장되었는데, 스택과 마찬가지로 함수 마지막에 복귀하는 명령어도 있겠죠. func2d 함수의 마지막 부분에는 r14 값을 pc값으로 바꾸는 명령어①가 있답니다. 명령어가 실행되면 r14->pc로 변하고②, func2d 호출한 주소의 다음 번지③로 점프하게 된답니다.
R13과 R14에 대해 모두 알아보았어요.
R13 같은 경우에는 프로그램 실행을 하는 코드 주소가 올 수가 없고 스택 포인터 주소만 올 수 있으며, R14같은 경우에는 스택 포인터 주소가 올 수 없고 프로그램 코드의 주소만 온다는 것을 알았답니다. 이 말은 하드웨어 디버깅 장비를 사용해서 디버깅을 하다 보면 R13과 R14의 레지스터 값만으로도 프로그램 실행이 비정상적으로 동작한다는 것을 예측할 수가 있답니다.
SDRAM - "205" startup – “301”
Written By Soto
댓글