728x90
반응형
3.7 프로시저
- 프로시저 호출은 소프트웨어에서의 주요 추상화
- 인자를 받을 수 있고 값을 리턴할 수 있는 코드 블럭(특정 기능이 구현된)의 추상화
- 함수, 메소드, 서브 루틴, 핸들러 등으로 사용
특징
프로시저가 다른 프로시저를 호출하고 리턴하는 일련의 동작은 다음과 같은 특징을 하나 이상 가질 수 있음
- 제어권 전달
- 프로그램 카운터(pc)는 호출되는 프로시저에 대한 코드의 시작주소로 설정
- 리턴할 때는 호출 인스트럭션 다음의 인스트럭션으로 설정
- 데이터 전달
- 호출자는 하나 이상의 매개변수를 피호출자에 제공할 수 있어야 함
- 피호출자는 호출자에게 하나의 값을 리턴할 수 있어야 함
- 메모리 할당과 반납
- 피호출자는 지역변수를 위한 공간을 할당할 수 있음
- 리턴할 때 이 공간을 반납할 수 있음
3.7.1 런타임 스택
3.7.2 제어의 이동
콜(Call)
call 인스트럭션은 두 가지 동작을 함
- call 인스트럭션 다음 주소를 스택에 push하고 pc를 오퍼랜드가 가리키는 주소로 설정
리턴(Return)
ret 인스트럭션은 돌아갈 주소를 스택에서 pop해와서 pc로 설정
Instruction | Description |
---|---|
call Label | Procedure call |
call *Operand | Procedure call |
ret | Return from call |
다음은 last 함수와 last 함수를 호출하는 first 함수의 C 코드다.
long last(long u, long v) {
return u \* v;
}
long first(long x) {
return last(x - 1, x + 1);
}
아래는 first 함수를 호출하는 main 코드
int main() {
return first(10);
}
gcc를 명령어 옵션 -Og로 실행하고 objdump -d first_last를 실행한 결과
0000000000000660 :
660: 48 89 f8 mov %rdi,%rax ; L1: u
663: 48 0f af c6 imul %rsi,%rax ; L2: u \* v
667: c3 retq ; L3: Return
0000000000000668 :
668: 48 8d 77 01 lea 0x1(%rdi),%rsi ; F1: x + 1
66c: 48 83 ef 01 sub $0x1,%rdi ; F2: x - 1
670: e8 eb ff ff ff callq 660 ; F3: Call last(x - 1, x + 1)
675: f3 c3 repz retq ; F4: Return
0000000000000677 :
677: bf 0a 00 00 00 mov $0xa,%edi ; M1: 10
67c: e8 e7 ff ff ff callq 668 ; M2: Call first(10)
681: f3 c3 repz retq ; M3: Return
인스트럭션의 실행 순서는 다음과 같다.
M1 - > M2 -> F1 -> F2 -> F3 -> L1 -> L2 -> L3 -> F4 -> M3
M1의 인스트럭션이 실행될 때, 스택 포인터 %rsp가 0x7fffffffdd58이라고 가정하면 다음과 같은 표를 작성할 수 있다.
LabelPCInstruction%rdi%rsi%rax%rsp*%rspDescription
Label | PC | Instruction | %rdi | %rsi | %rax | %rsp | *%rsp |
M1 | 0x677 | mov | 0x7fffffffdd58 | ||||
M2 | 0x67c | callq | 10 | 0x7fffffffdd58 | |||
F1 | 0x668 | lea | 10 | 0x7fffffffdd50 | 0x681 | ||
F2 | 0x66c | sub | 10 | 11 | 0x7fffffffdd50 | 0x681 | |
F3 | 0x670 | callq | 9 | 11 | 0x7fffffffdd50 | 0x681 | |
L1 | 0x660 | mov | 9 | 11 | 0x7fffffffdd48 | 0x675 | |
L2 | 0x663 | imul | 9 | 11 | 9 | 0x7fffffffdd48 | 0x675 |
L3 | 0x667 | retq | 9 | 11 | 99 | 0x7fffffffdd48 | 0x675 |
F4 | 0x675 | repz retq | 9 | 11 | 99 | 0x7fffffffdd50 | 0x681 |
M3 | 0x681 | repz retq | 9 | 11 | 99 | 0x7fffffffdd58 |
3.7.3 데이터 전송
x86-64에서는 최대 여섯 개의 정수형 인자(포인터, 정수)를 레지스터로 전달할 수 있음
- 함수가 6개보다 많은 정수형 인자를 필요로 할 때, 다른 인자는 스택으로 전달
- 스택에 저장할 때 데이터 길이는 8의 배수로 반올림
void proc(long a1, long *a1p, int a2, int *a2p, short a3, short *a3p, char a4, char *a4p) {
*a1p += a1;
*a2p += a2;
*a3p += a3;
*a4p += a4; }
proc:
.cfi\_startproc
movq 16(%rsp), %rax ; Fetch a4p
addq %rdi, (%rsi) ; \*a1p += a1
addl %edx, (%rcx) ; \*a2p += a2
addw %r8w, (%r9) ; \*a3p += a3
movl 8(%rsp), %edx ; Fetch a4
addb %dl, (%rax) ; \*a4p += a4
ret ; Return
인자 a1부터 a3p까지는 레지스터로 전달하고, 인자 a4, a4p는 스택을 통해 전달하는 걸 확인할 수 있음
3.7.4 스택에서의 지역저장공간
아래의 경우에는 지역변수가 메모리에 저장되어야 하는 경우
- Local Variables로 명명된 스택 프레임의 일부분이 생김
- 지역변수들은 여러 크기를 갖는데, 이들의 위치는 스택 포인터에 대한 상대 오프셋으로 나타낼 수 있음
- 지역변수를 모두 저장하기엔 레지스터의 수가 부족함
- 지역변수의 주소를 생성할 수 있어야 한다. 예를 들면 연산자 &
- 지역변수가 배열이거나 구조체여서 배열이나 구조체 참조로 접근돼야 함
3.7.5 레지스터를 이용하는 지역저장소
프로그램 레지스터들은 모든 프로시저들이 공유하는 단일 자원의 역할을 함
피호출자는 호출자가 나중에 사용할 게획인 일부 레지스터 값은 덮어쓰지 않음
- 관습적으로 %rbx, %rbp, %r12-%r15는 피호출자-저장 레지스터로 구분
(위의 레지스터들의 값을 보존해야함) - 스택 포인터를 제외한 다른 모든 레지스터들은 호출자-저장 레지스터로 구분
(함수에 의해 변경될 수 있음)
3.7.6 재귀 프로시저
각 프로시저 콜은 스택상에 자신만의 사적인 공간을 가지며, 따라서 별도의 호출들의 지역변수들은 서로 간섭하지 않음
- 스택운영방식은 프로시저가 호출될 때 지역저장소를 할당하고, 리턴하기 전에 이것을 반환하는 적절한 정책을 제공
- 함수를 재귀적으로 호출하는 것도 다른 함수의 호출과 마찬가지로 자신만의 개별적 저장공간을 사용
728x90
반응형
'크래프톤 정글 - TIL' 카테고리의 다른 글
크래프톤 정글 5기 TIL - Day22 (0) | 2024.04.11 |
---|---|
크래프톤 정글 5기 TIL - Day 21 (0) | 2024.04.10 |
크래프톤 정글 5기 TIL - Day 19(CS:APP) (0) | 2024.04.08 |
크래프톤 정글 5기 TIL - Day 18(CS:APP) (0) | 2024.04.06 |
크래프톤 정글 5기 TIL - Day 18(DP, Greedy, Knapsack) (0) | 2024.04.05 |