Implementation of Syscall() on Arm-Oabi. What Is "Svc #0X900071"

AOSP non-obvious syscall() implementation

This is how Linux's system-call ABI/API is designed, only a limited range of -errno values are allowed, to avoid using up half the range of possible return values for a small set of error codes. (Nobody will ever need more than 4095 errno codes?)

As @Timothy points out, mmap is a great example: it needs to return a pointer. mmap's return value is always page-aligned, so on systems with 4k pages or larger pages (i.e. almost(?) everything), -4095 is the highest magnitude negative number you can use without also being a valid page address.

-4095 = 2^32 - 4095 in a 32-bit 2's complement system. The highest page-start address is 2^32-4096. This is the obvious motivation for picking exactly this range as the ones that mean "error".

(But note that you still can't mmap the highest page of virtual address space, even for a 32-bit process under a 64-bit kernel, even though -4096UL would be a non-error return from mmap. The kernel internally uses the same in-band -errno code mechanism for pointers, not just for the system-call ABI, so it reserves the top page to prevent ever having a valid pointer that's also an error code. That also avoids the possibility of corner cases like a pointer to one-past-end of an array being 0 (NULL).)


The range of values which mean error is in theory platform-dependent and part of the syscall ABI, but AFAIK Linux uses [-4095..-1] on every platform it supports. The MUSL libc implementation hard-codes -4096UL in a C source file that's not x86 or ARM-specific: /src/internal/syscall_ret.c.

As @Tsyvarev points out, Linux defines MAX_ERRNO to 4095 in include/linux/err.h, not an arch-specific header. (A comment points out that it could be made arch-specific if it ever needs to be, and that it was specifically chosen because of how kernel pointers work.)

The x86-64 System V ABI also has an informative (not normative) section on Linux system calls, where this -4095 to -1 range is specified. (Search for -4095). (Other platforms using x86-64 System V can vary their syscall ABI, like FreeBSD or MacOS)


Fun fact: this means sys_getpriority can't return the nice level directly. Instead it returns 20-nice, which maps the -20..19 range of nice values to 40..1. The libc wrapper function has to decode the nice value after the generic retval > -4096UL check detects a non-error return value.


Fun fact #2: the syscall ABI on some Linux platforms (e.g. MIPS and PowerPC) returns -1 in the return value and the errno code in a separate register. Source: comments in this syscall inline-asm wrapper library. (Looks like a good library; avoids problems like missing "memory" clobbers that some syscall wrapper headers have.)


Related:

  • What are the return values of system calls in Assembly?
  • Linux System Calls, Error Numbers, and In-Band Signaling (using x86-64 examples, but the concept is of course identical). The key concept here is In-Band Signaling.
  • The Definitive Guide to Linux System Calls (on x86)

An issue with an ARM assembly function?

The problem is that you're not saving lr.

     bl _write
add sp, #64
bx lr

bl _write will overwrite lr which then points to add sp, #64, so your bx lr will just result in an endless loop on the last two instructions.

It should work if you modify your code like this:

__printTest:
push {lr}
sub sp, #64 /* yes, I know this is too much */
....
bl _write
add sp, #64
pop {pc}

As already stated in another answer, you should also use strb instead of str for byte-stores.



Related Topics



Leave a reply



Submit