-
CALL, RET, 함수 호출Computer Science/컴퓨터 구조 2024. 4. 25. 13:13
어셈블리어 명령어 중 CALL, RET 정리하고 함수 호출 과정 정리.
CALL하나의 명령어지만 아래 작업을 내부적으로 수행
- 현재 명령어 포인터(IP) 또는 프로그램 카운터(PC)의 다음 명령어 주소를 스택에 저장 <= PUSH
(스택에 해당하는 메모리 주소에 값을 STORE) - 실행하려는 명령어 포인터(IP) 또는 프로그램 카운터(PC)로 점프 <= JMP
CALL 은 1바이트(opcode) + 명령어 주소 4바이트 (operand)로 구성.
명령어 주소는 오프셋을 의미한다. 즉 CALL로 실행하고자 하는 함수의 주소가 현재 명령어의 주소 + 오프셋으로 계산되어 점프한다. 점프하기 전에 현재 명령어의 다음 명령어의 주소를 스택에 PUSH 하는데, 이 다음 명령어의 주소는 현재 명령어의 주소 + CALL 명령어의 크기(보통 5바이트)가 된다. 예를 들어 현재 명령어의 주소가 x 번지, CALL 명령어의 크기가 5바이트 이면 다음 명령어의 주소가 x+5번지가 되고 스택에 x+5번지의 주소값이 스택에 PUSH 되는 것이다.
CALL 명령어도 종류개 여러개다. 만약 오프셋이 4바이트를 넘는다면 직접 주소값을 지정하는 다른 CALL 명령어가 사용될 수 있을 듯. 직접 주소를 지정한다거나, 레이블을 사용한다거나 (레이블과 지정된 주소의 매핑 데이터 구조)
—이전 함수의 RBP 값이 스택에 PUSH되고 현재 RSP(스택의 최상단, 이전 RBP가 PUSH된 주소의 다음(-방향) 주소)가 RBP 레지스터에 저장된다. 그리고 현 RBP 값과 스택에 저장된 이전 RBP 값을 사용해서 이전 함수로 되돌아갈 텐데 아래 자세히
—
RET
POP RBP -> 현재 RBP값을 RBP에 저장되어 있는 값으로 교체
RET -> Return Address 로 PC 복귀
- 반환값 RAX에 저장
- RSP에 RBP의 값을 넣으면서 로컬변수나 임시 데이터 해제
- POP RBP -> 스택 메모리의 RBP가 가리키는 주소의 내용을 RBP 레지스터로 저장하고 스택포인터 증가시켜 스택의 RBP 해제
- RET
프로그램 카운터에 RSP가 가리키는 Return Address를 PC 혹은 IP에 넣고 RSP 증가시켜 이전 함수로 복귀하고 프로그램 카운터는 Return Address 부터 명령어 실행.
아마 cpu아키텍처 설계에서 RET 명령어 자체를 스택포인터 RSP를 증가시키고 설정된 프로그램 카운터 PC부터 실행하도록 명령어를 설계한 듯.
항상 PC 주소값의 명령어를 실행을 하는 게 아닐 수 있다. 아키텍처마다 다르다.
반환 값은 일반적으로 EAX 또는 RAX, R0(ARM)에 반환되기 직전에 저장되어 전달된다.
함수 호출 시에 스택에 데이터가 PUSH 되는 순서
- 매개변수(Parameter) : 매개변수의 오른쪽에서 왼쪽으로 스택에 PUSH 되어 함수 내에서 왼쪽부터 읽을 수 있게 한다.
Primitive 타입 - 실제값이 PUSH 됨.
Reference 타입 - 주소값이 PUSH 됨.
C++ 혹은 Go의 Struct는 실제값을 전달될 수도 주소값이 전달될 수도 있음. (call-by-value, call-by-reference)
구조체의 실제값(value)이 전달된다면 전체 내용이 복사되어 스택에 저장된다.함수 내에서 매개변수를 사용할 때는 베이스 포인터 RBP 기준으로 메모리에 접근하여 사용한다.
- 반환 주소(Return Address) : CALL 명령어 실행 시에 현재 CALL 명령어의 주소의 다음 명령어의 주소가 Return Address가 되어 스택에 PUSH 된다.
- 이전 베이스 포인터(Previous Frame Pointer): 함수가 자신의 스택 프레임을 설정하기 위해 현재 베이스 포인터(EBP 또는 RBP)의 값, 즉 이전 함수의 BP를 스택에 PUSH.
그 후에 자신의 스택 포인터(ESP 또는 RSP)의 값을 베이스 포인터(EBP 또는 RBP)에 복사한다. (RSP는 항상 스택의 최상단 포인터이므로)
이렇게 함으로써 이전 함수의 스택 프레임에 대한 정보를 보존하고, 현재 함수의 지역 변수와 매개변수에 접근할 수 있는 기준점인 현재 함수의 베이스 포인터를 설정. - 지역 변수(Local Variables): 함수 내에서 선언된 지역 변수는 함수의 스택 프레임 내에 할당. 지역 변수들은 함수 실행 동안 필요한 데이터를 저장하는 데 사용된다.
-> 컴파일러가 지역변수 총 메모리 크기를 계산하고 크기만큼 RSP 뺀다.
-> 각 지역변수의 위치와 값은 컴파일러가 알아서 배치하여 저장. 스택메모리의 사용량 효율은 컴파일러 능력에 따라...
지역 변수 할당 후의 스택 포인터를 RSP로 저장한다. RSP는 항상 스택의 최상단 포인터이므로.
지역 변수의 저장 순서는 반드시 코드 순서대로 저장되는 것은 아니고 컴파일러에 따라 다양할 수 있다.
각 지역변수는 베이스 포인터로부터의 오프셋으로 접근한다.
각 지역변수에 대응하는 오프셋의 정보는 컴파일 타임에 결정된다.
컴파일러가 각 지역변수에 해당하는 오프셋을 사용하여 알아서 기계어(어셈블리어) 코드로 변환한다. - 임시 데이터(Temporary Data): 함수 실행 중 생성되는 임시 데이터나 함수 호출 결과 등이 필요한 경우, 이 데이터 역시 스택에 저장될 수 있다.
'Computer Science > 컴퓨터 구조' 카테고리의 다른 글
레지스터와 명령어 사이클 (0) 2023.08.11 명령어의 구조 (0) 2023.08.05 - 현재 명령어 포인터(IP) 또는 프로그램 카운터(PC)의 다음 명령어 주소를 스택에 저장 <= PUSH