代码之家  ›  专栏  ›  技术社区  ›  r3mus n0x

在gcc中用c++17可以使用显式寄存器变量吗?

  •  5
  • r3mus n0x  · 技术社区  · 6 年前

    我正在使用 explicit register variables 使用未注册的寄存器将参数传递给原始Linux系统 machine-specific constraints (如x86_64上的r8、r9、r10) 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]
    

    它似乎忽略了寄存器分配和程序分段,因为没有正确调用SysCurrar。不过,这段代码在clang 6中编译和运行良好。 注意:我实际上有6个syscall函数(最多6个参数),但是为了简化示例,这里只显示了1个参数版本。

    我意识到 注册 关键字本身并不是很有用,这就是它被删除的原因,但是这个特定的用例在我看来是一个例外,所以删除对它的编译器支持似乎也是不合理的。

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

    2 回复  |  直到 6 年前
        1
  •  3
  •   Florian Weimer    6 年前

    在我看来这是个gcc错误。C++ 17警告是一个红色鲱鱼。代码对我来说很好(当用GCC 7编译时),但是它在 -O0 .

    根据 documentation for local register variables ,这不是预期的,因此这可能是gcc错误。根据 this bug report ,它甚至与优化无关,但最终是由模板的使用引起的。

    我建议只对最终系统调用包装器中的系统调用参数的数量进行超载,并使用 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));
    }
    

    您将不得不使用更好的转换(可能是类型索引转换函数),当然,您仍然需要以其他方式处理SysCAR-ABI方差(X32)。 long long 而不是 长的 ,电源有两个返回寄存器,而不是一个,等等),但这也是一个问题与你原来的方法。

        2
  •  4
  •   Peter Cordes    6 年前

    看来您发现了GCC错误:GNU寄存器ASM局部变量在模板函数中不起作用。(clang正确地编译了您的示例)。显然这已经是 a known bug ,谢谢@florian找到它。

    -Wregister 触发只是第一个错误的症状: GNU寄存器ASM本地变量不会触发警告 . 但是在一个模板中,gcc将它们编译为简单的 register int foo = bar; 没有 asm 声明的一部分。那么海湾合作委员会 思想 你只是在用普通的 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++字符串文字连接)。我们可以让编译器告诉我们它认为它在做什么,而不必费解。

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


    代替模板,可以使用Myl现有的便携式标题:

    它是一个C库,所以它可能需要一点额外的铸造来保持C++编译器的快乐。或者避免在ARM头中将临时表达式用作左值。

    但应该注意弗洛里安所指出的大多数问题。它有一个许可证,所以您可以将它的syscall包装头复制到您的项目中。它们工作时不与musl的其他部分相连接,并且是真正的内联的。

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