// Calculate the nth sequence number and return it.
unsigned long long sequence(unsigned long long n) {
if (n < 2) {
return 1;
}
// This function cannot clear its stack memory until the next call returns.
return n + sequence(n-1);
}
int main(int argc, char** argv) {
if (argc < 2) {
printf("Give me the number that you want to sum up to.\n");
return 0;
}
int n = atoi(argv[1]);
printf("The sequence number for %i is %llu\n", n, sequence(n));
return 0;
}
```
---
## Assembly
```asm
.file "blub.c"
.text
.globl sequence
.type sequence, @function
sequence:
.LFB6:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movq %rdi, -8(%rbp)
cmpq $1, -8(%rbp)
ja .L2
movl $1, %eax
jmp .L3
.L2:
movq -8(%rbp), %rax
subq $1, %rax
movq %rax, %rdi
call sequence
movq -8(%rbp), %rdx
addq %rdx, %rax
.L3:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE6:
.size sequence, .-sequence
.section .rodata
.align 8
.LC0:
.string "Give me the number that you want to sum up to."
.align 8
.LC1:
.string "The sequence number for %i is %llu\n"
.text
.globl main
.type main, @function
main:
.LFB7:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
cmpl $1, -20(%rbp)
jg .L5
leaq .LC0(%rip), %rax
movq %rax, %rdi
call puts@PLT
movl $0, %eax
jmp .L6
.L5:
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rdi
call atoi@PLT
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
cltq
movq %rax, %rdi
call sequence
movq %rax, %rdx
movl -4(%rbp), %eax
movl %eax, %esi
leaq .LC1(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
.L6:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE7:
.size main, .-main
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
```
---
## What Is It?
* All compile-time information is encoded into the single assembly file
* Constants, functions, code
* This assembly will be converted into machine code
* So some lines aren't instructions
---
## Non-Assembly
* The strings that begin with a period (`.`) are directives to the assembler and linker
* They aren't assembly, but are used when converting this code into a binary file
* We'll usually ignore them when discussing assembly
.file "example25_b.c"
.text
.globl sequence
.type sequence, @function
---
## Cleaned Up
```asm
.file "blub.c"
.text
.globl sequence
.type sequence, @function
sequence:
.LFB6:
endbr64
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp)
cmpq $1, -8(%rbp)
ja .L2
movl $1, %eax
jmp .L3
.L2:
movq -8(%rbp), %rax
subq $1, %rax
movq %rax, %rdi
call sequence
movq -8(%rbp), %rdx
addq %rdx, %rax
.L3:
leave
ret
.LFE6:
.size sequence, .-sequence
.section .rodata
.align 8
.LC0:
.string "Give me the number that you want to sum up to."
.align 8
.LC1:
.string "The sequence number for %i is %llu\n"
.text
.globl main
.type main, @function
main:
.LFB7:
endbr64
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
cmpl $1, -20(%rbp)
jg .L5
leaq .LC0(%rip), %rax
movq %rax, %rdi
call puts@PLT
movl $0, %eax
jmp .L6
.L5:
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rdi
call atoi@PLT
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
cltq
movq %rax, %rdi
call sequence
movq %rax, %rdx
movl -4(%rbp), %eax
movl %eax, %esi
leaq .LC1(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
.L6:
leave
ret
.LFE7:
.size main, .-main
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
```
---
## Tags
* Code is stored in memory somewhere
* We keep track of what code we are executing with a pointer to the current instruction
* Call the **instruction pointer** (or **program counter** in some systems)
* We don't know the memory address yet, so the compiler leaves a placeholder
* Both "sequence" and ".LFB6" refer to the memory address of the function
```asm
sequence:
.LFB6:
```
---
## Code
// Calculate the nth sequence number and return it.
unsigned long long sequence(unsigned long long n) {
if (n < 2) {
return 1;
}
// This function cannot clear its stack memory until the next call returns.
return n + sequence(n-1);
}
sequence:
.LFB6:
endbr64
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp)
cmpq $1, -8(%rbp)
ja .L2
movl $1, %eax
jmp .L3
.L2:
movq -8(%rbp), %rax
subq $1, %rax
movq %rax, %rdi
call sequence
movq -8(%rbp), %rdx
addq %rdx, %rax
.L3:
leave
ret
---
## No More Variables
* Notice there is no mention of **n** anywhere in that assembly
* Assembly is done on real hardware
* No more variables
* Instead, everything must exist in hardware
* Usually, this means in the `registers`
---
## Registers
---
## General Purpose Registers
* With x86-64, each register is 64 bits
* But can be treated as 32, 16, or 8
* qword, dword, word, byte
* Just change the name
* rax, eax, ax
* ah, al for the high and low bytes
---
## Backwards compatibility
* These registers began their lives smaller than they are today
* So the old names have stuck around
* `e` is for extended
* `r` is the 64 bit prefix
* May be confusing at first, but register prefixes and instruction suffixes are mostly consistent
---
## Immediate Addressing
* We can use constant values with registers
* e.g. `$5` refers to the constant value 5
* This sets the destination to 5
* `movq $5 dst`
* Immediate values can be a source, but not a destination
---
## Register Addressing
* More common to work with register values
* `movq %rax, %rdi`
* Moves the value in rax to the value in rdi
* A register can be either a source or a destination of an operand
---
## Direct and Indirect
* These are pointer equivalents
* **Absolute** (or **direct**) addressing uses a hard-coded address
* `movq 0x40abcd, dst`
* Would move `memory[0x40abcd]` to destination
* **Indirect** addressing gets the address from a register
* `moveq (%rax), dst`
* Would move `memory[%rax]` to destination
---
## A Note on Memory
* `memory[0x40abcd]` means an offset into the program memory
* Remember, the stack and the heap exist in the same address space
* Just one big, flat chunk of memory
* We've seen this when we printed out the values of pointers
---
## Displacement and Scaling
* Arrays are very common, so syntax supports them
* `8(%rax)` is `memory[%rax + 8]`
* like `char_ptr[8]`
* But most displacement has a scaling factor, e.g. ints have a scale of 4 bytes
* `8(%rax, %rcx, 4)` is `memory[%rax + 8 + 4*%rcx]`
* Can skip the displacement for familiar array access
* `(%rax, %rcx, 4)` is `memory[%rax + 4*%rcx]`
---
## Operands and Instructions
* Operands must be one of immediate, register, or memory
* They are represented by numbers when translated to machine code
* So complicated instructions actually take up more "space" than simple ones
* We'll dive more into that later
---
## Accessing Registers Example
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rdi
call atoi@PLT
* This sets up to the call to `atoi(argv[1])`
* Puts `memory[%rbp-32]` into `%rax`
* This is `argv`
* Adds 8
* argv points to pointers, so `argv+1` is adding 8 bytes
* Moves the *memory* at `%rax` into `%rax`
* Fetching `argv[1]`
* Moves to `%rdi`, which is used to pass the first argument to a function
---
## Register Uses
* By convention, some registers have specific uses
* %rax is where return values are placed
* %rdi is where we put the first argument to a function
* %rsi, %rdx, and %rcx are the 2nd through 3rd
* %rsp is the current location of the stack, %rbp is the previous stack location
---
## Instruction Suffixes
call atoi@PLT
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
cltq
movq %rax, %rdi
call sequence
movq %rax, %rdx
* This calls atoi, then moves the integer result in `eax` to memory at `rbp`
* `eax` is the 32-bit name of the `rax` register
* `l` is the suffix for 32-bit longs/ints
* The first `movl` stores the int in `memory[rbp-4]`
* Then transfers it back into `eax`
* This effectively clears the upper 4 bytes of `eax`
---
## Assembly Tricks
* Assembly is confusing because of side-effects
* `movl` clears the upper four bytes of an 8 byte register
* We could have used a binary & with a mask
* but we already required a copy of the number, so why not reuse it?
movl %eax, -4(%rbp)
movq $0xFFFFFFFF, %rbx
andq %rax, %rbx
---
## Type Conversion
call atoi@PLT
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
cltq
movq %rax, %rdi
call sequence
movq %rax, %rdx
* Before the call to the sequence function, convert the `int` to a `long long`
* `cltq`, convert long to quad (it does padding, 1s if the number is negative)
* After that, we use the full 64 bit register, `rax`
* The `movq` moves the `long long` into 64-bit quad word
* Then calls the sequence function, whose result is returned in `rax`
---
## Instruction Suffixes
call atoi@PLT
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
cltq
movq %rax, %rdi
call sequence
movq %rax, %rdx
* `rdi` (and the `di` register in general) is where we pass arguments
* `rdi` is the first argument, `rsi` the second, then `rdx`, `rcx`, `r8`, `r9`
* and then we need to store arguments on the stack
* So functions with too many arguments are slow
---
## Operator Suffix meanings
* **b**: byte
* **w**: word (2 bytes)
* **l**: long/double word (4 bytes)
* **q**: quad word (8 bytes)
* e.g. **movq** is move quad, **movl** is move long
---
## The Function
sequence:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp)
cmpq $1, -8(%rbp)
ja .L2
movl $1, %eax
jmp .L3
* Pushes register **rbp**'s value (64 bit) on the stack
* This remembers where the caller's section of stack began
* Why rbp rather than ebp or bp?
* Function uses long longs, which are 64 bits
---
## Handling Variables
sequence:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp)
cmpq $1, -8(%rbp)
ja .L2
movl $1, %eax
jmp .L3
* Going to use stack memory, how do we allocate it?
* Subtract 16 from the stack pointer: `subq $16, %rsp`
* Remember, our stack grows from the top
* So we subtract to add space
* `movq %rdi, -8(%rbp)` moves the first argument onto the top of the stack
-v-
## Example
```C
/*
* Stack Vs Heap example
*/
#include
#include
void a() {
unsigned char stack_memory[16];
unsigned char *heap_memory = calloc(16, sizeof(unsigned char));
printf("a stack memory is %p\n", stack_memory);
printf("a heap memory is %p\n", heap_memory);
free(heap_memory);
return;
}
void b() {
unsigned char stack_memory[16];
unsigned char *heap_memory = calloc(16, sizeof(unsigned char));
printf("b stack memory is %p\n", stack_memory);
printf("b heap memory is %p\n", heap_memory);
a();
free(heap_memory);
return;
}
void c() {
unsigned char stack_memory[16];
unsigned char *heap_memory = calloc(16, sizeof(unsigned char));
printf("c stack memory is %p\n", stack_memory);
printf("c heap memory is %p\n", heap_memory);
b();
free(heap_memory);
return;
}
void d() {
unsigned char stack_memory[16];
unsigned char *heap_memory = calloc(16, sizeof(unsigned char));
printf("d stack memory is %p\n", stack_memory);
printf("d heap memory is %p\n", heap_memory);
c();
free(heap_memory);
return;
}
int main(void) {
d();
return 0;
}
```
-v-
## Example Output
* Example values not guaranteed, direction is consistent
$ ./a.out
d stack memory is 0x7ffc2eceb250
d heap memory is 0x55d2ddab12a0
c stack memory is 0x7ffc2eceb210
c heap memory is 0x55d2ddab16d0
b stack memory is 0x7ffc2eceb1d0
b heap memory is 0x55d2ddab16f0
a stack memory is 0x7ffc2eceb190
a heap memory is 0x55d2ddab1710
---
## Stack Documentation
---
## The Function
// Calculate the nth sequence number and return it.
unsigned long long sequence(unsigned long long n) {
if (n < 2) {
return 1;
}
// This function cannot clear its stack memory until the next call returns.
return n + sequence(n-1);
}
sequence:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp)
cmpq $1, -8(%rbp)
ja .L2
movl $1, %eax
jmp .L3
* `rdi` has our argument
* `mov` it into `memory[rbp-8]`, which is the first long long we made space for on the stack
* The compare it to the constant value `1`
* `ja` jumps to the tag if the unsigned comparison was true
* Jumping simply changes the instruction pointer, and we continue execution from there
---
## Branching
// Calculate the nth sequence number and return it.
unsigned long long sequence(unsigned long long n) {
if (n < 2) {
return 1;
}
// This function cannot clear its stack memory until the next call returns.
return n + sequence(n-1);
}
movl $1, %eax
jmp .L3
.L2:
movq -8(%rbp), %rax
subq $1, %rax
movq %rax, %rdi
call sequence
movq -8(%rbp), %rdx
addq %rdx, %rax
.L3:
leave
ret
* If we don't jump, we save `1` on `eax`
* Then jump to **L3** and return from the program
---
## Otherwise
// Calculate the nth sequence number and return it.
unsigned long long sequence(unsigned long long n) {
if (n < 2) {
return 1;
}
// This function cannot clear its stack memory until the next call returns.
return n + sequence(n-1);
}
cmpq $1, -8(%rbp)
ja .L2
movl $1, %eax
jmp .L3
.L2:
movq -8(%rbp), %rax
subq $1, %rax
movq %rax, %rdi
call sequence
movq -8(%rbp), %rdx
addq %rdx, %rax
.L3:
leave
ret
* Otherwise, the recursive call is in **L2**
* Put the stored value of n into %rax, subtract 1, put it into the argument register %rdi, and call sequence
* Then fetch the previously stored value of **n** in `memory[%rbp-8]` into %rdx
* Add %rdx into %rax (which still has the value of **n**) and return
---
## Pause
* You aren't going to magically follow everything instantly
* Assembly has a small vocabulary, but it is esoteric
* We can use assembly from within C, which allows an easier introduction
---
## Previous Stack Example
```C
#include
#include
#include "stack.h"
int main(int argc, char** argv) {
if (argc < 2) {
printf("This program requires a data file.\n");
return 1;
}
// Open datafile for reading.
FILE* datafile = fopen(argv[1], "r");
if (datafile == NULL) {
printf("Error opening file.\n");
return 2;
}
Stack s = newStack(10);
// We don't need to get complicated with sscanf
// Just read in one character at a time
int input = fgetc(datafile);
// EOF is defined in stdio.h, means "End Of File"
while (input != EOF) {
push(&s, input);
// For the next loop iteration
input = fgetc(datafile);
}
// Finished with the file
fclose(datafile);
// Reverse
while (0 < len(s)) {
putchar(pop(&s));
}
putchar('\n');
freeStack(s);
return 0;
}
```
---
## Stacks and "The Stack"
* "The stack" is always present
* If we aren't passing our struct Stack around, can we just use stack memory?
* Sure!
---
## Using the Stack
```c
#include
#include
int main(int argc, char** argv) {
if (argc < 2) {
printf("This program requires a data file.\n");
return 1;
}
// Open datafile for reading.
FILE* datafile = fopen(argv[1], "r");
if (datafile == NULL) {
printf("Error opening file.\n");
return 2;
}
// We don't need to get complicated with sscanf
// Just read in one character at a time
int input = fgetc(datafile);
int pushed = 0;
// EOF is defined in stdio.h, means "End Of File"
while (input != EOF) {
// The double %% indicates a register, a single % means a user-provided variable.
__asm__(
"push %0"
: // No outputs
: "r" ((long long)input) // Input value. "push" requires an 8 byte value
:
);
// This is rewritten into assembly.
// push %0 tells the compiler that we are going to give it an input argument
// The : marks a section for outputs, which are aren't using
// The next : marks inputs, which we are using.
// The input variable is loaded into any register "r"
++pushed;
// For the next loop iteration
input = fgetc(datafile);
}
// Finished with the file
fclose(datafile);
// Reverse
long long out = 0;
while (pushed > 0) {
__asm__(
"pop %0"
: "=r" (out) // Output into the out variable.
:
:
);
putchar(out);
--pushed;
}
putchar('\n');
return 0;
}
```
---
## Converted Assembly
```asm
.file "blub.c"
.text
.section .rodata
.align 8
.LC0:
.string "This program requires a data file."
.LC1:
.string "r"
.LC2:
.string "Error opening file."
.text
.globl main
.type main, @function
main:
.LFB6:
endbr64
pushq %rbp
movq %rsp, %rbp
subq $48, %rsp
movl %edi, -36(%rbp)
movq %rsi, -48(%rbp)
cmpl $1, -36(%rbp)
jg .L2
leaq .LC0(%rip), %rax
movq %rax, %rdi
call puts@PLT
movl $1, %eax
jmp .L3
.L2:
movq -48(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
leaq .LC1(%rip), %rdx
movq %rdx, %rsi
movq %rax, %rdi
call fopen@PLT
movq %rax, -16(%rbp)
cmpq $0, -16(%rbp)
jne .L4
leaq .LC2(%rip), %rax
movq %rax, %rdi
call puts@PLT
movl $2, %eax
jmp .L3
.L4:
movq -16(%rbp), %rax
movq %rax, %rdi
call fgetc@PLT
movl %eax, -24(%rbp)
movl $0, -20(%rbp)
jmp .L5
.L6:
movl -24(%rbp), %eax
cltq
#APP
# 24 "blub.c" 1
push %rax
# 0 "" 2
#NO_APP
addl $1, -20(%rbp)
movq -16(%rbp), %rax
movq %rax, %rdi
call fgetc@PLT
movl %eax, -24(%rbp)
.L5:
cmpl $-1, -24(%rbp)
jne .L6
movq -16(%rbp), %rax
movq %rax, %rdi
call fclose@PLT
movq $0, -8(%rbp)
jmp .L7
.L8:
#APP
# 47 "blub.c" 1
pop %rax
# 0 "" 2
#NO_APP
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movl %eax, %edi
call putchar@PLT
subl $1, -20(%rbp)
.L7:
cmpl $0, -20(%rbp)
jg .L8
movl $10, %edi
call putchar@PLT
movl $0, %eax
.L3:
leave
ret
.LFE6:
.size main, .-main
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
```
---
## Loop
int input = fgetc(datafile);
int pushed = 0;
while (input != EOF) {
__asm__(
"push %0"
: // No outputs
: "r" ((long long)input) // Input value. "push" requires an 8 byte value
:
);
++pushed;
// For the next loop iteration
input = fgetc(datafile);
}
* `input` at -24(%rbp)
* `pushed` at -20(%rbp)
* `datafile` at -16(%rbp)
```asm
call fgetc@PLT
movl %eax, -24(%rbp)
movl $0, -20(%rbp)
jmp .L5
.L6:
movl -24(%rbp), %eax
cltq
#APP
# 24 "blub.c" 1
push %rax
# 0 "" 2
#NO_APP
addl $1, -20(%rbp)
movq -16(%rbp), %rax
movq %rax, %rdi
call fgetc@PLT
movl %eax, -24(%rbp)
.L5:
cmpl $-1, -24(%rbp)
jne .L6
```
---
# Printing Loop
// Finished with the file
fclose(datafile);
// Reverse
long long out = 0;
while (pushed > 0) {
__asm__(
"pop %0"
: "=r" (out) // Output into the out variable.
:
:
);
putchar(out);
--pushed;
}
putchar('\n');
* `pushed` at -20(%rbp)
* `datafile` at -16(%rbp)
* `out` at -8(%rbp)
* The value of '\n' is 10
```asm
movq -16(%rbp), %rax
movq %rax, %rdi
call fclose@PLT
movq $0, -8(%rbp)
jmp .L7
.L8:
#APP
# 47 "blub.c" 1
pop %rax
# 0 "" 2
#NO_APP
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movl %eax, %edi
call putchar@PLT
subl $1, -20(%rbp)
.L7:
cmpl $0, -20(%rbp)
jg .L8
movl $10, %edi
call putchar@PLT
```
---
## The end!
* You made it!
* Confused? We'll spend more time on this topic
* I don't expect people to absorb this through a single presentation
* Read the book! Read online examples!
* Review the code! Use `gcc -S` to generate your own code
* You can compile assembly files with gcc, try it out!