함수 호출 규약

함수 호출 규약

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

x86

c19f4089-3624-4ea4-8181-5a4d8c983d61
c19f4089-3624-4ea4-8181-5a4d8c983d61

cdcel

; caller
0x00001011 <+7>:     push   0x2
0x00001013 <+9>:     push   0x1
0x00001015 <+11>:    call   0x1000 <callee>
0x0000101a <+16>:    add    esp,0x8

; callee
0x00001000 <+0>:     endbr32
0x00001004 <+4>:     push   ebp
0x00001005 <+5>:     mov    ebp,esp

; 대략적인 그림
+-----------------+ 낮은주소
| caller ebp      | <- esp/ebp
+-----------------+
| return address  |
+-----------------+
| stack param (1) |
+-----------------+
| stack param (2) |
+-----------------+ 높은주소
  • x86에서는 레지스터 수가 적어서 스택을 통해 인자를 전달하게 된다. 순서는 마지막 인자부터 push해서 첫번째 인자가 가장 ebp에 가깝게 저장한다.
  • 마찬가지로 caller에서 스택을 정리한다.

x86-64

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

System V AMD64 ABI (SYSV)

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

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

; caller function
0x55555555518a <caller+5>     mov    rbp, rsp
0x55555555518d <caller+8>     push   7            ; stack 사용
0x55555555518f <caller+10>    mov    r9d, 6
0x555555555195 <caller+16>    mov    r8d, 5
0x55555555519b <caller+22>    mov    ecx, 4
0x5555555551a0 <caller+27>    mov    edx, 3
0x5555555551a5 <caller+32>    mov    esi, 2
0x5555555551aa <caller+37>    movabs rax, 0x1b69b4bacd05f15
0x5555555551b4 <caller+47>    mov    rdi, rax
0x5555555551b7 <caller+50>    call   0x555555555129 <callee>
0x5555555551bc <caller+55>    add    rsp,0x8      ; 사용했던 stack 만큼만 정리

; callee function
0x555555555129 <callee>:	endbr64
0x55555555512d <callee+4>:	push   rbp
0x55555555512e <callee+5>:	mov    rbp,rsp
0x555555555131 <callee+8>:	mov    QWORD PTR [rbp-0x18],rdi   ; 인자들을 전부 스택에 넣는다. (rbp)
0x555555555135 <callee+12>:	mov    DWORD PTR [rbp-0x1c],esi
0x555555555138 <callee+15>:	mov    DWORD PTR [rbp-0x20],edx
0x55555555513b <callee+18>:	mov    DWORD PTR [rbp-0x24],ecx
0x55555555513e <callee+21>:	mov    DWORD PTR [rbp-0x28],r8d
0x555555555142 <callee+25>:	mov    DWORD PTR [rbp-0x2c],r9d

; 대략적인 그림
+-----------------+ 낮은주소
| r9d,...,rdi, etc| <- rbp-0x2c (이 위치부터 파라미터가 저장됨)
+-----------------+
| caller rbp      | <- rsp/rbp
+-----------------+
| return address  |
+-----------------+
| stack param (7) | <- 레지스터 부족으로 스택에서 관리하는 파라미터
+-----------------+ 높은주소
  • 6개의 인자를 rdi, rsi, rdx, rcx, r8, r9, 스택 순으로 저장해서 전달된다. x86처럼 저장은 뒤쪽 파라미터부터 한다.
    예를들어서 인자가 3개면 rdx(3), rsi(2), rdi(1) 순서이다.
  • caller에서 함수 리턴 이후 사용된 스택을 정리한다.
  • 함수의 반환값은 rax에 저장된다.

Comments

ESC
Type to search...