⭐️ 3.4 Accessing Information
시대가 발전해오면서 8086 → IA32 → x86-64로 아키텍처도 함께 발전했는데, 그 유산을 버리지 않고 아직까지도 사용하고 있다. 16개의 레지스터가 있는데, 8086시절에 쓰던 %ax
~ %bp
까지의 8개의 16비트 레지스터, IA32에서 쓰던 %eax
~ %ebp
까지의 8개의 32비트 레지스터, x86-64에서 쓰는 %rax
~ %rbp
이렇게 8개의 64비트 레지스터를 사용하고 있다. 같은 이름으로 시작하는 레지스터들은 모두 같은 공간을 가리키고 있지만 그 크기가 다를 뿐이다. 1바이트만 필요하면 %a
시리즈를 사용하면 되는 식.
8개의 레지스터 %r8
~ %r15
가 x86-64시절에 추가되었다고
서로 다른 레지스터들은 범용목적을 제외하곤 고유한 역할이 있다.
IA32와 x86의 차이가 뭐야?
x86은 초기 16비트 아키텍처에서부터 32비트 및 64비트 확장까지 다양한 버전의 아키텍처를 포함하는 용어이며, IA-32는 그 중에서도 32비트 x86 아키텍처를 특별히 가리키는 용어입니다. x86-64 아키텍처는 64비트 확장을 가진 x86 아키텍처 중 하나로, 현대 64비트 컴퓨팅에서 널리 사용됩니다.
x86-64 아키텍처에서 사용하는 레지스터 목록을 보여줘
x86-64 아키텍처(CPU 아키텍처)는 다양한 레지스터를 제공합니다. 아래에는 주요 x86-64 레지스터 목록을 나열하겠습니다. 이 목록은 일반적으로 사용되는 레지스터를 포함하고 있습니다.
-
일반 목적 레지스터 (General-Purpose Registers):
RAX
: return valueRBX
: Callee savedRCX
: 4th argumentRDX
: 3rd argumentRSI
: 2nd argumentRDI
: 1st argumentRBP
: Callee saved (Stack Base Pointer)RSP
: Stack PointerR8
,R9
: 5,6th argumentR10
,R11
: Caller savedR12
,R13
,R14
,R15
: Callee saved
-
인덱스 레지스터 (Index Registers):
- RSI (Source Index)
- RDI (Destination Index)
-
베이스 레지스터 (Base Registers):
- RBX (Base Register)
- RBP (Base Pointer)
-
카운터 레지스터 (Counter Registers):
- RCX (Counter Register)
-
데이터 레지스터 (Data Registers):
- RAX (Accumulator Register)
- RDX (Data Register)
-
스택 포인터 (Stack Pointer):
- RSP (Stack Pointer)
-
베이스 포인터 (Base Pointer):
- RBP (Base Pointer)
-
플래그 레지스터 (Flags Register):
- RFLAGS (Flags Register) - 이 레지스터에는 다양한 상태 플래그(예: 제로 플래그, 오버플로우 플래그 등)가 저장됩니다.
-
명령 포인터 (Instruction Pointer):
- RIP (Instruction Pointer) - 다음 실행할 명령의 주소를 가리키는 레지스터입니다.
일반목적 레지스터들을 보니까 rbx, rbp처럼 "Callee Saved"라는 목적을 가진 레지스터들이 있더라. 그리고 "Caller Saved" 목적을 가진 레지스터들도 있던데 각각 무슨 역할을 하는 레지스터들이야?
- Callee Saved Registers
- callee가 리턴할 때 복원해야 하는 레지스터들.
- rbx(base register), rbp(stack base pointer), rdi, rsi, r12, r13, r14, r15
- Caller Saved Registers
- Caller가 함수를 call 하기 전에 메모리에 저장해야 하는 레지스터들.
- rax(return value), rcx(counter), rdx, r8, r9, r10, r11
3.4.1. Operand Specifiers#
피연산자들의 타입에 대해 알아보자.
- Immediate
$Imm
- 값 리터럴을 담는데 쓰임
- Register
R[ra]
- 레지스터 이름을 가리킨다.
- Memory
M[Imm]
,M[Imm + R[rb]]
,M[R[rb]+R[ri]]
,M[Imm + R[rb] + R[ri]]
,M[R[ri] * s]
,M[Imm + R[ri] * s]
, etc.- 메모리 주소값을 참조한다.
- 메모리 인덱스 연산을 제공하나보네
3.4.2. Data Movement Instructions#
The generality of the operand notation allows a simple data movement instruction to express a range of possibilities that in many machines would require a number of different instructions
피연산자 표기법의 범용성 덕분에 많은 기계에서 여러 명령어가 필요한 다양한 기능을 간단한 데이터 이동 명령어로 표현할 수 있습니다.
크기가 다를뿐, 여러 mov 연산자는 동일한 역할을 가지고 있다. Destination으로 Source 데이터를 옮기는 명령으로 Source, Destination 모두 레지스터, 메모리공간을 뜻할 수는 있지만 Source, Destination 모두 동시에 메모리주소를 가리킬 수는 없다. 따라서 메모리 A에 있는 데이터를 메모리 B로 옮기기 위해선 두 번의 연산이 필요하다.
3.4.3. Data Movement Example#
단순 값을 수정하는 함수 exchange
를 GCC로 컴파일 했을때 어떤 어셈블리 코드가 나오는지 직접 실습을 하는 장이다.
First, we see that what we call "pointers" in C are simply addresses. Dereferencing a pointer involves copying that pointer into a register, and then using this register in a memory reference.
Second, local variables such as x are often kept in registers rather than stored in memory locations. Register access is much faster than memory access.
3.4.4. Pushing and Popping Stack Data#
스택의 top을 가리키는 레지스터는 %rsp
이다. 스택 포인터는 스택의 TOP을 의미하는구나. 스택포인터는 높은 곳에서 시작해 낮은 곳으로 내려간다는 점 기억하자.
새로운 함수 호출시 pushq
를 사용하고 함수 반환시 popq
를 사용한다. pushq
를 명령하면 현재 스택포인터의 값을 8만큼 감소시킨 뒤 새 스택포인터의 자리에 값을 저장한다. popq
는 반대로 리턴에 해당하는 결과값을 %rax
에 저장한 뒤 다시 %rsp
를 8만큼 증가시킨다.
- [x] 스택 포인터가 가리키는 메모리 공간에 저장할 수 있는 용량이 고작 1 words 밖에 안하는 것 같은데, 그 작은 공간 안에 함수가 다 들어간가는 건가? 아니면 함수의 실제 위치를 참조하는 걸까?
- code 영역에 있는 함수의 주소를 rbp가 가리킨다.