Assembly Language Registers

Registers in x64 Assembly are small, fast storage locations directly accessible by the CPU and 64 bits (8 bytes) in size. They are specialized, high-speed storage areas where the CPU temporarily stores data.

In terms of speed, accessing a register is faster than any other type of memory or storage. Accessing data in a register is faster than accessing a (L1, L2, etc.) CPU cache, and much faster than accessing any type of RAM. This is because the register is right in the heart of the processor itself.

On the other hand, registers are highly space-limited.
Systems with x86 architecture have only 32-bit registers, while x64 systems have 64-bit registers.

The size of a CPU’s registers is a fundamental aspect that determines the amount of data they can handle. Another way of saying this is that x86 systems have 32-bit architecture; by design, this means that the registers will be 32-bits. Similarly, x64 systems have 64-bit architecture and therefore have 64-bit registers.

The transition from 32-bit to 64-bit architecture brought about larger register sizes, offering enhanced capabilities in terms of memory addressing, data precision, and overall system performance.

Now that we understand registers at a high level, let’s take a deeper look at what they are and how they work.

Types of Registers in x64 Assembly

There are different types of registers in x64 Assembly, and different sources categorize them in different ways. In this guide we want to be as thorough as possible while aligning with the documentation as much as possible. For this reason, we divide them as follows:

  1. General-Purpose Registers (GPRs) – The most common registers found in code, there are several subcategories of GPRs, which we will see below.
  2. The Instruction Pointer (RIP) – Points to the location of the next instruction.
  3. The Flags Register (RFLAGS) – Also called the status register or controls register.
  4. Media Registers
  5. Segment Registers

We will cover each of these categories below.

x64 General-Purpose Registers (GPRs)

In the x64 architecture, there are 16 general-purpose registers, each capable of holding 64 bits of data. These registers can be used for many things, as indicated by the name ‘general-purpose’.

However several GPRs have specific purposes that are typically adhered to. In other words, not all GPRs are completely general. As such, not all ‘general-purpose’ registers are actually used for storing all types of data.

The Evolution of the GPRs

General-purpose registers have been around since the early days of computing, and have grown in size along with computer architectures.

A great way to learn how modern x64 registers work is to look at their evolution.

8-Bit Architecture (8008)

Launched in 1972, the Intel 8008 processor had an 8-bit (1 byte) architecture. The first register was called ‘A’, for ‘accumulator’, and it had a size of 8-bits:

The 8-bit A register.
Fig 1: The A ‘accumulator’ register was 8-bits.

16-Bit Architecture (8086)

Then in 1978, Intel released the 8086, which featured a 16-bit architecture.

The register size was doubled and the accumulator was renamed ‘Accumulator Extended’ (AX). It was split into two sub-registers: AL (Accumulator Low) and AH (Accumulator High):

The 16-bit AX register.
Fig 2: The 16-bit AX register was divided into two 8-bit sub-registers, AH and AL.

32-Bit Architecture (x86)

In 1985, Intel launched the i386, which started the x86 line of processors. Once again the register size doubled.

The 32-bit accumulator register was named ‘Extended AX’ (EAX). The AX, AH, and AL sub-registers are all accessible within EAX:

The 32-bit EAX register.
Fig 3: The 32-bit EAX register retained AX, AH, and AL as sub-registers.

The sub-register AX allows us to access the lower 16 bits of EAX. And within AX are the two 8-bit sub-registers, AH and AL. But you may also have noticed that there’s a blank area to the lower-left of EAX. This is because there are no sub-registers that correspond with the upper sixteen bits of EAX.

64-bit Architecture (x64)

With the advent of x64 and 64-bit architecture, register size doubled once again and the EAX register was renamed RAX. However a new naming convention was also adopted, and the RAX register is also known as the R0 register:

The 64-bit RAX register.
Fig 4: The 64-bit RAX register further extends EAX. We can still reference EAX and its sub-registers for added control.

We can see that the RAX register contains a 32-bit EAX sub-register, a 16-bit AX sub-register, and two 8-bit sub-registers AH and AL.

When you manipulate the contents of one of these sub-registers, it affects the corresponding portion of the larger register. For example, modifying the value in AL will also alter the lower 8 bits of AX, EAX, and RAX.

GPR Sub-Registers

Let’s look at an example to better understand how registers and sub-registers work. In this example, we will look at the value of RAX and then pull the values of each of it’s sub-registers.

Given the following value of RAX, what are the values of EAX, AX, AH, and AL?

RAX = 10000000 01000000 00100000 00010000 00001000 00000100 00000010 00000001

Click to see the answer!

EAX = 00001000 00000100 00000010 00000001
AX = 00000010 00000001
AH = 00000010
AL = 00000001

The following table strives to make this relationship more clear:

RAX1000000001000000001000000001000000001000000001000000001000000001
EAX00001000000001000000001000000001
AX0000001000000001
AH00000010
AL00000001

This hierarchical structure allows for flexibility in working with different sizes of data, optimizing performance, and interfacing with various hardware components. It’s important to note that when using the larger register (e.g., RAX), the entire content of the sub-registers is affected. Conversely, modifying a sub-register affects only the corresponding part of the larger register. This flexibility is particularly useful when dealing with different data sizes and aligning with the expectations of specific instructions or memory access patterns.

The x64 architecture also added 8 general-purpose registers, named r8 through r15. The following table shows all 16 GPRs and their sub-registers:

RegisterLower 32 bitsLower 16 bits8 bits
raxeaxaxah/al
rbxebxbxbh/bl
rcxecxcxch/cl
rdxedxdxdh/dl
rsiesisisil
rdiedididil
rbpebpbpbpl
rspespspspl
r8r8dr8wr8b
r9r9dr9wr9b
r10r10dr10wr10b
r11r11dr11wr11b
r12r12dr12wr12b
r13r13dr13wr13b
r14r14dr14wr14b
r15r15dr15wr15b

An In-Depth Look at the GPRs

The first 8 registers are named with acronyms: rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp. You may also see them referred to as r0, r1, r2, r3, r4, r5, r6, r7, and r8.

However, the acronyms are helpful descriptors of their functionalities.

The first four – rax, rbx, rcx, and rdx – are sometimes referred to as data registers.

The next two – rsi and rdi – are index registers.

The last two – rbp and rsp – are pointer registers.

x64 Data Registers

There are four general-purpose registers (GPRs) that are commonly referred to as data registers: rax, rbx, rcx, and rdx. These registers are extremely common, and a variety of operations including arithmetic and logic.

However each one also has a special purpose:

RAX accumulator for arithmetic operations and input/output.
RBX base register for use with arrays.
RCX counter for loops.
RDXdata register used to improve the precision of RAX in mathematical and input/output operations.

x64 Index Registers

Index registers are GPRs that are commonly used for string operations.

RSI source index for string operations
RDI destination index for string operations.

x64 Pointer Registers

There are three pointer registers in total: RBP, RSP, and RIP. However only the first two are typically considered to be general-purpose registers (GPRs):

RBPbase pointer that holds the address of the current stack frame.
RSPstack pointer that points to the top address of the stack.

Key flags include the zero flag (ZF), carry flag (CF), and overflow flag (OF).

The RIP Register

We saw that RBP and RSP are part of the 16 general-purpose registers (GPRs) in the table above.

The RIP is a bit different; it was added in x64 and is often grouped apart from the primary 16 GPRs. However in terms of functionality it is just as easy to understand. It simply points to the location (in memory) of the next instruction.

RIPinstruction pointer, which holds the address of the next instruction (the address is also called the program counter).

The Flags Register

The flags register is unique; each individual bit is used as a binary ‘flag’, indicating whether some condition is set or not.

Segment Registers

Segments are areas in memory that are designated for specific uses. Three segments are commonly defined: code, data, and stack. The segment registers contain the memory address of each segment in code so that the processor knows where to look.

CS code segment register, which points to the location of the code segment. The code segment contains the processor instructions to be executed.
DS – data segment register. The data segment contains global and local variables.
SS
stack segment register, used to store the starting address of the stack.
ES, FS, GS – used for extra data.

Conclusion

In the world of low-level programming, where every bit and byte matters, the x64 architecture stands as a testament to the evolution of computing power. At the heart of the x64 lie the registers, small yet powerful storage locations embedded within the processor.

Understanding these registers is akin to wielding the keys to a machine’s inner sanctum, offering unparalleled control and efficiency.

In this article, we’ve covered x86-64 Assembly registers at a high level. We’ve seen the major types of registers and briefly covered the most common registers, which we can expect to encounter frequently in assembly code.