gpt4 book ai didi

c++ - 生成的代码与扩展 ASM 的预期不匹配

转载 作者:行者123 更新时间:2023-11-30 05:39:46 25 4
gpt4 key购买 nike

我有一个 CpuFeatures 类。该类的要求很简单:(1) 保留EBXRBX,(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
...

我不喜欢我所看到的,因为看起来 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 变量进行交换(交换)的修改。

#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 之后交换 %ebx4(%edi)。通过该指令,ebx 恢复到 cpuid 之前的状态,4(%edi) 现在具有 ebx 的状态就在 cpuid 被执行之后。剩余的 movl 行将 eaxecxedx 寄存器放入剩余的 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

编译时会出现这样的警告

warning: -fPIC ignored for target (all code is position independent)

这是因为在 Windows 下,所有 32 位代码在被 Windows 动态加载器加载时都可以重新基址。可以在这个 Dr. Dobbs article 中找到有关重新定位的更多信息.有关 Windows 可移植可执行格式 (PE) 的信息可以在此 Wiki article 中找到. Cygwin 和 MinGW 不需要担心在针对 32 位代码时保留 ebx/rbx 因为在他们的平台上 PIC 已经由操作系统处理,其他 re-based 工具,和链接器。

关于c++ - 生成的代码与扩展 ASM 的预期不匹配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32131003/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com