Search code examples
androidassemblyandroid-ndksystem-callsmultiplatform

SYSCALL_INLINE in Android


I need to use syscall internally in Android NDK to prevent hooking of the wrapper functions. In Linux there are macros like SYSCALL_INLINE which allows using syscall without wrapper function. Thus the macro embeds the syscall assembly code into the project directly.

I could not find similar macro in Android NDK.

Maybe I can write my own functions like this one; https://git.busybox.net/uClibc/tree/libc/sysdeps/linux/arm/syscall.c

But I need to have arm, arm_64, x86 and x86_64 versions of the same function.

Can you help me? How can I find a solution?


Solution

  • Android's Linux kernel still uses the same system-call numbers and ABI as regular Linux, doesn't it? (So How to access the system call from user-space?) So you should be able to use the normal methods, with call numbers from <asm/unistd.h>.

    You could use the MUSL libc syscall inline functions in arch/x86_64/syscall_arch.h. It has different ones for each different number of args, instead of one big one.


    MUSL has versions of syscall_arch.h for ARM, AArch64, i386, and x86-64, as well as other architectures it supports. It's licensed under a permissive MIT license, so you can just copy those headers.

    For example, their ARM version defines __asm_syscall as a macro that (in non-Thumb mode) does asm volatile("svc 0" : "=r"(r0) : inputs); return r0;, and defines wrappers like:

    static inline long __syscall3(long n, long a, long b, long c)
    {
        register long r7 __ASM____R7__ = n;  // macro trickery for not clobbering r7 in thumb mode (where it may be the frame pointer)
        register long r0 __asm__("r0") = a;
        register long r1 __asm__("r1") = b;
        register long r2 __asm__("r2") = c;
        __asm_syscall(R7_OPERAND, "0"(r0), "r"(r1), "r"(r2));
        // includes "=r0"(r0) and return r0
     // FIXME: add a "memory" clobber because pointed-to memory can be an input or output
    }
    

    Unfortunately this is not safe: this doesn't tell the compiler that pointer operands are dereferenced, so it might treat stores into a buffer before write() as dead stores and optimize them away!

    This is trivial to fix: add a "memory" clobber. Or see How can I indicate that the memory *pointed* to by an inline ASM argument may be used? for ways to just tell the compiler about one operand, for a specific system call where you know which operands are pointers and whether they're inputs, outputs, or both.

    IDK if that was part of glibc's motivation for removing its similar syscall macros and only providing a non-inline syscall function. Or maybe they didn't want to encourage people to embed the system-call ABI into their program so it could in theory change to become more efficient in the future.

    You'd use it like

    #include <asm/unistd.h>   // for __NR_write
    #include <stdlib.h>       // for ssize_t
    #include "syscall_arch.h"
    
    // doesn't set errno or force all error returns to -1
    // return values from -1 to -4095 are errors, e.g. -EBADF or -EFAULT
    
    __attribte__((noinline))  // hack for inline asm unsafety
    ssize_t my_write(int fd, const void *buf, size_t count) {
        return __syscall3(__NR_write, fd, (long)buf, count);
    }
    

    I put this on the Godbolt compiler explorer with enough of ARM syscall_arch.h copied in to make this compile. Some of Godbolt's ARM gcc installs have missing <asm/unistd.h>, but gcc5.4 has a working one. The result in ARM mode is:

    my_write:
        str     r7, [sp, #-4]!
        mov     r7, #4
    @ system-calling convention mostly matches function-calling convention
    @ so args are in the right registers already
        svc 0
        ldr     r7, [sp], #4
        bx      lr
    

    And of course this function can inline into a caller so the save/restore of r7 happens once for the whole function.

    (edit): this would be unsafe if inlined into a caller where dead stores could optimize away. A better brute-force option would be a memory clobber on the inline asm statement, or more work would be to add a dummy memory operand for system calls that read or write user-space memory (see at&t asm inline c++ problem). Or for munmap to make sure no stores into the page(s) being freed sink past it and happen after the memory is unmapped.

    Even without inlining, it's inter-procedural optimization makes this not strictly safe, so __attribute__((noinline,noipa)), or just use a "memory" clobber in the asm statement!