x86 Architecture
x86 아키텍처는 인텔이 개발한 명령어 집합 구조(Instruction Set Architecture, ISA)이다. 대표적인 CISC(Complex Instruction Set Computer) 아키텍처로, 수많은 기능과 복잡한 구조를 가진다.
Intel x86 Evolution: Milestones
Name | Year | Transistors | MHz | Description |
---|
8086 | 1978 | 29K | 5-10 | 최초로 x86을 사용한 16비트 프로세서 |
386 | 1985 | 275K | 16-33 | x86 최초로 32비트 아키텍처(IA-32) 사용 |
Pentium 4E | 2004 | 125M | 2800-3800 | 64비트 아키텍처(x86-64) 사용 |
Core 2 | 2006 | 291M | 1060-3500 | 멀티 코어 프로세서 |
Core i7 | 2008 | 731M | 1700-3900 | 네이티브 쿼드 코어 |
싱글 코어의 클럭 향상에 따른 전력 소모와 발열이 한계에 다다르자, 코어 수를 늘려 성능을 향상시킨 멀티 코어 프로세서가 등장했다.
x86 Clones: Advanced Micro Devices (AMD)
인텔이 x86을 포기하고 완전히 새로운 64비트 아키텍처(IA-64)를 개발하는 동안 AMD는 x86의 64비트 확장 아키텍처(x86-64)를 발표하였고, x86 호환성을 갖춘 x86-64가 시장에서 승리하면서 오늘날 거의 모든 프로세서가 이를 사용하고 있다.
C, Assembly, Machine Code
Turning C into Machine Code
---
config:
flowchart:
htmlLabels: false
---
flowchart TD
c1[/"p1.c"/]
c2[/"p2.c"/]
s1[/"p1.s"/]
s2[/"p2.s"/]
o1[/"p1.o"/]
o2[/"p2.o"/]
a[/"Static libraries (.a)"/]
p[/"Executable program (p)"/]
Compiler["Compiler (gcc -Og -S)"]
Assembler["Assembler (gcc or as)"]
Linker["Linker (gcc or ld)"]
c1 & c2 --> Compiler --> s1 & s2 --> Assembler --> o1 & o2
o1 & o2 & a --> Linker --> p
Compiling into Assembly
다음은 sum.c
파일의 일부이다.
1
2
3
4
5
6
| long plus(long x, long y);
void sumstore(long x, long y, long *dest) {
long t = plus(x, y);
*dest = t;
}
|
이를 gcc -Og -S sum.c
와 같이 컴파일하면 생성되는 sum.s
파일을 통해 어셈블리 코드를 확인할 수 있다.
gcc
옵션에 대한 설명은 여기 참고
1
2
3
4
5
6
7
| sumstore:
pushq %rbx
movq %rdx, %rbx
call plus
movq %rax, (%rbx)
popq %rbx
ret
|
Disassembling Machine Code
디스어셈블러(Disassembler)는 어셈블러와 반대로 기계 코드를 어셈블리 코드로 변환한다. gcc -Og sum.c -o sum
과 같이 컴파일하여 sum
프로그램을 생성한 뒤, objdump -d sum
을 입력하여 결과를 확인해 보자.
1
2
3
4
5
6
7
| 0000000000001196 <sumstore>:
1196: 53 push %rbx
1197: 48 89 d3 mov %rdx,%rbx
119a: e8 ea ff ff ff call 1189 <plus>
119f: 48 89 03 mov %rax,(%rbx)
11a2: 5b pop %rbx
11a3: c3 ret
|
Assembly Basics
x86-64 Integer Registers
x86-64는 정수와 포인터를 저장할 수 있는 16개의 레지스터를 가지고 있다.
64-bit register | Lower 32 bits | Lower 16 bits | Lower 8 bits |
---|
%rax | %eax | %ax | %al |
%rbx | %ebx | %bx | %bl |
%rcx | %ecx | %cx | %cl |
%rdx | %edx | %dx | %dl |
%rsi | %esi | %si | %sil |
%rdi | %edi | %di | %dil |
%rsp | %esp | %sp | %spl |
%rbp | %ebp | %bp | %bpl |
%r8 | %r8d | %r8w | %r8b |
%r9 | %r9d | %r9w | %r9b |
%r10 | %r10d | %r10w | %r10b |
%r11 | %r11d | %r11w | %r11b |
%r12 | %r12d | %r12w | %r12b |
%r13 | %r13d | %r13w | %r13b |
%r14 | %r14d | %r14w | %r14b |
%r15 | %r15d | %r15w | %r15b |
Operand Types
Type | Example |
---|
Immediate | $0x400 , $-533 |
Register | %rax , %r13 |
Memory | (%rax) |
Immediate는 상수 데이터를 의미한다. (상수 앞에 $
를 붙임)
Moving Data
movq Source, Dest
명령은 Source
의 값을 Dest
에 복사한다. (mov
+ q
: quad word)
Source | Dest | Example | C analog |
---|
Immediate | Register | movq $0x4, %rax | temp = 0x4; |
| Memory | movq $-147, (%rax) | *p = -147; |
Register | Register | movq %rax, %rdx | temp2 = temp1; |
| Memory | movq %rax, (%rdx) | *p = temp; |
Memory | Register | movq (%rax), %rdx | temp = *p; |
한 메모리 위치에서 다른 메모리 위치로 직접 복사하는 것은 허용되지 않는다. 따라서 메모리에서 레지스터로 복사한 뒤, 해당 레지스터의 값을 메모리에 복사하는 두 단계를 거쳐야 한다.
Simple Memory Addressing Modes
Mode | Register | Memory |
---|
Normal | (R) | Mem[Reg[R]] |
Displacement | D(R) | Mem[Reg[R] + D] |
(R)
: 레지스터 R
의 값에 해당하는 메모리 주소 참조 (포인터 역참조와 동일)D(R)
: (R)
에서 변위 D
만큼 이동한 메모리 주소 참조
Example of Simple Addressing Modes
1
2
3
4
5
6
| void swap(long *xp, long *yp) {
long t0 = *xp;
long t1 = *yp;
*xp = t1;
*yp = t0;
}
|
1
2
3
4
5
6
| swap:
movq (%rdi), %rax
movq (%rsi), %rdx
movq %rdx, (%rdi)
movq %rax, (%rsi)
ret
|
포인터가 가리키는 주소와 해당 주소에 저장된 값이 다음과 같을 때,
Name | Address (p ) | Value (*p ) |
---|
xp | 0x120 | 123 |
yp | 0x100 | 456 |
각 레지스터에는 다음과 같은 값이 저장된다.
Register | Value |
---|
%rdi | 0x120 |
%rsi | 0x100 |
%rax | 123 |
%rdx | 456 |
Complete Memory Addressing Modes
Register | Memory |
---|
D(Rb, Ri, S) | Mem[Reg[Rb] + S*Reg[Ri] + D] |
D
: 변위Rb
: 베이스 레지스터Ri
: 인덱스 레지스터 (%rsp
제외)S
: 스케일
배열 참조를 구현하는 데 유용한 형태이다.
Address Computation Examples
Register | Value |
---|
%rdx | 0xf000 |
%rcx | 0x0100 |
Expression | Address computation | Address |
---|
0x8(%rdx) | 0xf000 + 0x8 | 0xf008 |
(%rdx, %rcx) | 0xf000 + 0x100 | 0xf100 |
(%rdx, %rcx, 4) | 0xf000 + 4*0x100 | 0xf400 |
0x80(, %rdx, 2) | 2*0xf000 + 0x80 | 0x1e080 |
Load Effective Address (LEA)
leaq Source, Dest
명령은 주소 계산(Source
)을 수행하여 결괏값을 레지스터(Dest
)에 쓴다. 다음 예시와 같이 산술 연산을 수행하는 데 사용되기도 한다.
1
2
3
| long m12(long x) {
return x * 12;
}
|
1
2
| leaq (%rdi,%rdi,2), %rax ; ret = x + x*2
salq $2, %rax ; ret <<= 2
|
Some Arithmetic Operations
Format | Computation |
---|
addq Source, Dest | Dest += Src |
subq Source, Dest | Dest -= Src |
imulq Source, Dest | Dest *= Src |
salq Source, Dest | Dest <<= Src |
shlq Source, Dest | Dest <<= Src |
sarq Source, Dest | Dest >>= Src (산술 시프트) |
shrq Source, Dest | Dest >>= Src (논리 시프트) |
xorq Source, Dest | Dest ^= Src |
andq Source, Dest | Dest &= Src |
orq Source, Dest | Dest |= Src |
incq Dest | Dest += 1 |
decq Dest | Dest -= 1 |
negq Dest | Dest = -Dest |
notq Dest | Dest = ~Dest |
Arithmetic Expression Example
1
2
3
4
5
6
7
8
9
| long arith(long x, long y, long z) {
long t1 = x + y;
long t2 = z + t1;
long t3 = x + 4;
long t4 = y * 48;
long t5 = t3 + t4;
long rval = t2 * t5;
return rval;
}
|
1
2
3
4
5
6
7
8
| arith:
leaq (%rdi,%rsi), %rax
addq %rdx, %rax
leaq (%rsi,%rsi,2), %rdx
salq $4, %rdx
leaq 4(%rdi,%rdx), %rdx
imulq %rdx, %rax
ret
|
컴파일러는 최대한 빠른 명령어를 사용하여 요구 사항을 구현하기 위해 여러 가지 방법을 시도한다. 그 결과 저수준에서의 연산 순서가 고수준에서 지정한 순서와 달라질 수 있다.
References