함수 호출 규약
2024년 6월 1일
함수 호출 규약 #
함수를 호출하면서 Caller(호출자)의 스택 상태와 리턴주소를 저장하고, Callee(피호출자)의 인자를 전달해줘야하는데, 이걸 컴파일러가 상황에 맞게 선택해서 컴파일하는 것을 함수호출 규약이라고 한다.
CPU 아키텍쳐에 따라, 아키텍쳐가 같아도 컴파일러에 따라 달라질 수 있고, 같은 호출규약이라도 컴파일러마다 다르게 구현하기도 한다.
x86 #
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 #
System V AMD64 ABI (SYSV) #
리눅스에서 file 명령을 사용해보면 SYSV 문자열을 확인할 수 있다.
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에 저장된다.