ibl*_*lue 17 c x86 gcc inline-assembly compiler-optimization
似乎gcc 4.6.2删除了它认为从函数中未使用的代码.
int main(void) {
goto exit;
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
没有启用优化,只是gcc -m32 -o test test.c(-m32因为我在64位机器上).
我怎么能阻止这种行为?
编辑:最好通过使用编译器选项,而不是通过修改代码.
看起来就是这样 - 当gcc看到函数中的代码无法访问时,它会将其删除.其他编译器可能不同.
在gcc编译的早期阶段是构建"控制流图" - 一个"基本块"的图形,每个条件都没有条件,通过分支连接.在发出实际代码时,将丢弃无法从根访问的图形部分.
这不是优化阶段的一部分,因此不受编译选项的影响.
所以任何解决方案都需要gcc考虑代码是否可达.
我的建议:
您可以将它放在可到达的地方,并跳过有问题的指令,而不是将汇编代码放在无法到达的地方(GCC可能会删除它).
int main(void) {
goto exit;
exit:
__asm__ __volatile__ (
"jmp 1f\n"
"jmp $0x0\n"
"1:\n"
);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
另外,请参阅此主题有关该问题.
我不相信有一种可靠的方法只使用编译选项来解决这个问题.无论用于编译的选项如何,优选的机制都可以完成工作并在未来版本的编译器上工作.
在接受的答案中,对原始文件进行了编辑,建议使用此解决方案:
int main(void) {
__asm__ ("jmp exit");
handler:
__asm__ __volatile__("jmp $0x0");
exit:
return 0;
}
Run Code Online (Sandbox Code Playgroud)
首先jmp $0x0应该是jmp 0x0.其次,C标签通常会被翻译成本地标签.jmp exit实际上并不跳转到标签exit中Ç功能,跳转到exit在功能Ç库有效地绕过return 0在底部main.使用Godbolt和GCC 4.6.4我们得到了这个非优化的输出(我已经修剪了我们不关心的标签):
main:
pushl %ebp
movl %esp, %ebp
jmp exit
jmp 0x0
.L3:
movl $0, %eax
popl %ebp
ret
Run Code Online (Sandbox Code Playgroud)
.L3实际上是本地标签exit.您将无法exit在生成的程序集中找到标签.如果存在C库,它可以编译和链接.不要像这样在内联汇编中使用C本地goto标签.
从GCC 4.5(OP使用4.6.x)开始,支持asm goto扩展的程序集模板.asm goto允许您指定内联汇编可能使用的跳转目标:
6.45.2.7转到标签
asm goto允许汇编代码跳转到一个或多个C标签.asm goto语句中的GotoLabels部分包含汇编代码可能跳转到的所有C标签的逗号分隔列表.GCC假定asm执行落到下一个语句(如果不是这种情况,请考虑在asm语句之后使用__builtin_unreachable内在函数).可以通过使用热标签属性和冷标签属性来改进asm goto的优化(请参阅标签属性).
asm goto语句不能有输出.这是由于编译器的内部限制:控制传输指令不能有输出.如果汇编代码确实修改了任何内容,请使用"memory"clobber强制优化器将所有寄存器值刷新到内存,并在asm语句之后根据需要重新加载它们.
另请注意,asm goto语句始终隐式地被视为volatile.
要在汇编程序模板中引用标签,请在其前面加上'%l'(小写'L'),后跟在GotoLabels中的(从零开始)位置加上输入操作数的数量.例如,如果asm有三个输入并引用两个标签,请将第一个标签称为"%l3",将第二个标签称为"%l4".
或者,您可以使用括在括号中的实际C标签名称来引用标签.例如,要引用名为carry的标签,可以使用'%l [carry]'.使用此方法时,标签仍必须列在GotoLabels部分中.
代码可以这样写:
int main(void) {
__asm__ goto ("jmp %l[exit]" :::: exit);
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我们可以用asm goto.我更喜欢__asm__,asm因为如果编译-ansi或-std=?选项它不会发出警告.在clobbers之后,您可以列出内联汇编可能使用的跳转目标.C实际上并不知道我们是否跳转,因为GCC不分析内联汇编模板中的实际代码.它不能删除这个跳转,也不能假设死代码之后发生了什么.使用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
Run Code Online (Sandbox Code Playgroud)
具有GCC 4.6.4输出的Godbolt看起来仍然正确并显示为:
main:
jmp .L2 # <------ this is the goto exit
jmp 0x0
.L2: # <------ exit label
xorl %eax, %eax
ret
Run Code Online (Sandbox Code Playgroud)
无论您是打开还是关闭优化,此机制也应该起作用,无论您是编译64位还是32位x86目标都无关紧要.
当扩展内联汇编模板中没有输出约束时,该asm语句是隐式volatile.这条线
__asm__ __volatile__("jmp 0x0");
Run Code Online (Sandbox Code Playgroud)
可以写成:
__asm__ ("jmp 0x0");
Run Code Online (Sandbox Code Playgroud)asm goto语句被认为是隐含的不稳定的.它们也不需要volatile修饰符.
更新2012/6/18
想一想,可以将其放在goto exit一个asm块中,这意味着只需要更改1行代码:
int main(void) {
__asm__ ("jmp exit");
handler:
__asm__ __volatile__("jmp $0x0");
exit:
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这比我下面的其他解决方案要干净得多(也可能比 @ugoren 当前的解决方案更好)。
这非常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;
}
Run Code Online (Sandbox Code Playgroud)
即使-O3保留jmp了:
testl %eax, %eax
je .L2
.L3:
jmp $0x0
.L2:
xorl %eax, %eax
ret
Run Code Online (Sandbox Code Playgroud)
(这看起来确实很狡猾,所以我希望有一种更好的方法来做到这一点。编辑只需在作品volatile前面放置一个,这样就不需要执行内联汇编技巧。)x