ASM (x86-64)

Learn about the assembly language understood by our home computers

Easy

ASM (x86-64)
Memory

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.

ComparisonMemory/RAMRegisters
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...

CODE [0x400000]
Registers
rax:
rbx:
rcx:
rdx:
rdi:
rsi:
r8 :
r9 :
r10:
r11:
r12:
r13:
r14:
r15:
rbp:
rsp:
rip:
IN [0x1000-0x1fff]
1000 | 00 00 00 00 00 00 00 00
|........|
1008 | 00 00 00 00 00 00 00 00
|........|
1010 | 00 00 00 00 00 00 00 00
|........|
1018 | 00 00 00 00 00 00 00 00
|........|
1020 | 00 00 00 00 00 00 00 00
|........|
1028 | 00 00 00 00 00 00 00 00
|........|
1030 | 00 00 00 00 00 00 00 00
|........|
1038 | 00 00 00 00 00 00 00 00
|........|
1040 | 00 00 00 00 00 00 00 00
|........|
1048 | 00 00 00 00 00 00 00 00
|........|
1050 | 00 00 00 00 00 00 00 00
|........|
1058 | 00 00 00 00 00 00 00 00
|........|
1060 | 00 00 00 00 00 00 00 00
|........|
1068 | 00 00 00 00 00 00 00 00
|........|
1070 | 00 00 00 00 00 00 00 00
|........|
1078 | 00 00 00 00 00 00 00 00
|........|
1080 | 00 00 00 00 00 00 00 00
|........|
1088 | 00 00 00 00 00 00 00 00
|........|
1090 | 00 00 00 00 00 00 00 00
|........|
1098 | 00 00 00 00 00 00 00 00
|........|
10a0 | 00 00 00 00 00 00 00 00
|........|
10a8 | 00 00 00 00 00 00 00 00
|........|
10b0 | 00 00 00 00 00 00 00 00
|........|
10b8 | 00 00 00 00 00 00 00 00
|........|
10c0 | 00 00 00 00 00 00 00 00
|........|
10c8 | 00 00 00 00 00 00 00 00
|........|
10d0 | 00 00 00 00 00 00 00 00
|........|
10d8 | 00 00 00 00 00 00 00 00
|........|
10e0 | 00 00 00 00 00 00 00 00
|........|
10e8 | 00 00 00 00 00 00 00 00
|........|
10f0 | 00 00 00 00 00 00 00 00
|........|
10f8 | 00 00 00 00 00 00 00 00
|........|
OUT [0x2000-0x2fff]
2000 | 00 00 00 00 00 00 00 00
|........|
2008 | 00 00 00 00 00 00 00 00
|........|
2010 | 00 00 00 00 00 00 00 00
|........|
2018 | 00 00 00 00 00 00 00 00
|........|
2020 | 00 00 00 00 00 00 00 00
|........|
2028 | 00 00 00 00 00 00 00 00
|........|
2030 | 00 00 00 00 00 00 00 00
|........|
2038 | 00 00 00 00 00 00 00 00
|........|
2040 | 00 00 00 00 00 00 00 00
|........|
2048 | 00 00 00 00 00 00 00 00
|........|
2050 | 00 00 00 00 00 00 00 00
|........|
2058 | 00 00 00 00 00 00 00 00
|........|
2060 | 00 00 00 00 00 00 00 00
|........|
2068 | 00 00 00 00 00 00 00 00
|........|
2070 | 00 00 00 00 00 00 00 00
|........|
2078 | 00 00 00 00 00 00 00 00
|........|
2080 | 00 00 00 00 00 00 00 00
|........|
2088 | 00 00 00 00 00 00 00 00
|........|
2090 | 00 00 00 00 00 00 00 00
|........|
2098 | 00 00 00 00 00 00 00 00
|........|
20a0 | 00 00 00 00 00 00 00 00
|........|
20a8 | 00 00 00 00 00 00 00 00
|........|
20b0 | 00 00 00 00 00 00 00 00
|........|
20b8 | 00 00 00 00 00 00 00 00
|........|
20c0 | 00 00 00 00 00 00 00 00
|........|
20c8 | 00 00 00 00 00 00 00 00
|........|
20d0 | 00 00 00 00 00 00 00 00
|........|
20d8 | 00 00 00 00 00 00 00 00
|........|
20e0 | 00 00 00 00 00 00 00 00
|........|
20e8 | 00 00 00 00 00 00 00 00
|........|
20f0 | 00 00 00 00 00 00 00 00
|........|
20f8 | 00 00 00 00 00 00 00 00
|........|
STACK [0xf000-0xffff]
ff00 (+00) | 0x0000000000000000
ff08 (+08) | 0x0000000000000000
ff10 (+10) | 0x0000000000000000
ff18 (+18) | 0x0000000000000000
ff20 (+20) | 0x0000000000000000
ff28 (+28) | 0x0000000000000000
ff30 (+30) | 0x0000000000000000
ff38 (+38) | 0x0000000000000000
ff40 (+40) | 0x0000000000000000
ff48 (+48) | 0x0000000000000000
ff50 (+50) | 0x0000000000000000
ff58 (+58) | 0x0000000000000000
ff60 (+60) | 0x0000000000000000
ff68 (+68) | 0x0000000000000000
ff70 (+70) | 0x0000000000000000
ff78 (+78) | 0x0000000000000000
ff80 (+80) | 0x0000000000000000
ff88 (+88) | 0x0000000000000000
ff90 (+90) | 0x0000000000000000
ff98 (+98) | 0x0000000000000000
ffa0 (+a0) | 0x0000000000000000
ffa8 (+a8) | 0x0000000000000000
ffb0 (+b0) | 0x0000000000000000
ffb8 (+b8) | 0x0000000000000000
ffc0 (+c0) | 0x0000000000000000
ffc8 (+c8) | 0x0000000000000000
ffd0 (+d0) | 0x0000000000000000
ffd8 (+d8) | 0x0000000000000000
ffe0 (+e0) | 0x0000000000000000
ffe8 (+e8) | 0x0000000000000000
fff0 (+f0) | 0x0000000000000000
fff8 (+f8) | 0x0000000000000000

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).

The absolute addressing we use in line 1 is not commonly used, but we used it for better demonstration purposes.

Relative addressing is a more popular way that memory is accessed and can be done with syntax like so:

Initializing...

CODE [0x400000]
Registers
rax:
rbx:
rcx:
rdx:
rdi:
rsi:
r8 :
r9 :
r10:
r11:
r12:
r13:
r14:
r15:
rbp:
rsp:
rip:

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...

CODE [0x400000]
Registers
rax:
rbx:
rcx:
rdx:
rdi:
rsi:
r8 :
r9 :
r10:
r11:
r12:
r13:
r14:
r15:
rbp:
rsp:
rip:
IN [0x1000-0x1fff]
1000 | 00 00 00 00 00 00 00 00
|........|
1008 | 00 00 00 00 00 00 00 00
|........|
1010 | 00 00 00 00 00 00 00 00
|........|
1018 | 00 00 00 00 00 00 00 00
|........|
1020 | 00 00 00 00 00 00 00 00
|........|
1028 | 00 00 00 00 00 00 00 00
|........|
1030 | 00 00 00 00 00 00 00 00
|........|
1038 | 00 00 00 00 00 00 00 00
|........|
1040 | 00 00 00 00 00 00 00 00
|........|
1048 | 00 00 00 00 00 00 00 00
|........|
1050 | 00 00 00 00 00 00 00 00
|........|
1058 | 00 00 00 00 00 00 00 00
|........|
1060 | 00 00 00 00 00 00 00 00
|........|
1068 | 00 00 00 00 00 00 00 00
|........|
1070 | 00 00 00 00 00 00 00 00
|........|
1078 | 00 00 00 00 00 00 00 00
|........|
1080 | 00 00 00 00 00 00 00 00
|........|
1088 | 00 00 00 00 00 00 00 00
|........|
1090 | 00 00 00 00 00 00 00 00
|........|
1098 | 00 00 00 00 00 00 00 00
|........|
10a0 | 00 00 00 00 00 00 00 00
|........|
10a8 | 00 00 00 00 00 00 00 00
|........|
10b0 | 00 00 00 00 00 00 00 00
|........|
10b8 | 00 00 00 00 00 00 00 00
|........|
10c0 | 00 00 00 00 00 00 00 00
|........|
10c8 | 00 00 00 00 00 00 00 00
|........|
10d0 | 00 00 00 00 00 00 00 00
|........|
10d8 | 00 00 00 00 00 00 00 00
|........|
10e0 | 00 00 00 00 00 00 00 00
|........|
10e8 | 00 00 00 00 00 00 00 00
|........|
10f0 | 00 00 00 00 00 00 00 00
|........|
10f8 | 00 00 00 00 00 00 00 00
|........|
OUT [0x2000-0x2fff]
2000 | 00 00 00 00 00 00 00 00
|........|
2008 | 00 00 00 00 00 00 00 00
|........|
2010 | 00 00 00 00 00 00 00 00
|........|
2018 | 00 00 00 00 00 00 00 00
|........|
2020 | 00 00 00 00 00 00 00 00
|........|
2028 | 00 00 00 00 00 00 00 00
|........|
2030 | 00 00 00 00 00 00 00 00
|........|
2038 | 00 00 00 00 00 00 00 00
|........|
2040 | 00 00 00 00 00 00 00 00
|........|
2048 | 00 00 00 00 00 00 00 00
|........|
2050 | 00 00 00 00 00 00 00 00
|........|
2058 | 00 00 00 00 00 00 00 00
|........|
2060 | 00 00 00 00 00 00 00 00
|........|
2068 | 00 00 00 00 00 00 00 00
|........|
2070 | 00 00 00 00 00 00 00 00
|........|
2078 | 00 00 00 00 00 00 00 00
|........|
2080 | 00 00 00 00 00 00 00 00
|........|
2088 | 00 00 00 00 00 00 00 00
|........|
2090 | 00 00 00 00 00 00 00 00
|........|
2098 | 00 00 00 00 00 00 00 00
|........|
20a0 | 00 00 00 00 00 00 00 00
|........|
20a8 | 00 00 00 00 00 00 00 00
|........|
20b0 | 00 00 00 00 00 00 00 00
|........|
20b8 | 00 00 00 00 00 00 00 00
|........|
20c0 | 00 00 00 00 00 00 00 00
|........|
20c8 | 00 00 00 00 00 00 00 00
|........|
20d0 | 00 00 00 00 00 00 00 00
|........|
20d8 | 00 00 00 00 00 00 00 00
|........|
20e0 | 00 00 00 00 00 00 00 00
|........|
20e8 | 00 00 00 00 00 00 00 00
|........|
20f0 | 00 00 00 00 00 00 00 00
|........|
20f8 | 00 00 00 00 00 00 00 00
|........|
STACK [0xf000-0xffff]
ff00 (+00) | 0x0000000000000000
ff08 (+08) | 0x0000000000000000
ff10 (+10) | 0x0000000000000000
ff18 (+18) | 0x0000000000000000
ff20 (+20) | 0x0000000000000000
ff28 (+28) | 0x0000000000000000
ff30 (+30) | 0x0000000000000000
ff38 (+38) | 0x0000000000000000
ff40 (+40) | 0x0000000000000000
ff48 (+48) | 0x0000000000000000
ff50 (+50) | 0x0000000000000000
ff58 (+58) | 0x0000000000000000
ff60 (+60) | 0x0000000000000000
ff68 (+68) | 0x0000000000000000
ff70 (+70) | 0x0000000000000000
ff78 (+78) | 0x0000000000000000
ff80 (+80) | 0x0000000000000000
ff88 (+88) | 0x0000000000000000
ff90 (+90) | 0x0000000000000000
ff98 (+98) | 0x0000000000000000
ffa0 (+a0) | 0x0000000000000000
ffa8 (+a8) | 0x0000000000000000
ffb0 (+b0) | 0x0000000000000000
ffb8 (+b8) | 0x0000000000000000
ffc0 (+c0) | 0x0000000000000000
ffc8 (+c8) | 0x0000000000000000
ffd0 (+d0) | 0x0000000000000000
ffd8 (+d8) | 0x0000000000000000
ffe0 (+e0) | 0x0000000000000000
ffe8 (+e8) | 0x0000000000000000
fff0 (+f0) | 0x0000000000000000
fff8 (+f8) | 0x0000000000000000

Here are a few key observations:

  1. The stack grows towards lower addresses (rsp decreases after push)
  2. push and pop cause rsp 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)
  1. After sub rsp, 8, we can see that our 0xdeadbeef 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