// 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 nth fibonocci number that you want to see.");
return 0;
}
int n = atoi(argv[1]);
printf("The %i sequence number is %llu\n", n, sequence(n));
return 0;
}
```
---
## Assembly
```asm
.file "example25_b.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 nth fibonocci number that you want to see."
.align 8
.LC1:
.string "The %i sequence number 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
movl $0, %eax
call printf@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 stuff in there aren't instructions
---
## Tags
* If you use a GOTO statement in C you go to a tag
* So these exist in C as well
* Just add a `:` after a name
* They mark a part of code so that a location in the code can be referred to by name
* These mark the beginning of the **sequence** function:
```asm
sequence:
.LFB6:
```
---
## 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
---
## 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:
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
.cfi_def_cfa 7, 8
ret
.cfi_endproc
---
## 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`
---
## Register Addressing
* More common to work with register values
* `movq %rax, %rdi`
* Moves the value in rax to the value in rdi
---
## Direct and Indirect
* These are pointer equivalents
* Direct uses a hard-coded address
* `movq 0x40abcd, dst`
* Would move `memory[0x40abcd]` to destination
* `moveq (%rax), dst`
* Would move `memory[%rax]` to destination
* The first is `direct`, the second is `indirect`
---
## 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]`
---
## 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
---
## 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 `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]`
* Latest used to print
* 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 and 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
* 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
---
## Suffix meanings
* `b`: byte
* `w`: word (2 bytes)
* `l`: long/double word (4 bytes)
* `q`: quad word (8 bytes)
---
## 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
* Saves register `rbp` (64 bit) on the stack
* This stores the current state of register bp before we use it
* 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
* Ugh, what?
* Remember, our stack grows from the top
* So we subtract to add space
-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
---
## 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 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 has the recursive call's return value) and return
---
## So simple
* Questions?
---
## Practicality
* You aren't going to learn assembly in a few weeks
* Sorry
* But learn some of it
* To better understand C
* To better understand your hardware
---
## Simpler: ASM in C
```c
#include // For the errno macro (which is treated as an int with global scope)
#include // For the bool type
#include // For printf
#include
#include // For strtol, stroll, strold, etc
#include // For strlen
/*
* Return true if both numbers represented by the string fit into the long long
* type and were parsed correctly.
* Put the read values into the array pointed to by long_longs.
*/
bool readLongLongs(char* str_one, char* str_two, long long* long_longs) {
// Note: It is cleaner code to put the strings into an array loop over them.
// There are only two though, so duplicating the code is fine.
char* strings[2] = {str_one, str_two};
for (int i = 0; i < 2; ++i) {
// Zero the error macro so we can check for errors later
errno = 0;
char* endptr = strings[i];
long long value = strtoll(strings[i], &endptr, 16);
// Check for error. We don't care what it was, but this catches range errors.
if (0 != errno) {
return false;
}
// Failure if this didn't parse all of the characters.
if (*endptr != '\0') {
return false;
}
long_longs[i] = value;
}
// If we reached this point, both values must have worked.
return true;
}
/*
* Print a number bit by bit
* A pointer is obtained something like this: unsigned char* bytes = (char*)&number;
* The number of bytes is from sizeof(number).
*/
void printBytes(unsigned char* bytes, int num_bytes) {
for (int byte = num_bytes-1; byte >= 0; --byte) {
for (int bit = 7; bit >= 0; --bit) {
printf("%i", bytes[byte]>>bit & 0x1);
}
// Put spaces between each byte
if (byte > 0) {
printf(" ");
}
}
printf("\n");
}
int main(int argc, char** argv) {
if (argc < 3) {
printf("Please provide 2 numbers as inputs.\n");
return 1;
}
long long long_longs[2];
if (readLongLongs(argv[1], argv[2], long_longs)) {
long long sum = long_longs[0] + long_longs[1];
bool overflow = false;
// jo is jump if overflow bit is set
asm("jo OVERFLOW");
asm("jmp NOOVERFLOW");
asm("OVERFLOW:");
// We jump over this line if no overflow is detected.
overflow = true;
asm("NOOVERFLOW:");
if (overflow) {
printf(" ");
}
printBytes((unsigned char*)&long_longs[0], sizeof(long long));
if (overflow) {
printf(" ");
}
printBytes((unsigned char*)&long_longs[1], sizeof(long long));
if (overflow) {
// If two positive numbers overflowed, then the padded bit is 0
// If two negative numbers overflowed, then the padded bit is 1
// It is not necessary to check both numbers as the sum of a
// positive and a negative number cannot overflow.
if (0 < long_longs[0]) {
printf("0 ");
}
else {
printf("1 ");
}
}
printBytes((unsigned char*)&sum, sizeof(sum));
}
else {
printf("Could not read inputs.\n");
return 2;
}
return 0;
}
```
---
## Status Registers
---
## 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) {
asm("push %0" : : "r" ((long long)input) :);
// 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) : :);
putchar(out);
--pushed;
}
putchar('\n');
return 0;
}
```
---
## Assembly
```asm
.file "example41.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:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
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
# 23 "example41.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
# 41 "example41.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
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.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:
```
---
# Complicated! Ugly! Confusing!
* Yes!
* Focus on the goal
* Every assembly instruction is translated into a number
* That number is given to your hardware, making it do something
gcc example41.c -g -c -o example41.o
objdump -d -M intel -S example41.o
---
## Objdump
```
example41.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 :
#include
#include
int main(int argc, char** argv) {
0: f3 0f 1e fa endbr64
4: 55 push rbp
5: 48 89 e5 mov rbp,rsp
8: 48 83 ec 30 sub rsp,0x30
c: 89 7d dc mov DWORD PTR [rbp-0x24],edi
f: 48 89 75 d0 mov QWORD PTR [rbp-0x30],rsi
if (argc < 2) {
13: 83 7d dc 01 cmp DWORD PTR [rbp-0x24],0x1
17: 7f 19 jg 32
printf("This program requires a data file.\n");
19: 48 8d 05 00 00 00 00 lea rax,[rip+0x0] # 20
20: 48 89 c7 mov rdi,rax
23: e8 00 00 00 00 call 28
return 1;
28: b8 01 00 00 00 mov eax,0x1
2d: e9 b4 00 00 00 jmp e6
}
// Open datafile for reading.
FILE* datafile = fopen(argv[1], "r");
32: 48 8b 45 d0 mov rax,QWORD PTR [rbp-0x30]
36: 48 83 c0 08 add rax,0x8
3a: 48 8b 00 mov rax,QWORD PTR [rax]
3d: 48 8d 15 00 00 00 00 lea rdx,[rip+0x0] # 44
44: 48 89 d6 mov rsi,rdx
47: 48 89 c7 mov rdi,rax
4a: e8 00 00 00 00 call 4f
4f: 48 89 45 f0 mov QWORD PTR [rbp-0x10],rax
if (datafile == NULL) {
53: 48 83 7d f0 00 cmp QWORD PTR [rbp-0x10],0x0
58: 75 16 jne 70
printf("Error opening file.\n");
5a: 48 8d 05 00 00 00 00 lea rax,[rip+0x0] # 61
61: 48 89 c7 mov rdi,rax
64: e8 00 00 00 00 call 69
return 2;
69: b8 02 00 00 00 mov eax,0x2
6e: eb 76 jmp e6
}
// We don't need to get complicated with sscanf
// Just read in one character at a time
int input = fgetc(datafile);
70: 48 8b 45 f0 mov rax,QWORD PTR [rbp-0x10]
74: 48 89 c7 mov rdi,rax
77: e8 00 00 00 00 call 7c
7c: 89 45 e8 mov DWORD PTR [rbp-0x18],eax
int pushed = 0;
7f: c7 45 ec 00 00 00 00 mov DWORD PTR [rbp-0x14],0x0
// EOF is defined in stdio.h, means "End Of File"
while (input != EOF) {
86: eb 19 jmp a1
asm("push %0" : : "r" ((long long)input) :);
88: 8b 45 e8 mov eax,DWORD PTR [rbp-0x18]
8b: 48 98 cdqe
8d: 50 push rax
// 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;
8e: 83 45 ec 01 add DWORD PTR [rbp-0x14],0x1
// For the next loop iteration
input = fgetc(datafile);
92: 48 8b 45 f0 mov rax,QWORD PTR [rbp-0x10]
96: 48 89 c7 mov rdi,rax
99: e8 00 00 00 00 call 9e
9e: 89 45 e8 mov DWORD PTR [rbp-0x18],eax
while (input != EOF) {
a1: 83 7d e8 ff cmp DWORD PTR [rbp-0x18],0xffffffff
a5: 75 e1 jne 88
}
// Finished with the file
fclose(datafile);
a7: 48 8b 45 f0 mov rax,QWORD PTR [rbp-0x10]
ab: 48 89 c7 mov rdi,rax
ae: e8 00 00 00 00 call b3
// Reverse
long long out = 0;
b3: 48 c7 45 f8 00 00 00 mov QWORD PTR [rbp-0x8],0x0
ba: 00
while (pushed > 0) {
bb: eb 14 jmp d1
asm("pop %0" : "=r" (out) : :);
bd: 58 pop rax
be: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
putchar(out);
c2: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
c6: 89 c7 mov edi,eax
c8: e8 00 00 00 00 call cd
--pushed;
cd: 83 6d ec 01 sub DWORD PTR [rbp-0x14],0x1
while (pushed > 0) {
d1: 83 7d ec 00 cmp DWORD PTR [rbp-0x14],0x0
d5: 7f e6 jg bd
}
putchar('\n');
d7: bf 0a 00 00 00 mov edi,0xa
dc: e8 00 00 00 00 call e1
return 0;
e1: b8 00 00 00 00 mov eax,0x0
}
e6: c9 leave
e7: c3 ret
```
---
## What is ADD?
---
## What is ADD?
---
## What is ADD?
8e: 83 45 ec 01 add DWORD PTR [rbp-0x14],0x1
* 83 means ADD, with immediate addressing
* 45
* ec
* 01 is the operand
---
## What is PUSH?
---
## What is PUSH?
* `rax` is register 0
* So opcode 50 is PUSH into register 0
* Notice that the very common operation is a single byte
---
## Phew
* This is complicated stuff!
* Next time
* More examples
* Show how this relates to CPU architecture