简体   繁体   English

gcc删除内联汇编程序代码

[英]gcc removes inline assembler code

It seems like gcc 4.6.2 removes code it considers unused from functions. 似乎gcc 4.6.2删除了它认为从函数中未使用的代码。

test.c test.c的

int main(void) {
  goto exit;
  handler:
    __asm__ __volatile__("jmp 0x0");
  exit:
  return 0;
}

Disassembly of main() 拆卸main()

   0x08048404 <+0>:     push   ebp
   0x08048405 <+1>:     mov    ebp,esp
   0x08048407 <+3>:     nop    # <-- This is all whats left of my jmp.
   0x08048408 <+4>:     mov    eax,0x0
   0x0804840d <+9>:     pop    ebp
   0x0804840e <+10>:    ret

Compiler options 编译器选项

No optimizations enabled, just gcc -m32 -o test test.c ( -m32 because I'm on a 64 bit machine). 没有启用优化,只有gcc -m32 -o test test.c-m32因为我在64位机器上)。

How can I stop this behavior? 我怎么能阻止这种行为?

Edit: Preferably by using compiler options, not by modifing the code. 编辑:最好通过使用编译器选项,而不是通过修改代码。

Looks like that's just the way it is - When gcc sees that code within a function is unreachable, it removes it. 看起来就是这样 - 当gcc看到函数中的代码无法访问时,它会删除它。 Other compilers might be different. 其他编译器可能不同。
In gcc , an early phase in compilation is building the "control flow graph" - a graph of "basic blocks", each free of conditions, connected by branches. gcc ,编译的早期阶段是构建“控制流图” - 一个“基本块”的图形,每个条件都没有条件,通过分支连接。 When emitting the actual code, parts of the graph, which are not reachable from the root, are discarded. 在发出实际代码时,将丢弃无法从根访问的图形部分。
This isn't part of the optimization phase, and is therefore unaffected by compilation options. 这不是优化阶段的一部分,因此不受编译选项的影响。

So any solution would involve making gcc think that the code is reachable. 所以任何解决方案都会让gcc认为代码是可以访问的。

My suggestion: 我的建议:

Instead of putting your assembly code in an unreachable place (where GCC may remove it), you can put it in a reachable place, and skip over the problematic instruction: 您可以将它放在可到达的地方,并跳过有问题的指令,而不是将汇编代码放在无法到达的地方(GCC可能会删除它)。

int main(void) {
     goto exit;

     exit:
     __asm__ __volatile__ (
        "jmp 1f\n"
        "jmp $0x0\n"
        "1:\n"
    );
    return 0;
}

Also, see this thread about the issue . 另外,请参阅此主题有关该问题

I do not believe there is a reliable way using just compile options to solve this. 我不相信有一种可靠的方法只使用编译选项来解决这个问题。 The preferable mechanism is something that will do the job and work on future versions of the compiler regardless of the options used to compile. 无论用于编译的选项如何,优选的机制都可以完成工作并在未来版本的编译器上工作。


Commentary about Accepted Answer 关于公认答案的评论

In the accepted answer there is an edit to the original that suggests this solution: 在接受的答案中,对原始文件进行了编辑,建议使用此解决方案:

int main(void) {
  __asm__ ("jmp exit");

  handler:
      __asm__ __volatile__("jmp $0x0");
  exit:
  return 0;
}

First off jmp $0x0 should be jmp 0x0 . 首先关闭jmp $0x0应该是jmp 0x0 Secondly C labels usually get translated into local labels. 其次, C标签通常会被翻译成本地标签。 jmp exit doesn't actually jump to the label exit in the C function, it jumps to the exit function in the C library effectively bypassing the return 0 at the bottom of main . jmp exit实际上没有跳转到C函数中的标签exit ,它跳转到C库中的exit函数,有效地绕过main底部的return 0 Using Godbolt with GCC 4.6.4 we get this non-optimized output (I have trimmed the labels we don't care about): 使用Godbolt和GCC 4.6.4我们得到了这个非优化的输出(我已经修剪了我们不关心的标签):

main:
        pushl   %ebp
        movl    %esp, %ebp
        jmp exit
        jmp 0x0
.L3:
        movl    $0, %eax
        popl    %ebp
        ret

.L3 is actually the local label for exit . .L3实际上是exit的本地标签。 You won't find the exit label in the generated assembly. 您将无法在生成的程序集中找到exit标签。 It may compile and link if the C library is present. 如果存在C库,它可以编译和链接。 Do not use C local goto labels in inline assembly like this. 不要像这样在内联汇编中使用C本地goto标签。


Use asm goto as the Solution 使用asm goto作为解决方案

As of GCC 4.5 (OP is using 4.6.x) there is support for asm goto extended assembly templates . 从GCC 4.5(OP使用4.6.x)开始,支持asm goto扩展程序集模板 asm goto allows you to specify jump targets that the inline assembly may use: asm goto允许您指定内联汇编可能使用的跳转目标:

6.45.2.7 Goto Labels 6.45.2.7转到标签

asm goto allows assembly code to jump to one or more C labels. asm goto允许汇编代码跳转到一个或多个C标签。 The GotoLabels section in an asm goto statement contains a comma-separated list of all C labels to which the assembler code may jump. asm goto语句中的GotoLabels部分包含汇编代码可能跳转到的所有C标签的逗号分隔列表。 GCC assumes that asm execution falls through to the next statement (if this is not the case, consider using the __builtin_unreachable intrinsic after the asm statement). GCC假定asm执行落到下一个语句(如果不是这种情况,请考虑在asm语句之后使用__builtin_unreachable内在函数)。 Optimization of asm goto may be improved by using the hot and cold label attributes (see Label Attributes). 可以通过使用热标签属性和冷标签属性来改进asm goto的优化(请参阅标签属性)。

An asm goto statement cannot have outputs. asm goto语句不能有输出。 This is due to an internal restriction of the compiler: control transfer instructions cannot have outputs. 这是由于编译器的内部限制:控制传输指令不能有输出。 If the assembler code does modify anything, use the "memory" clobber to force the optimizers to flush all register values to memory and reload them if necessary after the asm statement. 如果汇编代码确实修改了任何内容,请使用“memory”clobber强制优化器将所有寄存器值刷新到内存,并在asm语句之后根据需要重新加载它们。

Also note that an asm goto statement is always implicitly considered volatile. 另请注意,asm goto语句始终隐式地被视为volatile。

To reference a label in the assembler template, prefix it with '%l' (lowercase 'L') followed by its (zero-based) position in GotoLabels plus the number of input operands. 要在汇编程序模板中引用标签,请在其前面加上'%l'(小写'L'),后跟在GotoLabels中的(从零开始)位置加上输入操作数的数量。 For example, if the asm has three inputs and references two labels, refer to the first label as '%l3' and the second as '%l4'). 例如,如果asm有三个输入并引用两个标签,请将第一个标签称为“%l3”,将第二个标签称为“%l4”。

Alternately, you can reference labels using the actual C label name enclosed in brackets. 或者,您可以使用括在括号中的实际C标签名称来引用标签。 For example, to reference a label named carry, you can use '%l[carry]'. 例如,要引用名为carry的标签,可以使用'%l [carry]'。 The label must still be listed in the GotoLabels section when using this approach. 使用此方法时,标签仍必须列在GotoLabels部分中。

The code could be written this way: 代码可以这样写:

int main(void) {
  __asm__ goto ("jmp %l[exit]" :::: exit);
  handler:
      __asm__ __volatile__("jmp 0x0");
  exit:
  return 0;
}

We can use asm goto . 我们可以使用asm goto I prefer __asm__ over asm since it will not throw warnings if compiling with -ansi or -std=? 我更喜欢__asm__ over asm因为如果使用-ansi-std=?编译它不会发出警告-std=? options. 选项。 After the clobbers you can list the jump targets the inline assembly may use. 在clobbers之后,您可以列出内联汇编可能使用的跳转目标。 C doesn't actually know if we jump or not as GCC doesn't analyze the actual code in the inline assembly template. C实际上并不知道我们是否跳转,因为GCC不分析内联汇编模板中的实际代码。 It can't remove this jump, nor can it assume what comes after is dead code. 它不能删除这个跳转,也不能假设死代码之后的内容。 Using Godbolt with GCC 4.6.4 the unoptimized code (trimmed) looks like: 使用Godbolt与GCC 4.6.4未经优化的代码(修剪)看起来像:

main:
        pushl   %ebp
        movl    %esp, %ebp
        jmp .L2                   # <------ this is the goto exit
        jmp 0x0
.L2:                              # <------ exit label
        movl    $0, %eax
        popl    %ebp
        ret

The Godbolt with GCC 4.6.4 output still looks correct and appears as: 具有GCC 4.6.4输出的Godbolt看起来仍然正确并显示为:

main:
        jmp .L2                   # <------ this is the goto exit
        jmp 0x0
.L2:                              # <------ exit label
        xorl    %eax, %eax
        ret

This mechanism should also work whether you have optimizations on or off, and shouldn't matter whether you are compiling for 64-bit or 32-bit x86 targets. 无论您是打开还是关闭优化,此机制也应该起作用,无论您是编译64位还是32位x86目标都无关紧要。


Other Observations 其他观察

  • When there are no output constraints in an extended inline assembly template the asm statement is implicitly volatile. 当扩展内联汇编模板中没有输出约束时, asm语句是隐式volatile。 The line 这条线

     __asm__ __volatile__("jmp 0x0"); 

    Can be written as: 可以写成:

     __asm__ ("jmp 0x0"); 
  • asm goto statements are considered implicitly volatile. asm goto语句被认为是隐式不稳定的。 They don't require a volatile modifier either. 它们也不需要volatile改性剂。

Would this work, make it so gcc can't know its unreachable 这会有用吗,让它如此gcc无法知道它无法到达

int main(void)  
{ 
    volatile int y = 1;
    if (y) goto exit;
handler:
    __asm__ __volatile__("jmp 0x0");  
exit:   
    return 0; 
}

If a compiler thinks it can cheat you, just cheat back: (GCC only) 如果编译器认为它可以欺骗你,只需作弊:(仅限GCC)

int main(void) {
    {
        /* Place this code anywhere in the same function, where
         * control flow is known to still be active (such as at the start) */
        extern volatile unsigned int some_undefined_symbol;
        __asm__ __volatile__(".pushsection .discard" : : : "memory");
        if (some_undefined_symbol) goto handler;
        __asm__ __volatile__(".popsection" : : : "memory");
    }
    goto exit;
handler:
    __asm__ __volatile__("jmp 0x0");
    exit:
    return 0;
}

This solution will not add any additional overhead for meaningless instructions, though only works for GCC when used with AS (as is the default). 此解决方案不会为无意义指令添加任何额外开销,但仅在与AS一起使用时才适用于GCC(默认情况下)。

Explaination: .pushsection switches text output of the compiler to another section, in this case .discard (which is deleted during linking by default). 解释: .pushsection将编译器的文本输出切换到另一个部分,在本例中为.discard (默认情况下在链接期间删除)。 The "memory" clobber prevents GCC from trying to move other text within the section that will be discarded. "memory" clobber阻止GCC尝试移动将被丢弃的部分中的其他文本。 However, GCC doesn't realize (and never could because the __asm__ s are __volatile__ ) that anything happening between the 2 statements will be discarded. 但是,GCC没有意识到(并且永远不可能因为__asm____volatile__ )2个语句之间发生的任何事情都将被丢弃。

As for some_undefined_symbol , that is literally just any symbol that is never being defined (or is actually defined, it shouldn't matter). 对于some_undefined_symbol ,这实际上只是从未定义的任何符号(或实际定义的,它应该无关紧要)。 And since the section of code using it will be discarded during linking, it won't produce any unresolved-reference errors either. 并且由于使用它的代码段将在链接期间被丢弃,因此它也不会产生任何未解析的引用错误。

Finally, the conditional jump to the label you want to make appear as though it was reachable does exactly that. 最后,条件跳转到您想要制作的标签看起来好像是可以到达的那样。 Besides that fact that it won't appear in the output binary at all, GCC realizes that it can't know anything about some_undefined_symbol , meaning it has no choice but to assume that both of the if's branches are reachable, meaning that as far as it is concerned, control flow can continue both by reaching goto exit , or by jumping to handler (even though there won't be any code that could even do this) 除了它根本不会出现在输出二进制文件中之外,GCC意识到它对some_undefined_symbol ,这意味着它别无选择,只能假设两个if的分支都是可达的,这意味着值得关注的是,控制流可以通过到达goto exit或跳转到handler来继续(即使没有任何代码甚至可以执行此操作)

However, be careful when enabling garbage collection in your linker ld --gc-sections (it's disabled by default), because otherwise it might get the idea to get rid of the still unused label regardless. 但是,在链接器ld --gc-sections启用垃圾收集时要小心(默认情况下禁用它),否则它可能会想到摆脱仍然未使用的标签。

EDIT: Forget all that. 编辑:忘记这一切。 Just do this: 这样做:

int main(void) {
    __asm__ __volatile__ goto("" : : : : handler);
    goto exit;
handler:
    __asm__ __volatile__("jmp 0x0");
exit:
    return 0;
}

Update 2012/6/18 更新2012/6/18

Just thinking about it, one can put the goto exit in an asm block, which means that only 1 line of code needs to change: 考虑一下,可以将goto exit放在asm块中,这意味着只需要更改一行代码:

int main(void) {
  __asm__ ("jmp exit");

  handler:
    __asm__ __volatile__("jmp $0x0");
  exit:
  return 0;
}

That is significantly cleaner than my other solution below (and possibly nicer than @ugoren's current one too). 这比我下面的其他解决方案要清晰得多(也可能比@ ugoren当前的更好)。


This is pretty hacky, but it seems to work: hide the handler in a conditional that can never be followed under normal conditions, but stop it from being eliminated by stopping the compiler from being able to do its analysis properly with some inline assembler. 这是非常hacky,但它似乎工作:将处理程序隐藏在正常条件下永远不会被遵循的条件中,但是通过阻止编译器能够使用某些内联汇编程序正确地进行分析来阻止它被消除。

int main (void) {
  int x = 0;
  __asm__ __volatile__ ("" : "=r"(x));
  // compiler can't tell what the value of x is now, but it's always 0

  if (x) {
handler:
    __asm__ __volatile__ ("jmp $0x0");
  }

  return 0;
}

Even with -O3 the jmp is preserved: 即使使用-O3jmp也会被保留:

    testl   %eax, %eax   
    je      .L2     
.L3:
    jmp $0x0
.L2:
    xorl    %eax, %eax 
    ret

(This seems really dodgy, so I hope there is a better way to do this. edit just putting a volatile in front of x works so one doesn't need to do the inline asm trickery.) (这看起来狡猾,所以我希望有更好的方法来做到这一点。 编辑只是在x前面放一个volatile可以了,所以不需要做内联asm技巧。)

I've never heard of a way to prevent gcc from removing unreachable code; 我从来没有听说过阻止gcc删除无法访问的代码的方法; it seems that no matter what you do, once gcc detects unreachable code it always removes it (use gcc's -Wunreachable-code option to see what it considers to be unreachable). 似乎无论你做什么,一旦gcc检测到无法访问的代码,它总是将其删除(使用gcc的-Wunreachable-code选项来查看它认为无法访问的内容)。

That said, you can still put this code in a static function and it won't be optimized out: 也就是说,您仍然可以将此代码放在静态函数中,并且不会进行优化:

static int func()
{
    __asm__ __volatile__("jmp $0x0");
}

int main(void)
{
    goto exit;

handler:
    func();

exit:
    return 0;
}

PS PS
This solution is particularily handy if you want to avoid code redundancy when implanting the same "handler" code block in more than one place in the original code. 如果您希望在原始代码中的多个位置植入相同的“处理程序”代码块时避免代码冗余,则此解决方案特别方便。

gcc may duplicate asm statements inside functions and remove them during optimisation (even at -O0), so this will never work reliably. gcc可以在函数内复制asm语句并在优化期间删除它们(即使在-O0),因此这将永远无法可靠地工作。

one way to do this reliably is to use a global asm statement (ie an asm statement outside of any function). 可靠地执行此操作的一种方法是使用全局asm语句(即任何函数之外的asm语句)。 gcc will copy this straight to the output and you can use global labels without any problems. gcc会将此直接复制到输出中,您可以毫无问题地使用全局标签。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM