본문으로 이동

x86 어셈블리/x86 아키텍처

위키책, 위키책

x86 아키텍쳐

[+/-]

x86 아키텍쳐는 8개의 범용 레지스터(GPR; General-Purpose Register), 6개의 세그먼트 레지스터, 1개의 플래그 레지스터, 마지막으로 명령어 포인터를 갖는다. 64비트 x86에는 더 많은 레지스터가 있다.

범용 레지스터 (GPR) - 32비트 지정이름

[+/-]

8개의 범용 레지스터는 다음과 같다:

  1. AX (Accumulator register). 산술 연산에 사용.
  2. CX (Counter register). 시프트/회전 연산과 루프에서 사용.
  3. DX (Data register). 산술 연산과 I/O 명령에서 사용.
  4. BX (Base register). 데이터의 주소를 가리키는 포인터로 사용. (세그멘티드 모드에서는 세그멘트 레지스터 DS로 존재)
  5. SP (Stack Pointer register). 스택의 최상단을 가리키는 포인터로 사용.
  6. BP (Stack Base Pointer register). 스택의 베이스를 가리키는 포인터로 사용.
  7. SI (Source Index register). 스트림 명령에서 소스를 가리키는 포인터로 사용.
  8. DI (Destination Index register). 스트림 명령에서 도착점을 가리키는 포인터로 사용.

위의 나열 순서는 다음에 더욱 자세히 다루게 될 스택 삽입 명령에서 사용되는 순서이다.

모든 레지스터는 16비트와 32비트 모드에서 모두 접근 가능하다. 16비트 모드에서 레지스터는 위의 리스트에 있는 두 글자의 약자로 통칭된다. 32비트 모드에서는 16비트의 두 자 앞에 'E'(extended)를 붙여 표시한다. 예를 들어, 'EAX'는 32비트 값을 갖는 어큐뮬레이터 레지스터이다.

비슷하게, 64비트 버전에서는 'E' 대신 'R'을 사용한다. 즉, 'EAX'의 64비트 버전은 'RAX'가 된다.

처음 네 개의 16비트 레지스터 (AX, CX, DX, BX)는 두 개의 8비트 레지스터로 각각 접근할 수 있다. LSB, 또는 낮은 반은 'X' 대신 'L'을 써서 접근할 수 있으며, MSB, 또는 높은 반은 'H'를 써서 접근할 수 있다. 예를 들어, CL은 카운터 레지스터의 LSB이다.

전체적으로 우리는 어큐뮬레이터, 카운터, 데이터, 베이스 레지스터에 5가지 방법으로 접근할 수 있다. (64비트, 32비트, 16비트, 8비트 LSB, 8비트 MSB) 나머지 네 레지스터는 3가지 방법으로 접근 가능하다. (64비트, 32비트, 16비트) 요약하면 다음과 같다:

Register Accumulator Counter Data Base Stack Pointer Stack Base Pointer Source Destination
64-bit RAX RCX RDX RBX RSP RBP RSI RDI
32-bit EAX ECX EDX EBX ESP EBP ESI EDI
16-bit AX CX DX BX SP BP SI DI
8-bit AH AL CH CL DH DL BH BL

세그먼트 레지스터

[+/-]

6개의 세그먼트 레지스터들은 다음과 같다:

  • Stack Segment (SS). 스택을 가리킨다.
  • Code Segment (CS). 코드를 가리킨다.
  • Data Segment (DS). 데이터를 가리킨다.
  • Extra Segment (ES). 추가적인 데이터를 가리킨다 ('Extra'의 첫 글자 'E').
  • F Segment (FS). 많은 추가적인 데이터를 가리킨다 ('E' 다음은 'F').
  • G Segment (GS). 더 많은 추가적인 데이터를 가리킨다 ('F' 다음은 'G').

많은 최신 운영체제에서 대부분의 애플리케이션들은 (FreeBSD, Linux 또는 Microsoft Windows와 같이) 거의 모든 세그먼트 레지스터가 동일한 위치를 가리키는 메모리 모델을 사용하는 대신, 페이징을 사용하여 세그먼트 레지스터의 사용을 비활성화 한다. 하지만 일반적으로 FS와 GS는 이 규칙에서 예외이며, 대신 스레드 특정 데이터를 가리키는데 사용된다.

EFLAGS 레지스터

[+/-]

EFLAGS 는 프로세서의 작동 결과와 상태를 저장하기 위해 부울 값들의 모음으로 사용되는 32비트 레지스터이다.

비트들의 이름은 다음과 같다:

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
0 0 0 0 0 0 0 0 0 0 ID VIP VIF AC VM RF
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 NT IOPL OF DF IF TF SF ZF 0 AF 0 PF 1 CF

비트의 이름이 0 과 1 인 것은 예약된 비트들로써 수정될 수 없다.

The different use of these flags are:
0. CF : Carry Flag. Set if the last arithmetic operation carried (addition) or borrowed (subtraction) a bit beyond the size of the register. This is then checked when the operation is followed with an add-with-carry or subtract-with-borrow to deal with values too large for just one register to contain.
2. PF : Parity Flag. Set if the number of set bits in the least significant byte is a multiple of 2.
4. AF : Adjust Flag. Carry of Binary Code Decimal (BCD) numbers arithmetic operations.
6. ZF : Zero Flag. Set if the result of an operation is Zero (0).
7. SF : Sign Flag. Set if the result of an operation is negative.
8. TF : Trap Flag. Set if step by step debugging.
9. IF : Interruption Flag. Set if interrupts are enabled.
10. DF : Direction Flag. Stream direction. If set, string operations will decrement their pointer rather than incrementing it, reading memory backwards.
11. OF : Overflow Flag. Set if signed arithmetic operations result in a value too large for the register to contain.
12-13. IOPL : I/O Privilege Level field (2 bits). I/O Privilege Level of the current process.
14. NT : Nested Task flag. Controls chaining of interrupts. Set if the current process is linked to the next process.
16. RF : Resume Flag. Response to debug exceptions.
17. VM : Virtual-8086 Mode. Set if in 8086 compatibility mode.
18. AC : Alignment Check. Set if alignment checking of memory references is done.
19. VIF : Virtual Interrupt Flag. Virtual image of IF.
20. VIP : Virtual Interrupt Pending flag. Set if an interrupt is pending.
21. ID : Identification Flag. Support for CPUID instruction if can be set.

명령어 포인터

[+/-]

분기가 수행되지 않은 경우, EIP 레지스터는 다음 에 실행되어야 할 명령어의 주소를 가지고 있다.

EIP는 오직 call 명령어를 사용해 스택을 통해서만 읽을 수 있다.

Memory

[+/-]

The x86 architecture is little-endian, meaning that multi-byte values are written least significant byte first. (This refers only to the ordering of the bytes, not to the bits.)

So the 32 bit value B3B2B1B016 on an x86 would be represented in memory as:

Little endian representation
B0 B1 B2 B3

For example, the 32 bits double word 0x1BA583D4 (the 0x denotes hexadecimal) would be written in memory as:

Little endian example
D4 83 A5 1B

This will be seen as 0xD4 0x83 0xA5 0x1B when doing a memory dump.

Two's Complement Representation

[+/-]

Two's complement is the standard way of representing negative integers in binary. The sign is changed by inverting all of the bits and adding one.

Two's complement example
Start: 0001
Invert: 1110
Add One: 1111

0001 represents decimal 1

1111 represents decimal -1

Addressing modes

[+/-]

The addressing mode indicates the manner in which the operand is presented.

Register Addressing
(operand address R is in the address field)
mov ax, bx  ; moves contents of register bx into ax
Immediate
(actual value is in the field)
mov ax, 1   ; moves value of 1 into register ax

or

mov ax, 010Ch ; moves value of 0x010C into register ax
Direct memory addressing
(operand address is in the address field)
.data
my_var dw 0abcdh ; my_var = 0xabcd
.code
mov ax, [my_var] ; copy my_var content in ax (ax=0xabcd)
Direct offset addressing
(uses arithmetics to modify address)
byte_tbl db 12,15,16,22,..... ; Table of bytes
mov al,[byte_tbl+2]
mov al,byte_tbl[2] ; same as the former
Register Indirect
(field points to a register that contains the operand address)
mov ax,[di]
The registers used for indirect addressing are BX, BP, SI, DI
Base-index
mov ax,[bx + di]
For example, if we are talking about an array, BX contains the address of the beginning of the array, and DI contains the index into the array.
Base-index with displacement
mov ax,[bx + di + 10]

General-purpose registers (64-bit naming conventions)

[+/-]

틀:Main 64-bit x86 adds 8 more general-purpose registers, named R8, R9, R10 and so on up to R15. It also introduces a new naming convention that must be used for these new registers and can also be used for the old ones (except that AH, CH, DH and BH have no equivalents). In the new convention:

  • R0 is RAX.
  • R1 is RCX.
  • R2 is RDX.
  • R3 is RBX.
  • R4 is RSP.
  • R5 is RBP.
  • R6 is RSI.
  • R7 is RDI.
  • R8,R9,R10,R11,R12,R13,R14,R15 are the new registers and have no other names.
  • R0D~R15D are the lowermost 32 bits of each register. For example, R0D is EAX.
  • R0W~R15W are the lowermost 16 bits of each register. For example, R0W is AX.
  • R0L~R15L are the lowermost 8 bits of each register. For example, R0L is AL.

틀:Main As well, 64-bit x86 includes SSE2, so each 64-bit x86 CPU has at least 8 registers (named XMM0~XMM7) that are 128 bits wide, but only accessible through SSE instructions. They cannot be used for quadruple-precision (128-bit) floating-point arithmetic, but they can each hold 2 double-precision or 4 single-precision floating-point values for a SIMD parallel instruction. They can also be operated on as 128-bit integers or vectors of shorter integers. If the processor supports AVX, as newer Intel and AMD desktop CPUs do, then each of these registers is actually the lower half of a 256-bit register (named YMM0~YMM7), the whole of which can be accessed with AVX instructions for further parallelization.

Stack

[+/-]

The stack is a Last In First Out (LIFO) data structure; data is pushed onto it and popped off of it in the reverse order.

 mov ax, 006Ah
 mov bx, F79Ah
 mov cx, 1124h
 push ax

You push the value in AX onto the top of the stack, which now holds the value $006A.

push bx

You do the same thing to the value in BX; the stack now has $006A and $F79A.

push cx

Now the stack has $006A, $F79A, and $1124.

call do_stuff

Do some stuff. The function is not forced to save the registers it uses, hence us saving them.

pop cx

Pop the last element pushed onto the stack into CX, $1124; the stack now has $006A and $F79A.

pop bx

Pop the last element pushed onto the stack into BX, $F79A; the stack now has just $006A.

pop ax

Pop the last element pushed onto the stack into AX, $006A; the stack is empty.

The Stack is usually used to pass arguments to functions or procedures and also to keep track of control flow when the call instruction is used. The other common use of the Stack is temporarily saving registers.

CPU Operation Modes

[+/-]

Real Mode

[+/-]

Real Mode is a holdover from the original Intel 8086. You generally won't need to know anything about it (unless you are programming for a DOS-based system or, more likely, writing a boot loader that is directly called by the BIOS).

The Intel 8086 accessed memory using 20-bit addresses. But, as the processor itself was 16-bit, Intel invented an addressing scheme that provided a way of mapping a 20-bit addressing space into 16-bit words. Today's x86 processors start in the so-called Real Mode, which is an operating mode that mimics the behavior of the 8086, with some very tiny differences, for backwards compatibility.

In Real Mode, a segment and an offset register are used together to yield a final memory address. The value in the segment register is multiplied by 16 (shifted 4 bits to the left) and the offset is added to the result. This provides a usable address space of 1 MB. However, a quirk in the addressing scheme allows access past the 1 MB limit if a segment address of 0xFFFF (the highest possible) is used; on the 8086 and 8088, all accesses to this area wrapped around to the low end of memory, but on the 80286 and later, up to 65520 bytes past the 1 MB mark can be addressed this way if the A20 address line is enabled. See: The A20 Gate Saga.

One benefit shared by Real Mode segmentation and by Protected Mode Multi-Segment Memory Model is that all addresses must be given relative to another address (this is, the segment base address). A program can have its own address space and completely ignore the segment registers, and thus no pointers have to be relocated to run the program. Programs can perform near calls and jumps within the same segment, and data is always relative to segment base addresses (which in the Real Mode addressing scheme are computed from the values loaded in the Segment Registers).

This is what the DOS *.COM format does; the contents of the file are loaded into memory and blindly run. However, due to the fact that Real Mode segments are always 64 KB long, COM files could not be larger than that (in fact, they had to fit into 65280 bytes, since DOS used the first 256 of a segment for housekeeping data); for many years this wasn't a problem.

Protected Mode

[+/-]

Flat Memory Model

[+/-]

If programming in a modern operating system (such as Linux, Windows), you are basically programming in flat 32-bit mode. Any register can be used in addressing, and it is generally more efficient to use a full 32-bit register instead of a 16-bit register part. Additionally, segment registers are generally unused in flat mode, and it is generally a bad idea to touch them.

Multi-Segmented Memory Model

[+/-]

Using a 32-bit register to address memory the program can access (almost) all of the memory in a modern computer. For earlier processors (with only 16-bit registers) the segmented memory model was used. The 'CS', 'DS', and 'ES' registers are used to point to the different chunks of memory. For a small program (small model) the CS=DS=ES. For larger memory models, these 'segments' can point to different locations.