procedure, caller, calle 등의 terminology와 stack pointer Runtime에 stack 등을 알아보자.
caller는 함수를 호출한 부분, calle는 함수 그 자체를 생각하면 된다. caller가 함수를 호출하면 calle인 함수 정의부 부분으로 들어간다.
calle가 알아야 할 것들은 아래와 같다.
- 인자값의 위치(레지스터의 위치)
- 반환값의 주소(caller의 위치)
- 반환값의 위치(레지스터의 위치)
- caller와 calle는 같은 CPU에서 실행되기 때문에 같은 레지스터를 사용한다.
그렇기 때문에 중첩 사용이 가능하다. 이는 문제를 유발하므로 caller saved & callee saved 레지스터를 사용한다. 이를 calling convention이라 칭한다.
caller는 함수의 호출 위치이므로 calle로 이동 후 다시 제자리로 리턴해야한다. 이를 call과 return의 이미지로 확인할 수 있다.
call이란 instruction은 jmp와 비슷하게 작동한다. 하지만 가장 큰 차이점은 절차(이동) 이후에 복귀 주소로 jmp한다는 것이다. 반환 주소를 따로 저장해야한다는 점이다. 레지스터만으로 중첩된 함수의 호출을 모두 다룰 수 없다. 재귀함수 같은 경우 양이 상당히 많기 때문이다. 사전에 중첩된 함수의 개수를 알 수도 없다. 그렇게 때문에 LIFO(last in first out)의 구조를 가지고 있는 stack을 메모리로써 사용하는 것이다. 함수의 호출방식과 적합하기 때문이다.
RPC(Remote Procedure call)이란, 별도의 원격 제어를 위한 코딩 없이 다른 주소 공간에서 리모트의 함수나 프로시저를 실행 할 수 있게 해주는 프로세스간 통신입니다. 즉, 위치에 상관없이 RPC를 통해 개발자는 위치에 상관없이 원하는 함수를 사용할 수 있습니다.
"함수나 프로시저"라는 부분이 있는데 이 글에선 함수부분만 다루겠다.
이 Procedure의 원리로 인해 어느 위치에서나 함수를 호출하고 사용할 수 있는 것이다. 왜냐하면 리턴 위치와 값, 인자 값 등을 내부적으로 알아서 저장하고 jmp하기 때문이다.
레지스터는 저장하는데 제한이 있기 때문에 함수호출의 양을 모르는 상태에선 사용할 수 없다.
Runtime Stack이란 Runtime에 procedure context(문맥)을 저장하는 메모리이다.
여기서 문맥이란 중첩된 함수의 정보를 의미한다. 같은 함수라도 리턴주소와 인자가 다르기 때문이다.
이를 별도로 저장해야한다.
위는 런타임 때의 함수 호출과 반환 방식이다.
스택은 값이 저장될 때 큰 주소에서 작은 주소로 커진다.
- %rsp: stack의 top element의 주소를 가지고 있다.
- %rbp: first element의 주소를 가지고 있다.
- %rip: 프로세서가 현재 진행하고 있는 명령의 주소를 가지고 있다. 명령 포인터라고 부른다.
- pushq: runtime stack에 quad word(8byte) 크기를 insert한다.
- popq: runtime stack에 quad word(8byte) 크기를 pop한다.
위는 작동 방식이다. top은 주소가 작은 방향으로 늘어난다는 것을 알고 본다면 이해하기 쉬울 것이다.
스택을 이용한 함수의 호출 반환을 알아보자.
이를 procedure call, return이라 칭한다
- Procedure call: return address를 stack에 push 이후 callee로 jmp
- Procedure return: return address를 stack에서 pop 이후 caller로 jmp