4

我从没想过我会发布一个装配问题。:-)

GCC中,有一个asm 函数的扩展版本。这个函数可以接受四个参数:汇编代码、输出列表、输入列表和覆盖列表。

我的问题是,覆盖列表中的寄存器是否已清零?以前在其中的值会发生什么(来自其他代码执行)。

更新:在考虑我迄今为止的答案时(谢谢!),我想补充一点,虽然寄存器列在 clobber-list 中,但它(在我的实例中)正在 pop ( popl) 命令中使用。没有其他参考。

4

3 回答 3

7

不,它们没有归零。覆盖列表(通常称为clobber 列表)的目的是通知 GCC,作为 asm 指令的结果,clobber 列表中列出的寄存器将被修改,因此编译器应保留任何目前住。

例如,在 x86 上,该cpuid指令使用四个固定寄存器返回四部分信息:%eax、和%ebx,基于 的输入值。如果我们只对 and 中的结果感兴趣,那么我们可能(天真地)写:%ecx%edx%eax%eax%ebx

int input_res1 = 0; // also used for first part of result 
int res2;
__asm__("cpuid" : "+a"(input_res1), "=b"(res2) ); 

这将在 C 变量中获得结果的第一部分和第二部分,input_res1并且res2; 但是,如果GCC 正在使用%ecx%edx保存其他数据;它们会在gcc 不知情的情况下被cpuid指令覆盖。为了防止这种情况;我们使用clobber列表

int input_res1 = 0; // also used for first part of result 
int res2;
__asm__("cpuid" : "+a"(input_res1), "=b"(res2)
                : : "%ecx", "%edx" );

正如我们告诉 GCC%ecx并且%edx将被这个 asm 调用覆盖,它可以正确地处理这种情况——要么不使用%ecxor %edx,要么在函数之前将它们的值保存到堆栈中asm并在之后恢复。

更新:

关于您的第二个问题(为什么您会在指令列表中看到一个寄存器popl) - 假设您asm看起来像:

__asm__("popl %eax" : : : "%eax" );

然后这里的代码从堆栈中弹出一个项目,但它并不关心实际值 - 它可能只是保持堆栈平衡,或者此代码路径中不需要该值。通过这种方式编写,而不是:

int trash // don't ever use this.
__asm__("popl %0" : "=r"(trash));

您不必显式创建临时变量来保存不需要的值。诚然,在这种情况下,两者之间并没有太大的区别,但是带有 clobber 的版本清楚地表明您并不关心堆栈中的值。

于 2009-06-30T22:26:12.360 回答
5

如果“清零”是指“寄存器中的值被替换为 0,以防止我知道其他函数在做什么”,那么不,寄存器在使用前不会清零。但这并不重要,因为您告诉 GCC 您计划将信息存储在那里,而不是您想读取当前存在的信息。

您将此信息提供给 GCC,以便(阅读文档)“您无需猜测哪些寄存器或内存位置将包含您要使用的数据”当您完成汇编代码时(例如,您没有记住数据是在堆栈寄存器中,还是在其他寄存器中)。

GCC 需要大量的汇编代码帮助,因为“编译器……不解析汇编指令模板,也不知道它的含义,甚至不知道它是否是有效的汇编输入。扩展的 asm 特性最常用于机器指令编译器本身不知道存在。”

更新

GCC 被设计为一个多遍编译器。许多通行证实际上是完全不同的程序。形成“编译器”的一组程序将您的源代码从 C、C++、Ada、Java 等转换为汇编代码。然后一个单独的程序(用于 GNU Assembler)获取该汇编代码并将gas其转换为二进制文件(然后对二进制文件执行更多操作)。存在汇编块以将文本直接传递到,并且存在 clobber-list(和输入列表),以便编译器可以进行任何所需的设置,以在 C、C++、Ada、Java 等方面之间传递信息和这ldcollect2gasgas一方面,并​​保证当前在寄存器中的任何重要信息都可以通过在汇编块运行之前将其复制到内存(然后从内存中复制回来)来保护免受汇编块的影响。

另一种方法是保存和恢复每个汇编代码块的每个寄存器。在具有大量寄存器的 RISC 机器上可能会变得昂贵(例如,Itanium 有 128 个通用寄存器、另外 128 个浮点寄存器和 64 个 1 位寄存器)。

我已经有一段时间没有编写任何汇编代码了。而且我使用 GCC 的命名寄存器功能比使用特定寄存器做事有更多的经验。所以,看一个例子:

#include <stdio.h>

long foo(long l)
{
    long result;
    asm (
        "movl %[l], %[reg];"
        "incl %[reg];"
        : [reg] "=r" (result)
        : [l] "r" (l)
    );
    return result;
}

int main(int argc, char** argv)
{
    printf("%ld\n", foo(5L));
}

我已经要求一个输出寄存器,我将reg在汇编代码中调用它,并且 GCC 将result在完成时自动复制到变量中。无需在 C 代码和汇编代码中为该变量指定不同的名称;我这样做只是为了表明这是可能的。无论 GCC 决定使用哪个物理寄存器——无论是%%eax, %%ebx,%%ecx等等——当我进入汇编块时,GCC 都会负责将该寄存器中的任何重要数据复制到内存中,这样我就可以充分使用该寄存器直到结束的组装块。

我还要求提供一个输入寄存器,我将l在 C 和汇编中调用它。lGCC 承诺,当我进入汇编块时,它决定给我的任何物理寄存器都将具有当前 C 变量中的值。GCC 还将做任何必要的记录保存,以在我进入汇编块之前保护恰好在该寄存器中的任何数据。

如果我在汇编代码中添加一行怎么办?说:

"addl %[reg], %%ecx;"

由于 GCC 的编译器部分不检查汇编代码,它不会保护%%ecx. 如果幸运的话,%%ecx可能恰好是 GCC 决定用于%[reg]or的寄存器之一%[l]。如果我不走运,我会“神秘地”改变程序其他部分的值。

于 2009-06-30T19:08:51.573 回答
4

我怀疑覆盖列表只是为了给 GCC 一个提示,在 ASM 调用中不要在这些寄存器中存储任何有价值的东西;因为 GCC 不分析你给它的 ASM,并且某些指令具有触及代码中未明确命名的其他寄存器的副作用,所以这是告诉 GCC 的方法。

于 2009-06-30T02:20:33.253 回答