본문 바로가기

Developement

ARM assembler, 내가 공부하고 만다.

DOS 시절과 Windows98 초기 시절 느린 platform 속도 때문에 ASM 을 쓰긴 했지만 요즘 컴파일러 와 CPU 등이 워낙 빨라서 다시는 ASM 은 손 안대려 했더만 ... MS VC2005 가 저를 손대게 하는군요.

기본적으로 x86 및 Protected mode 에서의 x86 ASM 을 알고 있기 때문에 다양한 레지스터를 가진 ARM 이 크게 어렵지는 않아 보여 몇가지 정리 해 봅니다.

APCS 및 Assembler Function, Argument for PC.
- 변수의 생성 시기 및 위치는 각각 : 
초기화된 전역 변수일 경우 Initialized Data 로.
초기화되지 않은 전역 변수는 BSS 로,
로컬 변수들은 각 code 수행시 stack 에 생성되게 됨.
- 함수 호출은 stack 에
오른쪽에서 왼쪽 순으로 stack 에 push 됨.
모든 return 값들은 Rn 레지스터에 push  됨.

ARM 의 특수 용도 레지스터들
R13 ( SP : Stack Pointer ) - C/C++ 에서 stack 을 관리 하기 위해 포인터관리 용도로 사용.
R14 ( LR : Link Register ) - 특정 함수 호출 뒤 return 될 위치를 저장할 목적으로 사용.
R14 ( PC : Program Counter ) - 실행되는 code 의 위치 저장. 다른 platform 과 동일.
CPSR ( Current Porcessor Status Register ) : 연산 결과, IRQ/FIQ 금지 및 동작 모드 등을 설정함.

APCS 에 의한 argument 전달
4byte alignment
R0,R1,R2,R3 로 4바이트씩 나눠서 전달함.
argument 의 전달 크기가 4바이트를 넘을 시 , R0~R3 를 사용하여 전달 하게 됨.
argument 가 4개를 넘을 시 stack 을 통해서 전달하게 됨.

return용 register
이건 x86 에 없는 참 편리한 기능?
R0 : 4byte 이하의 기본형 데이터가 기록됨.
R1 : 8byte 이하의 기본형 데이터. R0 과 R1 이 둘다 쓰이면 8byte 가 된다.
R0 : 기본형 또는 확장형 데이터가 기록되기도 함. 어쨋든 이놈이 이찌방.
R0 : 그 외에도 구조체 및 공용체 등의 실 데이터의 주소가 넘어옴... 뭘 해도 R0 부터란 말.

context switching
호출된 함수에서 register 를 사용할 시에 :
호출 받은 함수에서 레지스터 값을 변경시키면 되돌아 간 후, 원래의 함수에서 레지스터 값 변경으로 문제가 발생 할 것이다. (x86 은 그래서 POP/PUSH 를 쓰지?)
고로 LR 를 이용해서 원래 위치로 가게 되면 그당시의 레지스터 값들이 호출 될 당시로 되어 있어야 할 것이다.
그래서 LR 사용은 중요:
중복된 함수 호출이 있을 경우 LR 은 중복적으로 백업 되어야 할 것이다.
이를 위해 context switching 이 있다!
위 사항들 덕분에 scratch registers 를 제외한 모든 레지스터에 대해 원래 값을 유지할 필요가 있다.
고로 x86 의 pop/push 와 마찬가지로 context switching 기능은 stack 을 이용해서 각 레지스터들을 저장하고 로드하게 된다.

scratch datas ???
각 R0 부터 R3 까지 , 그리고 R12 는 사용자에 의해 자유로히 사용이 가능하다.
다만, 다음 register 들은 조심히 사용해야 겠다.
LR : 함수 호출로 인해 LR 이 변경 되지 않도록 하자.
또한 LR 포인터로 이동시 이미 사용된 R0,R1,R2,R3 및 R12 레지스터는 변경 된 상태일 수 있음을 고려 해야 한다.

Multiple Load 및 Store
다중의 메모리 이동을 위해 LDM 및 STM 이 사용됨.
(op) {cond} <mode> Rn, <reg list>
{cond} : 조건부 실행을 위한 조건
<mode> : 주고 계산 방식을 지정함 = IA, IB, DA, DB, FA, FD, EA, ED
Rn : 메모리를 가리키고 있는 베이스 주소 레지스터 (R0~R3 또는 R12)

ex)
LDMDB R1,{R1-R4, R12, LR}
R1 에 든 포인터에 있는 값을 읽어 들인다.
STMNEIA R0 {R0,R1,R2,SP}
R0 에 든 포인터 주소에 값을 쓴다.

STM, LDM 이 수행 되어도 base register 값은 고정이다!
STM, LDM 수행 후 base register 를 변경하도록 하려면 ! 을 붙인다.
(op) {cond} <mode> Rn!,<reg list>
ex)
LDMDB R1!, {R1,R2,LR}

각 주소 계산 지정자
IA (Increment After) : 값을 읽고/쓰고 나서 주소가 증가됨.
AB (Increment Before) : 먼저 주소를 증가 시키고 나서 값을 씀/읽음.
DA (Decrement After) : 값을 일고/쓰고 나서 주소를 감소시킴
DB (Decrement Before) : 먼저 주소를 감소시킨다음 값을 씀/읽음.
FA (Full Ascend) : 유효한 데이터를 지칭함, push 될 때 마다 주소가 증가됨.
FD (Full Descend) : 유효한 데이터를 지칭함. push 될 때 마다 주소가 감소됨.
EA (Empty Ascend) : 유효한 데이터의 다음을 지칭함. push 될 때 마다 주소가 증가
ED (Empty Descend) : 유효한 데이터의 다음을 지칭함. push 때 마다 주소가 감소됨.

Stack & Push & Pop
FD stack 에 push 및 pop
: 4byte 의 데이터 하나를 push 한다.
예)
STR R0, [SP, #-4]!
MOV PC,LR
다중의 데이터를 Push 하고 pop 한다면?
Push 의 경우
1) LDMIA R0, {R0 - R3}
2) STMDB SP!, {R0 - R3}
3) MOV PC,LR
1) base register 로 부터 R0 - R3 을 로드 하고
2) R0 ~ R3 을 stack 에 push 한 다음, base register 값을 변경한다.
3) PC 를 LR 주소로 이동시킴. 
Pop 의 경우
1) LDMIA SP!, {R1 - R3, R12}
2) STMIA R0, {R1 - R13, R12}
3) MOV PC,LR
1) stack pointer 로 부터 R1 - R13 및, R12 에 로드를 시킴.
2) R1 ~ R13 , R12 를 base register 에 저장시킴.
3) PC 를 LR 위치로 set !!!

FD stack 을 사용한 다중 데이터 push 및 pop은 되는걸까?
STMDB 나 LDMIA 를 이용한 push 와 pop 은 어렵다 ... 가능이나 한가?
STMFD 와 LDMFD 로 대체하면 가능. 그냥 FD stack 에다가 넣고 빼면 되므로.

RLIST 지시어
register 목록을 이름으로 관리하도록 한다.
<id> RLIST <reg list>

C 언어 호환 함수의 구현법
이 부분은 ASM(S 파일) 을 구성하면서 C 의 calling type 을 맞춰 보는 부분이 되겄다.
이걸로 stub library 를 깡으로 만들 수 있다.

Context Switching를 꼭 사용하자 -
호출 받은 함수가 stratch register 이외의 register 를 훼손하는 경우 사용하도록 한다.
만약에 코드 수행 중에 R4 나 R5 가 어떤 이유에서든 손상 된 경우 statkc 을 이용해서 이전 값을 저장하고 복구 해 놔야만 한다는 것이다.

C 와 호환되는 calling convention
다른 함수를 호출하지 않고 scratch register 만 훼손될 경우 ...
- context switching 이 불필요 하고
- MOV PC,LR 로 간단히 LR 위치로 복귀가능.

또는 호출받은 함수에서 다른 함수를 호출 하는 경우엔?
- Push 의 경우 : STR LR, [SP, #-4]!
- Pop 의 경우 : LDR LR,[SP], #4

호출 받은 함수에서 scratch register 이외의 레지스터를 훼손시키는 경우엔?
- Push 의 경우 : STMFD SP!, [regs, LR]
- Pop 의 경우 : LDMFD SP!, [regs, PC]

호출 받은 함수에서 다른 함수를 호출 하면서 stack 을 이용하여 argument 를 전달 하는 초 복잡한 경우엔?
이건 머리좀 굴려야 한다 ... ASM 에서 이런건 안만들고 그냥 C 로 만드는 것이 좋겠지만 ..
굳이 한다면 ... 호출한 함수에서 MOV PC,LR 로 복귀 되고 나면 stack 을 통해서 전달한 만큼 stack pointer 를 증가 시키는 방법을 쓰도록 한다.

또한 원래 함수가 stack 을 통해 argument 를 전달 한 경우엔?
stack pointer 를 기준으로 하여 data 를 읽고 쓴 다음 stack pointer 는 원래 값을 유지 시키도록 하자.

또는 다른 함수를 tail-call 하는 경우는 ?!?
점점 생각하기 어렵다 ... 
호출한 함수에서 바로 원래 함수로 복귀 시키거나,
BL 이 아닌 B 로 호출한다.
단, 이때 훼손된 register 들은 모두 복구 하고 LR 은 처음 값으로 유지 시켜야 한다.

오늘은 요까지 ...
to be continued !