我有一个CpuFeatures
课程。该类的要求很简单:(1)保留EBX
或RBX
,以及(2)记录CPUID
中EAX/EBX/ECX/EDX
返回的值。我不确定生成的代码是否是我想要的代码。
CpuFeatures
类代码使用GCC Extended ASM。这是相关代码:
struct CPUIDinfo
{
word32 EAX;
word32 EBX;
word32 ECX;
word32 EDX;
};
bool CpuId(word32 func, word32 subfunc, CPUIDinfo& info)
{
uintptr_t scratch;
__asm__ __volatile__ (
".att_syntax \n"
#if defined(__x86_64__)
"\t xchgq %%rbx, %q1 \n"
#else
"\t xchgl %%ebx, %k1 \n"
#endif
"\t cpuid \n"
#if defined(__x86_64__)
"\t xchgq %%rbx, %q1 \n"
#else
"\t xchgl %%ebx, %k1 \n"
#endif
: "=a"(info.EAX), "=&r"(scratch), "=c"(info.ECX), "=d"(info.EDX)
: "a"(func), "c"(subfunc)
);
if(func == 0)
return !!info.EAX;
return true;
}
以下代码是在Cygwin i386上使用-g3 -Og
编译的。当我在调试器下检查它时,我不喜欢我所看到的。
Dump of assembler code for function CpuFeatures::DoDetectX86Features():
...
0x0048f355 <+1>: sub $0x48,%esp
=> 0x0048f358 <+4>: mov $0x0,%ecx
0x0048f35d <+9>: mov %ecx,%eax
0x0048f35f <+11>: xchg %ebx,%ebx
0x0048f361 <+13>: cpuid
0x0048f363 <+15>: xchg %ebx,%ebx
0x0048f365 <+17>: mov %eax,0x10(%esp)
0x0048f369 <+21>: mov %ecx,0x18(%esp)
0x0048f36d <+25>: mov %edx,0x1c(%esp)
0x0048f371 <+29>: mov %ebx,0x14(%esp)
0x0048f375 <+33>: test %eax,%eax
...
我不喜欢我所看到的内容,因为 如果我更改操作数以使用带 相关问题是What ensures reads/writes of operands occurs at desired times with extended ASM? 我做错了什么(除了浪费了无数个小时应该花费5或15分钟的时间)?EBX/RBX
<{1}} xchg %ebx,%ebx
+11
。此外,看起来保存的EBX/RBX
被保存为CPUID
的结果,而不是EBX
(CPUID
返回的xchg %ebx,%ebx
的实际值+15
mov %ebx,0x14(%esp)
之前的+29
。{/ p>
"=&m"(scratch)
的内存操作,则生成的代码为:0x0048f35e <+10>: xchg %ebx,0x40(%esp)
0x0048f362 <+14>: cpuid
0x0048f364 <+16>: xchg %ebx,0x40(%esp)
答案 0 :(得分:1)
下面的代码是我用来编译上面的示例代码的完整示例,包括直接交换(交换)到info.EBX
变量的修改。
#include <inttypes.h>
#define word32 uint32_t
struct CPUIDinfo
{
word32 EAX;
word32 EBX;
word32 ECX;
word32 EDX;
};
bool CpuId(word32 func, word32 subfunc, CPUIDinfo& info)
{
__asm__ __volatile__ (
".att_syntax \n"
#if defined(__x86_64__)
"\t xchgq %%rbx, %q1 \n"
#else
"\t xchgl %%ebx, %k1 \n"
#endif
"\t cpuid \n"
#if defined(__x86_64__)
"\t xchgq %%rbx, %q1 \n"
#else
"\t xchgl %%ebx, %k1 \n"
#endif
: "=a"(info.EAX), "=&m"(info.EBX), "=c"(info.ECX), "=d"(info.EDX)
: "a"(func), "c"(subfunc)
);
if(func == 0)
return !!info.EAX;
return true;
}
int main()
{
CPUIDinfo cpuInfo;
CpuId(1, 0, cpuInfo);
}
你应该做的第一个观察是我选择使用info.EBX内存位置进行实际交换。这消除了需要另一个临时变量或寄存器。
我使用-g3 -Og -S -m32
汇编为32位代码并获得了这些感兴趣的说明:
xchgl %ebx, 4(%edi)
cpuid
xchgl %ebx, 4(%edi)
movl %eax, (%edi)
movl %ecx, 8(%edi)
movl %edx, 12(%edi)
%edi
恰好包含info
结构的地址。 4(%edi)
恰好是info.EBX
的地址。我们在%ebx
之后交换4(%edi)
和cpuid
。通过该指令,ebx
将恢复为cpuid
之前的状态,而4(%edi)
现在具有ebx
执行cpuid
之后的权限。其余movl
行将eax
,ecx
,edx
通过info
寄存器注册到%edi
结构的其余部分。
上面生成的代码就是我所期望的。
使用scratch
变量(并使用约束"=&m"(scratch)
)的代码永远不会在汇编程序模板之后使用,因此%ebx,0x40(%esp)
具有您想要的值,但它永远不会被移动到任何有用的位置。您必须将scratch
变量复制到info.EBX
(即info.EBX = scratch;
)并查看生成的所有结果指令。在某些时候,数据将从生成的汇编指令中的scratch
内存位置复制到info.EBX
。
更新 - Cygwin和MinGW
我对Cygwin代码输出正确无法完全满意。在半夜,我有一个啊哈!时刻。当动态链接加载器加载图像(DLL等)并通过重新基础修改图像时,Windows已经拥有自己的位置无关代码。不需要像在Linux 32位共享库中那样进行额外的PIC处理,因此ebx
/ rbx
没有问题。这就是为什么Cygwin和MinGW在使用-fPIC
警告:-fPIC被目标忽略(所有代码都与位置无关)
这是因为在Windows下,所有32位代码都可以在Windows动态加载程序加载时重新基于。有关重新定位的更多信息,请参阅此Dr. Dobbs article。有关Windows可移植可执行格式(PE)的信息可在此Wiki article中找到。 Cygwin和MinGW不需要担心在定位32位代码时保留ebx
/ rbx
,因为在他们的平台上,PIC已经由操作系统,其他重新基础工具和链接器处理。