4

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!

Achmed
  • 313
  • 3
  • 11
  • _"it doesn't seem to like the register name of r7"_ What _exactly_ is the error message that you get? – Michael Aug 09 '21 at 12:01
  • @Michael `Error: undefined symbol r7 used as an immediate value` – Achmed Aug 09 '21 at 12:02
  • 2
    Shouldn't you be using the `w` registers if you want the lower 32 bits of a register? So that first line probably ought to be `mov w8, w7` (assuming that you want zero-extention). – Michael Aug 09 '21 at 12:07
  • @Michael thank you for that; it now successfully compiles, but it doesn't work as intended, I'm not too sure that I have the correct structure for my own interpretation of the function, do you think it is correct? Sorry about asking, I'm just new to Aarch64 assembly. – Achmed Aug 09 '21 at 12:20
  • 2
    Have a look at Rich Felker's beautifully written [musl libc](https://musl.libc.org/) library. Specifically, under: `arch/aarch64/` -> `syscall_arch.h` and `bits/syscall.h.in` – Brett Hale Aug 09 '21 at 14:54
  • You do know there's already a `syscall` function in the standard C library that does exactly this? Is there a particular reason you want to rewrite it instead of just using it? – Nate Eldredge Aug 09 '21 at 16:41
  • 1
    By the way, to avoid terminology confusion: what you describe here isn't the syscall *handler* - that would be the code in the kernel to which the `svc` instruction transfers control. I would say what you have here is code to *invoke* the system call. – Nate Eldredge Aug 09 '21 at 16:42
  • 1
    @NateEldredge: More specifically, I'd call this a "generic *wrapper* for `svc`". – Peter Cordes Aug 10 '21 at 01:54
  • @NateEldredge I am not using a standard C library for this, as this is for my own LibC; inefficient I know - but for what I am doing, requires my own implementation of LibC to be developed. – Achmed Aug 10 '21 at 22:42

1 Answers1

4

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.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • I'd just return the raw system call return value; whoever's calling this clunky inefficient function instead of using normal system call wrappers like `read(2)` or multi-arch inline-asm wrappers from https://github.com/linux-on-ibm-z/linux-syscall-support is presumably doing so for a reason, and can manually check for error with `retval >= (unsigned long)-MAX_ERRNO`. Along with manually decoding the non-error return values from `getpriority` for example. – Peter Cordes Aug 10 '21 at 01:59
  • @PeterCordes Couldn't the value be validated against `cmn x0, #4095` (4095 being the equivalent of MAX_ERRNO) to calculate the return value; in response to erroneous syscall responses. – Achmed Aug 10 '21 at 22:47
  • @Achmed: Yes, *in the caller*, to decode the in-band error return mechanism, I think that might be a valid implementation of `x > -4096`. Anything you do in the wrapper function runs into the problem Nate pointed out: with only one register return value, every possible bit-pattern is a possible return value with a different meaning, so any processing that maps multiple values to one loses information. – Peter Cordes Aug 10 '21 at 23:46