I am using explicit register variables to pass parameters to a raw Linux syscall using registers that don't have machine-specific constraints (such as r8, r9, r10 on x86_64) as suggested here.
#include <asm/unistd.h>
#ifdef __i386__
#define _syscallOper "int $0x80"
#define _syscallNumReg "eax"
#define _syscallRetReg "eax"
#define _syscallReg1 "ebx"
#define _syscallReg2 "ecx"
#define _syscallReg3 "edx"
#define _syscallReg4 "esi"
#define _syscallReg5 "edi"
#define _syscallReg6 "ebp"
#define _syscallClob
#else
#define _syscallOper "syscall"
#define _syscallNumReg "rax"
#define _syscallRetReg "rax"
#define _syscallReg1 "rdi"
#define _syscallReg2 "rsi"
#define _syscallReg3 "rdx"
#define _syscallReg4 "r10"
#define _syscallReg5 "r8"
#define _syscallReg6 "r9"
#define _syscallClob "rcx", "r11"
#endif
template <typename Ret = long, typename T1>
Ret syscall(long num, T1 arg1)
{
register long _num __asm__(_syscallNumReg) = num;
register T1 _arg1 __asm__(_syscallReg1) = arg1;
register Ret _ret __asm__(_syscallRetReg);
__asm__ __volatile__(_syscallOper
: "=r"(_ret)
: "r"(_num), "r"(_arg1)
: _syscallClob);
return _ret;
}
extern "C" void _start()
{
syscall(__NR_exit, 0);
}
However this feature requires the use of register
keyword which was deprecated in C++11 and removed in C++17. So when I compile this code with GCC 7 (-std=c++17 -nostdlib
) it gives me a warning:
ISO C++1z does not allow ‘register’ storage class specifier [-Wregister]
and it seems to ignore the register allocation and the program segfaults because syscall wasn't called properly. This code however compiles and works fine in Clang 6. Note: I actually have 6 syscall functions (up to 6 arguments) but only 1-argument version is shown here for the sake of minimal example.
I realize that register
keyword by itself wasn't really useful that's why it was removed, but this specific use case seems like an exception to me so it seems unreasonable to remove compiler support for it as well.
I also realize that this use case is compiler-specific (i.e. non-standard), so my question is about compiler support rather then removal from the standard.
It appears you've found a GCC bug: GNU register-asm local variables don't work inside template functions. (clang compiles your example correctly). Apparently this was already a known bug, thanks for @Florian for finding it.
-Wregister
triggering is just a symptom of this first bug: GNU register-asm local variables don't trigger the warning. But in a template, gcc compiles them as they were plain register int foo = bar;
without the asm
part of the declaration. So GCC thought you were just using plain register
variables, not register-asm.
In a regular function, your code compiles fine with no warnings, even with -std=c++17
.
#define T1 unsigned long
#define Ret T1
// template <typename Ret = long, typename T1>
... your code unchanged ...
__asm__ __volatile__(_syscallOper " #operands in %0, %1, %2"
...
On Godbolt with gcc7.3 -O3:
_start:
movl $60, %eax
xorl %edx, %edx
syscall #operands in %rax, %rax, %edx
ret
But clang6.0 doesn't have this bug, and we get:
_start: # @_start
movl $60, %eax
xorl %edi, %edi
syscall #operands in %rax, %rax, %edi
retq
Notice the asm comment I appended to your template (with C++ string-literal concatenation). We can just get the compiler to tell us what it thinks it's doing, instead of having to puzzle things out.
(Mostly posting this answer to discuss that debugging technique; Florian's answer already covers the specifics of this actual case.)
It's a C library, so it might need a bit of extra casting to keep a C++ compiler happy. Or avoiding use of temporary expressions as lvalues, in the ARM headers.
But it should take care of most of the issues Florian pointed out. It has a permissive license, so you can just copy its syscall wrapper headers into your project. They work without linking against the rest of MUSL, and are truly inline.
http://git.musl-libc.org/cgit/musl/tree/arch/x86_64/syscall_arch.h is the x86-64 version.
This looks like a GCC bug to me. The C++17 warning is a red herring. The code works fine with optimization for me (when compiled with GCC 7), but it breaks at -O0
.
According to the documentation for local register variables, this is not expected, so this is likely a GCC bug. According to this bug report, it is not even related to optimization, but ultimately caused by the use of a template.
I suggest to overload only on the number of system call arguments in the ultimate system call wrapper, and use long
types for all arguments and the result:
inline long syscall_base(long num, long arg1)
{
register long _num __asm__(_syscallNumReg) = num;
register long _arg1 __asm__(_syscallReg1) = arg1;
register long _ret __asm__(_syscallRetReg);
__asm__ __volatile__(_syscallOper
: "=r"(_ret)
: "r"(_num), "r"(_arg1)
: _syscallClob);
return _ret;
}
template <typename Ret = long, typename T1>
Ret syscall(long num, T1 arg1)
{
return (Ret) (syscall_base(num, (long) arg1));
}
You'll have to use something nicer for the casts (probably type-indexed conversion functions), and of course you still have to deal with the syscall ABI variance in some other way (x32 has long long
instead of long
, and POWER has two return registers instead of one, etc.), but that's also a problem with your original approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With