RISC-V user-space function calls calling conventions are clear to me. It's also clear that a0-a5
are used to pass arguments to kernel and a7
to store system call number before an ecall
. What I can't find is some documentation about assumptions I can do after execution of ecall
instruction.
The following lines are part of the output of
objdump -d -M no-aliases libc.so.6
in the <__open64_nocancel>
(glibc 2.35 on RISC-V Ubuntu 22, but the same thing happens on 2.37 and 2.39)
...
ada12: 861a c.mv a2,t1
ada14: 00000073 ecall
ada18: 77fd c.lui a5,0xfffff
ada1a: 02a7e963 bltu a5,a0,ada4c <__open64_nocancel+0x7e>
ada1e: 2501 c.addiw a0,0
ada20: 6722 c.ldsp a4,8(sp)
ada22: 000e3783 ld a5,0(t3)
...
As you can see the last instruction uses t3
as base address to access memory without being reinitialized after the ecall
. My question is: what can I safely assume from this?
Can caller-saved registers from user-space calling conventions be safely used after ecall
without saving and restoring the value across ecall
? I know a0-a1
are used to store return values from the system call, but what about a2-a7
? May I assume they are clobbered?
I was expecting that all temporary registers would be considered clobbered, but the given example makes me wonder about argument registers. Maybe that's different since they serve a specific purpose to the kernel.
Registers other than the return-value are unmodified by system-calls on Linux, except for a few special cases on some ISAs (like x86-64 syscall
itself clobbers RCX and R11).
The kernel system-call entry points save all user-space registers so ptrace
(debugging) and other things that want a struct of the user-space regs can just work on a process that's blocked in a system call.
The syscall(2)
man page (https://man7.org/linux/man-pages/man2/syscall.2.html) has a table of register usage, with a note at the bottom:
Note that these tables don't cover the entire calling convention - some architectures may indiscriminately clobber other registers not listed here.
As far as I know, there aren't any extra clobbers for Linux's RISC-V system-call calling convention, just the return value in a0
(and a1
for 64-bit return values).