Fra*_*k V 4 assembly gcc inline-assembly
我从未想过我会发布一个装配问题.:-)
在GCC中,有一个asm函数的扩展版本.此函数可以采用四个参数:汇编代码,输出列表,输入列表和覆盖列表.
我的问题是,覆盖列表中的寄存器是否归零?先前在那里的值(来自其他代码执行)会发生什么.
更新:到目前为止考虑我的答案(谢谢!),我想补充一点,虽然在clobber-list中列出了一个寄存器,但它(在我的实例中)正在pop(popl)命令中使用.没有其他参考.
不,他们没有被淘汰.覆盖列表(通常称为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) );
Run Code Online (Sandbox Code Playgroud)
这会得到结果C变量的第一和第二部分input_res1和res2; 但是,如果海湾合作委员会正在使用%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" );
Run Code Online (Sandbox Code Playgroud)
正如我们已告知GCC %ecx并且%edx将被此asm调用覆盖,它可以正确处理这种情况 - 通过不使用%ecx或%edx,或者在asm函数之前将其值保存到堆栈并在之后恢复.
更新:
关于你的第二个问题(为什么你看到一个popl指令列在clobber列表中的寄存器) - 假设你asm看起来像:
__asm__("popl %eax" : : : "%eax" );
Run Code Online (Sandbox Code Playgroud)
然后这里的代码是从堆栈中弹出一个项目,但它并不关心实际值 - 它可能只是保持堆栈平衡,或者在此代码路径中不需要该值.通过这种方式写作,而不是:
int trash // don't ever use this.
__asm__("popl %0" : "=r"(trash));
Run Code Online (Sandbox Code Playgroud)
您不必显式创建临时变量来保存不需要的值.不可否认,在这种情况下,两者之间没有太大差异,但带有clobber的版本清楚地表明您不关心堆栈中的值.
如果通过"归零"表示"寄存器中的值被替换为0以防止我知道其他功能正在做什么"那么不,则寄存器在使用前不会被清零.但这并不重要,因为你告诉GCC你打算在那里存储信息,而不是你想要读取当前存在的信息.
您将此信息提供给GCC,以便(在阅读文档时)"当您完成汇编代码时,您无需猜测哪些寄存器或存储器位置将包含您要使用的数据"(例如,您没有记住数据是否在堆栈寄存器或其他寄存器中.
GCC需要大量的汇编代码帮助,因为"编译器...不会解析汇编器指令模板,也不知道它是什么意思,甚至不知道它是否是有效的汇编器输入.扩展的asm功能最常用于机器指令编译器本身不知道存在."
GCC被设计为多通道编译器.许多通行证实际上是完全不同的程序.一组形成"编译器"的程序将您的源代码从C,C++,Ada,Java等转换为汇编代码.然后一个单独的程序(gas用于GNU汇编)开该汇编代码并将其转换为二进制(然后ld和collect2做更多的事情二进制).存在汇编块以直接传递文本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));
}
Run Code Online (Sandbox Code Playgroud)
我已经要求输出寄存器,我将reg在汇编代码中调用,并且GCC将result在完成时自动复制到变量.没有必要在C代码和汇编代码中给这个变量赋予不同的名称; 我只是表明它是可能的.无论物理寄存器GCC决定使用-无论是%%eax,%%ebx,%%ecx等- GCC注册到内存中,当我进入组装模块,这样我才能完全使用该寄存器,直到最后将采取复制从任何重要数据的护理装配块
我还要求输入寄存器,我将l在C和汇编中调用它.GCC承诺,l当我进入汇编程序块时,它决定给我的任何物理寄存器都将具有当前C变量中的值.GCC还将进行任何所需的记录保存,以保护在进入装配块之前恰好位于该寄存器中的任何数据.
如果我在汇编代码中添加一行怎么办?说:
"addl %[reg], %%ecx;"
Run Code Online (Sandbox Code Playgroud)
由于GCC的编译器部分不检查汇编代码,因此它不会保护数据%%ecx.如果我很幸运,%%ecx可能恰好是寄存器GCC决定用一个%[reg]或%[l].如果我不幸运,我会"神秘地"改变我程序其他部分的值.