Linux环境下的动态链接库基础与常见调试技巧总结

Linux环境下的动态链接库基础与常见调试技巧总结

  • 要点速览
    • Linux 三个调试必会命令
    • 两个重要的环境变量
  • 动态链接库的基础
    • 动态链接库的编译及使用
  • Linux 下动态链接库的调试技巧
    • 常见问题一:
    • 常见问题二:
  • LD_PRELOAD之偷梁换柱

要点速览

Linux 三个调试必会命令

ldd  查看可执行文件和动态链接库文件的依赖
nm 查看对象的名称列表
readelf -s 
c++filt 帮助转化 nm/objectdump 等工具显示的符号转化成可读的名称

两个重要的环境变量

LD_LIBRARY_PATH : 显示指定动态链接库的路径
LD_PRELOAD: 偷梁换柱,优先执行

动态链接库的基础

动态链接库顾名思义是程序执行的时候动态加载链接的库函数。关于其详细的定义很多文章都已经做了详细的说明,本文主要通过一个小例子说明动态库的使用及其常见的调试技巧。

动态链接库的编译及使用

假如我们有一个 libdanymic_link.so的动态链接库,其只包含一个myprint的函数。

//dynamic_link.h
#ifndef _DYMAMIC_H_
#define _DYMAMIC_H_
#include
int myprint();
#endif
//dynamic_link.cpp
#include "dynamic_link.h"
int myprint()
{
       printf("hello, world Yeah\n");
}

编译生成动态链接库:

gcc -o libdynamic_link.so -fPIC -shared dynamic_link.cpp

使用动态链接库:

#include "dynamic_link.h"
int main()
{
   myprint();
   return 0;
}

gcc -o test_dynamic_link -L./ -ldynamic_link test_dynamic_link.cpp

输出:

./test_dynamic_link: error while loading shared libraries: libdynamic_link.so: cannot open shared object file: No such file or directory

Linux 下动态链接库的调试技巧

常见问题一:

error while loading shared libraries: libxxx.so: cannot open shared object file: No such file or directory

解决办法:
1)利用 ldd 命令查看可执行文件/动态链接库 文件的依赖
2) 添加 LD_LIBRARY_PATH 为 “not found” 的动态链接库指定路径

$ ldd test_dynamic_link
linux-vdso.so.1 =>  (0x00007ffcd3f72000)
        libdynamic_link_preload.so => not found
        libc.so.6 => /lib64/libc.so.6 (0x00007feb99345000)
        /lib64/ld-linux-x86-64.so.2 (0x0000556775112000)

在上面小例子中,我们看到我们定义了libdanymic_link.so并且已经成功编译,但是在执行过程中却出现找不到 libdynamic_link.so。因为 libdanymic_link.so是用户自定义的一个第三方库,并不在动态库默认查找路径中,此时我们需要借助 LD_LIBRARY_PATH 指定动态链接库的路径

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./

输出:

hello, world Yeah

常见问题二:

symbol lookup error: ./test_dynamic_link: undefined symbol: _Z7myprintv

动态链接库一般是第三方库函数,一般情况下第三方库函数发生更新时只需要做动态库的替换即可,并不需要重新编译当前应用。但是,如果此时第三方库函数(完整)名称发生了改变,调用时不做匹配的修改可能会出现符号找不到的现象。
比如,假如我在写 dynamic_link.cpp 的时候一不小心将 myprint的名称改成了myprint1:

#include "dynamic_link.h"
int myprint1()
{
       printf("hello, world Yeah\n");
}

此时我们编译是可以正常通过的,但是我们这时候在不重新编译测试函数,直接执行就会出现上述找不到符号定义的错误。

gcc -o libdynamic_link.so -fPIC -shared dynamic_link.cpp

解决办法:
1)通过 c++filt 查看它的的实际函数名称,然后猜测可能是出问题的动态链接库
2)通过 nm 命令查看改符号在对应的链接库中是否正确定义

c++filt _Z7myprintv
myprint()

因为这里我们知道myprint是在libdynamic_link.so 中定义的,所以可以直接锁定, 有时候可能需要多试几个文件。这里在用 grep 匹配的时候可以用函数名子串可以防止漏掉目标行。

$ nm -A libdynamic_link.so |grep  myprint
libdynamic_link.so:00000000000006c5 T _Z8myprint1v
$ nm -A libdynamic_link.so |grep  mypri
libdynamic_link.so:00000000000006c5 T _Z8myprint1v
$ c++filt _Z8myprint1v
myprint1()

通过分析我们发现,库函数中可以找到 myprint1但是却没有myprint所以导致了符号找不到的问题。

LD_PRELOAD之偷梁换柱

说白了意思就是说,我们可以实现一个跟原来函数声明完全相同的函数,做一个不同的实现版本。比如这里的myprint函数,我重新写一个dynamic_link_preload.cpp, 内容如下:

#include "dynamic_link.h"

int myprint(){
    printf("myprint LD_PRELOAD\n");
}

这个文件重新定义了myprint函数的实现,然后在测试的时候我只需要:

$ export LD_PRELOAD=./libdynamic_link_preload.so
$ ./test_dynamic_link
myprint LD_PRELOAD

这个的好处就是可以在不修改源码的情况下方便替换第三方库的实现,比如c++编译中默认的内存分配函数malloc的分配算法可能是 TCMalloc, 但是我们发现在有些工作负载中 JEMalloc可能会获得更好的性能 ,这时候我们就可以借助 LD_PRELOAD 方便替换底层分配算法,关于这几种分配算法的详细内容清关注后续博客。

你可能感兴趣的:(Linux,C++)