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

Generated code not matching expectations with Extended ASM

本文关键字:扩展 ASM 不匹配 代码      更新时间:2023-10-16

我有一个CpuFeatures类。该类的要求很简单:(1)保留EBXRBX,(2)在EAX/EBX/ECX/EDX中记录CPUID返回的值。我不确定生成的代码是我想要的代码。

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的结果,而不是CPUID返回的EBX的实际值(+15时的xchg %ebx,%ebx,在+29时的mov %ebx,0x14(%esp)之前)。

如果我将操作数更改为使用"=&m"(scratch)的内存op,则生成的代码是:

0x0048f35e <+10>:    xchg   %ebx,0x40(%esp)
0x0048f362 <+14>:    cpuid
0x0048f364 <+16>:    xchg   %ebx,0x40(%esp)

一个相关的问题是如何确保操作数的读/写在扩展ASM所需的时间发生?

我做错了什么(除了浪费了无数个小时的事情,应该花5或15分钟)?

下面的代码是一个完整的示例,我使用它来编译上面的示例代码,包括直接交换(swap)到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)现在具有cpuid执行后ebx的状态。其余的movl行通过%edi寄存器将eaxecxedx寄存器放入info结构体的其余部分。

上面生成的代码是我所期望的。

您的代码与scratch变量(并使用约束"=&m"(scratch))永远不会在汇编器模板之后使用,因此%ebx,0x40(%esp)有您想要的值,但它永远不会被移动到任何有用的地方。您必须将scratch变量复制到info.EBX(即。info.EBX = scratch;)并查看生成的所有结果指令。在生成的汇编指令中,数据将从scratch内存位置复制到info.EBX

Update - Cygwin and MinGW

我不完全满意Cygwin代码输出是正确的。半夜的时候,我恍然大悟!的时刻。当动态链接加载器加载图像(DLL等)并通过重基修改图像时,Windows已经完成了自己的位置无关代码。不需要像在Linux 32位共享库中那样进行额外的PIC处理,因此ebx/rbx没有问题。这就是为什么Cygwin和MinGW在使用-fPIC

编译时会显示这样的警告。

警告:-fPIC忽略目标(所有代码与位置无关)

这是因为在Windows下,所有32位代码在被Windows动态加载器加载时都可以重新基于。在多布斯博士的文章中可以找到更多关于重新定位的信息。关于windows可移植可执行文件格式(PE)的信息可以在这篇Wiki文章中找到。Cygwin和MinGW在瞄准32位代码时不需要担心保留ebx/rbx,因为在他们的平台上PIC已经由操作系统,其他重基工具和链接器处理。