我有一个CpuFeatures
类。该类的要求很简单:(1)保留EBX
或RBX
,以及(2)将CPUID
返回的值记录在EAX/EBX/ECX/EDX
中。我不确定生成的代码是否是我想要的代码。CpuFeatures
类代码使用GCC扩展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
...
我不喜欢自己看到的内容,因为它似乎没有保留
EBX/RBX
(位于xchg %ebx,%ebx
的+11
)。另外,看起来保留的EBX/RBX
是作为CPUID
的结果保存的,而不是EBX
返回的CPUID
的实际值(在xchg %ebx,%ebx
处的+15
,在mov %ebx,0x14(%esp)
处的+29
之前)。如果我将操作数更改为使用带有
"=&m"(scratch)
的内存操作,则生成的代码为:0x0048f35e <+10>: xchg %ebx,0x40(%esp)
0x0048f362 <+14>: cpuid
0x0048f364 <+16>: xchg %ebx,0x40(%esp)
一个相关的问题是What ensures reads/writes of operands occurs at desired times with extended ASM?
我在做什么错(除了在本来应该花费5或15分钟的事情上浪费无数小时)?
最佳答案
下面的代码是一个完整的示例,我用来编译您的示例代码,其中包括直接交换到info.EBX
变量的exchange(swap)的修改。
#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
进行编译时会发出这样的警告的原因这是因为在Windows下,由Windows动态加载程序加载时,所有32位代码都可以重新基于。可以在Dr. Dobbs article中找到有关重新基准化的更多信息。在此Wiki article中可以找到有关Windows可移植可执行格式(PE)的信息。 Cygwin和MinGW在定位32位代码时无需担心保留
ebx
/ rbx
,因为在其平台上,PIC已由操作系统,其他重新定型工具和链接器处理。关于c++ - 生成的代码与扩展ASM不符期望,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/32131003/