다른 명령
| (같은 사용자의 중간 판 7개는 보이지 않습니다) | |||
| 6번째 줄: | 6번째 줄: | ||
* Callee는 return address가 어디에 저장되어 있는지 알아야 한다.(보통 스택에 저장) | * Callee는 return address가 어디에 저장되어 있는지 알아야 한다.(보통 스택에 저장) | ||
* Caller는 return value가 어디에 저장되어 있는지 알아야 한다. | * Caller는 return value가 어디에 저장되어 있는지 알아야 한다. | ||
이때 caller와 caller는 같은 레지스터를 사용하고 있으므로, '''calling convention'''이 존재하여 매개변수, 반환값, return address 등을 어디에 저장할 지를 약속한다. | 이때 caller와 caller는 같은 레지스터를 사용하고 있으므로, '''calling convention'''이 존재하여 매개변수, 반환값, return address 등을 어디에 저장할 지를 약속한다. 레지스터들의 용도는 아래와 같다. | ||
{| class="wikitable" | |||
|+ | |||
!용도 | |||
!레지스터 | |||
!비고 | |||
|- | |||
|Return value | |||
|%rax | |||
|Callee가 저장 | |||
|- | |||
|Arguments | |||
|%rdi, %rsi, %rdx, %rcx, %r8, %r9 | |||
|Caller가 저장 | |||
|- | |||
|Caller-saved | |||
|%r10, %r11 | |||
|Caller가 저장 | |||
|- | |||
|Callee-saved | |||
|%rbx, %r12~%r15, %rbp | |||
|Callee가 저장 | |||
|- | |||
|Special | |||
|%rsp | |||
|stack pointer, 복원 자동 | |||
|} | |||
==Procedure Control Flow== | ==Procedure Control Flow== | ||
| 30번째 줄: | 56번째 줄: | ||
* Callee 주의점: 반환값을 %rax에 저장하되, 해당 값이 8byte를 초과할 경우, 포인터를 대신 반환해야 한다. | * Callee 주의점: 반환값을 %rax에 저장하되, 해당 값이 8byte를 초과할 경우, 포인터를 대신 반환해야 한다. | ||
== | ==Passing data== | ||
어셈블리에서 함수와 함수 사이에 데이터(인자)를 주고 받기 위해서는 몇가지 규약이 사용된다. 이는 다음과 같다: | |||
# register의 첫 6개는 인자로 사용된다. | |||
#* %rdi, %rsi, %rdx, %rcx, %r8, %r9 | |||
# return 값은 %rax에 저장된다. | |||
예제 코드에서도, <code>call 0x400550</code>를 사용하기 조금 전에 <code>mov $0x3,%esi</code>, <code>mov $0x5,%edi</code>과 같은 명령어를 사용하여 함수에 전달할 인자를 초기화 하는 것을 볼 수 있다. 또한, <code>ret</code>을 사용하기 조금 전에 <code>mov %rdi,%rax</code>, <code>imul %rsi,%rax</code>과 같은 명령어를 사용하여 %rax를 초기화하는 것을 볼 수 있다. | |||
함수는 인자 외에도 다양한 임시 값이나 변수들을 register에 저장해 사용한다. 하지만 '''caller/callee save'''라는 개념이 없다면, register를 이용한 변수들의 사용에 상당한 제약이 생긴다. 왜냐하면 어떤 함수에서 %rbx를 사용하고자 할 때, 해당 레지스터는 이전에 호출된 함수에서 이미 덮어쓰기되어 있을 수 있기 때문이다. '''Caller/Callee save'''는 caller에서 사용된 register를 보존하여 callee에서 자유롭게 register들을 사용할 수 있도록 하는 개념이다. 이 개념을 구현하기 위해서 register는 '''caller-saved register'''와 '''callee-saved register'''로 나누어진다. | |||
===Caller-saved registers=== | |||
Caller-saved register는 '''caller 측에서 백업/복원에 대한 책임이 있는 register'''이다. 이는 caller 함수가 register 값을 메모리에 백업한 후, callee 함수가 시작/종료된 후 메모리에 백업된 값을 이용해서 register를 복원하여 구현된다. 이를 통해서 '''callee가 caller-saved register들을 자유롭게 사용할 수 있도록 보장'''한다. 이때 caller-saved register들에는 %rdi, %rsi, %rdx, %rcx, %r8 ~ %r11가 있다. | |||
===Callee-saved registers=== | |||
Callee-saved register는 '''callee 측에서 백업/복원 책임이 있는 register'''이다. 이는 callee 함수가 해당 register들을 사용하고자 할 때 백업해둔 후, callee 함수가 해당 register 사용을 완료한 후 해당 register의 값을 복원하여 구현된다. 즉 '''callee가 자유롭게 사용하되, 스스로 복구해야 하는 레지스터'''이다. Callee-saved register들에는 %rbx, %r12 ~ %r14이 있다. | |||
__예제 코드에는 callee 함수인 multistore에서 사용되는 %rbx를 caller 함수에서 보존하는 것을 보여준다. %rbx register는 callee-saved이지만,__ | |||
== | ==Stack based languages== | ||
[[파일:Stack Frame.jpg|대체글=Figure 1. Stack Frame|섬네일|302x302픽셀|Figure 1. Stack Frame]] | |||
Stack 기반 언어(Stack based language)는 말 그대로 스택을 기반으로 하는 언어이다. 이러한 언어는 재귀 함수를 지원하며, 여러 번 같은 함수를 호출할 수 있으므로, '''각 호출마다 상태(지역 변수, return address 등)를 스택에서 따로 관리하고 저장'''해야 한다. 이때 스택은 함수 실행 상태를 저장하는 '''frame들의 집합으로 구성'''된다. 이때 stack frame에는 다음과 같은 내용이 저장된다. | |||
* '''return address''': callee 함수가 반환된 후 다시 돌아갈 명령어 주소 | |||
* '''local variables''': 해당 함수 내에서 사용되는 지역 변수들(register에 모두 저장할 수 없을 정도로 많으면 사용) | |||
* '''Caller saved registers''': Caller의 레지스터를 백업 | |||
* '''Callee saved registers''': Callee측이 레지스터를 사용하기 전에 백업(필요할 경우) | |||
이때 함수가 호출된 직후 frame이 생성되며, 해당 함수가 return되기 직전에 frame이 제거된다. 또한, '''%rsp는 현재 스택의 top을 가리키는 레지스터'''이다. | |||
==각주== | ==각주== | ||
[[분류:컴퓨터 시스템]] | [[분류:컴퓨터 시스템]] | ||
2025년 4월 19일 (토) 08:32 기준 최신판
상위 문서: Assembly
개요
함수 f가 함수 g를 호출할 때, f는 caller에 해당하고, g는 callee에 해당한다. caller와 callee는 정상적으로 작동하기 위해 다음 정보들을 알아야 한다.
- Callee는 사용할 매개변수가 어디에 저장되어 있는지 알아야 한다.(보통 스택, 레지스터에 저장)
- Callee는 return address가 어디에 저장되어 있는지 알아야 한다.(보통 스택에 저장)
- Caller는 return value가 어디에 저장되어 있는지 알아야 한다.
이때 caller와 caller는 같은 레지스터를 사용하고 있으므로, calling convention이 존재하여 매개변수, 반환값, return address 등을 어디에 저장할 지를 약속한다. 레지스터들의 용도는 아래와 같다.
| 용도 | 레지스터 | 비고 |
|---|---|---|
| Return value | %rax | Callee가 저장 |
| Arguments | %rdi, %rsi, %rdx, %rcx, %r8, %r9 | Caller가 저장 |
| Caller-saved | %r10, %r11 | Caller가 저장 |
| Callee-saved | %rbx, %r12~%r15, %rbp | Callee가 저장 |
| Special | %rsp | stack pointer, 복원 자동 |
Procedure Control Flow
Procedure control flow의 주축을 이루는 명령어는 ret와 call이다. 이때 두 명령어를 구현하는 데에는 메모리의 스택 자료구조가 사용된다.
Function call
함수 호출에는 call Dest 명령어가 사용된다. 이는 현재에서 지정된 함수 주소(Dest)로 jump하며, 돌아올 주소(스택 포인터)를 스택에 저장한다. 이는 다음과 같은 순서로 작동한다:
- 현재 명령어의 바로 다음 주솟값(return address)을 스택에 push한다.
- call 명령의 크기는 5bytes 크기이므로 %rip+5 (call 명령의 다음 주소)를 스택에 저장한다.
- Dest가 가리키는 주소로 jump한다.
- 프로그램 카운터[1]로 사용되는 %rip에 지정된 함수 주소(Dest)를 저장한다.
이때 스택에 추가적으로 return address를 저장하므로 %rsp(스택 포인터)의 값은 8 감소한다.
Function Return
Call 명령어를 통해 호출된 함수의 실행이 끝나면, ret 명령어를 사용해 원래의 주소(return address)로 복귀해야 한다. 이는 다음과 같은 방식으로 작동한다:
- 스택에서 값을 pop하고, return address를 얻는다. 이때 %rsp의 값은 8 증가한다.
- %rip에 return address를 할당하고, 이를 통해 return address로 jump한다.
즉, ret는 pop %rip과 동일한 효과이다.
x86-64 Return Values
x86-64 시스템에서 기본적인 규칙은 함수가 종료된 뒤의 반환값은 %rax에 저장된다는 것이다. 이때 caller와 callee는 각각 주의할 점이 존재한다.
- Caller 주의점: Callee를 호출하기 전에 %rax의 값을 먼저 백업해두어야 한다.
- Callee 주의점: 반환값을 %rax에 저장하되, 해당 값이 8byte를 초과할 경우, 포인터를 대신 반환해야 한다.
Passing data
어셈블리에서 함수와 함수 사이에 데이터(인자)를 주고 받기 위해서는 몇가지 규약이 사용된다. 이는 다음과 같다:
- register의 첫 6개는 인자로 사용된다.
- %rdi, %rsi, %rdx, %rcx, %r8, %r9
- return 값은 %rax에 저장된다.
예제 코드에서도, call 0x400550를 사용하기 조금 전에 mov $0x3,%esi, mov $0x5,%edi과 같은 명령어를 사용하여 함수에 전달할 인자를 초기화 하는 것을 볼 수 있다. 또한, ret을 사용하기 조금 전에 mov %rdi,%rax, imul %rsi,%rax과 같은 명령어를 사용하여 %rax를 초기화하는 것을 볼 수 있다.
함수는 인자 외에도 다양한 임시 값이나 변수들을 register에 저장해 사용한다. 하지만 caller/callee save라는 개념이 없다면, register를 이용한 변수들의 사용에 상당한 제약이 생긴다. 왜냐하면 어떤 함수에서 %rbx를 사용하고자 할 때, 해당 레지스터는 이전에 호출된 함수에서 이미 덮어쓰기되어 있을 수 있기 때문이다. Caller/Callee save는 caller에서 사용된 register를 보존하여 callee에서 자유롭게 register들을 사용할 수 있도록 하는 개념이다. 이 개념을 구현하기 위해서 register는 caller-saved register와 callee-saved register로 나누어진다.
Caller-saved registers
Caller-saved register는 caller 측에서 백업/복원에 대한 책임이 있는 register이다. 이는 caller 함수가 register 값을 메모리에 백업한 후, callee 함수가 시작/종료된 후 메모리에 백업된 값을 이용해서 register를 복원하여 구현된다. 이를 통해서 callee가 caller-saved register들을 자유롭게 사용할 수 있도록 보장한다. 이때 caller-saved register들에는 %rdi, %rsi, %rdx, %rcx, %r8 ~ %r11가 있다.
Callee-saved registers
Callee-saved register는 callee 측에서 백업/복원 책임이 있는 register이다. 이는 callee 함수가 해당 register들을 사용하고자 할 때 백업해둔 후, callee 함수가 해당 register 사용을 완료한 후 해당 register의 값을 복원하여 구현된다. 즉 callee가 자유롭게 사용하되, 스스로 복구해야 하는 레지스터이다. Callee-saved register들에는 %rbx, %r12 ~ %r14이 있다.
__예제 코드에는 callee 함수인 multistore에서 사용되는 %rbx를 caller 함수에서 보존하는 것을 보여준다. %rbx register는 callee-saved이지만,__
Stack based languages
Stack 기반 언어(Stack based language)는 말 그대로 스택을 기반으로 하는 언어이다. 이러한 언어는 재귀 함수를 지원하며, 여러 번 같은 함수를 호출할 수 있으므로, 각 호출마다 상태(지역 변수, return address 등)를 스택에서 따로 관리하고 저장해야 한다. 이때 스택은 함수 실행 상태를 저장하는 frame들의 집합으로 구성된다. 이때 stack frame에는 다음과 같은 내용이 저장된다.
- return address: callee 함수가 반환된 후 다시 돌아갈 명령어 주소
- local variables: 해당 함수 내에서 사용되는 지역 변수들(register에 모두 저장할 수 없을 정도로 많으면 사용)
- Caller saved registers: Caller의 레지스터를 백업
- Callee saved registers: Callee측이 레지스터를 사용하기 전에 백업(필요할 경우)
이때 함수가 호출된 직후 frame이 생성되며, 해당 함수가 return되기 직전에 frame이 제거된다. 또한, %rsp는 현재 스택의 top을 가리키는 레지스터이다.
각주
- ↑ 프로그램 카운터(%rip)다음에 실행될 명령어의 주소를 저장한다.