GCC的asm扩展版

时间:2009-06-30 02:11:30

标签: gcc assembly inline-assembly

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

GCC中,有一个extended version of the asm function。此函数可以采用四个参数:汇编代码,输出列表,输入列表和覆盖列表。

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

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

3 个答案:

答案 0 :(得分:7)

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

例如,在x86上,cpuid指令使用四个固定寄存器返回四个部分的信息:%eax%ebx%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_res1res2中结果的第一部分和第二部分;但如果 GCC正在使用%ecx%edx来保存其他数据;它们会被cpuid指令覆盖,而不知道gcc知道。为了防止这种情况我们使用 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调用覆盖,它可以正确处理这种情况 - 不使用%ecx%edx ,或者在asm函数之前将其值保存到堆栈并在之后恢复。

<强>更新

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

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

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

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

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

答案 1 :(得分:5)

如果通过“归零”表示“寄存器中的值被0替换以防止我知道其他功能正在做什么”那么不是,寄存器在使用前不会被清零。但这并不重要,因为你告诉海湾合作委员会你打算在那里存储信息,而不是你想要读取目前在那里的信息。

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

GCC需要对汇编代码提供很多帮助,因为“编译器...不会解析汇编器指令模板,也不知道它是什么意思,甚至不知道它是否是有效的汇编器输入。扩展的asm功能最常用对于机器指令,编译器本身不知道存在。“

更新

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

另一种方法是保存和恢复每个汇编代码块的每个寄存器。在具有大量寄存器的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将负责将该寄存器中的任何重要数据复制到存储器中装配块,以便我完全使用该寄存器,直到装配块结束。

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

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

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

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

答案 2 :(得分:4)

我怀疑覆盖列表只是为了给GCC提示不要在ASM调用中存储这些寄存器中的任何有价值的内容;由于GCC没有分析你给出的ASM,并且某些指令有副作用触及代码中没有明确命名的其他寄存器,这是告诉GCC的方法。