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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ ~~ System calls for the Linux kernel ~~
~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~
~ The kernel preserves every register except rax, rcx, and r11. The system
~ call number goes in rax, as does the return value. Parameters go in rdi,
~ rsi, rdx, r10, r8, and r9, in that order. [SysV] A.2.1.
~
~ Notice that rsi is our control stack, so we have to save it (for
~ syscalls with at least two parameters). We can use the value stack to do
~ that, since rsp is preserved, or we can use one of the other registers. We
~ don't ourselves save other registers because our caller should do that, if
~ it cares.
~
~ In the kernel source, you may find the following files useful to
~ reference:
~
~ * arch/x86/entry/syscalls/syscall_64.tbl
~ * include/linux/syscalls.h
~ * include/linux/compat.h
~
~ Don't be confused by tools/scripts/syscall.tbl, it's not for x86.
~
~ This file loads early, before dynamic.e. So, although it superficially
~ appears to be able to do allocation and high-level flow control, those come
~ from the transforms, which means they're shallow implementations. The stuff
~ here can't be moved later, because input.e relies on it, and the dynamic
~ stuff relies on that. So, there's additional Linux functionality in another
~ file that loads later, linux-dynamic.e.
~ (call number -- return value)
: syscall-0
[ here @
:rax pop-reg64 ~ syscall number
syscall
:rax push-reg64 ~ return value
here ! ] ;asm
~ (first param, call number -- return value)
: syscall-1
[ here @
:rax pop-reg64 ~ syscall number
:rdi pop-reg64 ~ first param
syscall
:rax push-reg64 ~ return value
here ! ] ;asm
~ (first param, second param, call number -- return value)
: syscall-2
[ here @
:rsi :rbx mov-reg64-reg64 ~ save rsi
:rax pop-reg64 ~ syscall number
:rsi pop-reg64 ~ second param
:rdi pop-reg64 ~ first param
syscall
:rbx :rsi mov-reg64-reg64 ~ restore rsi
:rax push-reg64 ~ return value
here ! ] ;asm
~ (first param, second param, third param, call number -- return value)
: syscall-3
[ here @
:rsi :rbx mov-reg64-reg64 ~ save rsi
:rax pop-reg64 ~ syscall number
:rdx pop-reg64 ~ third param
:rsi pop-reg64 ~ second param
:rdi pop-reg64 ~ first param
syscall
:rbx :rsi mov-reg64-reg64 ~ restore rsi
:rax push-reg64 ~ return value
here ! ] ;asm
~ (first param, second param, third param, fourth param, call number
~ -- return value)
: syscall-4
[ here @
:rsi :rbx mov-reg64-reg64 ~ save rsi
:rax pop-reg64 ~ syscall number
:r10 pop-extrareg64 ~ fourth param
:rdx pop-reg64 ~ third param
:rsi pop-reg64 ~ second param
:rdi pop-reg64 ~ first param
syscall
:rbx :rsi mov-reg64-reg64 ~ restore rsi
:rax push-reg64 ~ return value
here ! ] ;asm
~ (first param, second param, third param, fourth param, fifth param,
~ call number -- return value)
: syscall-5
[ here @
:rsi :rbx mov-reg64-reg64 ~ save rsi
:rax pop-reg64 ~ syscall number
:r8 pop-extrareg64 ~ fifth param
:r10 pop-extrareg64 ~ fourth param
:rdx pop-reg64 ~ third param
:rsi pop-reg64 ~ second param
:rdi pop-reg64 ~ first param
syscall
:rbx :rsi mov-reg64-reg64 ~ restore rsi
:rax push-reg64 ~ return value
here ! ] ;asm
~ (first param, second param, third param, fourth param, fifth param,
~ sixth param, call number -- return value)
: syscall-6
[ here @
:rsi :rbx mov-reg64-reg64 ~ save rsi
:rax pop-reg64 ~ syscall number
:r9 pop-extrareg64 ~ sixth param
:r8 pop-extrareg64 ~ fifth param
:r10 pop-extrareg64 ~ fourth param
:rdx pop-reg64 ~ third param
:rsi pop-reg64 ~ second param
:rdi pop-reg64 ~ first param
syscall
:rbx :rsi mov-reg64-reg64 ~ restore rsi
:rax push-reg64 ~ return value
here ! ] ;asm
~ Raw system calls
~ ~~~~~~~~~~~~~~~~
~ This does the Linux exit() system call, passing it an exit code taken
~ from the stack. It does not return.
~
~ (exit code -- *)
: sys-exit
[ here @
60 :rax mov-reg64-imm64 ~ syscall number
:rdi pop-reg64 ~ exit code
syscall
~ In the event we're still here, let's minimize confusion.
hlt
~ This one, uniquely, doesn't need to be followed by the "next" macro. It
~ is, though, since ;asm does that anyway.
here ! ] ;asm
~ This does the Linux write() system call, passing it an address from the
~ top of the stack and a length from the second position on the stack. It
~ writes to file descriptor 1, which is stdout.
~
~ For our length parameter, we can pop directly from the stack into rdx,
~ which directly becomes the syscall parameter. For our address parameter,
~ the syscall wants it in rsi, which we also care about, so we have to do a
~ little juggling.
~
~ (length to write, base address -- *)
: sys-write
[ here @
:rcx pop-reg64 ~ address from stack
:rdx pop-reg64 ~ length from stack, passed directly
:rsi push-reg64 ~ save rsi
1 :rax mov-reg64-imm64 ~ syscall number
1 :rdi mov-reg64-imm64 ~ file descriptor
:rcx :rsi mov-reg64-reg64 ~ pass address
syscall
:rsi pop-reg64 ~ restore rsi
here ! ] ;asm
~ (length to read, base address -- result code)
: sys-read
[ here @
:rcx pop-reg64 ~ address from stack
:rdx pop-reg64 ~ length from stack, passed directly
:rsi push-reg64 ~ save rsi
0 :rax mov-reg64-imm64 ~ syscall number
0 :rdi mov-reg64-imm64 ~ file descriptor
:rcx :rsi mov-reg64-reg64 ~ pass address
syscall
:rsi pop-reg64 ~ restore rsi
:rax push-reg64 ~ return length
here ! ] ;asm
|