是否可以在GCC中使用C++ 17中的显式寄存器变量?

r3m*_*n0x 5 c++ assembly gcc inline-assembly c++17

我使用显式寄存器变量将参数传递给原始Linux系统调用,使用没有机器特定约束的寄存器(如x86_64上的r8,r9,r10),如此处所示.

#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);
}
Run Code Online (Sandbox Code Playgroud)

但是,此功能需要register使用在C++ 11中弃用并在C++ 17中删除的关键字.所以当我用GCC 7(-std=c++17 -nostdlib)编译这段代码时,它会给我一个警告:

ISO C++1z does not allow ‘register’ storage class specifier [-Wregister]
Run Code Online (Sandbox Code Playgroud)

它似乎忽略了寄存器分配和程序段错误,因为没有正确调用syscall.然而,这段代码在Clang 6中编译并正常工作.注意:我实际上有6个系统调用函数(最多6个参数),但为了最小的例子,这里只显示了1参数版本.

我意识到register关键字本身并没有真正有用,这就是它被删除的原因,但是这个特定的用例对我来说似乎是个例外,因此删除编译器支持似乎也是不合理的.

我也意识到这个用例是编译器特定的(即非标准的),所以我的问题是关于编译器支持而不是从标准中删除.

Pet*_*des 6

您似乎发现了 GCC 错误:GNU register-asm 局部变量在模板函数中不起作用。(clang 正确编译了您的示例)。显然这已经是一个已知的错误,感谢@Florian 找到它。

-Wregister触发只是第一个错误的症状:GNU register-asm 局部变量不会触发警告。但是在模板中,gcc 编译它们,因为它们register int foo = bar;没有asm声明的部分。所以 GCC认为你只是使用普通register变量,而不是 register-asm。

在常规函数中,即使使用-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"
                ...
Run Code Online (Sandbox Code Playgroud)

在带有 gcc7.3 -O3 的 Godbolt 上

_start:
    movl    $60, %eax
    xorl    %edx, %edx
    syscall  #operands in %rax, %rax, %edx
    ret
Run Code Online (Sandbox Code Playgroud)

但是clang6.0没有这个bug,我们得到:

_start:                                 # @_start
    movl    $60, %eax
    xorl    %edi, %edi
    syscall #operands in %rax, %rax, %edi
    retq
Run Code Online (Sandbox Code Playgroud)

请注意我附加到您的模板的 asm 注释(使用 C++ 字符串-文字连接)。我们可以让编译器告诉我们它认为它在做什么,而不必费解。

(主要发布此答案是为了讨论该调试技术;弗洛里安的答案已经涵盖了这个实际案例的细节。)


您可以使用 MUSL 现有的可移植标头代替模板:

它是一个 C 库,因此可能需要一些额外的转换才能让 C++ 编译器满意。或者避免在 ARM 头文件中使用临时表达式作为左值。

但它应该解决弗洛里安指出的大部分问题。它有一个宽松的许可证,所以你可以将它的系统调用包装头复制到你的项目中。它们在不与 MUSL 的其余部分链接的情况下工作,并且是真正的内联。

http://git.musl-libc.org/cgit/musl/tree/arch/x86_64/syscall_arch.h 是 x86-64 版本。


Flo*_*mer 4

对我来说这看起来像是一个 GCC 错误。C++17 警告是一个转移注意力的内容。该代码对我来说可以很好地进行优化(当使用 GCC 7 编译时),但它会在以下位置中断:-O0.

根据本地寄存器变量的文档,这不是预期的,因此这可能是一个 GCC 错误。根据这个错误报告,它甚至与优化无关,但最终是由模板的使用引起的。

我建议仅重载最终系统调用包装器中的系统调用参数的数量,并对long所有参数和结果使用类型:

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));
}
Run Code Online (Sandbox Code Playgroud)

您必须使用更好的方法进行强制转换(可能是类型索引转换函数),当然您仍然必须以其他方式处理系统调用 ABI 差异(x32 有 而long long不是long,而 POWER 有两个返回寄存器一个,等等),但这也是你原来的方法的问题。