Assembly Tutorial – All About Registers

The registers on a CPU are the very fast and very small internal memory that the CPU, ideally, uses to do it’s calculations. The registers on a 64 bit CPU are 64 bits wide.

In the 64 bit x86 architecture there are 16 general purpose registers registers. They are named r0, r1, r2, r3r15. The first eight of these registers also have special names for historic reasons, they are, in order, rax, rbx, rcx, rdx, rsp, rbp, rsi, rdi. We usually use these names in our code as they are much more readable. Technically we can read and edit these 16 general purpose register as you wish. However three of them, rcx, rsp and rbp have certain restrictions.

The rcx register is used implicitly as the cycle counter by the loop instruction (c stands for cycle). If you use the loop instruction and the rcx register simultaneously things will go wrong. However as long as you aren’t using loop, then you are free to use rcx. The rsp register is used to point to the top of the stack in memory (sp stands for stack pointer). We only ever use rsp to access the stack. The rbp pointer is used to keep track of stack frames and we only ever use it when calling functions (bp stands for base pointer).

There is also the rip register that holds of the address in memory of the next instruction that will be executed. The kernel jumps to a different instruction by editing this register. You can access the rip register and edit it’s value if you wish, but it really isn’t advisable, if you want to jump to a different instruction you should always use the built-in jump commands.

There is also a completely separate set of registers used entirely by the kernel that you cannot access. The most interesting of these is the flags register which holds various flags that are set whenever the CPU performs a computation, for example the zero flag which indicates whether the result of the last computation was zero. These are the registers that are checked when we use a comparison instruction. There are various other registers that the kernel uses to keep track of things like the current protection level or virtual memory.

When using the 64 bit general purpose registers we usually read and write 64 bit values. However we can also read and write, 32 bit, 16 bit and 8 bit values. In my experience this is mostly useful when we want to check the value of specific bytes, for example when we are searching an ascii string for a specific character.

We can access these lower 32 bit, 16 bit and 8 bit segments of registers by adding a special suffix to the standard register names. To access the lowest byte we add b, to access the lowers two bytes we add w and to access the lowest four byte we add w. So for example to access the lower four bytes of r3, we use the name r3w, to access the lowest byte of r14 we use r14b.

Corresponding to the legacy register names, rax, rbx, rcx, rdx, rsp, rbp, rsi and rdi, there are similar legacy names for the lower portions of registers r0 to r7.

  • We access the bottom four bytes with eax, ebx, ecx, edx, esp, ebp, esi and edi.
  • The bottom two bytes are ax, bx, cx, dx, sp, bp, si and di.
  • We can access the bottom byte with al, bl, cl, dl, spl, bpl, sil, and dil.

We can also use special names to access the second to last byte of the registers rax, rbx, rcx and rdx, they are ah, bh, ch and dh. As we can see there is an inconsistent pattern to these register names. To access the bottom four bytes we replace ‘r’ with ‘e’, to access the lower two byte we remove the initial ‘r’.

When we want to use these lower portions of the registers we also have to change the instruction mnemonic we use. Normally we use movq to move a value into a register. We use a different instruction mnemonic for each different byte size.

  • To move eight bytes we use movq
  • To move four bytes we use movl
  • To move two bytes we use mov
  • To move one byte we use movb

There is one strange inconsistency with these lower registers. When we write to the lower portions of these registers the upper portions are left unaffected, except when we write to the lower four bytes, in which case the upper portion is filled with sign bits.

Let’s have a look at an example. The code below simply prints “Hello World” to the standard output and exits. It does this while only using the lower four bytes of each register.

.section .data
msg:
.string "Hello world\n"

.section .text

.globl _start
_start:

movl $1, %eax
movl $1, %edi
movl $msg, %esi
movl $12, %edx
syscall

movl $60, %eax
movl $0, %edi
syscall

A quick word on notation. Just as 8 bits make a byte, two bytes makes a word, four bytes makes a double word and eight bytes makes a quad word. Confusingly the term ‘word’ is often used to refer to the size of the largest memory address a CPU can access, that is four bytes on a 32 bit machine and eight bytes on a 64 bit machine.

Leave a Reply

Your email address will not be published. Required fields are marked *