.s
확장자의 어셈블리 파일로 translate 한다..o
확장자의 오브젝트 파일로 translate 한다.리소스가 제한된 임베디드 디바이스에서 개발을 할 때는 컴파일 과정조차 오버헤드가 될 수 있기 때문에 성능과 효율을 최대한 끌어내고자 할 때는 일부 코드를 어셈블리어로 짜기도 한다.
개발과정에서 사용하는 register는 대부분 범용으로 사용할 수 있지만, ARM에서는 RISC-V처럼 관례로 n번 register에 대한 사용처가 정해져있다. 반드시 따라야 하는 규칙은 아니지만, 자기 자신을 위한 코드가 아니라 남에게 보여줄 가능성도 있는 코드를 짠다는 점을 고려하면 이런 관례를 따르는 것이 좋다.
이전에도 다뤘지만, convention을 따라서 register를 사용하다보면 프로세서에 따라 다시 원래 값으로 변경해줘야 하는 경우가 있을 수 있다. 이것 역시 convention에서 정의하고 있는데, R0~R3
는 굳이 다시 원래 값으로 변경해줄 필요가 없지만 R4~R11
은 사용 후에는 다시 원래 값으로 restore 해줘야 한다.
이제 우리가 배워왔던 C가 어셈블리나 하드웨어에서는 어떤 식으로 작동하는지 한 번 알아보자.
Load & store 아키텍처를 따르는 프로세서는 속도와 효율을 위해 최대한 모든 연산에 register를 사용하려 노력한다. 이런 노력은 함수에서도 잘 나타나는데, 함수를 call 하거나 arguments를 넘겨주는 과정에서 어떤 register를 사용하는지 알아보자.
함수의 argument 관련한 register는 convention에 따르면 R0 ~ R3
4개가 고작이다. 따라서 4-byte 변수 4개 또는 8-byte 변수 2개를 겨우 사용할 수 있기 때문에 이보다 큰 값을 사용하기 위해서는 stack을 사용하는 방법이 있다.
int foo(char a, int b, char c) { // 1-byte a, c | 4-byte b
int x[8];
x[0] = a * b;
x[c] = b;
return a + b + c;
}
위와 같은 코드가 있을 때 ARM 어셈블리에서는 어떤 일이 발생하는지 공부해보자.
;;;101 int foo(char a, int b, char c)
0000ba b510 PUSH {r4, lr}
0000bc b088 SUB sp, sp, #0x20
;;;102 int x[8];
lr
과 r4
값을 stack에 저장한다.x
를 사용하므로 stack pointer를 그만큼 올려줘야 한다.lr
과 r4
를 PUSH
했으므로 총 2+8 = 10칸만큼 stack pointer를 옮겨줘야 하므로 0x20
을 SUB
해줘야 한다.SUB
연산을 통해 stack pointer를 아래로 내려줘야 한다.;;;105 return a + b + c;
0000ca 1840 ADDS r0, r0, r1
0000cc 1880 ADDS r0, r0, r2
;;;106 }
0000ce b008 ADD sp, sp, #0x20
r0
는 return value를 위한 register임이 convention에 기재돼있다. r0
에 r1
과 r2
를 더해서 return 해준다.SUB
해준 만큼 ADD
함으로써 간단히 끝마칠 수 있다.가장 대표적인 조건문인 if-else 문이 어떻게 표현되는지도 알아보자. RISC-V를 공부하면서 이미 다뤘던 내용이지만, 복습한다는 생각으로 다시 보도록 하자.
if (x) y++;
else y--;
위와 같은 간단한 if-else 구조의 c 코드가 있다고 생각해보자. 이를 어셈블리어로 바꾸면,
;;; if (x)
CMP r1, #0
BEQ Else
;;; y++;
ADDS r2, r2, #1
B Out
Else
;;; else y--;
SUBS r2, r2, #1
Out
APSR
의 flag bit가 set 되고, 이 값을 근거로 branch를 수행한다는 점을 기억하자.do { x += 2; } while (x < 20);
;;; do {
NOP
[Loop]
;;; x += 2;
ADDS r1, r1, #2
;;; while (x < 20);
CMP r1, #0x14
BCC [Loop]
while ( x > 10 ) { x = x + 1; }
;;; while (x > 10)
B [Check]
[Plus]
;;; x = x + 1;
ADDS r1, r1, #1
[Check]
CMP r1, #0xa
BCC [Plus]
while은 do-while과 달리 먼저 조건을 검사해주고 만족하지 않으면 뛰어넘어가기 위해 unconditional 하게 branch를 해주는 과정이 포함돼있다는 점이 특징이다.
for (i = 0; i < 10; ++i) { x += i; }
;;; for (i = 0; i < 10; ++i) {
MOVS r3, #0
B [Check]
[Plus]
;;; x += i; }
ADDS r1, r1, r3
ADDS r3, r3, #1
[Check]
CMP r3, #0xa
BCC [Plus]
for문도 마찬가지로 진입조건을 검사하기 위한 unconditional branch가 포함돼있다. i
는 r3
가 담당한다.