<- .instruction-sets[x86-64] ->
- All addresses and pointers are 64-bit, but virtual addresses must be in canonical form. Canonical form means that bit 47 and bits 48-63 must match since modern processors only support 48-bit for address space rather than the full 64-bit. If the address is not in canonical form, an exception will be raised
- Aside from the old x86 registers being extended from 32-bit to 64-bit, extra General Purpose Registers have also been added (e.g. r8 to r15)
- 16 general-purpose registers each 64-bits (RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15)
- DWORD (32-bit) version can be accessed with a D suffix, WORD (16-bit) with a W suffix, BYTE (8-bit) with a B suffix for registers R8 to R15
- For registers with alternate names like x86 (e.g. RAX, RCX), size access for register is same as x86. For example, 32-bit version of RAX is EAX and the 16-bit version is DX
- RBP is treated like another GPR. As a result, local variables are referenced through RSP
- 16 XMM registers, each 128-bit long. XMM registers are for SIMD instruction set, which is an extension to the x86-64 architecture. SIMD is for performing the same instruction on multiple data at once and/or for floating point operations
- Floating point operations were once done using stack-based instruction set that accesses the FPU Register Stack. But now, it can be done using the SIMD instruction set
- Parameters are passed to registers instead of the stack. Although additional ones will still be stored on stack
- Windows: first 4 parameters are placed in RCX, RDX, R8, and R9
- Linux: first 6 parameters are placed in RDI, RSI, RDX, RCX, R8, and R9
- Windows: on x86, Structured Exception Handling (SEH) is stored on the stack, which makes it vulnerable to buffer overflow attacks. SEH on x64 is implemented differently. SEH is table-based and no longer stored on the stack. It is instead stored in the PE Header
- Linux: for both x86 and x64, exception handling can be achieved through signals
- Easier in 64-bit code to differentiate between pointers and data values. The most common size for storing integers is 32 bits and pointers are always 64 bits
- Supports instruction pointer-relative addressing on data. Unlike x86, referencing data will not use absolute address but rather an offset from RIP
- Can't directly push an immediate value on the stack with the PUSH instruction in x86-64
- Needs to move the value into a register first and then push:
MOV RAX, 0x1122334455667788; PUSH RAX
- Needs to move the value into a register first and then push:
- Have direct access to RIP (instruction pointer) unlike x86
- This is valid:
MOV RAX, RIP
- This is valid:
x86 <- RERM[.instruction-sets] -> ARM