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