Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

8179
1390
307630


[Arm프로세서] AAPCS: Armv7: 함수를 호출할 때 쓰이는 R0 ~ R3 레지스터와 명령어 분석 Armv7: 함수 호출 규약

SP 레지스터, LR 레지스터(R14)와 더불어 눈여겨봐야 할 레지스터가 R0 ~ R3 레지스터입니다.
함수를 호출할 때 전달되는 인자는 R0 ~ R3 레지스터에 저장되고, 함수가 반환하는 값은 R0 레지스터로 저장되기 때문입니다.

R0 ~ R3 레지스터의 역할: 함수에 전달된 인자 저장

다음 예제 코드를 보면서 함수에 인자가 전달될 때의 세부 동작을 알아봅시다.

01 0001047c <main>:
02
03 int main(void)
04 {
05   1047c:       e92d4800        push    {fp, lr}
06   10480:       e28db004        add     fp, sp, #4
07   10484:       e24dd010        sub     sp, sp, #16
08        int x = 1;
09   10488:       e3a03001        mov     r3, #1
10   1048c:       e50b3008        str     r3, [fp, #-8]
11        int y = 2;
12   10490:       e3a03002        mov     r3, #2
13   10494:       e50b300c        str     r3, [fp, #-12]
14
15        int res = add_func(x, y);
16   10498:       e51b100c        ldr     r1, [fp, #-12]
17   1049c:       e51b0008        ldr     r0, [fp, #-8]
18   104a0:       ebffffe3        bl      10434 <add_func>
19   104a4:       e50b0010        str     r0, [fp, #-16]

위 코드의 핵심 루틴은 다음 15~18번째 줄입니다.

15        int res = add_func(x, y);
16   10498:       e51b100c        ldr     r1, [fp, #-12]
17   1049c:       e51b0008        ldr     r0, [fp, #-8]
18   104a0:       ebffffe3        bl      10434 <add_func>

먼저 16~17번째 줄을 분석하겠습니다. fp 레지스터가 가리키고 있는, 메모리 주소에 있는 데이터를 r1, r0 레지스터에 로딩하는 동작입니다.

이어서 18번째 줄은 add_func() 함수의 주소인 0x10434로 분기하는 동작입니다.

16~18번째 줄의 어셈블리 명령어에 대응하는 C 코드는 15번째 줄인데, x와 y라는 인자를 전달하면서 add_func() 함수를 호출합니다. 이 동작을 Arm 아키텍처 관점으로 이 함수에 전달되는 인자는 R0와 R1 레지스터에 저장된다고 분석할 수 있습니다.   

   * R0 레지스터: x
   * R1 레지스터: y

위 예시에서는 2개의 인자를 함수에 전달합니다. 그렇다면 함수에 전달되는 인자의 갯수가 3이면 어떻게 동작을 할까요? R0, R1, R2 레지스터에 함수의 인자가 저장됩니다. 

일반적으로 R0 ~ R3 레지스터에 함수에 전달되는 인자가 저장되는데, 함수의 인자가 4개인 경우 R0 ~ R3 레지스터에 인자가 저장됩니다. 만약 함수에 전달되는 인자의 갯수가 5개 이상이면 프로세스의 스택 공간에 인자를 저장하게 됩니다.

예제 코드 분석: R0 ~ R3 레지스터에 인자 저장

이번에는 add_func() 함수의 앞 부분 어셈블리 코드를 보면서, R0와 R1 레지스터에 저장된 인자가 어떻게 처리되는지 확인해 봅시다. 

01 00010434 <add_func>:
02
03 int add_func(int x, int y)
04 {
05   10434:       e92d4800        push    {fp, lr}
06   10438:       e28db004        add     fp, sp, #4
07   1043c:       e24dd010        sub     sp, sp, #16
08   10440:       e50b0010        str     r0, [fp, #-16]
09   10444:       e50b1014        str     r1, [fp, #-20]  ; 0xffffffec
10        int result = x + y;

위 어셈블리 명령어에서 눈여겨 볼 부분은 08~10번째 줄입니다.

08   10440:       e50b0010        str     r0, [fp, #-16]
09   10444:       e50b1014        str     r1, [fp, #-20]  ; 0xffffffec

먼저 08번째 줄을 분석하겠습니다.

'str r0, [fp, #-16]' 명령어는 fp 레지스터가 가리키는 주소에서 16만큼을 뺀 주소에 r0 레지스터의 값을 저장하는 동작입니다. 먼저 09번째 줄도 비슷한 명령어인데, fp 레지스터가 가리키는 주소에서 20만큼을 뺀 주소에 r1 레지스터의 값을 저장하는 동작입니다.

이를 다른 관점으로 해석하면, 함수에 전달된 x와 y인자를 스택 메모리 공간에 저장하는 동작이라고 볼 수 있습니다.

이번에는 add_func() 함수의 주소로 분기하는 부분과 add_func() 함수의 앞 부분의 어셈블리 코드를 합쳐서 같이 보겠습니다.

01   10498:       e51b100c        ldr     r1, [fp, #-12]
02   1049c:       e51b0008        ldr     r0, [fp, #-8]
03   104a0:       ebffffe3        bl      10434 <add_func>
...
04 00010434 <add_func>:
05   10434:       e92d4800        push    {fp, lr}
06   10438:       e28db004        add     fp, sp, #4
07   1043c:       e24dd010        sub     sp, sp, #16
08   10440:       e50b0010        str     r0, [fp, #-16]
09   10444:       e50b1014        str     r1, [fp, #-20]  ; 0xffffffec

위 예제 코드에서 03번째 줄은 R0와 R1 레지스터에 인자를 저장해 add_func() 함수의 주소로 분기하는 동작입니다. 이어서 add_func() 함수의 어셈블리 코드를 보면, 08~09번째 줄에서 함수에 전달된 인자를 담고 있는 r0, r1 레지스터를 저장합니다.

[정보]
어셈블리 명령어를 분석할 때, 명령어의 각각 의미를 문법에 치중하는 것보다, 이처럼 Arm 아키텍처의 동작 원리는 이해하면 배운 내용을 더 오랫동안 기억할 수 있습니다.

R0 레지스터의 역할: 함수의 반환값 저장

함수가 자신의 기능을 수행한 다음에 반환하는 결과값은 R0 레지스터에 저장됩니다.

이어서 add_func() 함수의 'return 구문'에 매핑되는 어셈블리 코드를 보면서, 이 내용에 대해 더 배워봅시다.

01 00010434 <add_func>:
02
03 int add_func(int x, int y)
04{
05   10434:       e92d4800        push    {fp, lr}
06   10438:       e28db004        add     fp, sp, #4
07...
08
09   10464:       ebffff9f        bl      102e8 <printf@plt>
10
11        return result;
12   10468:       e51b3008        ldr     r3, [fp, #-8]
13}
14   1046c:       e1a00003        mov     r0, r3
15   10470:       e24bd004        sub     sp, fp, #4
16   10474:       e8bd8800        pop     {fp, pc}
17   10478:       00010554        .word   0x00010554

11번째 줄을 보면 add_func() 함수는 'return result' 구문과 함께 실행 결과를 반환하고, 실행을 마무리합니다. 'return result' 구문에 대응되는 어셈블리 코드는 다음 12~16번째 줄입니다.

11        return result;
12   10468:       e51b3008        ldr     r3, [fp, #-8]
13}
14   1046c:       e1a00003        mov     r0, r3
15   10470:       e24bd004        sub     sp, fp, #4
16   10474:       e8bd8800        pop     {fp, pc}

보다시피, 14번째 줄에서 R0 레지스터의 값은 R0 레지스터에 이동됩니다. 이는 result를 r0 레지스터에 이동시키는 동작으로 해석될 수 있습니다. 이어서 16번째 줄을 실행되면 스택에 푸시된 fp, lr 레지스터의 값을 fp와 pc 레지스터에 로딩해 add_func() 함수를 호출한 다음에 복귀할 주소로 분기합니다.

이번에는 add_func() 함수가 실행을 끝낸 후 복귀해 실행되는 코드를 보겠습니다.

15        int res = add_func(x, y);
16   10498:       e51b100c        ldr     r1, [fp, #-12]
17   1049c:       e51b0008        ldr     r0, [fp, #-8]
18   104a0:       ebffffe3        bl      10434 <add_func>
19   104a4:       e50b0010        str     r0, [fp, #-16]

add_func() 함수의 마지막에 위치한 'pop {fp, pc}' 명령어를 실행하면, 19번째 줄과 같이 0x104a4 주소에 있는 'str r0, [fp, #-16]' 명령어가 보입니다. 이 명령어에 보이는 R0 레지스터에는 add_func() 함수가 반환하는 result 값이 저장돼 있습니다.

add_func() 함수가 반환하는 값을 res라는 변수가 저장하는데, 이에 해당하는 어셈블리 명령어가 'str r0, [fp, #-16]'입니다.

여기서 기억해야 할 중요한 포인트는 다음과 같습니다.

   “함수가 return 구문과 함께 반환하는 값은 r0 레지스터에 저장된다.”

이번에는 add_func() 함수의 마지막 부분과 add_func() 함수의 반환값을 읽는 어셈블리 코드를 함께 보겠습니다.

01   10498:       e51b100c        ldr     r1, [fp, #-12]
02   1049c:       e51b0008        ldr     r0, [fp, #-8]
03   104a0:       ebffffe3        bl      10434 <add_func>
...
04 00010434 <add_func>:
05   10434:       e92d4800        push    {fp, lr}
...
06 // <add_func> 함수의 마지막 어셈블리 코드
07   1046c:       e1a00003        mov     r0, r3
08   10470:       e24bd004        sub     sp, fp, #4
09   10474:       e8bd8800        pop     {fp, pc}
10   10478:       00010554        .word   0x00010554
...
11   104a4:       e50b0010        str     r0, [fp, #-16] 

위 코드의 핵심 명령어는 07번째 줄과 11번째 줄인데, "add_func() 함수가 반환하는 값은 r0 레지스터에 실려 온다"라는 내용을 그리며 분석하면 더 쉽게 이해할 수 있습니다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자



덧글

댓글 입력 영역