程序的链接和加载基础(2)

1.7 符号查找路径

 

通常在.h文件中声明函数,在.c/.cc文件(静态库/动态库)中定义函数。上面的演示中,在需要函数声明的地方,都是手工声明的。

 

头文件的查找路径,如果#include "header",那么header在当前路径查找,如果#include <header>,那么header/usr/include 和 /usr/local/include 路径查找。动态库和静态库在/lib/usr/lib 和 /usr/local/lib

除去这标准路径,还可以通过-I参数指定头文件的查找路径(具体哪个头文件在源代码里指定了),这个是预处理过程(CPPFLAGS)。通过-L参数指定库的位置(LDFLAGS),-l参数指定具体连接那个库(LIBS),这个是链接过程。

 

需要注意的是:无论是标准路径还是-L指定的路径,都需要用-l指定库的名字。例如-lmysqlclient,会在libmysqlclient.so中查找符号。


1.8 库的版本问题

 

$ gcc -o libtx.so -shared -fPIC tx.c

$ gcc -o libty.so.1.0 -shared -fPIC -Wl,-soname -Wl,libty.so.1 ty.c

$ gcc -o libty.so.2.0 -shared -fPIC -Wl,-soname -Wl,libty.so.2 ty.c

$ ls -l libt*

libtx.so

libty.so.1.0

libty.so.2.0

 

# echo $PWD > /etc/ld.so.conf.d/libt.conf

$ ls -l libt*

libtx.so

libty.so.1 -> libty.so.1.0

libty.so.1.0

libty.so.2 -> libty.so.2.0

libty.so.2.0

 

$ readelf -d libty.so.1.0 

 0x000000000000000e (SONAME)             Library soname: [libty.so.1]

ldconfig 读取soname,如果soname和文件名不同,和soname同名的软连接到文件。

 

$ gcc -o main main.c -ltx -lty -L.

/usr/bin/ld: cannot find -lty

collect2: ld returned 1 exit status

$ ln -sf libty.so.2 libty.so

$ gcc -o main main.c -ltx -lty -L.

$ readelf -d main

 0x0000000000000001 (NEEDED)             Shared library: [libty.so.2]

 

假如,我想链接libty.so.1仲么办?笨办法是把libty.so.1复制到新的目录,创建libty.so 到 libty.so.1的软连接。其实还可以这样。

$ gcc -o main main.c -ltx -L. libty.so.1

$ readelf -d main

 0x0000000000000001 (NEEDED)             Shared library: [libty.so.1]

(注意,libty.so.1前面的空格)不使用-l参数,而是直接把so的全路径写出来。想想你是怎么接静态库的你就明白了。


1.9 动态库/静态库和可执行文件的区别

 

动态库/静态库本质上只编译,不链接,就算库里面出现的未决(找不到定义,找符号的定义是链接和加载的事情)符号,也不会报错。

$ cat e.cc

const char *lx(const char *x);

const char *ecc(const char *x) { return lx(x); }

$ gcc -o libecc.so -shared -fPIC -Wall e.cc

$ nm libecc.so | grep -P "lx|ecc"

                 U _Z2lxPKc

000000000000055c T _Z3eccPKc

 

nm 查看 是未定义符号的意思(lx未定义),表明符号定义在代码段(ecc)。

 

$ cat e.cc

static const char *lx(const char *x);

const char *ecc(const char *x) { return lx(x); }

$ gcc -o libecc.so -shared -fPIC -Wall e.cc

e.cc:1: error: 'const char* lx(const char*)' used but never defined

 

编译器处理static函数的方式不一样,因为static函数的作用域是文件。

 

$ cat e.cc

namespace { const char *lx(const char *x); }

const char *ecc(const char *x) { return lx(x); }

$ g++ -o libecc.so -shared -fPIC -Wall e.cc

/usr/bin/ld: /tmp/ccINDBqF.o: relocation R_X86_64_PC32 against `(anonymous namespace)::lx(char const*)' can not be used when making a shared object; recompile with -fPIC

/usr/bin/ld: final link failed: Bad value

collect2: ld returned 1 exit status

 

编译器处理匿名namespacec++设计用来代替static函数的机制)也不允许未定义,它的处理方式也很不一样,做了链接处理。


2. 程序的加载


2.1 指定运行时库的加载地址

 

$ gcc -o main main.c -ltx -lty  -L. -Wl,-rpath -Wl,./lib

readelf -d main

 0x000000000000000f (RPATH)              Library rpath: [./lib]

 

这里用-rpath 指定运行时,优先从哪个个路径(多个路径用冒号分隔)加载动态库。该选项适用于程序有自己的so,但是又不想将该so安装到标准路径。

注意,这个路径和运行是加载有关,和链接时无关。


2.2 运行时库的加载顺序

 

编译时用rpath制定的路径

* LD_LIBRARY_PATH 环境变量指定的路径

* /etc/ld.so.cache 中指定的路径

* /lib 和 /usr/lib 标准路径


2.3 ldconfig 维护 /etc/ld.so.cache

 

$ cat /etc/ld.so.conf

include ld.so.conf.d/*.conf

/usr/local/lib

 

ldconfig 根据 ld.so.conf 的配置,生成类似库名到库地址的列表,并将结果保存在/etc/ld.so.cache中。例如

$ /sbin/ldconfig -p | grep stdc++

        libstdc++.so.6 (libc6,x86-64) => /usr/lib64/libstdc++.so.6

        libstdc++.so.6 (libc6) => /usr/lib/libstdc++.so.6

 

程序启动加载动态库时,查找/etc/ld.so.cache文件,如果找到对应的库,则去响应的路径加载。这也是很多时候,我们安装了库,需要执行ldconfig的原因。


3. 常用的工具

 

* nm 查看目标文件的符号,适合.o.a.soexe

* objdump 查看目标文件的信息。比nm更强大。

* readelf 查看ELF格式的可执行文件。

* addr2line

* ldd 查看依赖的共享库

* ldconfig

 

这些工具的使用详解man手册,我也只是知道几个选项。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值