So far we have used registers in two distinct ways. The first way is when we load values into registers and compare them to other values, like in the following code:
movq $4, %rax
movq $3, %rbx
cmpq %rbx, %rax
We’ve also used registers to store memory addresses when we used system calls. For example if we wanted to write the 50 bytes from the buffer named data_buffer
to stdout
we would use code like the following:
movq $1, %rax
movq $1, %rdi
movq $data_buffer, %rsi
movq $50, %rdx
In the third line, $data_buffer
, is the address of the buffer, so when we make the system call, the register contains an address for the data we are interested in rather than the data itself.
We can use registers like this more generally. Indeed, to access the value stored at the memory location contained in a register we wrap the register name in brackets, as below.
cmpq $0, (%rax)
In the above code, if the value stored in rax
is an accessible address in memory, that contains a value equal to zero, then the above condition is true. If rax
contains an accessible address in memory that contains a value other than zero the above condition is false. If the register rax contains the address of a region of memory we cannot access, for example the region before the instructions, the we will get a segmentation fault when our program runs.
We can also offset the address in a register by placing a constant value in front of the brackets like so:
cmpq $0, 8(%rax)
this value can be positive or negative and is specified in bytes.
Often however, we want to dynamically calculate addresses in our code, we do that with indexed addressing mode. This allows us to provide a constant base address, a constant multiplier, and two registers representing an offset and a multiplier. Specifically,
data_buffer(%rax, %rbx, 2)
refers to the memory address found when you start at the address of data_buffer, add the value contained in rax
and 2 times the value in rbx
(with all numeric values specified in bytes). Unfortunately you cannot use a negative multiple here. This addressing mode is particularly useful when we are iterating through strings or arrays of contiguous memory.