7.2. Common Instructions
In this section, we discuss several common assembly instructions. Table 1 lists the most foundational instructions in x86 assembly:
Instruction | Translation |
---|---|
|
S → D (i.e, copies value of S into D) |
|
S + D → D (adds S+D and stores result in D) |
|
D - S → D (subtracts S from D and stores result in D) |
Therefore, the sequence of instructions:
mov -0x4(%rbp),%eax add $0x2,%eax
Translate to:
-
Copy the value at location
%rbp + -0x4
in memory (M[%rbp - 0x4]
) to register%eax
. -
Add the value
$0x2
to register%eax
, and store the result in register%eax
.
The three instructions shown in Table 1 also form the building blocks for instructions that
maintain the organization of the program stack (i.e. the call stack). Recall that registers %rbp
and %rsp
refer
to the frame pointer and stack pointer respectively and are reserved by the compiler
for call stack management. Recall from our earlier discussion on
program memory that the
call stack typically stores local variables and parameters and
helps the program track its own execution (see Figure 1). On x86-64 systems, the execution stack grows toward
lower addresses. Like all stack data structures, operations occur at the "top" of the stack.

The x86-64 ISA provides two instructions (Table 2) to simplify call stack management:
Instruction |
Translation |
|
pushes a copy of sub $0x8, %rsp mov S, (%rsp) |
|
pops the top element off the stack and places it in location mov (%rsp), D add $0x8, %rsp |
Notice that while the three instructions in Table 1 require two operands, the push
and pop
instructions in Table 2 require only one operand apiece.
7.2.1. Putting it all together: a more concrete example
Let’s take a closer look at the adder2()
function:
//adds two to an integer and returns the result
int adder2(int a) {
return a + 2;
}
and its corresponding assembly code:
0000000000400526 <adder2>: 400526: 55 push %rbp 400527: 48 89 e5 mov %rsp,%rbp 40052a: 89 7d fc mov %edi,-0x4(%rbp) 40052d: 8b 45 fc mov -0x4(%rbp),%eax 400530: 83 c0 02 add $0x2,%eax 400533: 5d pop %rbp 400534: c3 retq
The assembly code consists of a push
instruction, followed by three mov
instructions, an add
instruction, a pop
instruction, and finally a retq
instruction. To understand
how the CPU executes this set of instructions, we need to revisit the structure
of program memory.
Recall that every time a program executes, the operating system allocates the new program’s
address space (also known as virtual memory). Virtual memory
and the related concept of processes are covered in greater
detail in chapter 13; for now, it suffices to think of processes as the abstraction of a
running program and virtual memory as the memory that is allocated to a single process.
Every process has its own region of memory called the call stack. Keep in mind that the call
stack is located in process/virtual memory, unlike registers (which are located on the CPU).
Figure 2 depicts a sample state of the call stack and registers prior to the execution
of the adder2()
function.

Notice that the stack grows toward lower addresses. Registers %eax
contains a junk value.
The single parameter to the adder2()
function (a
) is stored in register %rdi
by convention. Since
a
is of type int
, it is stored in component register %edi
, which is shown in the figure above.
Likewise, since the adder2()
function returns an int
, component register %eax
is used for
the return value instead of %rax
.
The addresses associated with the instructions in the code segment of program memory (0x400526
-0x400534
)
have been shortened to (0x526
-0x534
) to improve figure readability. Likewise, the addresses associated with the
call stack segment of program memory have been shortened to 0xd28
-0xd1c
from a range of 0x7fffffffdd28
- 0x7fffffffdd1c
for figure readability. In truth, call stack addresses occur at much higher addresses
in program memory than code segment addresses.
Pay close attention to the initial values of registers %rsp
and %rbp
: they
are 0xd28
and 0xd40
respectively. The arrow visually indicates the
currently executing instruction. The %rip
register (or instruction pointer)
shows the next instruction to execute. Initially, %rip
contains address
0x526
which corresponds to the first instruction in the adder2()
function.

The first instruction (push %rbp
) places a copy of %rbp
(or 0xd40
) on top of the
stack. After it executes, the %rip
register advances to the address of the next instruction to
execute (0x527
). The push
instruction decrements the stack pointer by 8 ("growing" the stack by 8 bytes),
resulting in a new %rsp
value of 0xd20
. Recall that the push %rbp
instruction is equivalent to:
sub $8, %rsp mov %rbp, (%rsp)
In other words, subtract 8 from the stack pointer and place a copy of the contents of %rbp
in the location pointed to by the dereferenced stack pointer (e.g. (%rsp)
).

Recall that the structure of the mov
instruction is mov S,D
where S
is the source location
and D
is the destination. Thus, the next instruction (mov %rsp, %rbp
) updates the value of
%rbp
to 0xd20
. The register %rip
advances to the address of the next instruction to execute,
or 0x52a
.

Next, mov %edi, -0x4(%rbp)
is executed. This is a bit more complicated
than the last mov
instruction. Let’s parse it piece by piece. First, recall that
the first parameter to any function is stored in register %rdi
. Since a
is of
type int
, the compiler stores the first parameter in component register %edi
.
Next, the operand -0x4(%rbp)
translates to
M[%rbp - 0x4]
. Since %rbp
contains the value 0xd20
, subtracting
4
from it yields 0xd1c
. Therefore, the mov
instruction
copies the value of register %edi
(or 0x28
) to location 0xd1c
on the stack.
The instruction pointer advances to address 0x52d
, the next address to be executed.
Note that storing the value 0x28
does not impact the stack pointer (%rsp
).
Therefore, as far as the program is concerned, the "top" of this stack is still
address 0xd20
.

The next mov
instruction (mov -0x4(%rbp), %eax
) copies the value at stack
location 0xd1c
(i.e. M[%rbp - 0x4]
or 0x28
) and stores it in register %eax
.
Register %rip
advances to the next instruction to be executed, or 0x530
.

Next, add $0x2, %eax
is executed. Recall that the add
instruction
has the form add S,D
and places the quantity S + D
in the destination D
. So,
add $0x2, %eax
adds the constant value $0x2
to the value stored in %eax
(or 0x28
), resulting in the value 0x2A
being stored in register %eax
. Register
%eip
advances to point to the next instruction to be executed, or 0x533
.

The next instruction that executes is pop %rbp
. This instruction "pops"
the value off of the top of the call stack and places it in destination register %rbp
.
Recall that this instruction is equivalent to the following sequence of
two instructions:
mov (%rsp), %rbp add $8, %rsp
Recall that the top of the stack is 0xd20
, since that is the value stored in
%rsp
. Therefore, once this instruction executes, the value (%rsp)
(i.e M[0xd20]
) is copied into register %rbp
. Thus %rbp
now contains
the value 0xd40
. The stack pointer increments by 8, since the stack
grows towards lower addresses (and consequently, shrinks toward higher ones).
The new value of %rsp
is 0xd28
, and %rip
now points to the address of
the last instruction to execute in this code snippet (i.e. 0x534
).
The last instruction executed is retq
. We will talk more about what
happens with retq
in future sections when we discuss function calls, but
for now it suffices to know that it prepares the call stack for returning from a
function. By convention, the register %rax
always contains the return value
(if one exists). In this case, since adder2()
is of type int
, the return
value is stored in component register %eax
and the function returns the
value 0x2A
, or 42
.
Before we continue, note that the end values of registers %rsp
and %rbp
are
0xd28
and 0xd40
respectively, which are the same values as when the function
started executing! This is normal and expected behavior with the call
stack. The purpose of the call stack is to store the temporary variables and data
of each function as it executes in the context of a program. Once a function
completes executing, the stack returns to the state it was prior to the
function call. As a result, it is common to see the following two
instructions at the beginning of a function:
push %rbp mov %rsp, %rbp
and the following two instructions at the end of a function:
pop %rbp retq