【问题标题】:Why the backtrace may be not correct when enable tail optimization为什么启用尾部优化时回溯可能不正确
【发布时间】:2021-05-28 11:28:06
【问题描述】:

https://www.gnu.org/software/libc/manual/html_node/Backtraces.html:

请注意,某些编译器优化可能会干扰获得有效的回溯。函数内联导致内联函数没有堆栈帧;尾调用优化将一个堆栈帧替换为另一个;帧指针消除将阻止回溯正确解释堆栈内容。

我想知道为什么在启用尾部优化时会发生这种情况以及如何避免它。

【问题讨论】:

    标签: gcc gdb clang compiler-optimization


    【解决方案1】:

    我想知道为什么在启用尾部优化时会发生这种情况

    尾调用优化会发生这种情况,因为您的递归函数在编译器完成后将不再递归,或者调用堆栈中将完全丢失尾递归函数。

    例子:

    // t.c
    int foo() { return 42; }
    int bar() { return foo(); }
    int main() { return bar(); }
    
    gcc -O2 -fno-inline t.c && gdb -q ./a.out
    
    (gdb) b foo
    Breakpoint 1 at 0x1140
    (gdb) run
    Starting program: /tmp/a.out
    
    Breakpoint 1, 0x0000555555555140 in foo ()
    (gdb) bt
    #0  0x0000555555555140 in foo ()
    #1  0x00007ffff7dfad0a in __libc_start_main (main=0x555555555040 <main>, argc=1, argv=0x7fffffffdc08, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdbf8) at ../csu/libc-start.c:308
    #2  0x000055555555507a in _start ()
    

    main()bar() 去哪儿了?我们知道main() 必须仍在堆栈中。

    它们是尾调用优化的:

    (gdb) disas main
    Dump of assembler code for function main:
       0x0000555555555040 <+0>:     xor    %eax,%eax
       0x0000555555555042 <+2>:     jmpq   0x555555555150 <bar>
    End of assembler dump.
    (gdb) disas bar
    Dump of assembler code for function bar:
       0x0000555555555150 <+0>:     xor    %eax,%eax
       0x0000555555555152 <+2>:     jmp    0x555555555140 <foo>
    End of assembler dump.
    

    如何避免。

    避免它的唯一方法是禁用尾调用优化(使用-fno-optimize-sibling-calls):

    gcc -O2 -fno-inline t.c -fno-optimize-sibling-calls && gdb -q ./a.out
    
    (gdb) b foo
    Breakpoint 1 at 0x1140
    (gdb) run
    Starting program: /tmp/a.out
    
    Breakpoint 1, 0x0000555555555140 in foo ()
    (gdb) bt
    #0  0x0000555555555140 in foo ()
    #1  0x0000555555555157 in bar ()
    #2  0x0000555555555047 in main ()
    

    【讨论】:

    • 感谢您的回答,但是如果 bar() 内部发生崩溃,则回溯仍然显示 bar() 的框架,这意味着回溯仍然指向导致崩溃的指令,我没有看到“尾调用优化用另一个堆栈帧替换一个堆栈帧”的情况`int foo() { return 42; }int bar() { int a[10]; a[2000]=1; return foo(); }int main() { return bar(); }`
    • @BiaoCao "回溯将显示bar()" -- 是的,但不是main()。换句话说,回溯是无效的,因为它缺少信息。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-08
    • 2020-06-13
    • 1970-01-01
    • 2013-10-28
    • 2021-05-09
    • 1970-01-01
    相关资源
    最近更新 更多