// 함수 호출
call function // = push %eip + jmp function
// 프롤로그
push %ebp // 이 시점에서 스택에서 과거 eip(리턴 주소) 및 과거 ebp(스택 복구를 위한)이 들어있다
mov %esp, %ebp // 스택 프레임을 새로 시작, ebp가 움직이는게 맞는거다. 헷갈리지 말자.
에필로그
leave // 스택 프레임 복구 -> mov %ebp, %esp(탑 축소) + pop %ebp(바텀 복구)
ret // 흐름(PC) 복구 -> pop %eip
callee가 정리하는 경우 ret 인스트럭션
ret 0x8 // 인자 개수만큼 스택을 정리함
이 경우 callee 입장에서 얼마만큼 정리해야할지 컴파일 타임에 정해야 하기 때문에, callee 정리 함수 호출 규약의 경우 가변 인자가 불가하다.
함수 호출 컨벤션
인자를 뒤에서부터 쌓는 이유는, ‘스택’이기 때문이다.
pop 할 때 마지막에 넣은것부터 나오니까. 사용측을 고려한 동작이다.
fastcall의 경우, 인자 쌓는게 좀 특이한데, 2개 이상의 뒤쪽 인자는 스택에 삽입 후, 앞쪽 인자를 ecx, edx레지스터에 담는다.
이 때도 실행 순서는 뒤에서부터이지만, 레지스터의 경우에는 스택이 아니기 때문에 인자 순서와 ecx, edx 삽입 순서는 동일하다.
아래 코드에서 a는 ecx, b는 edx에 저장되지만, 그 실행 순서가 b → a라는 의미다.
stdcall, cdecl의 경우 생각한대로 뒤쪽 인자부터 스택에 push/mov 된다.
일반적으로 인자를 쌓는 것은 결국 caller다.
stdcall → stdcall 호출임에도, 호출자가 스택에 인자를 넣었다
리턴이 4바이트를 넘어가는 경우
예를 들어, 8바이트인 long long의 경우 eax 및 edx에 나눠서 리턴한다.
주목할 점은, 상위 4바이트가 edx에 별도기록 된다는 것.
이 8바이트 리턴을 메모리 0x0에다 기록하려면,
mov %eax, 0x0 + mov %edx, 0x4로 해야 한다.
eax 4바이트는 -16부터, edx 4바이트는 -12부터 기록했다.
이렇게 하면 little endian을 지켜서 0x0: 08 00 00 00 00 00 00 00 이 기록될 것이며, 읽었을 때 8로 정확하게 나올 것이다.
little endian이라도 4바이트 단위로 읽을 때는 정상 순서로 나온다
시스템콜
시스템콜이 뭔데?
하드웨어 등 물리 자원을 다루는 커널 동작을 인터페이스로서 사전 정의해놓은 것
한 프로그램이 하드웨어까지 제어할 수 없고, 또 보안상 없어야 하기 때문에, 로우 레벨 구현들은 인터페이스 뒤로 숨겨야 한다.