Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to use explicit register variables in GCC with C++17?

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.

like image 829
r3mus n0x Avatar asked Jun 18 '18 19:06

r3mus n0x


2 Answers

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.)


Instead of templates, you can use MUSL's existing portable headers:

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.

like image 84
Peter Cordes Avatar answered Oct 13 '22 11:10

Peter Cordes


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.

like image 33
Florian Weimer Avatar answered Oct 13 '22 10:10

Florian Weimer