본문 바로가기

시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리

ARM64(Aarch64) - 함수 호출시 Stack Push(스택 푸쉬) 규약

아래와 같은 콜 스택에서 유저 공간에서 돌던 레지스터 Stack Push와 Exception 발생 시 Stack Push에 대해서 살펴봤어요. 이제는 평상시 함수 호출 시 어떻게 Stack Push를 하는 지 점검하려고 해요.
 
자 계속 그 동안 다뤄왔던 아래 "rild"란 프로세스의 콜스택에서 rpi_ipc_router_sendmsg()-> rpi_ipc_router_send_to() 으로 함수가 호출된 후 스택 푸쉬가 어떻게 수행되는 지 점검해볼께요.
-000|do_mem_abort()
-001|el1_da(asm)
 -->|exception
-002|ch_pop_remote_rx_intent()
-003|glink_tx_common()
-004|glink_txv()
-005|ipc_router_glink_xprt_write()
-006|rpi_ipc_router_write_pkt(inline)
-006|rpi_ipc_router_send_to()
-007|rpi_ipc_router_sendmsg()
-008|sock_sendmsg_nosec(inline)
-008|sock_sendmsg()
-009|SYSC_sendto(inline)
-009|sys_sendto()
-010|el0_svc_naked(asm)
 
rpi_ipc_router_sendmsg()함수 내에서 rpi_ipc_router_send_to() 함수를 호출하기 직전 ARM Instruction을 살펴 볼께요. 0xFFFFFF9F9A81C2A4주소가 rpi_ipc_router_send_to() 함수를 호출하거든요. 
이 때 스택 주소(SP)는  0xFFFFFFE4DE6A7CA0이에요. 
 
아래 코드를 보면 각각 파라미터로 입력하는 값들을 확인할 수 있는데요.
x0 = x23, x1 = x20, x3 = x29+0x88, x3 = x28
NSX:FFFFFF9F9A81C294|AA1703E0            mov     x0,x23
NSX:FFFFFF9F9A81C298|AA1403E1            mov     x1,x20
NSX:FFFFFF9F9A81C29C|910223A2            add     x2,x29,#0x88     ; x2,x29,#136
NSX:FFFFFF9F9A81C2A0|AA1C03E3            mov     x3,x28
NSX:FFFFFF9F9A81C2A4|97FFF7A3            bl      0xFFFFFF9F9A81A130   ; rpi_ipc_router_send_to
 
자 이제, rpi_ipc_router_send_to() 함수 호출이 되는 순간 어떤 짓을 할까요? 좀 살펴 볼께요.
NSX:FFFFFF9F9A81A130|A9B67BFD  rpi_ipc_router_send_to: stp     x29,x30,[SP,#-0xA0]!   ; x29,x30,[SP,#-160]!  //<<--[1]
NSX:FFFFFF9F9A81A134|910003FD                          mov     x29,SP
NSX:FFFFFF9F9A81A138|A90153F3                          stp     x19,x20,[SP,#0x10]   ; x19,x20,[SP,#16] //<<--[2]
NSX:FFFFFF9F9A81A13C|A9025BF5                          stp     x21,x22,[SP,#0x20]   ; x21,x22,[SP,#32]//<<--[3]
NSX:FFFFFF9F9A81A140|A90363F7                          stp     x23,x24,[SP,#0x30]   ; x23,x24,[SP,#48]
NSX:FFFFFF9F9A81A144|A9046BF9                          stp     x25,x26,[SP,#0x40]   ; x25,x26,[SP,#64]
NSX:FFFFFF9F9A81A148|A90573FB                          stp     x27,x28,[SP,#0x50]   ; x27,x28,[SP,#80]//<<--[4]
 
[1] "stp     x29,x30,[SP,#-0xA0]!"
이게 무슨 명령어이냐면, 스택 공간을 더 잡아주는 명령어에요. 이 명령어가 수행되면 스택 주소가 FFFFFFE4DE6A7C00로 바뀌게 되죠.
어떻게 변경되냐구요?  FFFFFFE4DE6A7C00=FFFFFFE4DE6A7CA0(기존스택주소)- 0xA0
 
그리고, 동시에 x29, x30을 스택 주소와 스택 주소+0x8 위치에 Push를 해줘요.
NSD:FFFFFFE4DE6A7C00| A0 7C 6A DE E4 FF FF FF  0xFFFFFFE4DE6A7CA0  //<<--x29, 새로운 스택 주소
NSD:FFFFFFE4DE6A7C08| A8 C2 21 09 80 FF FF FF  0xFFFFFF800921C2A8  \\vmlinux\ipc_router_socket\rpi_ipc_router_sendmsg+0x2C0  // <<--x30
NSD:FFFFFFE4DE6A7C10| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C18| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C20| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C28| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C30| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C38| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C40| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C48| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C50| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C58| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C60| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C68| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C70| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C78| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C80| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C88| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C90| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C98| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7CA0| 40 7D 6A DE E4 FF FF FF  0xFFFFFFE4DE6A7D40 //<<-- 기존 스택 주소
NSD:FFFFFFE4DE6A7CA8| 88 A0 5E 9A 9F FF FF FF  0xFFFFFF9F9A5EA088 \\vmlinux\socket\sock_sendmsg+0x48
 
[2] "stp     x19,x20,[SP,#0x10]   ; x19,x20,[SP,#16]"
스택 주소 FFFFFFE4DE6A7C00에서 0x10만큼 떨어진 주소에 x19와 x20을 푸쉬하네요.
NSD:FFFFFFE4DE6A7BF8| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C00| A0 7C 6A DE E4 FF FF FF  0xFFFFFFE4DE6A7CA0 // 스택 주소
NSD:FFFFFFE4DE6A7C08| A8 C2 21 09 80 FF FF FF  0xFFFFFF800921C2A8  \\vmlinux\ipc_router_socket\rpi_ipc_router_sendmsg+0x2C0
NSD:FFFFFFE4DE6A7C10| 19 00 00 00 00 00 00 00  0x0          // <<--x19
NSD:FFFFFFE4DE6A7C18| 80 8E 96 DF E4 FF FF FF  0xFFFFFFE4DF968E80 //<<--x20
NSD:FFFFFFE4DE6A7C20| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C28| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C30| 00 00 00 00 00 00 00 00  0x0
 
[3] "stp     x21,x22,[SP,#0x20]   ; x21,x22,[SP,#32]"
스택 주소 FFFFFFE4DE6A7C00에서 0x20만큼 떨어진 주소에 x21와 x22을 푸쉬하네요.
NSD:FFFFFFE4DE6A7C00| A0 7C 6A DE E4 FF FF FF  0xFFFFFFE4DE6A7CA0
NSD:FFFFFFE4DE6A7C08| A8 C2 21 09 80 FF FF FF  0xFFFFFF800921C2A8  \\vmlinux\ipc_router_socket\rpi_ipc_router_sendmsg+0x2C0
NSD:FFFFFFE4DE6A7C10| 19 00 00 00 00 00 00 00  0x19                \\vmlinux\Global\sha1_ce_offsetof_count+0x1
NSD:FFFFFFE4DE6A7C18| 80 8E 96 DF E4 FF FF FF  0xFFFFFFE4DF968E80
NSD:FFFFFFE4DE6A7C20| 00 15 C6 4F E5 FF FF FF  0xFFFFFFE54FC61500 //<<--x21
NSD:FFFFFFE4DE6A7C28| 80 D0 F4 56 E5 FF FF FF  0xFFFFFFE556F4D080 //<<--x22
 
[4] 이제 스택 푸쉬가 끝났어요. 어떤 값들인지 비교해볼까요?
[After]
NSD:FFFFFFE4DE6A7C00| A0 7C 6A DE E4 FF FF FF  0xFFFFFFE4DE6A7CA0   //<<--x29, 새로운 스택 주소
NSD:FFFFFFE4DE6A7C08| A8 C2 81 9A 9F FF FF FF  0xFFFFFF9F9A81C2A8 .vmlinux\ipc_router_socket\rpi_ipc_router_sendmsg+0x2C0 //<<--x30 
NSD:FFFFFFE4DE6A7C10| 00 00 00 00 00 00 00 00  0x0   //<<-- x19
NSD:FFFFFFE4DE6A7C18| 80 8E 96 DF E4 FF FF FF  0xFFFFFFE4DF968E80 //<-- x20
NSD:FFFFFFE4DE6A7C20| 00 15 C6 4F E5 FF FF FF  0xFFFFFFE54FC61500 //<-- x21
NSD:FFFFFFE4DE6A7C28| 80 D0 F4 56 E5 FF FF FF  0xFFFFFFE556F4D080 //<-- x22
NSD:FFFFFFE4DE6A7C30| 00 70 C6 4F E5 FF FF FF  0xFFFFFFE54FC67000 //<-- x23
NSD:FFFFFFE4DE6A7C38| 16 00 00 00 00 00 00 00  0x16                    //<-- x24
NSD:FFFFFFE4DE6A7C40| 38 00 00 00 00 00 00 00  0x38                      //<-- x25
NSD:FFFFFFE4DE6A7C48| 16 00 00 00 00 00 00 00  0x16         //<-- x26
NSD:FFFFFFE4DE6A7C50| 02 00 00 00 00 00 00 00  0x2         //<-- x27
NSD:FFFFFFE4DE6A7C58| 00 00 00 00 00 00 00 00  0x0         //<-- x28
NSD:FFFFFFE4DE6A7C60| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C68| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C70| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C78| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C80| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C88| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C90| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C98| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7CA0| 40 7D 6A DE E4 FF FF FF  0xFFFFFFE4DE6A7D40 //<<-- 기존 스택 주소
NSD:FFFFFFE4DE6A7CA8| 88 A0 5E 9A 9F FF FF FF  0xFFFFFF9F9A5EA088 \\vmlinux\socket\sock_sendmsg+0x48
 
[Before]
NSD:FFFFFFE4DE6A7C00| A0 7C 6A DE E4 FF FF FF  0xFFFFFFE4DE6A7CA0  //<<--x29, 새로운 스택 주소
NSD:FFFFFFE4DE6A7C08| A8 C2 21 09 80 FF FF FF  0xFFFFFF800921C2A8  \\vmlinux\ipc_router_socket\rpi_ipc_router_sendmsg+0x2C0  // <<--x30
NSD:FFFFFFE4DE6A7C10| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C18| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C20| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C28| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C30| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C38| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C40| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C48| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C50| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C58| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C60| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C68| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C70| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C78| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C80| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C88| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C90| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7C98| 00 00 00 00 00 00 00 00  0x0
NSD:FFFFFFE4DE6A7CA0| 40 7D 6A DE E4 FF FF FF  0xFFFFFFE4DE6A7D40 //<<-- 기존 스택 주소
NSD:FFFFFFE4DE6A7CA8| 88 A0 5E 9A 9F FF FF FF  0xFFFFFF9F9A5EA088 \\vmlinux\socket\sock_sendmsg+0x48
 
자 다시, rpi_ipc_router_send_to() 함수를 호출하기 직전 FFFFFF9F9A81C2A4 주소로 되돌아 가 볼께요.
아래와 같이 파라미터를 저장했지오.
x0 = x23, x1 = x20, x3 = x29+0x88, x3 = x28
NSX:FFFFFF9F9A81C294|AA1703E0            mov     x0,x23
NSX:FFFFFF9F9A81C298|AA1403E1            mov     x1,x20
NSX:FFFFFF9F9A81C29C|910223A2            add     x2,x29,#0x88     ; x2,x29,#136
NSX:FFFFFF9F9A81C2A0|AA1C03E3            mov     x3,x28
NSX:FFFFFF9F9A81C2A4|97FFF7A3            bl      0xFFFFFF9F9A81A130   ; rpi_ipc_router_send_to
 
그리고 rpi_ipc_router_send_to() 함수 호출 후 스택에 푸쉬된 값을 보면, x23은 0xFFFFFFE54FC67000 x20은 0xFFFFFFE4DF968E80 이거든요.
그런데 이 x23은 x0즉 첫 번 째 파라미터로 전달을 하고,  x20은 두 번 째 파라미터로 전달을 했죠.
NSD:FFFFFFE4DE6A7C00| A0 7C 6A DE E4 FF FF FF  0xFFFFFFE4DE6A7CA0   //<<--x29, 새로운 스택 주소
NSD:FFFFFFE4DE6A7C08| A8 C2 81 9A 9F FF FF FF  0xFFFFFF9F9A81C2A8 .vmlinux\ipc_router_socket\rpi_ipc_router_sendmsg+0x2C0 //<<--x30 
NSD:FFFFFFE4DE6A7C10| 00 00 00 00 00 00 00 00  0x0   //<<-- x19
NSD:FFFFFFE4DE6A7C18| 80 8E 96 DF E4 FF FF FF  0xFFFFFFE4DF968E80 //<-- x20
NSD:FFFFFFE4DE6A7C20| 00 15 C6 4F E5 FF FF FF  0xFFFFFFE54FC61500 //<-- x21
NSD:FFFFFFE4DE6A7C28| 80 D0 F4 56 E5 FF FF FF  0xFFFFFFE556F4D080 //<-- x22
NSD:FFFFFFE4DE6A7C30| 00 70 C6 4F E5 FF FF FF  0xFFFFFFE54FC67000 //<-- x23
NSD:FFFFFFE4DE6A7C38| 16 00 00 00 00 00 00 00  0x16                    //<-- x24
NSD:FFFFFFE4DE6A7C40| 38 00 00 00 00 00 00 00  0x38                      //<-- x25
NSD:FFFFFFE4DE6A7C48| 16 00 00 00 00 00 00 00  0x16         //<-- x26
NSD:FFFFFFE4DE6A7C50| 02 00 00 00 00 00 00 00  0x2         //<-- x27
NSD:FFFFFFE4DE6A7C58| 00 00 00 00 00 00 00 00  0x0         //<-- x28
 
이제 함수 호출 시 스택 푸쉬가 되는 규칙을 활용해서 디버깅을 좀 해볼까 해요.
 
이제 소스 코드를 열어 보아야 할 것 같은데요.
rpi_ipc_router_send_to() 함수 호출 시 전달되는 파라미터의 타입을 좀 더 자세히 알 수 있네요. 정리하면..
x0(x23) = 0xFFFFFFE54FC67000 = struct rpi_ipc_port *src, x1(x20) =  0xFFFFFFE4DF968E80 = struct sk_buff_head *out_skb_head
int rpi_ipc_router_send_msg(struct rpi_ipc_port *src,
       struct rpi_ipc_addr *dest,
       void *data, unsigned int data_len)
{
 struct sk_buff_head *out_skb_head;
 int ret;
 
 out_skb_head = rpi_ipc_router_buf_to_skb(data, data_len);
//.. 생략..
 ret = rpi_ipc_router_send_to(src, out_skb_head, dest, 0);
 
그럼 전달되는 파라미터는 확인해보면 아래와 같아요.
첫번 째 파라미터 (struct rpi_ipc_port*)
v.v %s %y %t (struct rpi_ipc_port*)0xFFFFFFE54FC67000
  (struct rpi_ipc_port *) (struct rpi_ipc_port*)0xFFFFFFE54FC67000 = 0xFFFFFFE54FC67000 =  -> (
    (struct list_head) list = (
      (struct list_head *) next = 0xFFFFFF9F9C19D1E0 = local_ports[5],
      (struct list_head *) prev = 0xFFFFFFE5775EC200 = ),
    (struct kref) ref = ((atomic_t) refcount = ((int) counter = 1)),
    (struct rpi_ipc_port_addr) this_port = ((uint32_t) node_id = 1, (uint32_t) port_id = 197),
    (struct rpi_ipc_port_name) port_name = ((uint32_t) service = 0, (uint32_t) instance = 0),
    (uint32_t) type = 0,
    (unsigned int) flags = 0,
    (struct mutex) port_lock_lhc3 = ((atomic_t) count = ((int) counter = 1), (spinlock_t) wait_lock
    (struct comm_mode_info) mode_info = ((int) mode = 0, (void *) xprt_info = 0x0 = ),
    (struct rpi_ipc_port_addr) dest_addr = ((uint32_t) node_id = 0, (uint32_t) port_id = 0),
    (int) conn_status = 0,
    (struct list_head) port_rx_q = ((struct list_head *) next = 0xFFFFFFE54FC67078 = , (struct list_
    (struct mutex) port_rx_q_lock_lhc3 = ((atomic_t) count = ((int) counter = 1), (spinlock_t) wait_
    (char [32]) rx_ws_name = "ipc000000c5_1490_rild",
    (struct wakeup_source *) port_rx_ws = 0xFFFFFFE54FC60100 = ,
 
두번 째 파라미터 (struct sk_buff_head*)
v.v %y %s %h (struct sk_buff_head*)0xFFFFFFE4DF968E80
  (struct sk_buff_head*)0xFFFFFFE4DF968E80 = 0xFFFFFFE4DF968E80 =  -> (
    next = 0xFFFFFFE54FC61500 = ,
    prev = 0xFFFFFFE54FC61500 = ,
    qlen = 0x1,
    lock = (rlock = (raw_lock = (owner = 0x1, next = 0x1))))
 
이런 방식으로 디버깅을 할 수 있어요. 이게 모두 ARM64 아키텍쳐에서 함수호출 시 스택 Push가 어떻게 수행되는지 알았기 때문에 가능한 것이지오. 참 쉽죠?