Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

9365
557
421924


[Arm프로세서] AAPCS: Armv8: 함수를 호출할 때 쓰이는 X0-X7 레지스터와 명령어 분석 Armv8: 함수 호출 규약

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

X0-X7 레지스터의 역할: 함수에 전달된 인자 저장

01 0x10000 <main>:
02 {
03 0x10000:   f81e0ffe    str x30, [sp, #-32]!
04     int res = add_func(x, y);
05 0x10004:   52800061    mov w1, #0x3 // #3
06 0x10008:   52800040    mov w0, #0x2 // #2
07 0x1000c:   97ffffef    bl  20000 <add_func>
08 0x10010:   b9001fe0    str w0, [sp, #28]
09     printf("add res = %d \n", res);

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

04     int res = add_func(x, y);
05 0x10004:   52800061    mov w1, #0x3 // #3
06 0x10008:   52800040    mov w0, #0x2 // #2
07 0x1000c:   97ffffef    bl  20000 <add_func>

먼저 05~06번째 줄을 분석하겠습니다. x0와 x1 레지스터에 각각 2와 3을 저장하는 동작입니다.

Armv8 아키텍처의 명령어에서 w0은 x0 레지스터를 뜻합니다. w0은 x0 레지스터 기준으로 하위 32비트에 저장되는 레지스터를 뜻하므로, x0으로 명시하는 경우가 많습니다.

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

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

   * X0 레지스터: x
   * X1 레지스터: y

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

일반적으로 X0-X7 레지스터에 함수에 전달되는 인자가 저장되는데, 함수의 인자가 8개인 경우 X0-X7 레지스터에 인자가 저장됩니다. 만약 함수에 전달되는 인자의 갯수가 9개 이상이면, 9번째 인자부터 프로세스의 스택 공간에 인자를 저장하게 됩니다.

예제 코드 분석: X0-X7 레지스터에 인자 저장

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

01 0x20000 <add_func>:
02 {
03 0x20000:   a9bf7bf3    stp x19, x30, [sp, #-16]!
04     int result = x + y;
05 0x20004:   0b010013    add w19, w0, w1
06     printf("result: %d\n", result);
07 0x20008:   2a1303e1    mov w1, w19
08 0x2000c:   f0003ea0    adrp    x0, 188000 
09 0x20010:   91182000    add x0, x0, #0x608
10 0x20014:   97ed3258    bl  1fe948 <printf>
11 }
12 0x20018:   2a1303e0    mov w0, w19
13 0x2001c:   a8c17bf3    ldp x19, x30, [sp], #16
14 0x20020:   d65f03c0    ret

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

04     int result = x + y;
05 0x20004:   0b010013    add w19, w0, w1

add_func() 함수에 전달되는 인자가 저장된 x0와 x1 레지스터의 값을 더해 x19 레지스터에 저장하는 동작입니다. 04번째 줄의 C 코드가 05번째 어셈블리 명령어에 해당되는 것입니다.

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

01     int res = add_func(x, y);
02 0x10004:   52800061    mov w1, #0x3 // #3
03 0x10008:   52800040    mov w0, #0x2 // #2
04 0x1000c:   97ffffef    bl  20000 <add_func>
...
05 0x20000 <add_func>:
06 {
07 0x20000:   a9bf7bf3    stp x19, x30, [sp, #-16]!
08     int result = x + y;
09 0x20004:   0b010013    add w19, w0, w1

위 예제 코드에서 02~03번째 줄은 X0와 X1 레지스터에 인자를 저장하는 동작입니다. 04번째 줄은 add_func() 함수의 주소로 분기하는 동작입니다. 아랫 부분에 있는 add_func() 함수의 어셈블리 코드를 보면, 09번째 줄에서 함수에 전달된 인자를 담고 있는 x0와 x1 레지스터의 값을 더해 x19 레지스터에 저장합니다.
X0 레지스터의 역할: 함수의 반환값 저장

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

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

01 0x20000 <add_func>:
02 {
03 0x20000:   a9bf7bf3    stp x19, x30, [sp, #-16]!
04     int result = x + y;
05 0x20004:   0b010013    add w19, w0, w1
06     printf("result: %d\n", result);
07 0x20008:   2a1303e1    mov w1, w19
08 0x2000c:   f0003ea0    adrp    x0, 188000 
09 0x20010:   91182000    add x0, x0, #0x608
10 0x20014:   97ed3258    bl  1fe948 <printf>
11     return result;
12 }
13 0x20018:   2a1303e0    mov w0, w19
14 0x2001c:   a8c17bf3    ldp x19, x30, [sp], #16
15 0x20020:   d65f03c0    ret

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

13 0x20018:   2a1303e0    mov w0, w19
14 0x2001c:   a8c17bf3    ldp x19, x30, [sp], #16
15 0x20020:   d65f03c0    ret

보다시피, 13번째 줄에서 X19 레지스터의 값은 X0 레지스터에 이동됩니다. 이는 result를 X0 레지스터에 저장하는 동작입니다.  

14번째 줄이 실행되면 스택에 푸시된 x19, x30 레지스터의 값을 x19와 x30 레지스터에 로딩합니다. 이어서 15번째 줄에 있는 ret 명령어를 실행하면 x30 레지스터의 값을 PC에 넣어줘, 복귀할 주소로 분기합니다.

Armv8 아키텍처: AAPCS(함수호출 규약)

   ❑ 스택과 관련된 명령어 
      * stp 명령어  
      * sub 명령어  
      * ldp 명령어  
   ❑ 브랜치와 복귀 명령어
      * bl 명령어  
      * RET 명령어
AAPCS와 C 코드 최적화


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



핑백

덧글

댓글 입력 영역