是否可以在 C++17 的 GCC 中使用显式寄存器变量?
Is it possible to use explicit register variables in GCC with C++17?
我正在使用 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 [=10=]x80"
#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]
它似乎忽略了寄存器分配和程序段错误,因为没有正确调用系统调用。然而,此代码在 Clang 6 中编译并运行良好。注意:我实际上有 6 个系统调用函数(最多 6 个参数),但为了最小示例,此处仅显示 1 个参数版本。
我意识到 register
关键字本身并不是很有用,这就是它被删除的原因,但这个特定用例对我来说似乎是一个例外,因此删除编译器对它的支持似乎也不合理.
我也意识到这个用例是特定于编译器的(即非标准),所以我的问题是关于编译器支持而不是从标准中删除。
我觉得这像是 GCC 错误。 C++17 警告是一个转移注意力的问题。该代码对我来说可以很好地优化(当使用 GCC 7 编译时),但它在 -O0
.
处中断
根据documentation for local register variables, this is not expected, so this is likely a GCC bug. According to 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));
}
你必须为转换使用更好的东西(可能是类型索引转换函数),当然你仍然必须以其他方式处理系统调用 ABI 差异(x32 有 long long
而不是 long
,并且 POWER 有两个 return 寄存器而不是一个,等等),但这也是您原始方法的问题。
您似乎发现了一个 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"
...
_start:
movl , %eax
xorl %edx, %edx
syscall #operands in %rax, %rax, %edx
ret
但是clang6.0没有这个bug,我们得到:
_start: # @_start
movl , %eax
xorl %edi, %edi
syscall #operands in %rax, %rax, %edi
retq
请注意我附加到您的模板的 asm 注释(使用 C++ string-literal 连接)。我们可以让编译器告诉我们它认为它在做什么,而不必费力去弄清楚。
(发布这个答案主要是为了讨论调试技术;Florian 的答案已经涵盖了这个实际案例的细节。)
您可以使用 MUSL 现有的便携式 headers:
而不是模板
它是一个 C 库,因此可能需要一些额外的转换才能让 C++ 编译器满意。或者在 ARM headers.
中避免使用临时表达式作为左值
但它应该解决了 Florian 指出的大部分问题。它有一个宽松的许可证,因此您只需将其系统调用包装器 headers 复制到您的项目中即可。它们无需链接到 MUSL 的其余部分即可工作,并且是真正的内联。
http://git.musl-libc.org/cgit/musl/tree/arch/x86_64/syscall_arch.h 是 x86-64 版本。
我正在使用 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 [=10=]x80"
#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]
它似乎忽略了寄存器分配和程序段错误,因为没有正确调用系统调用。然而,此代码在 Clang 6 中编译并运行良好。注意:我实际上有 6 个系统调用函数(最多 6 个参数),但为了最小示例,此处仅显示 1 个参数版本。
我意识到 register
关键字本身并不是很有用,这就是它被删除的原因,但这个特定用例对我来说似乎是一个例外,因此删除编译器对它的支持似乎也不合理.
我也意识到这个用例是特定于编译器的(即非标准),所以我的问题是关于编译器支持而不是从标准中删除。
我觉得这像是 GCC 错误。 C++17 警告是一个转移注意力的问题。该代码对我来说可以很好地优化(当使用 GCC 7 编译时),但它在 -O0
.
根据documentation for local register variables, this is not expected, so this is likely a GCC bug. According to 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));
}
你必须为转换使用更好的东西(可能是类型索引转换函数),当然你仍然必须以其他方式处理系统调用 ABI 差异(x32 有 long long
而不是 long
,并且 POWER 有两个 return 寄存器而不是一个,等等),但这也是您原始方法的问题。
您似乎发现了一个 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"
...
_start:
movl , %eax
xorl %edx, %edx
syscall #operands in %rax, %rax, %edx
ret
但是clang6.0没有这个bug,我们得到:
_start: # @_start
movl , %eax
xorl %edi, %edi
syscall #operands in %rax, %rax, %edi
retq
请注意我附加到您的模板的 asm 注释(使用 C++ string-literal 连接)。我们可以让编译器告诉我们它认为它在做什么,而不必费力去弄清楚。
(发布这个答案主要是为了讨论调试技术;Florian 的答案已经涵盖了这个实际案例的细节。)
您可以使用 MUSL 现有的便携式 headers:
而不是模板它是一个 C 库,因此可能需要一些额外的转换才能让 C++ 编译器满意。或者在 ARM headers.
中避免使用临时表达式作为左值但它应该解决了 Florian 指出的大部分问题。它有一个宽松的许可证,因此您只需将其系统调用包装器 headers 复制到您的项目中即可。它们无需链接到 MUSL 的其余部分即可工作,并且是真正的内联。
http://git.musl-libc.org/cgit/musl/tree/arch/x86_64/syscall_arch.h 是 x86-64 版本。