1 //! 32-bit x86 Linux system calls.
2 //!
3 //! There are two forms; `indirect_*` which take a callee, which allow calling
4 //! through the vDSO when possible, and plain forms, which use the `int 0x80`
5 //! instruction.
6 //!
7 //! Most `rustix` syscalls use the vsyscall mechanism rather than going using
8 //! `int 0x80` sequences, as vsyscall is much faster.
9 //!
10 //! Syscalls made with `int 0x80` preserve the flags register, while syscalls
11 //! made using vsyscall do not.
12 
13 #![allow(dead_code)]
14 
15 use crate::backend::reg::{
16     ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, R0,
17 };
18 use crate::backend::vdso_wrappers::SyscallType;
19 use core::arch::asm;
20 
21 #[inline]
indirect_syscall0( callee: SyscallType, nr: SyscallNumber<'_>, ) -> RetReg<R0>22 pub(in crate::backend) unsafe fn indirect_syscall0(
23     callee: SyscallType,
24     nr: SyscallNumber<'_>,
25 ) -> RetReg<R0> {
26     let r0;
27     asm!(
28         "call {callee}",
29         callee = in(reg) callee,
30         inlateout("eax") nr.to_asm() => r0,
31     );
32     FromAsm::from_asm(r0)
33 }
34 
35 #[inline]
indirect_syscall1( callee: SyscallType, nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, ) -> RetReg<R0>36 pub(in crate::backend) unsafe fn indirect_syscall1(
37     callee: SyscallType,
38     nr: SyscallNumber<'_>,
39     a0: ArgReg<'_, A0>,
40 ) -> RetReg<R0> {
41     let r0;
42     asm!(
43         "call {callee}",
44         callee = in(reg) callee,
45         inlateout("eax") nr.to_asm() => r0,
46         in("ebx") a0.to_asm(),
47     );
48     FromAsm::from_asm(r0)
49 }
50 
51 #[inline]
indirect_syscall1_noreturn( callee: SyscallType, nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, ) -> !52 pub(in crate::backend) unsafe fn indirect_syscall1_noreturn(
53     callee: SyscallType,
54     nr: SyscallNumber<'_>,
55     a0: ArgReg<'_, A0>,
56 ) -> ! {
57     asm!(
58         "call {callee}",
59         callee = in(reg) callee,
60         in("eax") nr.to_asm(),
61         in("ebx") a0.to_asm(),
62         options(noreturn)
63     )
64 }
65 
66 #[inline]
indirect_syscall2( callee: SyscallType, nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, ) -> RetReg<R0>67 pub(in crate::backend) unsafe fn indirect_syscall2(
68     callee: SyscallType,
69     nr: SyscallNumber<'_>,
70     a0: ArgReg<'_, A0>,
71     a1: ArgReg<'_, A1>,
72 ) -> RetReg<R0> {
73     let r0;
74     asm!(
75         "call {callee}",
76         callee = in(reg) callee,
77         inlateout("eax") nr.to_asm() => r0,
78         in("ebx") a0.to_asm(),
79         in("ecx") a1.to_asm(),
80     );
81     FromAsm::from_asm(r0)
82 }
83 
84 #[inline]
indirect_syscall3( callee: SyscallType, nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, a2: ArgReg<'_, A2>, ) -> RetReg<R0>85 pub(in crate::backend) unsafe fn indirect_syscall3(
86     callee: SyscallType,
87     nr: SyscallNumber<'_>,
88     a0: ArgReg<'_, A0>,
89     a1: ArgReg<'_, A1>,
90     a2: ArgReg<'_, A2>,
91 ) -> RetReg<R0> {
92     let r0;
93     asm!(
94         "call {callee}",
95         callee = in(reg) callee,
96         inlateout("eax") nr.to_asm() => r0,
97         in("ebx") a0.to_asm(),
98         in("ecx") a1.to_asm(),
99         in("edx") a2.to_asm(),
100     );
101     FromAsm::from_asm(r0)
102 }
103 
104 #[inline]
indirect_syscall4( callee: SyscallType, nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, a2: ArgReg<'_, A2>, a3: ArgReg<'_, A3>, ) -> RetReg<R0>105 pub(in crate::backend) unsafe fn indirect_syscall4(
106     callee: SyscallType,
107     nr: SyscallNumber<'_>,
108     a0: ArgReg<'_, A0>,
109     a1: ArgReg<'_, A1>,
110     a2: ArgReg<'_, A2>,
111     a3: ArgReg<'_, A3>,
112 ) -> RetReg<R0> {
113     let r0;
114     // a3 should go in esi, but `asm!` won't let us use it as an operand.
115     // Temporarily swap it into place, and then swap it back afterward.
116     //
117     // We hard-code the callee operand to use edi instead of `in(reg)` because
118     // even though we can't name esi as an operand, the compiler can use esi to
119     // satisfy `in(reg)`.
120     asm!(
121         "xchg esi, {a3}",
122         "call edi",
123         "xchg esi, {a3}",
124         a3 = in(reg) a3.to_asm(),
125         in("edi") callee,
126         inlateout("eax") nr.to_asm() => r0,
127         in("ebx") a0.to_asm(),
128         in("ecx") a1.to_asm(),
129         in("edx") a2.to_asm(),
130     );
131     FromAsm::from_asm(r0)
132 }
133 
134 #[inline]
indirect_syscall5( callee: SyscallType, nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, a2: ArgReg<'_, A2>, a3: ArgReg<'_, A3>, a4: ArgReg<'_, A4>, ) -> RetReg<R0>135 pub(in crate::backend) unsafe fn indirect_syscall5(
136     callee: SyscallType,
137     nr: SyscallNumber<'_>,
138     a0: ArgReg<'_, A0>,
139     a1: ArgReg<'_, A1>,
140     a2: ArgReg<'_, A2>,
141     a3: ArgReg<'_, A3>,
142     a4: ArgReg<'_, A4>,
143 ) -> RetReg<R0> {
144     let r0;
145     // Oof. a3 should go in esi, and `asm!` won't let us use that register as
146     // an operand. And we can't request stack slots. And there are no other
147     // registers free. Use eax as a temporary pointer to a slice, since it gets
148     // clobbered as the return value anyway.
149     asm!(
150         "push esi",
151         "push [eax + 0]",
152         "mov esi, [eax + 4]",
153         "mov eax, [eax + 8]",
154         "call [esp]",
155         "pop esi",
156         "pop esi",
157         inout("eax") &[callee as _, a3.to_asm(), nr.to_asm()] => r0,
158         in("ebx") a0.to_asm(),
159         in("ecx") a1.to_asm(),
160         in("edx") a2.to_asm(),
161         in("edi") a4.to_asm(),
162     );
163     FromAsm::from_asm(r0)
164 }
165 
166 #[inline]
indirect_syscall6( callee: SyscallType, nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, a2: ArgReg<'_, A2>, a3: ArgReg<'_, A3>, a4: ArgReg<'_, A4>, a5: ArgReg<'_, A5>, ) -> RetReg<R0>167 pub(in crate::backend) unsafe fn indirect_syscall6(
168     callee: SyscallType,
169     nr: SyscallNumber<'_>,
170     a0: ArgReg<'_, A0>,
171     a1: ArgReg<'_, A1>,
172     a2: ArgReg<'_, A2>,
173     a3: ArgReg<'_, A3>,
174     a4: ArgReg<'_, A4>,
175     a5: ArgReg<'_, A5>,
176 ) -> RetReg<R0> {
177     let r0;
178     // Oof again. a3 should go in esi, and a5 should go in ebp, and `asm!`
179     // won't let us use either of those registers as operands. And we can't
180     // request stack slots. And there are no other registers free. Use eax as a
181     // temporary pointer to a slice, since it gets clobbered as the return
182     // value anyway.
183     //
184     // This is another reason that syscalls should be compiler intrinsics
185     // rather than inline asm.
186     asm!(
187         "push ebp",
188         "push esi",
189         "push [eax + 0]",
190         "mov esi, [eax + 4]",
191         "mov ebp, [eax + 8]",
192         "mov eax, [eax + 12]",
193         "call [esp]",
194         "pop esi",
195         "pop esi",
196         "pop ebp",
197         inout("eax") &[callee as _, a3.to_asm(), a5.to_asm(), nr.to_asm()] => r0,
198         in("ebx") a0.to_asm(),
199         in("ecx") a1.to_asm(),
200         in("edx") a2.to_asm(),
201         in("edi") a4.to_asm(),
202     );
203     FromAsm::from_asm(r0)
204 }
205 
206 #[inline]
syscall0_readonly(nr: SyscallNumber<'_>) -> RetReg<R0>207 pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber<'_>) -> RetReg<R0> {
208     let r0;
209     asm!(
210         "int $$0x80",
211         inlateout("eax") nr.to_asm() => r0,
212         options(nostack, preserves_flags, readonly)
213     );
214     FromAsm::from_asm(r0)
215 }
216 
217 #[inline]
syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0>218 pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
219     let r0;
220     asm!(
221         "int $$0x80",
222         inlateout("eax") nr.to_asm() => r0,
223         in("ebx") a0.to_asm(),
224         options(nostack, preserves_flags)
225     );
226     FromAsm::from_asm(r0)
227 }
228 
229 #[inline]
syscall1_readonly( nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, ) -> RetReg<R0>230 pub(in crate::backend) unsafe fn syscall1_readonly(
231     nr: SyscallNumber<'_>,
232     a0: ArgReg<'_, A0>,
233 ) -> RetReg<R0> {
234     let r0;
235     asm!(
236         "int $$0x80",
237         inlateout("eax") nr.to_asm() => r0,
238         in("ebx") a0.to_asm(),
239         options(nostack, preserves_flags, readonly)
240     );
241     FromAsm::from_asm(r0)
242 }
243 
244 #[inline]
syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> !245 pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
246     asm!(
247         "int $$0x80",
248         in("eax") nr.to_asm(),
249         in("ebx") a0.to_asm(),
250         options(nostack, noreturn)
251     )
252 }
253 
254 #[inline]
syscall2( nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, ) -> RetReg<R0>255 pub(in crate::backend) unsafe fn syscall2(
256     nr: SyscallNumber<'_>,
257     a0: ArgReg<'_, A0>,
258     a1: ArgReg<'_, A1>,
259 ) -> RetReg<R0> {
260     let r0;
261     asm!(
262         "int $$0x80",
263         inlateout("eax") nr.to_asm() => r0,
264         in("ebx") a0.to_asm(),
265         in("ecx") a1.to_asm(),
266         options(nostack, preserves_flags)
267     );
268     FromAsm::from_asm(r0)
269 }
270 
271 #[inline]
syscall2_readonly( nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, ) -> RetReg<R0>272 pub(in crate::backend) unsafe fn syscall2_readonly(
273     nr: SyscallNumber<'_>,
274     a0: ArgReg<'_, A0>,
275     a1: ArgReg<'_, A1>,
276 ) -> RetReg<R0> {
277     let r0;
278     asm!(
279         "int $$0x80",
280         inlateout("eax") nr.to_asm() => r0,
281         in("ebx") a0.to_asm(),
282         in("ecx") a1.to_asm(),
283         options(nostack, preserves_flags, readonly)
284     );
285     FromAsm::from_asm(r0)
286 }
287 
288 #[inline]
syscall3( nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, a2: ArgReg<'_, A2>, ) -> RetReg<R0>289 pub(in crate::backend) unsafe fn syscall3(
290     nr: SyscallNumber<'_>,
291     a0: ArgReg<'_, A0>,
292     a1: ArgReg<'_, A1>,
293     a2: ArgReg<'_, A2>,
294 ) -> RetReg<R0> {
295     let r0;
296     asm!(
297         "int $$0x80",
298         inlateout("eax") nr.to_asm() => r0,
299         in("ebx") a0.to_asm(),
300         in("ecx") a1.to_asm(),
301         in("edx") a2.to_asm(),
302         options(nostack, preserves_flags)
303     );
304     FromAsm::from_asm(r0)
305 }
306 
307 #[inline]
syscall3_readonly( nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, a2: ArgReg<'_, A2>, ) -> RetReg<R0>308 pub(in crate::backend) unsafe fn syscall3_readonly(
309     nr: SyscallNumber<'_>,
310     a0: ArgReg<'_, A0>,
311     a1: ArgReg<'_, A1>,
312     a2: ArgReg<'_, A2>,
313 ) -> RetReg<R0> {
314     let r0;
315     asm!(
316         "int $$0x80",
317         inlateout("eax") nr.to_asm() => r0,
318         in("ebx") a0.to_asm(),
319         in("ecx") a1.to_asm(),
320         in("edx") a2.to_asm(),
321         options(nostack, preserves_flags, readonly)
322     );
323     FromAsm::from_asm(r0)
324 }
325 
326 #[inline]
syscall4( nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, a2: ArgReg<'_, A2>, a3: ArgReg<'_, A3>, ) -> RetReg<R0>327 pub(in crate::backend) unsafe fn syscall4(
328     nr: SyscallNumber<'_>,
329     a0: ArgReg<'_, A0>,
330     a1: ArgReg<'_, A1>,
331     a2: ArgReg<'_, A2>,
332     a3: ArgReg<'_, A3>,
333 ) -> RetReg<R0> {
334     let r0;
335     // a3 should go in esi, but `asm!` won't let us use it as an operand.
336     // Temporarily swap it into place, and then swap it back afterward.
337     asm!(
338         "xchg esi, {a3}",
339         "int $$0x80",
340         "xchg esi, {a3}",
341         a3 = in(reg) a3.to_asm(),
342         inlateout("eax") nr.to_asm() => r0,
343         in("ebx") a0.to_asm(),
344         in("ecx") a1.to_asm(),
345         in("edx") a2.to_asm(),
346         options(nostack, preserves_flags)
347     );
348     FromAsm::from_asm(r0)
349 }
350 
351 #[inline]
syscall4_readonly( nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, a2: ArgReg<'_, A2>, a3: ArgReg<'_, A3>, ) -> RetReg<R0>352 pub(in crate::backend) unsafe fn syscall4_readonly(
353     nr: SyscallNumber<'_>,
354     a0: ArgReg<'_, A0>,
355     a1: ArgReg<'_, A1>,
356     a2: ArgReg<'_, A2>,
357     a3: ArgReg<'_, A3>,
358 ) -> RetReg<R0> {
359     let r0;
360     asm!(
361         "xchg esi, {a3}",
362         "int $$0x80",
363         "xchg esi, {a3}",
364         a3 = in(reg) a3.to_asm(),
365         inlateout("eax") nr.to_asm() => r0,
366         in("ebx") a0.to_asm(),
367         in("ecx") a1.to_asm(),
368         in("edx") a2.to_asm(),
369         options(nostack, preserves_flags, readonly)
370     );
371     FromAsm::from_asm(r0)
372 }
373 
374 #[inline]
syscall5( nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, a2: ArgReg<'_, A2>, a3: ArgReg<'_, A3>, a4: ArgReg<'_, A4>, ) -> RetReg<R0>375 pub(in crate::backend) unsafe fn syscall5(
376     nr: SyscallNumber<'_>,
377     a0: ArgReg<'_, A0>,
378     a1: ArgReg<'_, A1>,
379     a2: ArgReg<'_, A2>,
380     a3: ArgReg<'_, A3>,
381     a4: ArgReg<'_, A4>,
382 ) -> RetReg<R0> {
383     let r0;
384     // As in `syscall4`, use xchg to handle a3. a4 should go in edi, and we can
385     // use that register as an operand. Unlike in `indirect_syscall5`, we don't
386     // have a `callee` operand taking up a register, so we have enough
387     // registers and don't need to use a slice.
388     asm!(
389         "xchg esi, {a3}",
390         "int $$0x80",
391         "xchg esi, {a3}",
392         a3 = in(reg) a3.to_asm(),
393         inlateout("eax") nr.to_asm() => r0,
394         in("ebx") a0.to_asm(),
395         in("ecx") a1.to_asm(),
396         in("edx") a2.to_asm(),
397         in("edi") a4.to_asm(),
398         options(nostack, preserves_flags)
399     );
400     FromAsm::from_asm(r0)
401 }
402 
403 #[inline]
syscall5_readonly( nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, a2: ArgReg<'_, A2>, a3: ArgReg<'_, A3>, a4: ArgReg<'_, A4>, ) -> RetReg<R0>404 pub(in crate::backend) unsafe fn syscall5_readonly(
405     nr: SyscallNumber<'_>,
406     a0: ArgReg<'_, A0>,
407     a1: ArgReg<'_, A1>,
408     a2: ArgReg<'_, A2>,
409     a3: ArgReg<'_, A3>,
410     a4: ArgReg<'_, A4>,
411 ) -> RetReg<R0> {
412     let r0;
413     // See the comments in `syscall5`.
414     asm!(
415         "xchg esi, {a3}",
416         "int $$0x80",
417         "xchg esi, {a3}",
418         a3 = in(reg) a3.to_asm(),
419         inlateout("eax") nr.to_asm() => r0,
420         in("ebx") a0.to_asm(),
421         in("ecx") a1.to_asm(),
422         in("edx") a2.to_asm(),
423         in("edi") a4.to_asm(),
424         options(nostack, preserves_flags, readonly)
425     );
426     FromAsm::from_asm(r0)
427 }
428 
429 #[inline]
syscall6( nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, a2: ArgReg<'_, A2>, a3: ArgReg<'_, A3>, a4: ArgReg<'_, A4>, a5: ArgReg<'_, A5>, ) -> RetReg<R0>430 pub(in crate::backend) unsafe fn syscall6(
431     nr: SyscallNumber<'_>,
432     a0: ArgReg<'_, A0>,
433     a1: ArgReg<'_, A1>,
434     a2: ArgReg<'_, A2>,
435     a3: ArgReg<'_, A3>,
436     a4: ArgReg<'_, A4>,
437     a5: ArgReg<'_, A5>,
438 ) -> RetReg<R0> {
439     let r0;
440     // See the comments in `indirect_syscall6`.
441     asm!(
442         "push ebp",
443         "push esi",
444         "mov esi, [eax + 0]",
445         "mov ebp, [eax + 4]",
446         "mov eax, [eax + 8]",
447         "int $$0x80",
448         "pop esi",
449         "pop ebp",
450         inout("eax") &[a3.to_asm(), a5.to_asm(), nr.to_asm()] => r0,
451         in("ebx") a0.to_asm(),
452         in("ecx") a1.to_asm(),
453         in("edx") a2.to_asm(),
454         in("edi") a4.to_asm(),
455         options(preserves_flags)
456     );
457     FromAsm::from_asm(r0)
458 }
459 
460 #[inline]
syscall6_readonly( nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>, a1: ArgReg<'_, A1>, a2: ArgReg<'_, A2>, a3: ArgReg<'_, A3>, a4: ArgReg<'_, A4>, a5: ArgReg<'_, A5>, ) -> RetReg<R0>461 pub(in crate::backend) unsafe fn syscall6_readonly(
462     nr: SyscallNumber<'_>,
463     a0: ArgReg<'_, A0>,
464     a1: ArgReg<'_, A1>,
465     a2: ArgReg<'_, A2>,
466     a3: ArgReg<'_, A3>,
467     a4: ArgReg<'_, A4>,
468     a5: ArgReg<'_, A5>,
469 ) -> RetReg<R0> {
470     let r0;
471     // See the comments in `indirect_syscall6`.
472     asm!(
473         "push ebp",
474         "push esi",
475         "mov esi, [eax + 0]",
476         "mov ebp, [eax + 4]",
477         "mov eax, [eax + 8]",
478         "int $$0x80",
479         "pop esi",
480         "pop ebp",
481         inout("eax") &[a3.to_asm(), a5.to_asm(), nr.to_asm()] => r0,
482         in("ebx") a0.to_asm(),
483         in("ecx") a1.to_asm(),
484         in("edx") a2.to_asm(),
485         in("edi") a4.to_asm(),
486         options(preserves_flags, readonly)
487     );
488     FromAsm::from_asm(r0)
489 }
490