| //! 32-bit x86 Linux system calls. |
| //! |
| //! There are two forms; `indirect_*` which take a callee, which allow calling |
| //! through the vDSO when possible, and plain forms, which use the `int 0x80` |
| //! instruction. |
| //! |
| //! Most `rustix` syscalls use the vsyscall mechanism rather than going using |
| //! `int 0x80` sequences, as vsyscall is much faster. |
| //! |
| //! Syscalls made with `int 0x80` preserve the flags register, while syscalls |
| //! made using vsyscall do not. |
| |
| #![allow(dead_code)] |
| |
| use crate::backend::reg::{ |
| ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, R0, |
| }; |
| use crate::backend::vdso_wrappers::SyscallType; |
| use core::arch::asm; |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn indirect_syscall0( |
| callee: SyscallType, |
| nr: SyscallNumber<'_>, |
| ) -> RetReg<R0> { |
| let r0; |
| asm!( |
| "call {callee}", |
| callee = in(reg) callee, |
| inlateout("eax") nr.to_asm() => r0, |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn indirect_syscall1( |
| callee: SyscallType, |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| ) -> RetReg<R0> { |
| let r0; |
| asm!( |
| "call {callee}", |
| callee = in(reg) callee, |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn indirect_syscall1_noreturn( |
| callee: SyscallType, |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| ) -> ! { |
| asm!( |
| "call {callee}", |
| callee = in(reg) callee, |
| in("eax") nr.to_asm(), |
| in("ebx") a0.to_asm(), |
| options(noreturn) |
| ) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn indirect_syscall2( |
| callee: SyscallType, |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| ) -> RetReg<R0> { |
| let r0; |
| asm!( |
| "call {callee}", |
| callee = in(reg) callee, |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn indirect_syscall3( |
| callee: SyscallType, |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| a2: ArgReg<'_, A2>, |
| ) -> RetReg<R0> { |
| let r0; |
| asm!( |
| "call {callee}", |
| callee = in(reg) callee, |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| in("edx") a2.to_asm(), |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn indirect_syscall4( |
| callee: SyscallType, |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| a2: ArgReg<'_, A2>, |
| a3: ArgReg<'_, A3>, |
| ) -> RetReg<R0> { |
| let r0; |
| // a3 should go in esi, but `asm!` won't let us use it as an operand. |
| // Temporarily swap it into place, and then swap it back afterward. |
| // |
| // We hard-code the callee operand to use edi instead of `in(reg)` because |
| // even though we can't name esi as an operand, the compiler can use esi to |
| // satisfy `in(reg)`. |
| asm!( |
| "xchg esi, {a3}", |
| "call edi", |
| "xchg esi, {a3}", |
| a3 = in(reg) a3.to_asm(), |
| in("edi") callee, |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| in("edx") a2.to_asm(), |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn indirect_syscall5( |
| callee: SyscallType, |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| a2: ArgReg<'_, A2>, |
| a3: ArgReg<'_, A3>, |
| a4: ArgReg<'_, A4>, |
| ) -> RetReg<R0> { |
| let r0; |
| // Oof. a3 should go in esi, and `asm!` won't let us use that register as |
| // an operand. And we can't request stack slots. And there are no other |
| // registers free. Use eax as a temporary pointer to a slice, since it |
| // gets clobbered as the return value anyway. |
| asm!( |
| "push esi", |
| "push DWORD PTR [eax + 0]", |
| "mov esi, DWORD PTR [eax + 4]", |
| "mov eax, DWORD PTR [eax + 8]", |
| "call DWORD PTR [esp]", |
| "pop esi", |
| "pop esi", |
| inout("eax") &[callee as _, a3.to_asm(), nr.to_asm()] => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| in("edx") a2.to_asm(), |
| in("edi") a4.to_asm(), |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[allow(clippy::too_many_arguments)] |
| #[inline] |
| pub(in crate::backend) unsafe fn 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> { |
| let r0; |
| // Oof again. a3 should go in esi, and a5 should go in ebp, and `asm!` |
| // won't let us use either of those registers as operands. And we can't |
| // request stack slots. And there are no other registers free. Use eax as a |
| // temporary pointer to a slice, since it gets clobbered as the return |
| // value anyway. |
| // |
| // This is another reason that syscalls should be compiler intrinsics |
| // rather than inline asm. |
| asm!( |
| "push ebp", |
| "push esi", |
| "push DWORD PTR [eax + 0]", |
| "mov esi, DWORD PTR [eax + 4]", |
| "mov ebp, DWORD PTR [eax + 8]", |
| "mov eax, DWORD PTR [eax + 12]", |
| "call DWORD PTR [esp]", |
| "pop esi", |
| "pop esi", |
| "pop ebp", |
| inout("eax") &[callee as _, a3.to_asm(), a5.to_asm(), nr.to_asm()] => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| in("edx") a2.to_asm(), |
| in("edi") a4.to_asm(), |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber<'_>) -> RetReg<R0> { |
| let r0; |
| asm!( |
| "int $$0x80", |
| inlateout("eax") nr.to_asm() => r0, |
| options(nostack, preserves_flags, readonly) |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> { |
| let r0; |
| asm!( |
| "int $$0x80", |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| options(nostack, preserves_flags) |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall1_readonly( |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| ) -> RetReg<R0> { |
| let r0; |
| asm!( |
| "int $$0x80", |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| options(nostack, preserves_flags, readonly) |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! { |
| asm!( |
| "int $$0x80", |
| in("eax") nr.to_asm(), |
| in("ebx") a0.to_asm(), |
| options(noreturn) |
| ) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall2( |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| ) -> RetReg<R0> { |
| let r0; |
| asm!( |
| "int $$0x80", |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| options(nostack, preserves_flags) |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall2_readonly( |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| ) -> RetReg<R0> { |
| let r0; |
| asm!( |
| "int $$0x80", |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| options(nostack, preserves_flags, readonly) |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall3( |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| a2: ArgReg<'_, A2>, |
| ) -> RetReg<R0> { |
| let r0; |
| asm!( |
| "int $$0x80", |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| in("edx") a2.to_asm(), |
| options(nostack, preserves_flags) |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall3_readonly( |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| a2: ArgReg<'_, A2>, |
| ) -> RetReg<R0> { |
| let r0; |
| asm!( |
| "int $$0x80", |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| in("edx") a2.to_asm(), |
| options(nostack, preserves_flags, readonly) |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall4( |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| a2: ArgReg<'_, A2>, |
| a3: ArgReg<'_, A3>, |
| ) -> RetReg<R0> { |
| let r0; |
| // a3 should go in esi, but `asm!` won't let us use it as an operand. |
| // Temporarily swap it into place, and then swap it back afterward. |
| asm!( |
| "xchg esi, {a3}", |
| "int $$0x80", |
| "xchg esi, {a3}", |
| a3 = in(reg) a3.to_asm(), |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| in("edx") a2.to_asm(), |
| options(nostack, preserves_flags) |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall4_readonly( |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| a2: ArgReg<'_, A2>, |
| a3: ArgReg<'_, A3>, |
| ) -> RetReg<R0> { |
| let r0; |
| asm!( |
| "xchg esi, {a3}", |
| "int $$0x80", |
| "xchg esi, {a3}", |
| a3 = in(reg) a3.to_asm(), |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| in("edx") a2.to_asm(), |
| options(nostack, preserves_flags, readonly) |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall5( |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| a2: ArgReg<'_, A2>, |
| a3: ArgReg<'_, A3>, |
| a4: ArgReg<'_, A4>, |
| ) -> RetReg<R0> { |
| let r0; |
| // As in `syscall4`, use xchg to handle a3. a4 should go in edi, and we can |
| // use that register as an operand. Unlike in `indirect_syscall5`, we don't |
| // have a `callee` operand taking up a register, so we have enough |
| // registers and don't need to use a slice. |
| asm!( |
| "xchg esi, {a3}", |
| "int $$0x80", |
| "xchg esi, {a3}", |
| a3 = in(reg) a3.to_asm(), |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| in("edx") a2.to_asm(), |
| in("edi") a4.to_asm(), |
| options(nostack, preserves_flags) |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall5_readonly( |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| a2: ArgReg<'_, A2>, |
| a3: ArgReg<'_, A3>, |
| a4: ArgReg<'_, A4>, |
| ) -> RetReg<R0> { |
| let r0; |
| // See the comments in `syscall5`. |
| asm!( |
| "xchg esi, {a3}", |
| "int $$0x80", |
| "xchg esi, {a3}", |
| a3 = in(reg) a3.to_asm(), |
| inlateout("eax") nr.to_asm() => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| in("edx") a2.to_asm(), |
| in("edi") a4.to_asm(), |
| options(nostack, preserves_flags, readonly) |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall6( |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| a2: ArgReg<'_, A2>, |
| a3: ArgReg<'_, A3>, |
| a4: ArgReg<'_, A4>, |
| a5: ArgReg<'_, A5>, |
| ) -> RetReg<R0> { |
| let r0; |
| // See the comments in `indirect_syscall6`. |
| asm!( |
| "push ebp", |
| "push esi", |
| "mov esi, DWORD PTR [eax + 0]", |
| "mov ebp, DWORD PTR [eax + 4]", |
| "mov eax, DWORD PTR [eax + 8]", |
| "int $$0x80", |
| "pop esi", |
| "pop ebp", |
| inout("eax") &[a3.to_asm(), a5.to_asm(), nr.to_asm()] => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| in("edx") a2.to_asm(), |
| in("edi") a4.to_asm(), |
| options(preserves_flags) |
| ); |
| FromAsm::from_asm(r0) |
| } |
| |
| #[inline] |
| pub(in crate::backend) unsafe fn syscall6_readonly( |
| nr: SyscallNumber<'_>, |
| a0: ArgReg<'_, A0>, |
| a1: ArgReg<'_, A1>, |
| a2: ArgReg<'_, A2>, |
| a3: ArgReg<'_, A3>, |
| a4: ArgReg<'_, A4>, |
| a5: ArgReg<'_, A5>, |
| ) -> RetReg<R0> { |
| let r0; |
| // See the comments in `indirect_syscall6`. |
| asm!( |
| "push ebp", |
| "push esi", |
| "mov esi, DWORD PTR [eax + 0]", |
| "mov ebp, DWORD PTR [eax + 4]", |
| "mov eax, DWORD PTR [eax + 8]", |
| "int $$0x80", |
| "pop esi", |
| "pop ebp", |
| inout("eax") &[a3.to_asm(), a5.to_asm(), nr.to_asm()] => r0, |
| in("ebx") a0.to_asm(), |
| in("ecx") a1.to_asm(), |
| in("edx") a2.to_asm(), |
| in("edi") a4.to_asm(), |
| options(preserves_flags, readonly) |
| ); |
| FromAsm::from_asm(r0) |
| } |