Syscall invoke in Aarch64 assembly

Viewed 717

I am working on a syscall handler function for an Aarch64 arm bit cpu, and I was looking at how it is done in x86 assembly, but I am unable to figure out how it would be done in Aarch64 assembly.

I was looking at this example on github: https://github.com/rockytriton/LLD/blob/main/linux_os/part1/src/start.S which is written in x86 assembly.

.globl _syscall
_syscall:
    movq %rdi, %rax
    movq %rsi, %rdi
    movq %rdx, %rsi
    movq %rcx, %rdx
    movq %r8, %r10
    movq %r9, %r8
    movq 8(%rsp), %r9
    syscall
    ret

And some online equivalents as shown in this answer do not satisfy the same function call design. Which is written in Aarch64 assembly.

/* Generated by gensyscalls.py. Do not edit. */

#include <private/bionic_asm.h>

    .hidden __set_errno

ENTRY(write)
    mov     x8, __NR_write
    svc     #0

    cmn     x0, #(MAX_ERRNO + 1)
    cneg    x0, x0, hi
    b.hi    __set_errno

    ret
END(write)

So far I have this code (poorly ported from x86 to Aarch64):

.globl _syscall
_syscall:
    mov     x8, r7
    svc     #0
    cmn     x0, #(4095 + 1)
    cneg    x0, x0, hi
    ret

It does not work, but I have tried nonetheless, ironically when I assemble it, it doesn't seem to like the register name of r7, I don't exactly understand why, as that should be a parameter of the function call (see below).

I have a layout for the function in a header file for my C program as so: unsigned long _syscall(int num, void *a0, void *a1, void *a2, void *a3, void *a4, void *a5), would anyone have any ideas on how to recreate the same syscall handler functionality in Aarch64 assembly - my attempts in porting across have yielded unsuccessful. I'm rather lost on this, as assembly isn't my strong point - ironically this is the only bit of assembly I need in my project.

Many thanks!

1 Answers

As shown at the other answer, when you issue the system call with svc, you should have the system call number in x8, and the parameters in x0-x5.

However, according to the ARM ABI, arguments are passed to your C function in registers x0-x7, left to right. (The document uses r0-r7 confusingly; this means x0-x7 or w0-w7 according to whether they are 64 or 32 bit values. AArch64 has no registers named r7 or similar, though ARM32 did.)

So the system call number is in w0 when it should be in x8 (or equivalently w8, since it will always be a positive 32-bit number); the first argument a0 is in x1 when it needs to be in x0; and so on. So you need some code to shuffle them around.

    mov w8, w0
    mov x0, x1
    mov x1, x2
    mov x2, x3
    mov x3, x4
    mov x4, x5
    mov x5, x6
    svc #0

(You could save some trouble if you put the num argument at the end of the argument list instead of the beginning, perhaps with the help of a macro if you don't want to change existing code. Then you would only need mov w8, w6 ; svc #0.)

On the other end, svc #0 leaves a return value in x0. However, if the system call failed, then x0 doesn't contain -1 but rather the negative of the errno code that should be returned (between -1 and -4095 inclusive). This test is the same as checking whether the return value, as an unsigned value, is higher than -4096. And when this happens, normally you want to set errno to -x0 and then set x0 to -1 (so that -1 is returned from the syscall function).

It's not clear how you want your syscall function to behave in case of error. With your code as it stands, it will negate the negative error code, and then return it by leaving it in x0, which is probably not what you want. For instance, if you try to use your syscall to open a file, and it fails with ENOENT because the file doesn't exist, then x0 will contain -2 upon return from the system call. Your test will negate it and your syscall function will return 2, which is indistinguishable from the open having succeeded and opened the file on fd 2. You probably want to come up with some other mechanism, but I don't know if you want a global variable like errno or something else.

Related