如何解决LTO 基础知识构建顺序以及如何分析核心转储
正如标题所说,我确实有两个关于 LTO 的基本问题。首先,这里有一些简单的演示源文件来展示我的基本理解问题。
void fnoop(void) {
}
testlib.c
(作为共享库)循环 fnoop 函数以演示优化并强制段错误为:
#include <stdio.h>
#include <stdlib.h>
void fnoop(void);
void force_sigsegv(void) {
int *p = NULL;
int a = 0;
unsigned long i = 0;
for (i; i < 1000000000; ++i) {
fnoop();
}
a = *p; //segfault
printf("print a: %d \n",a);
}
这里的主要应用程序与 libtestlib 链接为:
#include <stdio.h>
#include <stdlib.h>
void force_sigsegv(void);
int main(void) {
printf("force a sigsegv \n");
force_sigsegv();
return 0;
}
因此,这里有两种构建主应用程序的方法: (a) 构建 testlib 共享库和链接应用程序的单独步骤:
mkdir lto
gcc -flto -O3 -c noop.c
gcc -flto -O3 -fPIC -shared -Wl,-soname,libtestlib.so.1 -o lto/libtestlib.so.1 testlib.c noop.o
ln -sf libtestlib.so.1 lto/libtestlib.so
gcc -flto -O3 -o test_lto main.c -L $(pwd)/lto -ltestlib
(b) 单步构建 testlib 共享库和链接应用程序:
mkdir lto_single
gcc -flto -O3 -fPIC -shared -Wl,libtestlib.so.1 -o lto_single/libtestlib.so.1 testlib.c noop.c
ln -sf libtestlib.so.1 lto_single/libtestlib.so
gcc -flto -O3 -o test_lto_single main.c -L $(pwd)/lto_single -ltestlib
使用 (a) for 循环优化为:
00000000000011b0 <force_sigsegv>: 11b0: 8b 04 25 00 00 00 00 mov 0x0,%eax
11b7: 0f 0b ud2
11b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
和 (b) for 循环保持为:
00000000000011c0 <force_sigsegv>: 11c0: 53 push %rbx
11c1: bb 00 ca 9a 3b mov $0x3b9aca00,%ebx
11c6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
11cd: 00 00 00
11d0: e8 4b fe ff ff callq 1020 <fnoop@plt>
11d5: 48 83 eb 01 sub $0x1,%rbx
11d9: 75 f5 jne 11d0 <force_sigsegv+0x10>
11db: 8b 04 25 00 00 00 00 mov 0x0,%eax
11e2: 0f 0b ud2
11e4: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,1)
11eb: 00 00 00
11ee: 66 90 xchg %ax,%ax
问题 1:为什么在 (b) 的情况下 for 循环没有优化?
接下来:分析上面两个应用程序生成的核心转储。 假设我们有 test_lto,它使用来自 (a) 的优化的 testlib 共享库,在我们的生产系统中,它强制核心转储,然后用户将其发送给我。我将从以下方面开始分析: (1) 加载到gdb中查找crash地址,例如:
Core was generated by `./test_lto'.
Program terminated with signal SIGSEGV,Segmentation fault.
#0 0x00007fba3aa211b0 in ?? ()
(2) 使用readelf -n corefile
查找地址所属的对象,例如:
0x00007fba3aa20000 0x00007fba3aa21000 0x0000000000000000
/volume/LTO/lto/libtestlib.so.1
0x00007fba3aa21000 0x00007fba3aa22000 0x0000000000000001
/volume/LTO/lto/libtestlib.so.1
(3) 使用 readelf -t libtestlib.so.1
查找 lto 版本的 testlib 的文本段偏移量,例如:
[ 8] .text
PROGBITS 0000000000001040 0000000000001040 0
最后将这些信息添加到 gdb 中:
(gdb) core lto/core
[New LWP 1599]
Core was generated by `./test_lto'.
Program terminated with signal SIGSEGV,Segmentation fault.
#0 0x00007fba3aa211b0 in ?? ()
(gdb) add-symbol-file lto/libtestlib.so.1 0x00007fba3aa20000+0x1040
add symbol table from file "lto/libtestlib.so.1" at
.text_addr = 0x7fba3aa21040
(y or n) y
Reading symbols from lto/libtestlib.so.1...
(gdb) bt
#0 0x00007fba3aa211b0 in force_sigsegv ()
因此,无法收集(有用的)崩溃细节。即使使用来自 (b) 的 lto_single 版本的 testlib 也没有提供更多细节。但是,构建相同的对象而无需 -flto
但使用 -g
并使用相同的地址 0x00007fba3aa20000+0x1040
加载到 gdb 给出:
Reading symbols from nlto/libtestlib.so.1...
(gdb) bt
#0 force_sigsegv () at testlib.c:13
But(!) 检查第 13 行指向 for 循环内的 fnoop()
调用。所以跟踪不正确导致信息错误。
问题 2:如何分析优化二进制文件 (-flto) 生成的核心转储?
谢谢!
解决方法
解决了! GCC-6 存在限制(参见 here):
链接时优化不适用于调试信息的生成。将 -flto 与 -g 结合使用目前处于试验阶段,预计会产生意想不到的结果。
使用 GCC-10(也使用 GCC-9 进行检查)删除了此限制(请参阅 here)。
因此,当混合 -flto
和 -g
时,有关错误核心转储跟踪的问题得到解决(使用 GCC-9/10 进行测试)。照常做,发布剥离版本并保留调试版本以供事后分析。
然而,问题 1 仍然存在。
同样来自 GCC-9 文档:
启用链接时优化的另一种(更简单)方法是:
gcc -o myprog -flto -O2 foo.c bar.c
我想知道为什么这不会产生与(甚至文档说明应该的)相同的反汇编:
gcc -c -O2 -flto foo.c
gcc -c -O2 -flto bar.c
gcc -o myprog -flto -O2 foo.o bar.o
所以如果有人可以在这里提供帮助,请这样做:-)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。