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

时间:2018-06-18 19:29:51

标签: c++ gcc assembly c++17 inline-assembly

我使用explicit register variables使用没有machine-specific constraints的寄存器(例如x86_64上的r8,r9,r10)将参数传递给原始Linux系统调用,如建议的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);
}

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

ISO C++1z does not allow ‘register’ storage class specifier [-Wregister]

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

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

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

2 个答案:

答案 0 :(得分:4)

看来你发现了一个GCC错误:GNU register-asm局部变量在模板函数中不起作用。 (clang正确编译你的例子)。显然这已经是a known bug,感谢@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"
                ...

On Godbolt with gcc7.3 -O3

_start:
    movl    $60, %eax
    xorl    %edx, %edx
    syscall  #operands in %rax, %rax, %edx
    ret

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

_start:                                 # @_start
    movl    $60, %eax
    xorl    %edi, %edi
    syscall #operands in %rax, %rax, %edi
    retq

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

(主要是发布这个答案来讨论调试技术; Florian的答案已经涵盖了这个实际案例的细节。)

您可以使用MUSL现有的便携式标头代替模板:

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

但它应该照顾弗洛里安指出的大多数问题。它具有许可许可,因此您只需将其系统调用包装头复制到项目中即可。它们在没有与MUSL的其余部分链接的情况下工作,并且是真正的内联。

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

答案 1 :(得分:3)

这看起来像是GCC的错误。 C ++ 17警告是一个红色的鲱鱼。代码适用于我的优化(使用GCC 7编译时),但它在<?php function myFunc($param1) { echo $param1; } if(isset($_GET['action'])){ if(function_exists($_GET['action'])) { $_GET['action']($_GET['param']); } } 处中断。

根据documentation for local register variables,这不是预期的,所以这可能是一个GCC错误。根据{{​​3}},它甚至与优化无关,但最终是由模板的使用引起的。

我建议只重载最终系统调用包装器中系统调用参数的数量,并对所有参数和结果使用 tell application "Google Chrome" tell tab 3 of window 1 to set infoGrab to execute javascript "document.getElementsByClassName('field type-personId field-dsid')[0].innerHTML;" "document.getElementsByClassName('my Info')[0].innerHTML;" end tell 类型:

-O0

你必须为演员阵容使用更好的东西(可能是类型索引转换函数),当然你仍然需要以其他方式处理系统调用ABI方差(x32有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)); } ,POWER有两个返回寄存器而不是一个,等等,但这也是原始方法的问题。