iOS Arm64 Syscalls

iOS ARM64 Syscalls

The registers used for syscalls are completely arbitrary, and the resources you've picked are certainly wrong for XNU.

As far as I'm aware, the XNU syscall ABI for arm64 is entirely private and subject to change without notice so there's no published standard that it follows, but you can scrape together how it works by getting a copy of the XNU source (as tarballs, or viewing it online if you prefer that), grep for the handle_svc function, and just following the code.

I'm not gonna go into detail on where exactly you find which bits, but the end result is:

  • The immediate passed to svc is ignored, but the standard library uses svc 0x80.
  • x16 holds the syscall number
  • x0 through x8 hold up to 9 arguments*
  • There are no arguments on the stack
  • x0 and x1 hold up to 2 return values (e.g. in the case of fork)
  • The carry bit is used to report an error, in which case x0 holds the error code

* This is used only in the case of an indirect syscall (x16 = 0) with 8 arguments.

* Comments in the XNU source also mention x9, but it seems the engineer who wrote that should brush up on off-by-one errors.

And then it comes to the actual syscall numbers available:

  • The canonical source for "UNIX syscalls" is the file bsd/kern/syscalls.master in the XNU source tree. Those take syscall numbers from 0 up to about 540 in the latest iOS 13 beta.
  • The canonical source for "Mach syscalls" is the file osfmk/kern/syscall_sw.c in the XNU source tree. Those syscalls are invoked with negative numbers between -10 and -100 (e.g. -28 would be task_self_trap).
  • Unrelated to the last point, two syscalls mach_absolute_time and mach_continuous_time can be invoked with syscall numbers -3 and -4 respectively.
  • A few low-level operations are available through platform_syscall with the syscall number 0x80000000.

macOS 64-bit System Call Table

You need to add 0x2000000 to the call number using a syscalls.master file. I'm using the XNU bds/kern/syscalls.master file. Here's a function in the syscalls.master file that I'm going to call:

4   AUE_NULL    ALL { user_ssize_t write(int fd, user_addr_t cbuf, user_size_t nbyte); } 

In terms of which registers to pass arguments to, it's the same as 64-bit Linux. Arguments are passed through the rdi, rsi, rdx, r10, r8 and r9 registers, respectively. The write function takes three arguments, which are described in the following assembly:

mov rax, 0x2000004     ; sys_write call identifier
mov rdi, 1 ; STDOUT file descriptor
mov rsi, myMessage ; buffer to print
mov rdx, myMessageLen ; length of buffer
syscall ; make the system call

Error returns are different from Linux, though: on error, CF=1 and RAX=an errno code. (vs. Linux using rax=-4095..-1 as -errno in-band signalling.) See What is the relation between (carry flag) and syscall in assembly (x64 Intel syntax on Mac Os)?

RCX and R11 are overwritten by the syscall instruction itself, before any kernel code runs, so that part is necessarily the same as Linux.

why is the open() syscall supported on some Linux systems and not others?

The file you are looking at, arch/arm64/include/asm/unistd32.h, is the system call definitions for the arm32 compat mode.

The system call definitions for native aarch64 are from the generic system call table include/uapi/asm-generic/unistd.h, which you can see does not define __NR_open. The system call was not deleted - it never existed on aarch64.

The reason that __NR_open is not defined in the generic table is that the openat(2) system call was introduced later and is a strict superset of the _NR_open functionality, so there is no point in new architecture ports (created after openat(2) was introduced) supplying that system call - it is redundant. The POSIX open() function is supplied by the userspace C library, calling into the openat(2) system call.

Old architecture ports that existed prior to openat(2) - like x86, and arm32 - must continue to include open(2), because old programs/library binaries certainly exist for those architectures that call the open(2) system call. This concern does not apply to a new architecture port - the "breaking userspace" guarantee is that if it worked yesterday it will work today, but it does not say that if it worked on an existing architecture it can be recompiled and work on some new architecture.



Related Topics



Leave a reply



Submit