一、SO 查找路径

运行时,Linux动态链接库的搜索路径按优先级排序为:
1、编译目标代码时 ”-Wl,-rpath,” 指定的动态库搜索路径(当指定多个动态库搜索路径时,路径之间用冒号”:”分隔。);
2、环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径;
3、配置文件 /etc/ld.so.conf 中指定的动态库搜索路径;
4、默认的动态库搜索路径 /lib,如果是64位系统还包括 /lib64;
5、默认的动态库搜索路径 /usr/lib,如果是64位系统还包括 /usr/lib64;
注意: /usr/local/lib 和 /usr/local/lib64 不在标准路径之列。
**编译链接详解 - 图1

LD_PRELOAD 预加载库

两种方式设置 rpath

  1. # 两种方式设置 rpath
  2. # 1、在可执行文件中添加动态库绝对路径到 DT_RPATH
  3. gcc -Wl,-R/$LIBRARY_PATH -l$LIB_NAME
  4. # 2、设置 LD_RUN_PATH 环境变量
  5. export LD_RUN_PATH=$LIBRARY_PATH:$LD_RUN_PATH
  6. # runpath 在设置了runpath 后,rpath 就会被加载器忽略了。
  7. gcc -Wl,-R$LIBRARY_PATH -Wl,--enable-new-dtags -l$LIB_NAME

1、设置了 RUNPATH 的情况:
LD_PRELOAD -> LDLIBRARYPATH -> runpath -> ld.so.cache -> /lib 和 /usr/lib
2、没有设置 RUNPATH的情况:
rpath -> LDLIBRARYPATH -> ld.so.cache -> /lib 和 /usr/lib

二、gcc相关编译参数

2.1 静态检查

  1. -pedantic //检查代码是否符合 ANSI/ISO 标准:
  2. -Wall //输出警告信息
  3. -Wextra //输出更多的警告信息
  4. -Weffc++ //检查是否符合 Effective C++》这本书中提到的标准
  5. -w //关闭所有警告

2.2 调试与优化

  1. -g          //在编译时生成原生格式的调试符号信息,可以使用 gdb ddx 等调试器调试。-g 分为三个级别,默认为 -g2,其中 -g3 除包含 -g2 中的所有调试信息外,还包含源代码中定义的宏。
  2. -ggdb         //在编译时生成 gdb 专用格式的调试符号信息,信息更为丰富,但只能使用 gdb 调试,而不能使用其它调试器。级别设定与 -g 基本相同。
  3. -gdwarf        //如果升级 GCC 之后,发现 GDB 不能用了,那么在编译的时候加上 -gdwarf 选项试一下,如果还不行,再使用 -gdwarf-2 试试。
  4. -p           //将剖析(Profiling)信息加入到最终生成的二进制代码中,生成可以被通用剖析工具(Prof)能够识别的统计信息,它对于找出程序瓶颈很有帮助。生成后的可执行文件要运行一次,才能生成剖析文件,默认文件名是 gmon.out
  5. -pg         //生成只有 GNU 剖析工具(Gprof)才能识别的统计信息。
  6. -save-temps      //保存编译过程中生成的一系列中间文件。
  7. -On          //n 可以为 0~3,默认为 1,数字越大优化越高,一般使用 -O2 可以在优化长度、编译时间和代码大小之间取得较好平衡,开发调试建议使用 -O0
  8. -Os          //相当于 -O2.5,使用了所有 -O2 的优化选项,但却没有使用增加大小。也就是说,相对于 -O2,能减少一点可执行文件的大小。
  9. -s          //从执行文件中删除符号表与重定向信息,以减少执行文件的大小。跟直接编译之后,使用 strip 命令减小文件大小效果一致。

2.3 编译器扩展

这时候用gcc来编译C++程序会报缺少一些C++标准库,这时候可以使用 gcc -lstdc++ 或直接使用 g++ 命令。

解决 GDB 的问题
升级 gcc 之后,编译代码加上 -g 选项,在使用 gdb 调试时,可能会出现 Missing separate debuginfos, use: debuginfo-install libgcc-4.4.7-3.el6.x86_64 这样的错误,是因为只加 -g 选项,此时已经无法生成符号表了。解决方案有两种:
◆ 编译时加上 -gdwarf 选项试一下,如果还不行,再使用 -gdwarf-2 试试。
◆ 升级 gdb 版本,可升级至 7.6 ,正常的 ./configure && make && make install 之后,就可以正常使用了。如果 make 过程中出现 configure: error: no termcap library found 的错误,可以 yum install ncurses-devel 来解决。

预编译头:
gcc -x c++-header -c stdc++.h -o stdc++.h.gch -std=c++0x

6、当执行一个可执行文件时,如果报类似 `GLIBCXX_3.4.15’ not found 的错误,是因为编译这个可执行文件的 GCC版本和当前环境中的GCC版本不同,可以选择升级GCC版本,如下网页:
http://blog.csdn.net/davidwang9527/article/details/19197511

7、如果只是将GCC4.8.2编译的可执行文件配置到一台LINUX机器上运行,并不需要安装GCC4.8.2,只需要将原机器上的 /usr/local/gcc-4.8.2/lib64/libstdc++.so.6 文件复制到新机器的 /usr/lib64/libstdc++.so.6 即可(注意备份原文件)

2.4 链接不以lib前缀的so

  1. -l
  2. 如-ldl,链接的就是libdl.so
  3. -L 后面直接+ so即可
  4. 如-L /usr/lib64/libdl.so

2.5 -g选项与-rdynamic选项的差别

这主要是对可执行程序而言的,而编译动态库时,即使没有rdynamic选项,默认也会将非静态函数放入动态符号表中 默认情况下,可执行程序(非动态库)文件内我们定义的非静态函数,是不放到动态符号表中的,链接时只有加上-rdynamic才能将所有非静态函数加到动态符号表中。

1、-g选项新添加的是调试信息(一系列.debug_xxx段),被相关调试工具,比如gdb使用,可以被strip掉。
2、-rdynamic选项新添加的是动态连接符号信息,用于动态连接功能,比如dlopen()系列函数、backtrace()系列函数使用,不能被strip掉,即强制strip将导致程序无法执行:

  1. [root@www c]# ./t.rd
  2. test[root@www c]# strip -R .dynsym t.rd
  3. [root@www c]# ./t.rd
  4. ./t.rd: relocation error: ./t.rd: symbol , version GLIBC_2.2.5 not defined in file libc.so.6 with link time reference
  5. [root@www c]#

3、.symtab表在程序加载时会被加载器 丢弃 ,gdb等调试工具由于可以直接访问到磁盘上的二进制程序文件:

  1. [root@www c]# gdb t.g -q
  2. Reading symbols from /home/work/dladdr/c/t.g...done.
  3. (gdb)

因此可以使用所有的调试信息,这包括.symtab表;而backtrace()系列函数作为程序执行的逻辑功能,无法去读取磁盘上的二进制程序文件,因此只能使用.dynsym表。
4、-rdynamic选项不产生任何调试信息,因此在一般情况下,新增的附加信息比-g选项要少得多。除非是完全的静态连接,否则即便是没有加-rdynamic选项,程序使用到的外部动态符号,比如前面示例里的printf,也会被自动加入到.dynsym表。

示例项目

bar.c

  1. extern void foo(void);
  2. void bar(void)
  3. {
  4. foo();
  5. }
  6. main.c

main.c

  1. #include <dlfcn.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. void foo(void)
  5. {
  6. puts("Hello world");
  7. }
  8. int main(void)
  9. {
  10. void * dlh = dlopen("./libbar.so", RTLD_NOW);
  11. if (!dlh) {
  12. fprintf(stderr, "%s\n", dlerror());
  13. exit(EXIT_FAILURE);
  14. }
  15. void (*bar)(void) = dlsym(dlh,"bar");
  16. if (!bar) {
  17. fprintf(stderr, "%s\n", dlerror());
  18. exit(EXIT_FAILURE);
  19. }
  20. bar();
  21. return 0;
  22. }

在这里,bar.c成为共享库libbar.so,而main.c成为共享库 一个dlopen s libbar并从该库中调用bar()的程序. bar()调用foo(),它在bar.c中是外部的,并且是在main.c中定义的.
因此,没有-rdynamic:

  1. $ make test
  2. gcc -c -Wall -o main.o main.c
  3. gcc -c -Wall -fpic -o bar.o bar.c
  4. gcc -shared -o libbar.so bar.o
  5. gcc -o prog main.o -L. -lbar -ldl
  6. ./prog
  7. ./libbar.so: undefined symbol: foo
  8. Makefile:23: recipe for target 'test' failed

并使用-rdynamic:

  1. $ make clean
  2. rm -f *.o *.so prog
  3. $ make test LDEXTRAFLAGS=-rdynamic
  4. gcc -c -Wall -o main.o main.c
  5. gcc -c -Wall -fpic -o bar.o bar.c
  6. gcc -shared -o libbar.so bar.o
  7. gcc -rdynamic -o prog main.o -L. -lbar -ldl
  8. ./prog
  9. Hello world