生成的代码与扩展ASM

时间:2015-08-21 01:29:49

标签: c++ assembly g++

我有一个CpuFeatures课程。该类的要求很简单:(1)保留EBXRBX,以及(2)记录CPUIDEAX/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 <{1}} xchg %ebx,%ebx +11。此外,看起来保存的EBX/RBX被保存为CPUID的结果,而不是EBXCPUID返回的xchg %ebx,%ebx的实际值+15 mov %ebx,0x14(%esp)之前的+29。{/ p>

如果我更改操作数以使用带"=&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分钟的时间)?

1 个答案:

答案 0 :(得分:1)

下面的代码是我用来编译上面的示例代码的完整示例,包括直接交换(交换)到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的地址。我们在%ebx之后交换4(%edi)cpuid。通过该指令,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

编译时会出现这样的警告
  

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

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