生成的代码与扩展 ASM 的预期不匹配
Generated code not matching expectations with Extended ASM
我有一个CpuFeatures
class。 class 的要求很简单:(1) 保留 EBX
或 RBX
,以及 (2) 将 CPUID
返回的值记录在 EAX/EBX/ECX/EDX
中。我不确定生成的代码是否是我想要的代码。
CpuFeatures
class 代码使用 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 [=11=]x48,%esp
=> 0x0048f358 <+4>: mov [=11=]x0,%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
的结果,而不是 CPUID
返回的 EBX
的实际值(xchg %ebx,%ebx
在+15
,在 +29
处的 mov %ebx,0x14(%esp)
之前。
如果我将操作数更改为使用带有 "=&m"(scratch)
的内存操作,则生成的代码为:
0x0048f35e <+10>: xchg %ebx,0x40(%esp)
0x0048f362 <+14>: cpuid
0x0048f364 <+16>: xchg %ebx,0x40(%esp)
一个相关的问题是
我做错了什么(除了在本应花费 5 或 15 分钟的事情上浪费了无数时间)?
下面的代码是一个完整的示例,我用它来编译上面的示例代码,包括修改以直接交换(交换)到 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
的地址。我们在 cpuid
之后交换 %ebx
和 4(%edi)
。通过该指令,ebx
恢复到 cpuid
之前的状态,并且 4(%edi)
现在具有 ebx
在执行 cpuid
之后的状态。剩余的 movl
行将 eax
、ecx
、edx
寄存器通过 %edi
寄存器放入 info
结构的其余部分。
上面生成的代码是我所期望的。
带有 scratch
变量(并使用约束 "=&m"(scratch)
)的代码在汇编程序模板之后永远不会被使用,因此 %ebx,0x40(%esp)
具有您想要的值,但它永远不会移动到任何地方有用。您必须将 scratch
变量复制到 info.EBX
(即 info.EBX = scratch;
)并查看生成的所有结果指令。在某些时候,数据将从 scratch
内存位置复制到生成的汇编指令中的 info.EBX
。
更新 - Cygwin 和 MinGW
我对 Cygwin 代码输出的正确性并不完全满意。半夜我有一个啊哈!片刻。当动态 link 加载程序加载图像(DLL 等)并通过重新定位修改图像时,Windows 已经执行了自己的位置独立代码。不需要像在 Linux 32 位共享库中那样进行额外的 PIC 处理,因此 ebx
/rbx
没有问题。这就是为什么Cygwin和MinGW在用-fPIC
编译时会出现这样的警告
warning: -fPIC ignored for target (all code is position independent)
这是因为在 Windows 下,所有 32 位代码在被 Windows 动态加载器加载时都可以重新基址。可以在这个 Dr. Dobbs article. Information on the windows Portable Executable format (PE) can be found in this Wiki article 中找到更多关于重新定位的信息。 Cygwin 和 MinGW 不需要担心在针对 32 位代码时保留 ebx
/rbx
,因为在他们的平台上 PIC 已经由 OS、其他 re-base 工具和link呃。
我有一个CpuFeatures
class。 class 的要求很简单:(1) 保留 EBX
或 RBX
,以及 (2) 将 CPUID
返回的值记录在 EAX/EBX/ECX/EDX
中。我不确定生成的代码是否是我想要的代码。
CpuFeatures
class 代码使用 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 [=11=]x48,%esp
=> 0x0048f358 <+4>: mov [=11=]x0,%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
的结果,而不是 CPUID
返回的 EBX
的实际值(xchg %ebx,%ebx
在+15
,在 +29
处的 mov %ebx,0x14(%esp)
之前。
如果我将操作数更改为使用带有 "=&m"(scratch)
的内存操作,则生成的代码为:
0x0048f35e <+10>: xchg %ebx,0x40(%esp)
0x0048f362 <+14>: cpuid
0x0048f364 <+16>: xchg %ebx,0x40(%esp)
一个相关的问题是
我做错了什么(除了在本应花费 5 或 15 分钟的事情上浪费了无数时间)?
下面的代码是一个完整的示例,我用它来编译上面的示例代码,包括修改以直接交换(交换)到 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
的地址。我们在 cpuid
之后交换 %ebx
和 4(%edi)
。通过该指令,ebx
恢复到 cpuid
之前的状态,并且 4(%edi)
现在具有 ebx
在执行 cpuid
之后的状态。剩余的 movl
行将 eax
、ecx
、edx
寄存器通过 %edi
寄存器放入 info
结构的其余部分。
上面生成的代码是我所期望的。
带有 scratch
变量(并使用约束 "=&m"(scratch)
)的代码在汇编程序模板之后永远不会被使用,因此 %ebx,0x40(%esp)
具有您想要的值,但它永远不会移动到任何地方有用。您必须将 scratch
变量复制到 info.EBX
(即 info.EBX = scratch;
)并查看生成的所有结果指令。在某些时候,数据将从 scratch
内存位置复制到生成的汇编指令中的 info.EBX
。
更新 - Cygwin 和 MinGW
我对 Cygwin 代码输出的正确性并不完全满意。半夜我有一个啊哈!片刻。当动态 link 加载程序加载图像(DLL 等)并通过重新定位修改图像时,Windows 已经执行了自己的位置独立代码。不需要像在 Linux 32 位共享库中那样进行额外的 PIC 处理,因此 ebx
/rbx
没有问题。这就是为什么Cygwin和MinGW在用-fPIC
warning: -fPIC ignored for target (all code is position independent)
这是因为在 Windows 下,所有 32 位代码在被 Windows 动态加载器加载时都可以重新基址。可以在这个 Dr. Dobbs article. Information on the windows Portable Executable format (PE) can be found in this Wiki article 中找到更多关于重新定位的信息。 Cygwin 和 MinGW 不需要担心在针对 32 位代码时保留 ebx
/rbx
,因为在他们的平台上 PIC 已经由 OS、其他 re-base 工具和link呃。