我有一个CpuFeatures类。该类的要求很简单:(1)保留EBXRBX,以及(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寄存器将ecxedxinfo寄存器放入%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/

10-11 17:51