【问题标题】:Generated code not matching expectations with Extended ASM生成的代码与 Extended ASM 的预期不符
【发布时间】:2015-08-21 01:29:49
【问题描述】:

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

如果我将操作数更改为使用带有"=&amp;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 分钟的事情上浪费了无数小时)?

【问题讨论】:

  • 您可能错过了我对我们讨论的最后一个问题所做的评论。您可能会考虑使用特定的寄存器,例如 "=D" 而不是 =&amp;r 。然而,关于%ebx 的重用——这只是一个问题,如果%ebx 需要 被保留。我注意到您没有使用-fPIC 进行编译,这表明编译器可以免费使用ebx。如果你用-fPIC 编译会发生什么?
  • 我的猜测是-fPIC 不应该使用ebx,接下来可能会选择%esi(或其他可用寄存器)。
  • 还应该指出,即使使用-fPIC 生成 x86_64 也可能不是问题,因为 PIC 代码的使用还取决于所使用的内存模型(和 ABI)。 %rbx 可能不需要保留(在这些情况下,PIC 的处理方式不同)。这个article对X86_64上PIC的合理解释
  • @MichaelPetch - "...x86_64 即使使用 -fPIC 也可能不是问题" - 这就是问题所在。它可能需要(也可能不需要)。对我们来说,在所有情况下都更容易保存它以避免任何问题和未来(或过去)的问题。对我们来说,过去可以追溯到 1990 年代,因此我们需要确保我们不会破坏级别(旧)客户。

标签: c++ assembly g++


【解决方案1】:

下面的代码是我用来编译上面的示例代码的完整示例,包括将 exchange(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) 现在拥有ebx 在执行cpuid 之后的状态。剩余的movl 行通过%edi 寄存器将eaxecxedx 寄存器放入info 结构的其余部分。

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

带有scratch 变量的代码(并使用约束"=&amp;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 中可以找到更多关于重新定位的信息。可以在此Wiki article 中找到有关 Windows 可移植可执行格式 (PE) 的信息。 Cygwin 和 MinGW 无需担心在针对 32 位代码时保留 ebx/rbx,因为在他们的平台上 PIC 已经由操作系统、其他基础工具和链接器处理。

【讨论】:

  • 谢谢迈克尔。我不再信任这个扩展的 ASM,所以我放弃了它。它可能(也可能不会)产生正确的代码;但它显然无法合理审计。由于我们不能轻易验证它,所以使用它是没有意义的。
  • 没问题。我多年来一直在使用汇编模板,没有遇到任何问题。 cpuid 引起了我的注意,因为我实际上通过汇编模板在 GNU Backgammon 中使用了 cpuid。除此之外,您还必须小心,因为并非所有 i386 处理器都支持cpuid,因此您确实需要检查处理器是否支持cpuid。该检查是通过寻找pushflpopl 的副作用来完成的(不得不爱英特尔哈哈)
  • 酷,我不知道你是维护者。相关,如果您有兴趣支持它,我有几台 Apple 机器。我也使用 GNU 测试农场,我知道它没有。我的设置包括 G5(PowerPC,OS X 10.5)、MackBook(Intel,OS X 10.9)和 MacBook Pro(Intel,OS X 10.8;声称是 C++11 的古老 C++ 标准库)。跨度>
  • 也已经支持 OS/X(我们也在 macports 中)。我仍然保留十多年前自己的 G5,以便我可以为该平台进行构建。我也有几个mac mini。看起来你和我保留 Apple 设备的原因相似——测试我们的软件!
  • 这是传奇中的下一个问题:Assembler templates, asmSymbolicName and “error: expected string-literal”。就像我之前说的,代码及其验证应该花费大约 15 分钟。它已经成功地拖了好几天了。
猜你喜欢
  • 2018-08-23
  • 1970-01-01
  • 2012-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-25
  • 1970-01-01
相关资源
最近更新 更多