linux系统挂掉问题的分析

玩linux系统,经常遇到的一件事就是做了某个操作之后系统会突然挂掉,这要怎么办?

1. 首先我们要看log,看看是否会留下一些蛛丝马迹,比如PC/LR是否有留下来。
PC是ARM的一个寄存器,即程序计数器,他记下的是当前程序执行的位置;
LR是link register,它保存的是当前函数的返回地址,
所以我们可以善用PC/LR来帮助我们查找问题的根源。

2. 假设我们知道系统挂掉时的PC值,同时我们要知道你的系统中挂掉的process是哪一个,
这样再使用ps aux | grep my_process获取这个process的pid。
获得了process的pid,我们可以使用cat /proc/pid/maps > ./pid_maps获取该procss的虚拟地址空间。
注意每一个用户process的虚拟地址空间都可能不一样,因为虚拟地址空间的关系,
在系统中每一个用户process都认为自己是系统中唯一的一个process。

3. 因为我们有了PC值,所以接下来在pid_maps中找到PC值位于哪一个shared library中,
也就是说系统挂掉的点是在哪个.so中挂掉。此时我们根据PC值结合这只挂掉的libtest.so
计算出在libtest.so中的偏移量。

4. readelf -a ./libtest.so | grep offset
或者nm ./libtest.so | grep offset
或者objdump -d libtest.so > libtest_disassemble.txt(建议使用objdump反汇编)
来查看offset对应的代码中的位置。

5. 结合源代码进行分析,找到系统挂掉的具体位置。

使用这种方法的缺点是:
1. 如果系统挂掉时已经破坏了线程栈,那利用PC值分析的意义不大;
2. 如果系统是挂在内核空间,那也无法确认问题点,除非能够恢复出用户空间的线程栈。

nm命令会列出symbol value(symbol offset)、symbol type以及symbol name。
我们常见的symbol type有"T"和"t",这两种类型的symbol都位于text section(即代码段),
其中symbol type为"T"的symbol对应的是extern类型的函数,为"t"的则对应的是static类型的函数。
因为symbol value和symbol name是对应的,所以我们可以根据PC确定offset后找到
相应的symbol name从而确定问题点。
nm libtest.so > nm_libtest.txt

readelf命令用来显示elf格式文件的信息,以本文讨论的问题范畴来讲,感兴趣的是symbols。
所以我们可以使用readelf -s libtest.so > readelf_libtest.txt将libtest.so中的symbol
dump出来,以便于我们排查问题。

objdump用来dump obj类型文件的信息,有了这条命令我们可以对编译好的obj文件进行反汇编,
这样可以去查看代码执行的流程。很多时候,对我们解决问题会非常有帮助。
objdump -d libtest.so > objdump_libtest.txt

为什么要计算PC对应在.so(shared library)中的偏移量?
shared library不同于静态库:静态库是在编译时即被集成到可执行文件中;
而动态库是在你的程序运行时才被加载到RAM中。
在被映射到虚拟地址空间之前,动态连接库中的各个符号之间的相对地址是确定的;
但是程序运行之前,无法确定其在虚拟地址空间中的位置,所以才需要计算偏移量。

在可执行程序中,仅仅有一个指向动态连接库的指针。/etc/ld.so.conf是动态连接库的配置文件,
在运行程序时当需要某个动态库时,ldconfig根据/etc/ld.so.conf中的配置选择加载特定的动态库到RAM中。

从以上描述,我们知道使用动态库的优点是:(1)可执行程序的代码量会变小;(2)动态库
独立于可执行程序,所以当我们要修改动态库时我们仅仅需要重新编译这个动态库而不需要
将整个可执行程序都重新编译一次;(3)另外可以在程序运行时去替换新的动态库,给调试
程序带来了方便。

正因为动态库的独立性,所以也决定了其一些缺点的存在:
(1)如果你的系统中缺少某个你需要的动态库,那只能在你运行时才会发现问题;
(2)如果你调用的动态库中缺少你需要的API,那也只能在运行程序时才能发现问题。
在之前写过的文章里面,有做过类似的试验,这里我们仍然可以做几个试验:
(1)将libtest.so所在的path从LD_LIBRARY_PATH中移除,再执行你的程序看一下:
sh#error while loading shared libraries:
(2)在系统总暂时性的将你的libtest.so删除,再执行你的程序看一下:
sh#error while loading shared libraries:
(3)将某个API从libtest.so中拿掉,再执行你的程序看一下:
sh#./test_main: symbol lookup error: ./libtest.so: undefined symbol:

如果是加载某个动态库出错,那么先用ldd命令(ldd实际上只是一个script)来查看
可执行文件需要用到哪些共享库,然后依次检查这些共享库是否都存在;
接下来检查共享库所在的path是否都有添加在LD_LIBRARY_PATH中。

如果是动态库中缺少某个API从而导致执行程序时失败,那首先要确认缺少的API是哪一个(如何确认?),
接下来使用nm/readelf或者是objdump来获取libtest.so中的symbol了,然后再确认需要的symbol是否
包含在libtest.so了。
nm libtest.so | grep your_api
readelf -s libtest.so | grep your_api
objdump -d libtest.so | grep your api
因为我们故意移除了your_api,所以在结果中应该搜索不到;
另外我们可以搜索其它的symbol看一下,应该会搜索到的,这样可以确认我们用的工具命令没有问题。

待解决问题:
可执行程序是如何调用动态库中提供的API?
为什么有时候找不到符号,是因为在编译代码时被stripped掉了吗?

原文地址:https://www.cnblogs.com/keanuyaoo/p/3318038.html