【问题标题】:GCC's extended version of asmGCC的asm扩展版
【发布时间】:2009-06-30 02:11:29
【问题描述】:

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

GCC 中有一个extended version of the asm function。这个函数可以接受四个参数:汇编代码、输出列表、输入列表和覆盖列表。

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

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

【问题讨论】:

    标签: gcc assembly inline-assembly


    【解决方案1】:

    不,它们没有归零。覆盖列表(通常称为 clobber 列表)的目的是通知 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 中获得结果的第一部分和第二部分;但是 if 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 函数之前堆栈并在之后恢复。

    更新:

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

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

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

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

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

    【讨论】:

    • 你和 Max 的答案都回答了我的问题。但是,我只能选择一个答案作为我的最终答案,并且因为 Max(不知何故)更接近我的实际问题,所以我会给他打上绿色的复选标记。但是,如果可能的话,我会在你们两个之间分配功劳。您的回答也对我有所帮助,我要感谢您抽出时间来解决我的问题。
    【解决方案2】:

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

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

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

    更新

    GCC 被设计为多遍编译器。许多通行证实际上是完全不同的程序。形成“编译器”的一组程序将您的源代码从 C、C++、Ada、Java 等转换为汇编代码。然后一个单独的程序(gas,用于 GNU Assembler)获取该汇编代码并将其转换为二进制文件(然后 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] 的寄存器之一。如果我不走运,我会“神秘地”改变程序其他部分的值。

    【讨论】:

    • 感谢您的回答。您的回答(以及其他人)为我提供了我需要的信息...我更新了我的问题,并希望您在回答时考虑我的新信息...再次感谢。
    • 感谢您提供详细信息。我理解,更重要的是,它使我能够解决和理解我的程序问题。再次感谢您。
    【解决方案3】:

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

    【讨论】:

    • 感谢您的回答。这也是这个问题的宝贵信息。
    猜你喜欢
    • 2013-10-25
    • 2012-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多