함수 호출 규약

함수 호출 규약

2024년 6월 1일

함수 호출 규약 #

함수를 호출하면서 Caller(호출자)의 스택 상태와 리턴주소를 저장하고, Callee(피호출자)의 인자를 전달해줘야하는데, 이걸 컴파일러가 상황에 맞게 선택해서 컴파일하는 것을 함수호출 규약이라고 한다.
CPU 아키텍쳐에 따라, 아키텍쳐가 같아도 컴파일러에 따라 달라질 수 있고, 같은 호출규약이라도 컴파일러마다 다르게 구현하기도 한다.

x86 #

c19f4089-3624-4ea4-8181-5a4d8c983d61

cdcel #

 1; caller
 20x00001011 <+7>:     push   0x2
 30x00001013 <+9>:     push   0x1
 40x00001015 <+11>:    call   0x1000 <callee>
 50x0000101a <+16>:    add    esp,0x8
 6
 7; callee
 80x00001000 <+0>:     endbr32
 90x00001004 <+4>:     push   ebp
100x00001005 <+5>:     mov    ebp,esp
11
12; 대략적인 그림
13+-----------------+ 낮은주소
14| caller ebp      | <- esp/ebp
15+-----------------+
16| return address  |
17+-----------------+
18| stack param (1) |
19+-----------------+
20| stack param (2) |
21+-----------------+ 높은주소
  • x86에서는 레지스터 수가 적어서 스택을 통해 인자를 전달하게 된다. 순서는 마지막 인자부터 push해서 첫번째 인자가 가장 ebp에 가깝게 저장한다.
  • 마찬가지로 caller에서 스택을 정리한다.

x86-64 #

1fe47cf9-8395-4f83-8058-9726f764d988

System V AMD64 ABI (SYSV) #

리눅스에서 file 명령을 사용해보면 SYSV 문자열을 확인할 수 있다.

132fa8d6-3e28-43b9-86e7-d81234298033

 1; caller function
 20x55555555518a <caller+5>     mov    rbp, rsp
 30x55555555518d <caller+8>     push   7            ; stack 사용
 40x55555555518f <caller+10>    mov    r9d, 6
 50x555555555195 <caller+16>    mov    r8d, 5
 60x55555555519b <caller+22>    mov    ecx, 4
 70x5555555551a0 <caller+27>    mov    edx, 3
 80x5555555551a5 <caller+32>    mov    esi, 2
 90x5555555551aa <caller+37>    movabs rax, 0x1b69b4bacd05f15
100x5555555551b4 <caller+47>    mov    rdi, rax
110x5555555551b7 <caller+50>    call   0x555555555129 <callee>
120x5555555551bc <caller+55>    add    rsp,0x8      ; 사용했던 stack 만큼만 정리
13
14; callee function
150x555555555129 <callee>:	endbr64
160x55555555512d <callee+4>:	push   rbp
170x55555555512e <callee+5>:	mov    rbp,rsp
180x555555555131 <callee+8>:	mov    QWORD PTR [rbp-0x18],rdi   ; 인자들을 전부 스택에 넣는다. (rbp)
190x555555555135 <callee+12>:	mov    DWORD PTR [rbp-0x1c],esi
200x555555555138 <callee+15>:	mov    DWORD PTR [rbp-0x20],edx
210x55555555513b <callee+18>:	mov    DWORD PTR [rbp-0x24],ecx
220x55555555513e <callee+21>:	mov    DWORD PTR [rbp-0x28],r8d
230x555555555142 <callee+25>:	mov    DWORD PTR [rbp-0x2c],r9d
24
25; 대략적인 그림
26+-----------------+ 낮은주소
27| r9d,...,rdi, etc| <- rbp-0x2c (이 위치부터 파라미터가 저장됨)
28+-----------------+
29| caller rbp      | <- rsp/rbp
30+-----------------+
31| return address  |
32+-----------------+
33| stack param (7) | <- 레지스터 부족으로 스택에서 관리하는 파라미터
34+-----------------+ 높은주소
  • 6개의 인자를 rdi, rsi, rdx, rcx, r8, r9, 스택 순으로 저장해서 전달된다. x86처럼 저장은 뒤쪽 파라미터부터 한다.
    예를들어서 인자가 3개면 rdx(3), rsi(2), rdi(1) 순서이다.
  • caller에서 함수 리턴 이후 사용된 스택을 정리한다.
  • 함수의 반환값은 rax에 저장된다.
comments powered by Disqus