├── 8_asm_c ├── README.md ├── Makefile ├── main.c └── funcs.asm ├── .gitignore ├── Makefile ├── att.md ├── 1_io.asm ├── 2_addr.asm ├── 6_libc.asm ├── 3_jump.asm ├── 0_basic.asm ├── 4_leaf.asm ├── 5_nonleaf.asm ├── LICENSE ├── README.md └── 7_float.asm /8_asm_c/README.md: -------------------------------------------------------------------------------- 1 | A small appendix to show how C code can call assembly code. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | todo 2 | *.o 3 | 0_basic 4 | 1_io 5 | 2_addr 6 | 3_jump 7 | 4_leaf 8 | 5_nonleaf 9 | 6_libc 10 | 7_float 11 | 8_asm_c/fact 12 | -------------------------------------------------------------------------------- /8_asm_c/Makefile: -------------------------------------------------------------------------------- 1 | NASM := nasm 2 | ASMFLAGS += -g -f elf64 3 | CC := gcc 4 | CCFLAGS := -g -no-pie 5 | LD := ld 6 | LDFLAGS := 7 | LDLIBS := 8 | RM := rm 9 | 10 | C_OBJ := main.o 11 | OBJ := funcs.o 12 | PROG := fact 13 | 14 | .PHONY: all clean 15 | all: $(PROG) 16 | 17 | $(PROG): $(OBJ) $(C_OBJ) 18 | $(CC) -o $@ $^ 19 | 20 | $(OBJ): $(OBJ:%.o=%.asm) 21 | $(NASM) -o $@ $(ASMFLAGS) $< 22 | 23 | $(C_OBJ): $(C_OBJ:%.o=%.c) 24 | $(CC) -c -o $@ $(CCFLAGS) $< 25 | 26 | clean: 27 | $(RM) -f $(PROG) $(OBJ) $(C_OBJ) 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NASM := nasm 2 | ASMFLAGS += -g -f elf64 3 | CC := gcc 4 | CCFLAGS := -g -no-pie 5 | LD := ld 6 | LDFLAGS := 7 | LDLIBS := 8 | RM := rm 9 | MAKE := make 10 | 11 | TARGETS := 0_basic 1_io 2_addr 3_jump 4_leaf 5_nonleaf 12 | CTARGETS := 6_libc 7_float 13 | OBJ := $(addsuffix .o, $(TARGETS) $(CTARGETS)) 14 | 15 | EXTRA_DIRS := 8_asm_c 16 | 17 | .PHONY: all clean 18 | 19 | all: $(TARGETS) $(CTARGETS) 20 | @for i in $(EXTRA_DIRS); do $(MAKE) -C $$i $@; done 21 | 22 | clean: 23 | $(RM) -f $(TARGETS) $(CTARGETS) $(OBJ) 24 | @for i in $(EXTRA_DIRS); do $(MAKE) -C $$i $@; done 25 | 26 | $(TARGETS): %: %.o 27 | $(LD) -o $@ $< $(LDFLAGS) $(LDLIBS) 28 | 29 | $(CTARGETS): %: %.o 30 | $(CC) -o $@ $< $(CCFLAGS) $(LDFLAGS) $(LDLIBS) 31 | 32 | $(OBJ): %.o: %.asm 33 | $(NASM) -o $@ $< $(ASMFLAGS) -------------------------------------------------------------------------------- /att.md: -------------------------------------------------------------------------------- 1 | AT&T syntax (used by default in GNU assembler) has a few differences from 2 | Intel syntax: 3 | 4 | - immediate values must be prefixed with a dollar sign `$` 5 | 6 | - registers must be prefixed with a percent sign `%` 7 | 8 | - for instructions that have a source and a destination operand, 9 | their order is reversed: `instr SOURCE, DEST` 10 | 11 | - the addressing mode is in the form `displacment(base,index,scale)`. 12 | Only the base is required. 13 | (The address is calculated as base + scale \* index + displacment) 14 | 15 | - whenever specifying the operand's size is required, this is done as 16 | a single-letter suffix in the instruction's name: `byte`, `word`, 17 | `dword` and `qword` become suffixes `b`, `w`, `l` (long) and `q` 18 | respectively. 19 | 20 | Those differences are summarized by the table below. 21 | 22 | | Intel syntax | AT&T syntax | 23 | |:------------------------------------:|:------------------------------------:| 24 | | mov rax, 1 | mov $1, %rax | 25 | | mov dword [rsp], eax | movl %eax, (%rsp) | 26 | | mov word [rbp - 8], dx | movw %dx, -8(%rbp) | 27 | | lea rcx, qword [rsp + 2 \* rax + 3] | leaq 3(%rsp,%rax,2), %rcx | 28 | -------------------------------------------------------------------------------- /8_asm_c/main.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Luana Carmo M de F Barbosa 2 | // 3 | // This file is licensed under the CC-BY-SA 2.0 license. 4 | // See LICENSE for details. 5 | // 6 | // main.c: C code calling assembly code. 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #define BUFFER_SIZE 1024 14 | 15 | // declare the functions that were defined in the other file. 16 | // We use integer types with explicit sizes, to be on the safe side. 17 | // 18 | extern uint64_t factorial(uint64_t n); 19 | extern uint64_t uint2str(uint64_t n, uint8_t *buf, uint64_t bufsize); 20 | 21 | int main(int argc, char **argv) 22 | { 23 | if (argc != 2) { 24 | fprintf(stderr, "usage: %s number\n", argv[0]); 25 | exit(EXIT_FAILURE); 26 | } 27 | int num = atoi(argv[1]); 28 | 29 | // the function call works normally. Only an extra cast is needed 30 | uint64_t fact = factorial((uint64_t) num); 31 | //printf("%d\n", fact); 32 | 33 | uint8_t buf[BUFFER_SIZE]; 34 | // note that buf is passed to uint2str as a pointer, which the assembly 35 | // code will access through the addressing mode. 36 | // 37 | uint64_t last_index = uint2str(fact, buf, BUFFER_SIZE); 38 | if (last_index >= BUFFER_SIZE) { 39 | fprintf(stderr, "result is too large to fit in buffer\n"); 40 | exit(EXIT_FAILURE); 41 | } 42 | // the resulting string is not null terminated: add it manually. 43 | buf[last_index] = '\0'; 44 | 45 | // no newline because buf already has one 46 | printf("%d! = %s", num, buf); 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /8_asm_c/funcs.asm: -------------------------------------------------------------------------------- 1 | ; Copyright 2019 Luana Carmo M de F Barbosa 2 | ; 3 | ; This file is licensed under the CC-BY-SA 2.0 license. 4 | ; See LICENSE for details. 5 | ; 6 | 7 | ; funcs.asm: implementation of functions required by main.c. 8 | ; These were borrowed from other files in here, but we couldn't use those files 9 | ; directly because most contain a _start definition, which conflicts 10 | ; with C library's _start. 11 | ; Also, we need to mark those functions as global. 12 | 13 | ;Arguments from C are passed in to these registers in order: 14 | ;Integrers/pointers: rdi, rsi, rdx, rcx, r8, r9 15 | ;Floats: xmm0, xmm1, xmm2, xmm3, ..., xmm7 16 | ;Additional pointers are pushed on the stack 17 | 18 | ;Return 19 | ;Ints/pointers rax or rdx:rax 20 | ;Floats xmm0 or xmm1:xmm0 21 | 22 | ;we must preseve rbp, rbx, r12, r13, r14, r15 so we push/pop 23 | 24 | global uint2str 25 | global factorial 26 | 27 | ;extern uint64_t uint2str(uint64_t n, uint8_t *buf, uint64_t bufsize) 28 | ;so n maps to rdi 29 | ; *buf maps to rsi 30 | ; bufsize maps to rdx 31 | ;we write the data under *buf only bufsize bytes 32 | ;we return to rax the number of bytes written 33 | ; this function doesnt add a NULL BYTE, C will need to check if there are 34 | ; any leftover room to add it before printing. Else there is possibly info leftover/failure 35 | uint2str: 36 | push rbp 37 | mov rbp, rsp 38 | 39 | push rbx 40 | push r12 41 | push r13 42 | push r14 43 | push r15 44 | 45 | mov r12, rdi ; n 46 | mov r13, rsi ; buf 47 | mov r14, rdx ; bufsize 48 | 49 | xor r15, r15 ; i = 0 (counter) 50 | mov rax, r12 ; rax = n 51 | 52 | .loop: 53 | ; iterate over the number mod 10 to get the digits in reverse order 54 | 55 | cmp rax, 0 56 | je .done ; if(n == 0) break; 57 | cmp r15, r14 58 | jge .done ; if(i >= bufsize) break; 59 | 60 | xor rdx, rdx 61 | mov rdi, 10 62 | div rdi 63 | ; now n%10 is in rdx and n/10 is in rax. 64 | add dl, '0' 65 | mov byte [r13 + r15], dl ; write that value to the string 66 | 67 | inc r15 ; i++ 68 | jmp .loop 69 | .done: 70 | ; append a newline to the string 71 | mov byte [r13 + r15], 0x0a 72 | inc r15 73 | 74 | ; now we've written the string to buf, but it's reversed 75 | ; (the last digit appears first), so we call revstr to fix it. 76 | ; 77 | mov rdi, r13 ; buf 78 | lea rsi, [r15 - 1] ; number of chars written -1 (don't include the newline) 79 | call revstr 80 | 81 | mov rax, r15 ; return value: the counter 82 | 83 | pop r15 84 | pop r14 85 | pop r13 86 | pop r12 87 | pop rbx 88 | 89 | mov rsp, rbp 90 | pop rbp 91 | ret 92 | 93 | ; extern uint64_t factorial(uint64_t n); 94 | ; so n maps to rdi 95 | ; and we return via rax 96 | factorial: 97 | push rbp 98 | mov rbp, rsp 99 | push rbx 100 | push r12 101 | push r13 102 | push r14 103 | push r15 104 | 105 | mov r12, rdi ; n 106 | 107 | ; recursion base: n == 0 108 | cmp r12, 0 109 | je .zero 110 | 111 | mov rbx, rdi 112 | dec rdi 113 | call factorial 114 | 115 | xor rdx, rdx 116 | mul rbx 117 | 118 | ; ideally, we should check if the multiplication above overflowed... 119 | 120 | jmp .done 121 | .zero: 122 | mov rax, 1 ; 0! = 1 123 | .done: 124 | pop r15 125 | pop r14 126 | pop r13 127 | pop r12 128 | pop rbx 129 | mov rsp, rbp 130 | pop rbp 131 | ret 132 | 133 | revstr: 134 | xor r8, r8 135 | lea r9, [rsi - 1] 136 | 137 | .loop: 138 | cmp r8, r9 139 | jge .done 140 | 141 | ; swap s[r8] and s[r9] 142 | mov cl, byte [rdi + r8] 143 | xchg cl, byte [rdi + r9] 144 | mov byte [rdi + r8], cl 145 | 146 | inc r8 147 | dec r9 148 | jmp .loop 149 | .done: 150 | ret 151 | 152 | ; vim: set ft=nasm: -------------------------------------------------------------------------------- /1_io.asm: -------------------------------------------------------------------------------- 1 | ; Copyright 2018-2019 Luana Carmo M de F Barbosa 2 | ; 3 | ; This file is licensed under the CC-BY-SA 2.0 license. 4 | ; See LICENSE for details. 5 | ; 6 | 7 | ; (1) io.asm: I/O, system calls 8 | ; 9 | ; This file shows how to read from stdin and write to stdout in assembly, 10 | ; with a simple program that writes a prompt, reads a string and writes it back. 11 | ; 12 | 13 | global _start 14 | 15 | section .data 16 | ; The directive 'db' means 'declare bytes', while the label 'str' 17 | ; stores the address of the beginning of that string. 18 | ; Note the string declared is not null terminated, so we need 19 | ; to keep track of its size, which we do next (with STRSIZE). 20 | ; Also note that 0xA is the ascii value for Line Feed (newline, "\n"). 21 | ; 22 | str: db "Type something and I'll repeat it! (max 64 bytes)", 0xA 23 | 24 | ; The lone '$' is the address the assembler is currently at, 25 | ; so by subtracting it from 'str' we get the number of bytes in 26 | ; the declared string (and that still works if we change the string (*), 27 | ; since it's not a hardcoded size.) 28 | ; 29 | STRSIZE: equ $ - str 30 | 31 | ; File descriptors. 32 | ; A file descriptor is a number used in Unix to refer to an open file. 33 | ; Some special files are always open, and have fixed file descriptors: 34 | ; stdin: 0 35 | ; stdout: 1 36 | ; stderr: 2 37 | ; 38 | STDIN: equ 0 39 | STDOUT: equ 1 40 | 41 | ; We haven't used this section before: this is for unitialized space 42 | ; (meaning we can't tell its contents at first), which gets reserved 43 | ; when the program starts. 44 | ; 45 | section .bss 46 | ; resb: reserve bytes. (The argument is the number of bytes.) 47 | buf: resb 64 48 | BUF_SIZE: equ 64 ; keep the same number as above 49 | 50 | 51 | section .text 52 | _start: 53 | ; If you want to do anything actually useful in assembly, 54 | ; you'll need the OS's blessing through a system call. 55 | ; That's also the case for I/O: in order to write something 56 | ; to stdout, we'll need to use the 'write' system call. 57 | ; You can see the full list of system calls with 58 | ; $ man 2 syscalls 59 | ; And you can see more info about the syscall with 60 | ; $ man 2 61 | ; From write's manpage: 62 | ; ssize_t write(int fd, const void *buf, size_t count); 63 | ; [...] 64 | ; write() writes up to count bytes from the buffer starting 65 | ; at buf to the file referred to by the file descriptor fd. 66 | ; 67 | ; All these mov instructions place the system call arguments 68 | ; where they should be (more on this later). 69 | ; 70 | mov rax, 1 ; __NR_write (more on this later) 71 | mov rdi, STDOUT 72 | mov rsi, str 73 | mov rdx, STRSIZE 74 | 75 | ; In 64-bit mode, a system call is done with a dedicated 'syscall' 76 | ; instruction. In 32-bit, we'd need to use 'int 0x80' 77 | ; ('int' is the instruction for interrupt, 0x80 is the Linux 78 | ; kernel's interruption handler). 79 | ; The kernel knows which system call we want seeing the number 80 | ; in rax: it must be the number matching the system call. 81 | ; In 64 bits, those are defined in 82 | ; /usr/include/asm/unistd_64.h 83 | ; as macros __NR_, where is the system call. 84 | ; The arguments to the system call are also passed in registers: 85 | ; "The kernel interface uses %rdi, %rsi, %rdx, %r10, %r8 86 | ; and %r9." (ABI, appendix A, section A.2.1) 87 | ; Which explains the previous 'mov' instructions. 88 | ; 89 | syscall 90 | 91 | ; output works through a system call, and so does input, 92 | ; through the read system call. From read's manpage: 93 | ; ssize_t read(int fd, void *buf, size_t count); 94 | ; [...] 95 | ; read() attempts to read up to count bytes from file 96 | ; descriptor fd into the buffer starting at buf. 97 | ; 98 | ; Also, if the input has more bytes than count, the extra bytes 99 | ; won't be read. (*) 100 | ; 101 | 102 | ; note: instead of 103 | ; mov rax, 0 104 | ; we use the equivalent 105 | ; xor rax, rax 106 | ; which makes an XOR of the two operands and stores the result 107 | ; in the first one, as is usual in the Intel syntax. 108 | ; XOR'ing a value with itself always gives zero, so those are in fact 109 | ; equivalent. 110 | ; 111 | xor rax, rax ; __NR_read == 0 112 | mov rdi, STDIN 113 | mov rsi, buf 114 | mov rdx, BUF_SIZE 115 | syscall 116 | 117 | ; again from the read's manpage: 118 | ; On success, the number of bytes read is returned [...] 119 | ; It is not an error if this number is smaller than the 120 | ; number of bytes requested; this may happen for example 121 | ; because fewer bytes are actually available right now [...] 122 | ; 123 | ; We want to keep track of the return value so we can only 124 | ; write the number of bytes we've read. 125 | ; System calls always write their return value to rax. 126 | ; Since we need to write the number of the next system call 127 | ; to that register, we store the value in a different one. 128 | ; 129 | mov rbx, rax 130 | 131 | ; now, write it back to stdout 132 | mov rax, 1 ; __NR_write 133 | mov rdi, STDOUT 134 | mov rsi, buf 135 | mov rdx, rbx ; get the size from where we stored it 136 | syscall 137 | 138 | ; now we quit. Even that requires a system call: exit. 139 | mov rax, 60 140 | ; The argument to exit is the status code: 0 indicates success, 141 | ; non-zero indicates failure. 142 | ; 143 | xor rdi, rdi 144 | syscall 145 | 146 | ; Exercises 147 | ; 148 | ; === St Thomas' Wisdom === 149 | ; Verify all claims marked with (*). 150 | ; 151 | ; === Changing Stuff and Seeing What Happens === 152 | ; - Scramble the mov instructions before the first SYSCALL and see 153 | ; if it still works. 154 | ; 155 | ; === Your Turn === 156 | ; - Write a program that writes a prompt asking for the user's name, 157 | ; reads it, then prints back "Hello, " followed by the name that was read. 158 | ; Note: you can quickly debug system calls with the "strace" command. 159 | 160 | ; vim: set ft=nasm: 161 | -------------------------------------------------------------------------------- /2_addr.asm: -------------------------------------------------------------------------------- 1 | ; Copyright 2018-2019 Luana Carmo M de F Barbosa 2 | ; 3 | ; This file is licensed under the CC-BY-SA 2.0 license. 4 | ; See LICENSE for details. 5 | ; 6 | 7 | ; (2) addr.asm: addressing mode, mov and lea, etc 8 | global _start 9 | 10 | section .data 11 | str: db '0123456789',0xA 12 | strsiz: equ $ - str 13 | STDOUT: equ 1 14 | 15 | section .text 16 | _start: 17 | ; we want to print str several times after making changes 18 | ; to some of its bytes, to make sure the code works. 19 | ; 20 | mov rdx, strsiz 21 | ; copy the address of str to rsi. mov "doesn't know" it's an address 22 | ; because the addressing mode wasn't used. 23 | ; 24 | mov rsi, str 25 | mov rdi, STDOUT 26 | mov rax, 1 ; __NR_write 27 | 28 | ; rdx, rsi, and rdi will not be changed through most of this code, 29 | ; because we'll make a lot of syscalls using these same arguments, 30 | ; so we might as well not touch these registers. 31 | ; (syscalls do not change the contents of general purpose registers, 32 | ; except for rcx, r11 and rax). 33 | ; 34 | syscall 35 | 36 | ; In Intel syntax, the addressing mode is denoted by 37 | ; square brackets []. For example, 38 | ; mov [rsi], 3 39 | ; This would be equivalent, in C, to 40 | ; *rsi = 3; 41 | ; (assuming rsi were a variable you could use in C). 42 | ; This means we write 3 to the address whose value is in rsi, 43 | ; as if we were dereferencing a pointer. Needless to say, 44 | ; if the address is not valid, you'll get a segmentation fault. 45 | ; 46 | ; There's a problem with this, though. How many bytes do we want 47 | ; to write? (Keep in mind that, though rsi is 8 bytes, that's the 48 | ; size of the address, not the operand.) 49 | ; In C, we never have to ask that question, because C has types, 50 | ; and each type has a size in bytes. For instance: 51 | ; int *x; 52 | ; x = /* some valid address */ 53 | ; *x = 3; 54 | ; Here the answer to our question is obvious: the assignment writes 55 | ; sizeof(int) bytes. However, there's no such thing as types 56 | ; in assembly, so we must write the operand's size explicitly, say 57 | ; mov byte [rsi], 3 58 | ; we could also use 'word' for 2 bytes and 'dword' (double word) 59 | ; for 4 bytes. 60 | ; (as a side note: intel syntax generally requires one to write 61 | ; mov byte ptr [rsi], 3 62 | ; the 'ptr' is to make it explicit it's an address, but that's 63 | ; redundant since we're using the addressing mode anyway! 64 | ; So nasm decided to remove the 'ptr' keyword altogether.) 65 | ; 66 | 67 | mov byte [rsi], 'a' 68 | ; syscalls use rax as return register, so we need to restore 69 | ; rax to __NR_write = 1 every time. 70 | mov rax, 1 71 | syscall 72 | 73 | mov word [rsi], 'bc' 74 | mov rax, 1 75 | syscall 76 | 77 | mov dword [rsi], 'defg' 78 | mov rax, 1 79 | syscall 80 | 81 | ; So far we've simply accessed the address at one register, 82 | ; but you can make arithmetic in the addressing mode; that's 83 | ; the main reason why this mode is useful. 84 | ; From the basic architecture manual: 85 | ; "In 64-bit mode, a memory operand can be referenced by 86 | ; a segment selector and an offset. [...] 87 | ; The offset part of a memory address in 64-bit mode can be 88 | ; specified directly as a static value or through an address 89 | ; computation made up of one or more of the following components: 90 | ; Displacement -- An 8-bit, 16-bit, or 32-bit value. 91 | ; Base -- The value in a 64-bit general-purpose register. 92 | ; Index -- The value in a 64-bit general-purpose register. 93 | ; Scale factor -- A value of 2, 4, or 8 that is multiplied 94 | ; by the index value." 95 | ; In intel syntax, this is written as: 96 | ; [base + scale * index + displacement] 97 | ; (most of these are optional; see below). 98 | ; 99 | mov byte [rsi], 'h' ; base only 100 | 101 | mov rax, 1 102 | syscall 103 | 104 | mov byte [str], 'i' ; displacement only (constant) 105 | ; displacement only 106 | ; (constant as well; will be computed when assembling) 107 | mov byte [str+1], 'j' 108 | mov rax, 1 109 | syscall 110 | 111 | mov rbx, 3 112 | mov byte [rsi+2], 'k' ; base and displacement 113 | mov byte [rsi+rbx], 'l' ; base and index 114 | mov byte [rsi+2*rbx], 'm' ; base, scale and index 115 | mov byte [rsi+2*rbx+2], 'n' ; base, scale, index and displacement 116 | 117 | mov rax, 1 118 | syscall 119 | 120 | ; We know that 121 | ; mov rax, [addr_expr] 122 | ; would be equivalent, in C, to 123 | ; rax = *(addr_expr); 124 | ; meaning it copies whatever is in the address addr to rax. 125 | ; But what if we needed to use the artihemtic provided by the 126 | ; addressing mode, but copy the address itself, instead of its 127 | ; contents? That's when we use the lea (load effective address): 128 | ; lea rax, [addr_expr] 129 | ; would be equivalent to 130 | ; rax = (addr_expr); 131 | ; 132 | mov rbx, 2 133 | lea rsi, [str+2*rbx] ; str+4 134 | 135 | ; because of the arithmetic possible in the addressing mode, 136 | ; lea is sometimes used with expressions 137 | ; which aren't addresses at all. For instance, we know that 138 | ; lea rcx, [rax + 2 * rbx + 8] 139 | ; will load rcx with rax + 2 * rbx + 8. Whether this is a 140 | ; valid address or not doesn't change anything: unlike mov, 141 | ; lea never tries to access that location. 142 | ; It wouldn't be possible to do such calculation in a single 143 | ; instruction without using lea. That's not its original purpose, 144 | ; but hey, it works! 145 | ; 146 | mov rbx, -2 147 | lea rdx, [strsiz+2*rbx] ; use strsiz-4 as size in the write syscall 148 | 149 | mov rax, 1 150 | syscall 151 | 152 | ; TODO segment registers 153 | 154 | xor rdi, rdi 155 | mov rax, 60 156 | syscall 157 | 158 | ; Exercises 159 | ; 160 | ; === Your Turn === 161 | ; - Write a program that reads a string from stdin, changes the character 162 | ; in the middle of that string to a newline, then prints it again. 163 | ; (The "middle" can be obtained halving the string's size, which is 164 | ; returned by the read sysem call.) 165 | ; 166 | ; - Use the addressing mode to calculate 3*n + 1, where n is the value of 167 | ; a register of your choice. 168 | ; 169 | 170 | ; vim: set ft=nasm: 171 | -------------------------------------------------------------------------------- /6_libc.asm: -------------------------------------------------------------------------------- 1 | ; Copyright 2018-2019 Luana Carmo M de F Barbosa 2 | ; 3 | ; This file is licensed under the CC-BY-SA 2.0 license. 4 | ; See LICENSE for details. 5 | ; 6 | 7 | ; (6) libc.asm: using C library functions 8 | ; 9 | ; Here we'll revisit our old friends main() and printf(). 10 | ; Since C files turn into assembly at some point, it makes sense to expect it 11 | ; to be possible to call C library functions in assembly. But to do so, 12 | ; we must link our object file against the C library, and a few things must 13 | ; change for us to do that. 14 | ; 15 | ; Note: you'll need to use the -no-pie option to link this file with gcc. 16 | ; 17 | 18 | ; again, the 'global' directive tells nasm to export a symbol to the linker. 19 | ; When we were using _start, this just saved us from a warning when linking, 20 | ; because the linker is generally smart enough to find where _start is. 21 | ; Now, however, if this directive isn't used, the linker will complain that 22 | ; the C library has an undefined reference to main, and will fail. (*) 23 | ; (See below for why we're using main instead of _start.) 24 | ; 25 | global main 26 | 27 | ; these are the symbols we'll be using from the standard library. 28 | ; The linker doesn't need this, but nasm does, so it won't complain about 29 | ; undefined symbols. (*) 30 | ; 31 | extern printf 32 | extern atoi 33 | 34 | section .data 35 | ; note how we add the null byte at the end: this string will be used by 36 | ; printf(), so we need to do that. 37 | ; 38 | fmt: db '%d + %d = %d',0xA,0x0 39 | ; also, because the string is null terminated, we don't need to 40 | ; keep track of its size. 41 | 42 | usageMsg: db 'usage: %s first_number second_number',0xA,0x0 43 | 44 | ; this simple program adds two numbers given as command-line arguments 45 | ; and prints the result. 46 | ; 47 | section .text 48 | ; 49 | ; Note how we use main() here instead of _start. 50 | ; The C language defines main() as the first function to be called, but other 51 | ; than that - and unlike _start - main() is a normal function, which needs to be 52 | ; called with arguments, and which returns. So, who calls main()? 53 | ; And where is _start now? 54 | ; The answer to both of those is: the C library. 55 | ; Even if a C program does not use anything from the C library, it still must be 56 | ; linked against it because that library is what provides a _start definition, 57 | ; which will make all necessary preparations and call main(). 58 | ; 59 | ; So if we were to use _start here, the linker could complain twice: first 60 | ; because it can't find the definition of the 'main' symbol that the C library 61 | ; references, second because there's two definitions of _start. (*) 62 | ; 63 | main: 64 | push rbp 65 | mov rbp, rsp 66 | push rbx 67 | push r12 68 | push r13 69 | push r14 70 | push r15 71 | 72 | ; main has the prototype 73 | ; int main(int argc, char **argv); 74 | ; Those arguments are where you would expect them: argc, being the first 75 | ; parameter, is in rdi, and argv, the second one, is in rsi. 76 | ; We store those in callee-saved registers, as usual. 77 | ; 78 | mov r12, rdi ; argc 79 | mov r13, rsi ; argv 80 | 81 | ; allocate space for two ints (4 bytes each) 82 | sub rsp, 8 83 | 84 | ; Since argv[0] is the program name, the numbers we want to add were 85 | ; passed as strings in argv[1] and argv[2]. 86 | ; 87 | ; make sure that argc == 3 88 | cmp r12, 3 89 | jne .fail 90 | 91 | ; convert argv[1] and argv[2] to integers 92 | mov rdi, qword [r13 + 8] ; argv[1] 93 | call atoi 94 | mov dword [rsp], eax 95 | 96 | mov rdi, qword [r13 + 16] ; argv[2] 97 | call atoi 98 | mov dword [rsp+4], eax 99 | 100 | ; add the numbers. 101 | ; ADD won't take two memory locations as arguments, so we need to load 102 | ; one of them into a register. 103 | mov ecx, dword [rsp] 104 | add ecx, dword [rsp+4] 105 | 106 | ; finally, print the value! 107 | ; we want to call printf as: 108 | ; printf(fmt, num1, num2, sum); 109 | ; (where num1 and num2 are the ints corresponding converted from argv) 110 | mov rdi, fmt 111 | mov esi, [rsp] ; the argument is 4 bytes, so we use esi instead of rsi 112 | mov edx, [rsp+4] 113 | ; the sum of the two numbers is already in ecx 114 | 115 | ; this is all it takes to call printf(). Calling a library function 116 | ; is no different than calling any other function: the linker figures 117 | ; everything out for you. 118 | ; 119 | call printf 120 | 121 | xor rax, rax ; exit status 122 | jmp .end 123 | .fail: 124 | mov rdi, usageMsg 125 | mov rsi, [r13] ; argv[0] 126 | call printf 127 | mov rax, 1 ; exit status 128 | .end: 129 | pop r15 130 | pop r14 131 | pop r13 132 | pop r12 133 | pop rbx 134 | mov rsp, rbp 135 | pop rbp 136 | ret 137 | 138 | ; Exercises 139 | ; 140 | ; === St Thomas' Wisdom === 141 | ; Verify all claims marked with (*). 142 | ; 143 | ; === Reverse engineering === 144 | ; Feed a C compiler the following code: 145 | ; 146 | ; struct pair { 147 | ; int a; 148 | ; int b; 149 | ; }; 150 | ; 151 | ; int main(int argc, char **argv) 152 | ; { 153 | ; struct pair p = {2, 5}; 154 | ; /* do something with p */ 155 | ; return 0; 156 | ; } 157 | ; 158 | ; And see the resulting program's instructions. Where is p stored? 159 | ; Now, change the main function to: 160 | ; 161 | ; int main(int argc, char **argv) 162 | ; { 163 | ; struct pair *p = malloc(sizeof(struct pair)); 164 | ; p->a = 2; 165 | ; p->b = 5; 166 | ; /* do something with p */ 167 | ; free(p); 168 | ; return 0; 169 | ; } 170 | ; 171 | ; Where is p stored now? 172 | ; 173 | ; === Your Turn === 174 | ; - Write a program that takes any number of command line arguments, 175 | ; and prints their characters interleaved: the first character of each 176 | ; argument must be printed in sequence, then the second, and so on. 177 | ; For simplicity's sake, your program can refuse arguments of different 178 | ; lengths. 179 | ; 180 | ; - Write a program that reads a sequence of integers from standard input 181 | ; and sorts it using the qsort() function. 182 | ; 183 | ; === Fly Higher === 184 | ; We've shown how to use the standard C library with assembly, but you can use 185 | ; any C library in a similar fashion (as long as you link it against the final 186 | ; executable.) Write a simple assembly program that uses some library you're 187 | ; familiar with. 188 | ; If you're out of ideas, here are a few, with the suggested libraries in 189 | ; parenthesis: 190 | ; - Create a blank GUI window, then destroy it after N seconds (SDL, XCB) 191 | ; - Play a WAV file, or a single note with fixed duration (SDL, openAL?) 192 | ; - Encrypt user input with AES, or compute its MD5 hash (openSSL) 193 | ; - Fetch an HTML file from an HTTP URL (libcurl) 194 | ; 195 | ; (Note: we haven't yet explained how to deal with floating point numbers. 196 | ; If you believe you'll need to use them, feel free to skip to the next file 197 | ; and come back later.) 198 | 199 | ; vim: set ft=nasm: 200 | -------------------------------------------------------------------------------- /3_jump.asm: -------------------------------------------------------------------------------- 1 | ; Copyright 2018-2019 Luana Carmo M de F Barbosa 2 | ; 3 | ; This file is licensed under the CC-BY-SA 2.0 license. 4 | ; See LICENSE for details. 5 | ; 6 | 7 | ; (3) jump.asm: conditional and unconditional jumps, eip and eflags. 8 | ; 9 | ; All code written so far had instructions that were executed sequentially. 10 | ; But almost any program needs loops (while, for) and branches (if, else, ...); 11 | ; under the hood, those are all jumps. 12 | ; 13 | global _start 14 | 15 | section .data 16 | prompt_str: db 'Write something! (max 32 bytes)',0xA 17 | PROMPT_STRSIZE: equ $ - prompt_str 18 | 19 | unused_str: db "This string won't be printed",0xA 20 | UNUSED_STRSIZE: equ $ - unused_str 21 | 22 | less_str: db 'The input string has less than 16 bytes',0xA 23 | LESS_STRSIZE: equ $ - less_str 24 | 25 | more_str: db 'The input string has 16 bytes or more',0xA 26 | MORE_STRSIZE: equ $ - more_str 27 | 28 | section .bss 29 | buf: resb 32 30 | BUF_SIZE: equ 32 ; keep the same as above 31 | 32 | section .text 33 | _start: 34 | ; write the prompt string 35 | mov rax, 1 ; __NR_write 36 | mov rdi, 1 ; stdout 37 | mov rsi, prompt_str 38 | mov rdx, PROMPT_STRSIZE 39 | syscall 40 | 41 | ; jmp: unconditional jump. This instruction makes execution deviate from 42 | ; its usual path, in such a way that the instruction after my_label 43 | ; will be executed next. (This is the same as 'goto' in C.) 44 | ; But how does that work? 45 | ; From the basic architecture manual, section 3.5: 46 | ; 47 | ; "The instruction pointer (EIP) register contains the offset in the 48 | ; current code segment for the next instruction to be executed. 49 | ; It is advanced from one instruction boundary to the next 50 | ; in straight-line code or it is moved ahead or backwards by a number 51 | ; of instructions [...]" 52 | ; 53 | ; So the address of the next instruction is stored in this special 54 | ; register, eip. The manual goes on: 55 | ; 56 | ; "The EIP register cannot be accessed directly by software; it is 57 | ; controlled implicitly by control-transfer instructions 58 | ; (such as JMP, Jcc, CALL, and RET), interrupts, and exceptions." 59 | ; 60 | ; This means we can't use eip with the mov instruction, i.e. 61 | ; 'mov eip,