함수 호출 규약
함수의 호출 및 반환에 대한 약속. 함수의 흐름에 따라서, 함수를 호출하면 함수로 이동하고, 반환하면, 다시 원래 함수로 돌아가는 흐름을 보인다. 그래서 함수를 호출할 때는 반환된 이후를 위해 호출자(Caller)의 상태(Stack frame) 및 반환 주소(Return Address)를 저장해야 한다. 그리고 호출자는 피호출자(Callee)가 요구하는 인자를 전달 해줘야하고, 피호출자의 실행이 종료될 때는 반환 값을 전달받아야 한다.
일반적으로 컴파일러가 호출 규약에 맞게코드를 컴파일한다. 컴파일러는 CPU의 아키텍터에 적합한 호출규약을 선택한다.
하지만, 컴파일러의 도움 없이 어셈블릴 코드를 작성하려 하거나, 어셈블리로 작성된 코드를 읽으려면 함수 호출 규약을 알아야 한다.
함수 호출 규약의 종류
x86 아키텍처의 경우 레지스터를 통해 피호출자의 인자를 전달하기에는 레지스터의 수가 적으므로, 스택으로 인자를 전달하는 규약을 사용한다. x86-64 아키텍처에서는 레지스터가 많으므로 적은 수의 인자는 레지스터만 사용해서 인자를 전달하고, 인자가 너무 많을 때만 스택을 사용한다.
CPU의 아키텍처가 같아도, 컴파일러에 따라 호출 규약이 다를 수 있다. 컴파일 시, 윈도우는 MSVC, 리눅스에서는 gcc를 많이 사용한다. x86-64 아키텍처에서는 MSVC는 MS x64 호출규약을, gcc는 SYSTEM V호출 규약을 적용한다.
x86호출 규약 : cdecl
해당 아키텍처가 레지스터 수가 적어, 스택을 통해 인자를 전달한다. 인자를 정리하기 위해 호출자가 스택을 정리하는 특징이 있다. 스택을 통해 인자를 전달할 때, 마지막 인자부터 첫 번째 인자까지 거꾸로 스택에 push한다.
x86_64호출 규약 : SYSV
리눅스는 SYSTEM V(SYSV) Application Binary Interface(ABI)를 기반으로 만들어졌다. SYSV ABI는 ELF 포맷, 링킹 방법, 함수 호출 규약등의 내용을 담고 있다.
SYSV에서 정의한 함수 호출 규약 특징
1. 6개의 인자를 RDI, RSI, RDX, RCX, R8, R9에 순서대로 저장하여 전달한다.
2. Caller에서 인자 전달에 사용된 스택을 정리한다.
3. 함수의 반환 값은 RAX로 전달한다.