함수 호출 규약은 함수의 호출 및 반환에 대한 약속이다.
한 함수에서 다른 함수를 호출할 때, 프로그램의 실행 흐름은 다른 함수로 이동한다.
그 후 호출한 함수가 값을 반환하면, 다시 원래 함수로 돌아와서 기존의 실행 흐름을 이어나간다.
그러므로 함수를 호출할 때는 반환된 이후를 위해 호출자 의 상태 및 반환 주소 를 저장해야 한다.
또한, 호출자는 피호출자 가 요구하는 인자 를 전달해줘야 하며, 피호출자의 실행이 종료될 때는 반환 값을 전달 받아야 한다.
컴파일러는 지원하는 호출 규약 중, CPU 아키텍처에 적합한 것을 선택한다.
예를 들어 x86(32bit) 아키텍처는 레지스터를 통해 피호출자의 인자를 전달하기에는 레지스터의 수가 적으므로, 스택으로 인자를 전달하는 규약을 사용한다.
반대로 x86-64 아키텍처에서는 레지스터가 많으므로 적은 수의 인자는 레지스터만 사용해서 인자를 전달하고, 인자가 너무 많은 때만 스택을 사용한다.
CPU 의 아키텍처가 같아도, 컴파일러가 다르면 적용하는 호출 규약이 다를 수 있다.
x86(32bit) 아키텍처는 레지스터의 수가 적으므로, 스택을 통해 인자를 전달한다.
또한, 인자를 전달하기 위해 사용한 스택을 호출자가 정리하는 특징이 있다.
x86 아키텍처 호출규약 특징
1. 레지스터의 수가 적으므로 스택을 통해 인자 전달
2. 인자 전달을 위해 사용한 스택을 호출자 가 정리
리눅스는 SYSTEM V(SYSV) Application Binary interface(ABI) 를 기반으로 만들어졌다.
SYSV ABI 는 ELF 포맷, 링킹 방법, 함수 호출 규약 등의 내용을 담고 있다.
SYSV 함수 호출 규약 특징
1. 6개의 인자를 rdi, rsi, rdx, rcx, r8, r9 에 순서대로 저장하여 전달
2. Caller 에서 인자를 전달에 사용된 스택을 정리
3. 함수의 반환 값은 rax 로 전달
인자전달은 순서대로 rdi, rsi, rdx, rcx, r8, r9 레지스터에 들어가게 되고, 그 이상의 인자값이 있을 경우 스택에 추가적으로 들어간다.
함수의 내용을 실행시키기 전에는, 함수의 실행이 종료된 후 호출자로 실행이 돌아와야되므로 미리 반환 주소 를 스택에 push 한후 함수를 실행한다.
함수는 고유의 스택프레임을 가지고 있다. 함수를 실행시키면 스택프레임을 새로 만들어야 되므로 기존의 호출자의 스택 프레임 위치를 스택에 push 하여 저장한 후 새로운 함수의 스택프레임을 만든다.
함수의 반환값이 있다면 해당 반환값은 rax 레지스터로 옮기게 된다.
반환은 저장해두었던 스택 프레임 과 반환 주소 를 꺼내면서 이루어진다.
보통 leave 명령어로 스택 프레임을 꺼내고, ret 명령어로 복귀한다.