GCC的asm扩展版

时间:2021-11-07 19:37:21

I never thought I'd be posting an assembly question. :-)

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

In GCC, there is an extended version of the asm function. This function can take four parameters: assembly-code, output-list, input-list and overwrite-list.

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

My question is, are the registers in the overwrite-list zeroed out? What happens to the values that were previously in there (from other code executing).

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

Update: In considering my answers thus far (thank you!), I want to add that though a register is listed in the clobber-list, it (in my instance) is being used in a pop (popl) command. There is no other reference.

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

3 个解决方案

#1


If by "zeroed out" you mean "the values in the registers are replaced with 0's to prevent me from knowing what some other function was doing" then no, the registers are not zeroed out before use. But it shouldn't matter because you're telling GCC you plan to store information there, not that you want to read information that's currently there.

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

You give this information to GCC so that (reading the documentation) "you need not guess which registers or memory locations will contain the data you want to use" when you're finished with the assembly code (eg., you don't have to remember if the data will be in the stack register, or some other register).

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

GCC needs a lot of help for assembly code because "The compiler ... does not parse the assembler instruction template and does not know what it means or even whether it is valid assembler input. The extended asm feature is most often used for machine instructions the compiler itself does not know exist."

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

Update

GCC is designed as a multi-pass compiler. Many of the passes are in fact entirely different programs. A set of programs forming "the compiler" translate your source from C, C++, Ada, Java, etc. into assembly code. Then a separate program (gas, for GNU Assembler) takes that assembly code and turns it into a binary (and then ld and collect2 do more things to the binary). Assembly blocks exist to pass text directly to gas, and the clobber-list (and input list) exist so that the compiler can do whatever set up is needed to pass information between the C, C++, Ada, Java, etc. side of things and the gas side of things, and to guarantee that any important information currently in registers can be protected from the assembly block by copying it to memory before the assembly block runs (and copying back from memory afterward).

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

The alternative would be to save and restore every register for every assembly code block. On a RISC machine with a large number of registers that could get expensive (the Itanium has 128 general registers, another 128 floating point registers and 64 1-bit registers, for instance).

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

It's been a while since I've written any assembly code. And I have much more experience using GCC's named registers feature than doing things with specific registers. So, looking at an example:

我写了任何汇编代码已经有一段时间了。我使用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));
}

I have asked for an output register, which I will call reg inside the assembly code, and that GCC will automatically copy to the result variable on completion. There is no need to give this variable different names in C code vs assembly code; I only did it to show that it is possible. Whichever physical register GCC decides to use -- whether it's %%eax, %%ebx, %%ecx, etc. -- GCC will take care of copying any important data from that register into memory when I enter the assembly block so that I have full use of that register until the end of the assembly block.

我已经要求输出寄存器,我将在汇编代码中调用reg,并且GCC将在完成时自动复制到结果变量。没有必要在C代码和汇编代码中给这个变量赋予不同的名称;我只是表明它是可能的。无论GCC决定使用哪个物理寄存器 - 无论是%% eax,%% ebx,%% ecx等 - 当我进入汇编程序块时,GCC将负责将该寄存器中的任何重要数据复制到内存中,以便我在装配块结束之前充分利用该寄存器。

I have also asked for an input register, which I will call l both in C and in assembly. GCC promises that whatever physical register it decides to give me will have the value currently in the C variable l when I enter the assembly block. GCC will also do any needed recordkeeping to protect any data that happens to be in that register before I enter the assembly block.

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

What if I add a line to the assembly code? Say:

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

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

Since the compiler part of GCC doesn't check the assembly code it won't have protected the data in %%ecx. If I'm lucky, %%ecx may happen to be one of the registers GCC decided to use for %[reg] or %[l]. If I'm not lucky, I will have "mysteriously" changed a value in some other part of my program.

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

#2


No, they are not zeroed out. The purpose of the overwrite list (more commonly called the clobber list) is to inform GCC that, as a result of the asm instructions the register(s) listed in the clobber list will be modified, and so the compiler should preserve any which are currently live.

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

For example, on x86 the cpuid instruction returns information in four parts using four fixed registers: %eax, %ebx, %ecx and %edx, based on the input value of %eax. If we were only interested in the result in %eax and %ebx, then we might (naively) write:

例如,在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) ); 

This would get the first and second parts of the result in C variables input_res1 and res2; however if GCC was using %ecx and %edx to hold other data; they would be overwritten by the cpuid instruction without gcc knowing. To prevent this; we use the clobber list:

这将得到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" );

As we have told GCC that %ecx and %edx will be overwritten by this asm call, it can handle the situation correctly - either by not using %ecx or %edx, or by saving their values to the stack before the asm function and restoring after.

正如我们告诉GCC的那样,%ecx和%edx将被这个asm调用覆盖,它可以正确处理这种情况 - 不使用%ecx或%edx,或者在asm函数和恢复之前将它们的值保存到堆栈中后。

Update:

With regards to your second question (why you are seeing a register listed in the clobber list for a popl instruction) - assuming your asm looks something like:

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

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

Then the code here is popping an item off the stack, however it doesn't care about the actual value - it's probably just keeping the stack balanced, or the value isn't needed in this code path. By writing this way, as opposed to:

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

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

You don't have to explicitly create a temporary variable to hold the unwanted value. Admittedly in this case there isn't a huge difference between the two, but the version with the clobber makes it clear that you don't care about the value from the stack.

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

#3


I suspect the overwrite list is just to give GCC a hint not to store anything of value in these registers across the ASM call; since GCC doesn't analyze what ASM you're giving it, and certain instructions have side-effects that touch other registers not explicitly named in the code, this is the way to tell GCC about it.

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

#1


If by "zeroed out" you mean "the values in the registers are replaced with 0's to prevent me from knowing what some other function was doing" then no, the registers are not zeroed out before use. But it shouldn't matter because you're telling GCC you plan to store information there, not that you want to read information that's currently there.

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

You give this information to GCC so that (reading the documentation) "you need not guess which registers or memory locations will contain the data you want to use" when you're finished with the assembly code (eg., you don't have to remember if the data will be in the stack register, or some other register).

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

GCC needs a lot of help for assembly code because "The compiler ... does not parse the assembler instruction template and does not know what it means or even whether it is valid assembler input. The extended asm feature is most often used for machine instructions the compiler itself does not know exist."

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

Update

GCC is designed as a multi-pass compiler. Many of the passes are in fact entirely different programs. A set of programs forming "the compiler" translate your source from C, C++, Ada, Java, etc. into assembly code. Then a separate program (gas, for GNU Assembler) takes that assembly code and turns it into a binary (and then ld and collect2 do more things to the binary). Assembly blocks exist to pass text directly to gas, and the clobber-list (and input list) exist so that the compiler can do whatever set up is needed to pass information between the C, C++, Ada, Java, etc. side of things and the gas side of things, and to guarantee that any important information currently in registers can be protected from the assembly block by copying it to memory before the assembly block runs (and copying back from memory afterward).

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

The alternative would be to save and restore every register for every assembly code block. On a RISC machine with a large number of registers that could get expensive (the Itanium has 128 general registers, another 128 floating point registers and 64 1-bit registers, for instance).

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

It's been a while since I've written any assembly code. And I have much more experience using GCC's named registers feature than doing things with specific registers. So, looking at an example:

我写了任何汇编代码已经有一段时间了。我使用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));
}

I have asked for an output register, which I will call reg inside the assembly code, and that GCC will automatically copy to the result variable on completion. There is no need to give this variable different names in C code vs assembly code; I only did it to show that it is possible. Whichever physical register GCC decides to use -- whether it's %%eax, %%ebx, %%ecx, etc. -- GCC will take care of copying any important data from that register into memory when I enter the assembly block so that I have full use of that register until the end of the assembly block.

我已经要求输出寄存器,我将在汇编代码中调用reg,并且GCC将在完成时自动复制到结果变量。没有必要在C代码和汇编代码中给这个变量赋予不同的名称;我只是表明它是可能的。无论GCC决定使用哪个物理寄存器 - 无论是%% eax,%% ebx,%% ecx等 - 当我进入汇编程序块时,GCC将负责将该寄存器中的任何重要数据复制到内存中,以便我在装配块结束之前充分利用该寄存器。

I have also asked for an input register, which I will call l both in C and in assembly. GCC promises that whatever physical register it decides to give me will have the value currently in the C variable l when I enter the assembly block. GCC will also do any needed recordkeeping to protect any data that happens to be in that register before I enter the assembly block.

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

What if I add a line to the assembly code? Say:

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

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

Since the compiler part of GCC doesn't check the assembly code it won't have protected the data in %%ecx. If I'm lucky, %%ecx may happen to be one of the registers GCC decided to use for %[reg] or %[l]. If I'm not lucky, I will have "mysteriously" changed a value in some other part of my program.

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

#2


No, they are not zeroed out. The purpose of the overwrite list (more commonly called the clobber list) is to inform GCC that, as a result of the asm instructions the register(s) listed in the clobber list will be modified, and so the compiler should preserve any which are currently live.

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

For example, on x86 the cpuid instruction returns information in four parts using four fixed registers: %eax, %ebx, %ecx and %edx, based on the input value of %eax. If we were only interested in the result in %eax and %ebx, then we might (naively) write:

例如,在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) ); 

This would get the first and second parts of the result in C variables input_res1 and res2; however if GCC was using %ecx and %edx to hold other data; they would be overwritten by the cpuid instruction without gcc knowing. To prevent this; we use the clobber list:

这将得到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" );

As we have told GCC that %ecx and %edx will be overwritten by this asm call, it can handle the situation correctly - either by not using %ecx or %edx, or by saving their values to the stack before the asm function and restoring after.

正如我们告诉GCC的那样,%ecx和%edx将被这个asm调用覆盖,它可以正确处理这种情况 - 不使用%ecx或%edx,或者在asm函数和恢复之前将它们的值保存到堆栈中后。

Update:

With regards to your second question (why you are seeing a register listed in the clobber list for a popl instruction) - assuming your asm looks something like:

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

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

Then the code here is popping an item off the stack, however it doesn't care about the actual value - it's probably just keeping the stack balanced, or the value isn't needed in this code path. By writing this way, as opposed to:

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

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

You don't have to explicitly create a temporary variable to hold the unwanted value. Admittedly in this case there isn't a huge difference between the two, but the version with the clobber makes it clear that you don't care about the value from the stack.

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

#3


I suspect the overwrite list is just to give GCC a hint not to store anything of value in these registers across the ASM call; since GCC doesn't analyze what ASM you're giving it, and certain instructions have side-effects that touch other registers not explicitly named in the code, this is the way to tell GCC about it.

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