ASM_(x86-64)::Memory
Earlier we covered the concept of registers, which act as small temporary variables to store data between computations. Now, we need to understand what memory is.
What is memory
When we think of memory, we'd immediately think of human memory and our ability to store and recall facts, feelings and much more complex information. For those with more background in computers, you may think of hard-drive or SSD space as memory of the computer. However, when we speak of memory in terms of the CPU (like a x86), we generally are referring to RAM (random-access memory).
RAM or memory refers to a short-term data storage area which our CPU can access and modify for it's needs while running programs. While registers are supposed to be used for intermediate data in calculations, registers are limited in number and in capacity. Memory on the other hand, is generally much more abundant and can store much larger quantities of intermediate data. However, memory will be slower to read/write from than registers. Thus both have their pros and cons, and should be used appropriately depending on the context.
Comparison | Memory/RAM | Registers |
---|---|---|
Faster access | āļø | |
Greater capacity | āļø | |
Volatile? (Lost with power removed) | āļø | āļø |
An analogy for memory
We can try to extend our analogy from earlier lessons. If we recall that the CPU is a dumb chef Bob, and Bob stores his ingredients in bowls (registers) while working, then what would memory be? Memory in this analogy could be thought of as the counter-top, or the fridge. It can definitely store much larger quantities of ingredients (data) than a bowl could, but would require a bit more effort to walk to (access).
Using memory
So how do we access memory through x86-64 code? Memory addresses!
From the CPU's point of view, memory is treated like a large array of bytes (8-bit numbers). In order to differentiate the different areas of memory, the CPU labels them with virtual addresses. Virtual addresses are simply indexes from the start of memory (memory address 0).
By labelling all bytes of memory with this simple addressing, we can access any byte of memory with no issues. If you are comfortable with C programming, you may know about pointers. In fact, pointers just store the memory addresses of the areas in memory the pointers are pointing to.
As you can see, each labelled space on the table above is like an area in memory! Each space can store an item(data), like the apple. If the data is too large for one space, it can be stored across multiple continuous spaces in memory.
Enough theory, let's try to concretise our understanding with a real example!
Recall the mov
instruction taught earlier, we can actually mov
values to and from memory as well!
The follow code snippet will move a 8-byte value from our memory into a register.
Then, this value will be moved into our OUT
memory region @ address 0x2000
.
Initializing...
As you can see, we make use of the []
square brackets to dereference memory at addresses.
This can be dereferenced as an offset from 0
(used on line 1) or dereferenced from the value of a register (used on line 3).
Relative addressing is a more popular way that memory is accessed and can be done with syntax like so:
Initializing...
The Stack
You may have learnt about the stack data structure from your university/algorithm lessons. The stack data structure has great applications in our assembly programs as well!
When computer programs are loaded, it is common for the operation system (OS) to setup some basic features for the programs that are being run.
The stack is one such feature, and is simply a mapping of contiguous memory.
However, a special register rsp
will be set to an address within this stack.
Why is rsp
special you ask?
rsp
is named the stack pointer register, and is meant to point to the top of the stack.
Special instructions like push
and pop
will take note of where rsp
is pointing and act accordingly.
push
push
takes a single operand, which can be a register or memory operand.
It will take the value of the operand, and write it into the address pointed to by rsp
, it will then decrement the value of rsp
.
This behaviour is exactly like pushing an element into a stack data structure.
pop
pop
will take a single operand dst
.
It will retrieve the value pointed to by rsp
, and store that value into the dst
operand.
rsp
will then be incremented in order to "pop" the value off the stack.
This concept may be difficult the understand just from reading our explanation, so take some time to step through the instructions below.
Take note of the value of rsp
, rax
, rbx
when each instruction runs.
The STACK view should reflect the state of the stack for you to observe as well.
Initializing...
Here are a few key observations:
- The stack grows towards lower addresses (
rsp
decreases afterpush
) push
andpop
causersp
to be decremented and incremented by 8
- This is because on our 64-bit CPU, our stack elements are treated as 64-bit elements (8 bytes)
- After
sub rsp, 8
, we can see that our0xdeadbeef
value still stays on the stack, and was not removed.
- Our stack data structure "removes" items from the stack by simply incrementing the value of
rsp
(out of sight, out of mind) - Future pushes will just overwrite this value if necessary, so there is no need to waste CPU time to clear these values