1. x64 어셈블리 - 레지스터
설명 | 64bit | 32bit | 16bit | 8bit | 8bit |
Accumulator | RAX | EAX | AX | AH | AL |
Base | RBX | EBX | BX | BH | BL |
Counter | RCX | ECX | CX | CH | CL |
Data | RDX | EDX | DX | DH | DL |
RDI | EDI | DI | DIL | ||
RSI | ESI | SI | SIL | ||
Numbered(8~15) | R$n$ | R$n$D | R$n$W | R$n$B | |
Stack pointer | RSP | ESP | SP | SPL | |
Frame pointer | RBP | EBP | BP | BPL |
Name | Notes | Type | 64-bit long |
32-bit int |
16-bit short |
8-bit char |
rax | 함수의 반환값을 이 레지스터에 저장해둡니다. | scratch | rax | eax | ax | ah and al |
rcx | 전형적인 Scratch 레지스터. 몇몇 명령어에선 카운터로도 사용합니다. | scratch | rcx | ecx | cx | ch and cl |
rdx | Scratch 레지스터. | scratch | rdx | edx | dx | dh and dl |
rbx |
Preserved 레지스터: 저장 없이 사용하지 마세요! | preserved | rbx | ebx | bx | bh and bl |
rsp |
스택 포인터. 스택의 맨 위를 가리킵니다. |
preserved | rsp | esp | sp | spl |
rbp |
Preserved 레지스터. 때로 스택 포인터의 이전 값 또는 "스택 프레임의 베이스"를 저장하는 데 사용됩니다. | preserved | rbp | ebp | bp | bpl |
rsi | 64bit 리눅스에선 scratch 레지스터이자 두번째 함수의 인자를 넘겨줍니다. 64bit 윈도우에선 preserved 레지스터 입니다. |
scratch | rsi | esi | si | sil |
rdi | 64bit 리눅스에선 scratch 레지스터와 함수의 첫번째 인자값을 넘겨줍니다. 64bit 윈도우에선 preserved 레지스터 입니다. |
scratch | rdi | edi | di | dil |
r8 |
Scratch 레지스터. 이것들은 64비트 모드에서 추가되었습니다. 그래서 이름 대신 숫자를 가지고 있습니다. | scratch | r8 | r8d | r8w | r8b |
r9 |
Scratch 레지스터. | scratch | r9 | r9d | r9w | r9b |
r10 |
Scratch 레지스터. | scratch | r10 | r10d | r10w | r10b |
r11 |
Scratch 레지스터. | scratch | r11 | r11d | r11w | r11b |
r12 |
Preserved 레지스터. 사용은 할 수 있지만, 저장하고 사용 후 복구해야 합니다. |
preserved | r12 | r12d | r12w | r12b |
r13 |
Preserved 레지스터. | preserved | r13 | r13d | r13w | r13b |
r14 |
Preserved 레지스터. | preserved | r14 | r14d | r14w | r14b |
r15 |
Preserved 레지스터. | preserved | r15 | r15d | r15w | r15b |
Scratch 레지스터 : 원하는 모든 용도로 사용할 수 있습니다.
Preserved 레지스터 : 다른 곳에서 중요한 용도로 사용되므로 사용하는 경우 원래 값을 저장해 두고 사용한 후에 다시 원래 값으로 돌려놔야 합니다.
빨간색 : 64비트에서만 존재하는 레지스터입니다.
참고로 함수 호출할때 인자를 넘겨주는데 어셈블리로 바꾸면 함수를 호출할 때 여러가지의 값을 넘겨줄 수 없기 때문에어떤 레지스터에 몇번째 인자를 저장해야 하는지 정해진 호출 규약이 있습니다.x86-64에는 두가지의 호출규약이 있습니다.
마이크로소프트 x64 호출 규약과 시스템 V AMD64 ABI이 있습니다.
마이크로소프트 x64 호출 규약
마이크로소프트 x64 호출 규약은 윈도우(마이크로소프트 비주얼 C++, 인텔 C++ 컴파일러, 엠바카데로 컴파일러), UEFI 등에서 사용합니다.
1. RCX/XMM0
2. RDX/XMM1
3. R8/XMM2
4. R9/XMM3
이 이후 인자부터는 스택에 들어갑니다.
인자 타입 | 1번째 | 2번째 | 3번째 | 4번째 | 5번째 이상 |
부동 소수점 | XMM0 | XMM1 | XMM2 | XMM3 | 스택 |
정수 | RCX | RDX | R8 | R9 | 스택 |
자료형(8, 16, 32 또는 64비트 자료형), __m64 | RCX | RDX | R8 | R9 | 스택 |
모든 포인터 | RCX | RDX | R8 | R9 | 스택 |
XMM$n$은 128bit짜리 대형 레지스터 입니다.
시스템 V AMD64 ABI
시스템 V AMD64 ABI는 GNU/리눅스, BSD, OS X(GCC, 인텔 C++ 컴파일러) 등에서 사용합니다.
인자 타입 | 1번째 | 2번째 | 3번째 | 4번째 | 5번째 | 6번째 | 7번째 이상 |
부동 소수점, __m256 | XMM0 | XMM1 | XMM2 | XMM3 | XMM4 | XMM5 | XMM6~XMM7 |
나머지 자료형 | RDI | RSI | RDX | RCX | R8 | R9 | 스택 |
포인터 | RDI | RSI | RDX | RCX | R8 | R9 | 스택 |
보기 좀 불편하게 예제가 되어있지만 정리해보자면
s.a, s.b = int(4byte) + int(4byte) = 8byte(64bit)
s.d = double(8byte)
ld = long double(16 or 8byte)(여기선 16byte, 128bit)
long double 형식은 X87 클래스에 속한다. X87 클래스는 스택에 저장되는것 같다.
일반적인 레지스터
XMM, YMM
스택
func (e, f, s, g, h, ld, m, y, n, i, j, k)
e, f, (s.a, s.b), g, h, i 순서대로 6개의 레지스터에 값이 저장되게 됩니다.
그 다음에 나오는 j와 k는 레지스터를 다 사용했으므로 스택 프레임에 저장되게 됩니다.
s.d 는 부동소수점이기 때문에 XMM부분에 들어가고
m, y, n도 순차적으로 들어가게 됩니다.
근데 y만 YMM에 저장되어 있는데 y가 __m256 형식이라서 128bit 크기의 XMM에는 넣을 수 없기 때문에 YMM에 넣게 됩니다.
ld는 정확히는 모르지만 여러가지 이유로 스택에 저장되는것 같다.
YMM은 256bit 짜리 레지스터 입니다.
이러한 코드를 짜고 test 함수에 breakpoint 를 걸고 진행해봤다.
예상대로면
i1 = RDI = 1
i2 = RSI = 2
i3 = RDX = 3
이런 형태가 나올것이다.
RAX 0x3ff8000000000000
RBX 0x555555555210 (__libc_csu_init) ◂— endbr64
RCX 0x7ffff7edf1e7 (write+23) ◂— cmp rax, -0x1000 /* 'H=' */
RDX 0x3
RDI 0x1
RSI 0x2
R8 0x16
R9 0x7c
R10 0x7ffff7fb9be0 (main_arena+96) —▸ 0x5555555596a0 ◂— 0x0
R11 0x246
R12 0x555555555060 (_start) ◂— endbr64
R13 0x7fffffffe4e0 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffe3d0 ◂— 0x0
RSP 0x7fffffffe358 —▸ 0x5555555551e8 (main+113) ◂— add rsp, 0x40
RIP 0x555555555149 (test) ◂— endbr64
RDI = 0x1
RSI = 0x2
RDX = 0x3
예상한 그대로 나왔다!
부동 소수점은 계산하기 힘드니까 제외했다.