computer systems 3rd edition chap3를 인용했습니다.
Program Counter
- pc라고 불린다.
- intel x86-64 환경에서 rip 레지스터가 pc의 역할을 수행한다.
- 다음 명령어의 메모리 주소 정보를 저장한다.
Stack Pointer
- sp라고 불린다.
- intel x86-64 환경에서 rsp 레지스터가 sp 역할을 수행한다.
- 스택의 가장 끝 (top)을 가리킨다.
- 스택은 위에서 아래로 쌓이는 구조
call
caller 와 callee 로 나뉜다. caller 는 return address(돌아갈 주소) + argument 저장장소 정보를 가져야 한다. callee 는 return value 정보를 가져야 한다.
caller와 callee는 같은 cpu에서 run 되기 때문에, 같은 레지스터를 사용한다.
→ 레지스터 개수는 한정적이기 때문에 레지스터 재활용이 이뤄진다. 이 상황을 잘 다루는 것이 관건!
Calling Convention
시스템 환경에 따라 다르게 적용된다.
해당 글에서는 x86-64 환경을 기준으로 설명하도록 한다.
스택 = 스택프레임의 집합이다. 런타임 때 프로시져가 프로시저 call 단위의 정보를 스택프레임에 할당한다.
→ callee는 return address를 정리해줘야 한다.
- 항상 복귀가 필요하기 때문에 리턴 주소를 stack에 저장해둔다. (jmp instruction 과의 차이)
- caller argument register (rdi, rsi, rdx, rcx, r8, r9 6개) 스택은 아래에서 낮은주소로 쌓이기 때문에 , 해당 레지스터는 역순으로 저장된다. 역순으로 저장되어야 rdi, rsi, rdx,,, 순으로 pop될 수 있으니!!!
- callee register (rbp, rpx, r12~r15)
- caller와 callee는 같은 register을 사용한다.
Q. 왜 스택에 저장해야 할까?
→ 프로시저가 끝난 후, call은 저장된 return address로 다시 '되돌아' 가야 한다 . 하지만, 레지스터는 한정적이고 수많은 return addresses를 저장해야 할 수 있다.
스택 : 프로시저의 context 에 대한 정보를 저장하는 메모리 지역 LIFO(Last In First Out 구조)
x86-64 Stack and Stack Pointer
Stack : procedure을 관리하기 위한 메모리 공간
- stack frame의 집합
- 런타임 시점에 스택 프레임에 저장하기 때문에 run-time stack 이라고도 불린다.
- 낮은 주소방향으로 저장된다 !!
%rsp : 스택의 top(가장 낮은 주소) 주소를 담고 있는 레지스터
%rbp : 가장 최근 스택프레임의 첫번째 요소 주소 (거의 안 쓰임)
Push / Pop Stack Operation
pushq src
1. %rsp 를 8만큼 줄인다 (sub 8 byte)
Allocation subq $8, %rsp → 규약상 pushq만 존재한다 (b,w, l X )
2. %rsp 공간에 데이터 를 삽입한다. //%rsp 메모리 접근
Copy movq %rcx, (%rsp)
주어진 주소에 src의 content를 담는다.
popq dst
1. dst에 %rsp 값을 저장한다. //%rsp 메모리 접근
Store content movq (%rsp), %rcx //stack top에 있는 data를 저장
2. %rsp를 8만큼 증가시킨다.
addq $8, %rsp
→ 기존 bits 공간은 여전히 존재한다. 다만, 사용하지 않을 뿐!!
Prodecure Control Flow
프로시저의 call과 return을 위해 stack을 이용한다.
return address : call instruction 후 되돌아갈!! 주소 → 스택에 저장해뒀기에 기억할 수 있음!
call 인스터럭션은 함수의 시작부분으로 제어를 이동하는 반면, ret인스트럭션은 call다음에 오는 인스트럭션으로 제어를 되돌린다 !
Procedure call : call label
1. 스택에 return address 저장 (1) 8바이트 sub → 2) return address 저장)
2. label 로 점프!! %rip 는 call한 곳의 주소
Procedure return : ret
1. 스택에서 return address 를 pop 한다!! ( 1) return address 기억 → 2) 8바이트 add)
2. 해당 return address로 점프!! %rip는 return address
retq는 단순히 IA32가 아니라 X86-64 버전의 콜과 리턴 인스트럭션이라는 것을 강조한다.
x86-64/Linux Stack Frame
스택 프레임에 저장되는 경우
1. return address 가 저장되어야하는 경우 (함수 호출 jmp 발생 후 되돌아가야 할 때 )
- call 명령어에 의해 스택에 push 된다
2. 지역변수의 특수한 경우 (필요하다면 !)
- 연산자 &가 사용되었으며, 이 변수의 주소를 생성할 수 있어야 한다. (volatile)
- 일부 지역변수들이 배열 또는 구조체여서 이들이 배열이나 구조체 참조로 접근되어야 한다.
→ 스택보다 레지스터가 빠름 → 고로 스택 사용을 최소화 하자
gcc proc.c -c –Og -fcf-protection=none -mpreferred-stack-boundary=3
//gcc -Og 최적화 옵션
3. argument 가 6개를 초과할 경우
- 매개변수들을 스택으로 전달할 때, 모든 데이터 길이는 8의 배수로 반올림된다.
Low Level Debugging
gdb -tui
$make
$gdb -tui myincr
//-tui or ctrl+a in gdb
(gdb) l
(gdb) layout split #asm or src
(gdb) focus cmd #asm or src
b main
r
display /x $rip // gdb에서는 레지스터 $로 표기한다
display /x $rsp
display /x *(long *) ($rsp) // 해당 스택프레임 value 를 알고 싶다면 포인터로 접근
Passing data
- 레지스터는 메모리에 있지 않은 반면, stack은 메모리 상에 저장되어 있다.
- caller는 6개의 인자까지 레지스터를 사용한다.
(%생략) rdi, rsi, rdx, rcx, r8, r9
- 남은 인자들이 스택에 저장되어야 할 경우, 스택에서 순서대로 pop 될 수 있도록 역순으로 저장된다.
- callee는 리턴값을 %rax 에 저장한다.
void proc(long,long,long,long,long,long,long,long);
void call_proc() {
long x1 = 1;
long x2 = 2;
long x3 = 3;
long x4 = 4;
long x5 = 5;
long x6 = 6;
long x7 = 7;
long x8 = 8;
proc(x1, x2, x3, x4,x5, x6, x7, x8);
}
/*
pushq $0x8
pushq $0x7
mov $0x6,%r9d
mov $0x5,%r8d
mov $0x4,%ecx
mov $0x3,%edx
mov $0x2,%esi
mov $0x1,%edi
callq proc
add $0x10,%rsp //함수 호출 후 스택포인터를 조정하는 역할 스택 포인터를 원래 위치대로 !!
//실제로 주소가 쓰이지는 않고 인자만 전달되기 때문에
retq
*/
함수 호출 시, 스택에 푸시되는 것들은 크게 2가지로 나눌 수 있다.
- 리턴 주소 (Return Address): 함수 호출 시 call 명령어가 실행되면, 리턴 주소가 스택에 푸시됩니다. 이는 함수가 끝나고 돌아갈 주소입니다. 리턴 주소는 8바이트 크기입니다.
- 인자들 (Arguments): 호출되는 함수에 전달되는 인자들이 있습니다. proc 함수는 8개의 인자를 받습니다. 첫 6개는 레지스터 (%rdi, %rsi, %rdx, %rcx, %r8, %r9)를 통해 전달되고, 나머지 2개는 스택을 통해 전달됩니다. 이 인자들은 각각 8바이트 크기입니다. (x7, x8)
총 3개의 항목이 스택에 푸시되었으므로, 스택에서 이들을 제거하려면 24바이트를 정리해야 하지만, add $0x10, %rsp로 스택 포인터를 16바이트만 증가시키는 이유는, 실제로 x7과 x8만 스택에 저장되고, 리턴 주소는 함수 호출 후에 이미 정리되었기 때문입니다.
→ call 끝나는 순간 이미 return 주소는 스택에서 pop되고, %rsp 또한 정리된다.
Register Saving Convention
함수 호출 시, caller가 callee에 의해 레지스터 값이 덮어씌워지는 문제를 방지하기 위한 레지스터 저장 규약이 존재한다.
yoo:
movq $15213, %rdx ; 호출자에서 %rdx에 값 저장
call who ; who 함수 호출
addq %rdx, %rax ; %rdx에 저장된 원래 값을 %rax에 더함
ret ; 반환
who:
pushq %rdx ; %rdx를 스택에 저장
subq $18213, %rdx ; %rdx 값을 수정
popq %rdx ; %rdx를 스택에서 복원
ret ; 반환
1. caller가 레지스터를 저장한다
- caller가 call 이전 자신이 사용하는 레지스터 값을 스택에 저장하고, call 후에 그 값을 복원한다.
yoo:
movq $15213, %rdx ; 호출자에서 %rdx에 값 저장
pushq %rdx ; %rdx를 스택에 저장
call who ; who 함수 호출
popq %rdx ; %rdx를 스택에서 복원
addq %rdx, %rax ; %rdx에 저장된 원래 값을 %rax에 더함
ret ; 반환
who:
subq $18213, %rdx ; %rdx 값을 수정
ret ; 반환
2. callee가 레지스터를 저장한다.
- callee가 해당 레지스터값 사용 전 자신이 사용하는 레지스터를 스택에 저장하고, 반환 전에 복원한다.
yoo:
movq $15213, %rdx ; 호출자에서 %rdx에 값 저장
call who ; who 함수 호출
addq %rdx, %rax ; %rdx에 저장된 원래 값을 %rax에 더함
ret ; 반환
who:
pushq %rdx ; %rdx를 스택에 저장
subq $18213, %rdx ; %rdx 값을 수정
popq %rdx ; %rdx를 스택에서 복원
ret ; 반환
Each register is designated as the responsibility of either the caller or the callee to preserve its value.
Callee saved registers
: Callee saves values in its stack frame before using, then restores them before returning to
caller
- %rbx, %rbp, and %r12, %r13, %r14, %r15
Caller saved registers
: Caller saves values in its stack frame before calling Callee, then restores values after the call
- All other registers, except for the stack pointer %rsp
void multstore(long x, long y, long *dest) {
long t = mult2(x, y);
*dest = t;
}
long mult2(long a, long b) {
long s = a * b;
return s;
}
0000000000400540 <multstore>:
400540: push %rbx # %rbx 값 저장
400541: movq %rdx,%rbx # dest 주소(%rdx)를 %rbx에 저장
400544: call 400550 <mult2> # 주소 0x400550에 있는 mult2 함수 호출
400549: movq %rax,(%rbx) # %rax에 있는 결과값을 %rbx가 가리키는 주소(dest)에 저장
40054c: pop %rbx # %rbx 복원
40054d: ret # 함수 종료
'Major S-T-U-D-Y > System Programming' 카테고리의 다른 글
[시스템 프로그래밍] 7. Linking (1) 분할컴파일과 extern/static modifier (0) | 2024.11.21 |
---|---|
9. Derived type - array, pointer, and structure (1) (1) | 2024.11.12 |
GNU make (1) | 2024.10.18 |
4. Machine level representation basics (1) (0) | 2024.10.15 |
GNU vi 주요 명령어 정리 (0) | 2024.10.03 |