-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy path1_io.asm
160 lines (144 loc) · 5.09 KB
/
1_io.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
; Copyright 2018-2019 Luana Carmo M de F Barbosa
;
; This file is licensed under the CC-BY-SA 2.0 license.
; See LICENSE for details.
;
; (1) io.asm: I/O, system calls
;
; This file shows how to read from stdin and write to stdout in assembly,
; with a simple program that writes a prompt, reads a string and writes it back.
;
global _start
section .data
; The directive 'db' means 'declare bytes', while the label 'str'
; stores the address of the beginning of that string.
; Note the string declared is not null terminated, so we need
; to keep track of its size, which we do next (with STRSIZE).
; Also note that 0xA is the ascii value for Line Feed (newline, "\n").
;
str: db "Type something and I'll repeat it! (max 64 bytes)", 0xA
; The lone '$' is the address the assembler is currently at,
; so by subtracting it from 'str' we get the number of bytes in
; the declared string (and that still works if we change the string (*),
; since it's not a hardcoded size.)
;
STRSIZE: equ $ - str
; File descriptors.
; A file descriptor is a number used in Unix to refer to an open file.
; Some special files are always open, and have fixed file descriptors:
; stdin: 0
; stdout: 1
; stderr: 2
;
STDIN: equ 0
STDOUT: equ 1
; We haven't used this section before: this is for unitialized space
; (meaning we can't tell its contents at first), which gets reserved
; when the program starts.
;
section .bss
; resb: reserve bytes. (The argument is the number of bytes.)
buf: resb 64
BUF_SIZE: equ 64 ; keep the same number as above
section .text
_start:
; If you want to do anything actually useful in assembly,
; you'll need the OS's blessing through a system call.
; That's also the case for I/O: in order to write something
; to stdout, we'll need to use the 'write' system call.
; You can see the full list of system calls with
; $ man 2 syscalls
; And you can see more info about the syscall <foo> with
; $ man 2 <foo>
; From write's manpage:
; ssize_t write(int fd, const void *buf, size_t count);
; [...]
; write() writes up to count bytes from the buffer starting
; at buf to the file referred to by the file descriptor fd.
;
; All these mov instructions place the system call arguments
; where they should be (more on this later).
;
mov rax, 1 ; __NR_write (more on this later)
mov rdi, STDOUT
mov rsi, str
mov rdx, STRSIZE
; In 64-bit mode, a system call is done with a dedicated 'syscall'
; instruction. In 32-bit, we'd need to use 'int 0x80'
; ('int' is the instruction for interrupt, 0x80 is the Linux
; kernel's interruption handler).
; The kernel knows which system call we want seeing the number
; in rax: it must be the number matching the system call.
; In 64 bits, those are defined in
; /usr/include/asm/unistd_64.h
; as macros __NR_<foo>, where <foo> is the system call.
; The arguments to the system call are also passed in registers:
; "The kernel interface uses %rdi, %rsi, %rdx, %r10, %r8
; and %r9." (ABI, appendix A, section A.2.1)
; Which explains the previous 'mov' instructions.
;
syscall
; output works through a system call, and so does input,
; through the read system call. From read's manpage:
; ssize_t read(int fd, void *buf, size_t count);
; [...]
; read() attempts to read up to count bytes from file
; descriptor fd into the buffer starting at buf.
;
; Also, if the input has more bytes than count, the extra bytes
; won't be read. (*)
;
; note: instead of
; mov rax, 0
; we use the equivalent
; xor rax, rax
; which makes an XOR of the two operands and stores the result
; in the first one, as is usual in the Intel syntax.
; XOR'ing a value with itself always gives zero, so those are in fact
; equivalent.
;
xor rax, rax ; __NR_read == 0
mov rdi, STDIN
mov rsi, buf
mov rdx, BUF_SIZE
syscall
; again from the read's manpage:
; On success, the number of bytes read is returned [...]
; It is not an error if this number is smaller than the
; number of bytes requested; this may happen for example
; because fewer bytes are actually available right now [...]
;
; We want to keep track of the return value so we can only
; write the number of bytes we've read.
; System calls always write their return value to rax.
; Since we need to write the number of the next system call
; to that register, we store the value in a different one.
;
mov rbx, rax
; now, write it back to stdout
mov rax, 1 ; __NR_write
mov rdi, STDOUT
mov rsi, buf
mov rdx, rbx ; get the size from where we stored it
syscall
; now we quit. Even that requires a system call: exit.
mov rax, 60
; The argument to exit is the status code: 0 indicates success,
; non-zero indicates failure.
;
xor rdi, rdi
syscall
; Exercises
;
; === St Thomas' Wisdom ===
; Verify all claims marked with (*).
;
; === Changing Stuff and Seeing What Happens ===
; - Scramble the mov instructions before the first SYSCALL and see
; if it still works.
;
; === Your Turn ===
; - Write a program that writes a prompt asking for the user's name,
; reads it, then prints back "Hello, " followed by the name that was read.
; Note: you can quickly debug system calls with the "strace" command.
; vim: set ft=nasm: