Linux 关于共享库以及和共享库相关的几个环境变量
2013-01-11 22:57
411 查看
转载时请注明出处和作者联系方式
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静 <xianjimli at hotmail dot com>
我们知道函数名就是一个内存地址,这个地址指向函数的入口。调用函数就是压入参数,保存返回地址,然后跳转到函数名指向的代码。问题是,如果函数在共享库中,共享库加载的地址本身就不确定,函数地址也就不确定了,那如何调用共享库中的函数呢?这就是本文要回答的。
我们先来看一小段代码(test.c):
编译并反汇编:
gcc -g test.c -o test
objdump -S test
调用hello_world时,汇编代码对应于call 80483b4 <hello_world>,这是个绝对地址。hello_world是在可执行文件中,可执行文件是加载到一个固定地址的,因此hello_world的地址是确定的。
调用printf时,汇编代码对应于call 80482f0 <puts@plt>,这是个绝对地址。但函数名却是puts@plt,这是怎么回事呢?puts@plt显然是编译器加的一个中间函数,我们看一下这个函数对应的汇编代码:
现在我们用调试器分析一下:
gdb test
(gdb) b main
Breakpoint 1 at 0×80483d9: file test.c, line
12.
(gdb) r
Starting program: /root/test/plt/test
Breakpoint 1, main () at test.c:12
12 hello_world();
puts@plt先跳到*0×804962c,我们看看*0×804962c里有什么?
(gdb) x 0×804962c
0×804962c <_GLOBAL_OFFSET_TABLE_+20>:
0×080482f6
*0×804962c等于0×080482f6,这正是puts@plt中的第二行汇编代码的地址。也就是说puts@plt整个函数会顺序执行,直到跳转到0×80482c0.
再来看看0×80482c0处有什么,通过汇编可以看到:
ff 25 20
96 04
08 jmp *0×8049620
又跳到了*0×8049620,转的弯真多,没关系,我们再看*0×8049620:
(gdb) x 0×8049620
0×8049620 <_GLOBAL_OFFSET_TABLE_+8>:
0×009ce4c0
(gdb) x /wa 0×009ce4c0
0×9ce4c0 <_dl_runtime_resolve>:
0×8b525150
原来转来转去就是为了调用函数_dl_runtime_resolve, _dl_runtime_resolve的功能就是找到要调用函数(puts)的地址。
为什么不直接调用_dl_runtime_resolve,而要转这么多圈子呢?
先执行完这个函数hello_world:
(gdb) n
再回头来看看puts@plt的第一行代码:
80482f0: ff 25 2c
96 04
08 jmp *0×804962c
(gdb) x 0×804962c
0×804962c <_GLOBAL_OFFSET_TABLE_+20>:
0xa39a60 <puts>
对比前面的:
(gdb) x 0×804962c
0×804962c <_GLOBAL_OFFSET_TABLE_+20>:
0×080482f6
也就是说第一次执行时,通过_dl_runtime_resolve解析到函数地址,并保存puts的地址到0×804962c里,以后执行时就直接调用了。
Linux支持共享库已经有悠久的历史了,不再是什么新概念了。大家都知道如何编译、连接以及动态加载(dlopen/dlsym/dlclose)共享库。但是,可能很多人,甚至包括一些高手,对共享库相关的一些环境变量认识模糊。当然,不知道这些环境变量,也可以用共享库,但是,若知道它们,可能就会用得更好。下面介绍一些常用的环境变量,希望对家有所帮助:
LD_LIBRARY_PATH这个环境变量是大家最为熟悉的,它告诉loader:在哪些目录中可以找到共享库。可以设置多个搜索目录,这些目录之间用冒号分隔开。在linux下,还提供了另外一种方式来完成同样的功能,你可以把这些目录加到/etc/ld.so.conf中,或则在/etc/ld.so.conf.d里创建一个文件,把目录加到这个文件里。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。按照惯例,除非你用上述方式指明,loader是不会在当前目录下去找共享库的,正如shell不会在当前目前找可执行文件一样。
LD_PRELOAD这个环境变量对于程序员来说,也是特别有用的。它告诉loader:在解析函数地址时,优先使用LD_PRELOAD里指定的共享库中的函数。这为调试提供了方便,比如,对于C/C++程序来说,内存错误最难解决了。常见的做法就是重载malloc系列函数,但那样做要求重新编译程序,比较麻烦。使用LD_PRELOAD机制,就不用重新编译了,把包装函数库编译成共享库,并在LD_PRELOAD加入该共享库的名称,这些包装函数就会自动被调用了。在linux下,还提供了另外一种方式来完成同样的功能,你可以把要优先加载的共享库的文件名写在/etc/ld.so.preload里。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。
LD_ DEBUG这个环境变量比较好玩,有时使用它,可以帮助你查找出一些共享库的疑难杂症(比如同名函数引起的问题)。同时,利用它,你也可以学到一些共享库加载过程的知识。它的参数如下:
libs display library search paths
reloc display relocation processing
files display progress for input file
symbols display symbol table processing
bindings display information about symbol binding
versions display version dependencies
all all previous options combined
statistics display relocation statistics
unused determined unused DSOs
help display this help message and exit
BIND_NOW这个环境变量与dlopen中的flag的意义是一致,只是dlopen中的flag适用于显示加载的情况,而BIND_NOW/BIND_NOT适用于隐式加载。
LD_PROFILE/LD_PROFILE_OUTPUT:为指定的共享库产生profile数据,LD_PROFILE指定共享库的名称,LD_PROFILE_OUTPUT指定输出profile文件的位置,是一个目录,且必须存在,默认的目录为/var/tmp/或/var/profile。通过profile数据,你可以得到一些该共享库中函数的使用统计信息。
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静 <xianjimli at hotmail dot com>
我们知道函数名就是一个内存地址,这个地址指向函数的入口。调用函数就是压入参数,保存返回地址,然后跳转到函数名指向的代码。问题是,如果函数在共享库中,共享库加载的地址本身就不确定,函数地址也就不确定了,那如何调用共享库中的函数呢?这就是本文要回答的。
我们先来看一小段代码(test.c):
#include <stdio.h> void hello_world(void) { printf("Hello world!/n"); return; } int main(int argc, char* argv[]) { hello_world(); return 0; }
编译并反汇编:
gcc -g test.c -o test
objdump -S test
void hello_world(void) { 80483b4: 55 push %ebp 80483b5: 89 e5 mov %esp,%ebp 80483b7: 83 ec 08 sub $0x8,%esp printf("Hello world!/n"); 80483ba: c7 04 24 b4 84 04 08 movl $0x80484b4,(%esp) 80483c1: e8 2a ff ff ff call 80482f0 <puts@plt> return; } 80483c6: c9 leave 80483c7: c3 ret 080483c8 <main>: int main(int argc, char* argv[]) { 80483c8: 8d 4c 24 04 lea 0x4(%esp),%ecx 80483cc: 83 e4 f0 and $0xfffffff0,%esp 80483cf: ff 71 fc pushl -0x4(%ecx) 80483d2: 55 push %ebp 80483d3: 89 e5 mov %esp,%ebp 80483d5: 51 push %ecx 80483d6: 83 ec 04 sub $0x4,%esp hello_world(); 80483d9: e8 d6 ff ff ff call 80483b4 <hello_world> return 0; 80483de: b8 00 00 00 00 mov $0x0,%eax }
调用hello_world时,汇编代码对应于call 80483b4 <hello_world>,这是个绝对地址。hello_world是在可执行文件中,可执行文件是加载到一个固定地址的,因此hello_world的地址是确定的。
调用printf时,汇编代码对应于call 80482f0 <puts@plt>,这是个绝对地址。但函数名却是puts@plt,这是怎么回事呢?puts@plt显然是编译器加的一个中间函数,我们看一下这个函数对应的汇编代码:
080482f0 <puts@plt>: 80482f0: ff 25 2c 96 04 08 jmp *0x804962c 80482f6: 68 10 00 00 00 push $0x10 80482fb: e9 c0 ff ff ff jmp <_init+0x30>
现在我们用调试器分析一下:
gdb test
(gdb) b main
Breakpoint 1 at 0×80483d9: file test.c, line
12.
(gdb) r
Starting program: /root/test/plt/test
Breakpoint 1, main () at test.c:12
12 hello_world();
puts@plt先跳到*0×804962c,我们看看*0×804962c里有什么?
(gdb) x 0×804962c
0×804962c <_GLOBAL_OFFSET_TABLE_+20>:
0×080482f6
*0×804962c等于0×080482f6,这正是puts@plt中的第二行汇编代码的地址。也就是说puts@plt整个函数会顺序执行,直到跳转到0×80482c0.
再来看看0×80482c0处有什么,通过汇编可以看到:
ff 25 20
96 04
08 jmp *0×8049620
又跳到了*0×8049620,转的弯真多,没关系,我们再看*0×8049620:
(gdb) x 0×8049620
0×8049620 <_GLOBAL_OFFSET_TABLE_+8>:
0×009ce4c0
(gdb) x /wa 0×009ce4c0
0×9ce4c0 <_dl_runtime_resolve>:
0×8b525150
原来转来转去就是为了调用函数_dl_runtime_resolve, _dl_runtime_resolve的功能就是找到要调用函数(puts)的地址。
为什么不直接调用_dl_runtime_resolve,而要转这么多圈子呢?
先执行完这个函数hello_world:
(gdb) n
再回头来看看puts@plt的第一行代码:
80482f0: ff 25 2c
96 04
08 jmp *0×804962c
(gdb) x 0×804962c
0×804962c <_GLOBAL_OFFSET_TABLE_+20>:
0xa39a60 <puts>
对比前面的:
(gdb) x 0×804962c
0×804962c <_GLOBAL_OFFSET_TABLE_+20>:
0×080482f6
也就是说第一次执行时,通过_dl_runtime_resolve解析到函数地址,并保存puts的地址到0×804962c里,以后执行时就直接调用了。
Linux支持共享库已经有悠久的历史了,不再是什么新概念了。大家都知道如何编译、连接以及动态加载(dlopen/dlsym/dlclose)共享库。但是,可能很多人,甚至包括一些高手,对共享库相关的一些环境变量认识模糊。当然,不知道这些环境变量,也可以用共享库,但是,若知道它们,可能就会用得更好。下面介绍一些常用的环境变量,希望对家有所帮助:
LD_LIBRARY_PATH这个环境变量是大家最为熟悉的,它告诉loader:在哪些目录中可以找到共享库。可以设置多个搜索目录,这些目录之间用冒号分隔开。在linux下,还提供了另外一种方式来完成同样的功能,你可以把这些目录加到/etc/ld.so.conf中,或则在/etc/ld.so.conf.d里创建一个文件,把目录加到这个文件里。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。按照惯例,除非你用上述方式指明,loader是不会在当前目录下去找共享库的,正如shell不会在当前目前找可执行文件一样。
LD_PRELOAD这个环境变量对于程序员来说,也是特别有用的。它告诉loader:在解析函数地址时,优先使用LD_PRELOAD里指定的共享库中的函数。这为调试提供了方便,比如,对于C/C++程序来说,内存错误最难解决了。常见的做法就是重载malloc系列函数,但那样做要求重新编译程序,比较麻烦。使用LD_PRELOAD机制,就不用重新编译了,把包装函数库编译成共享库,并在LD_PRELOAD加入该共享库的名称,这些包装函数就会自动被调用了。在linux下,还提供了另外一种方式来完成同样的功能,你可以把要优先加载的共享库的文件名写在/etc/ld.so.preload里。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。
LD_ DEBUG这个环境变量比较好玩,有时使用它,可以帮助你查找出一些共享库的疑难杂症(比如同名函数引起的问题)。同时,利用它,你也可以学到一些共享库加载过程的知识。它的参数如下:
libs display library search paths
reloc display relocation processing
files display progress for input file
symbols display symbol table processing
bindings display information about symbol binding
versions display version dependencies
all all previous options combined
statistics display relocation statistics
unused determined unused DSOs
help display this help message and exit
BIND_NOW这个环境变量与dlopen中的flag的意义是一致,只是dlopen中的flag适用于显示加载的情况,而BIND_NOW/BIND_NOT适用于隐式加载。
LD_PROFILE/LD_PROFILE_OUTPUT:为指定的共享库产生profile数据,LD_PROFILE指定共享库的名称,LD_PROFILE_OUTPUT指定输出profile文件的位置,是一个目录,且必须存在,默认的目录为/var/tmp/或/var/profile。通过profile数据,你可以得到一些该共享库中函数的使用统计信息。
相关文章推荐
- 关于Linux驱动文件的加载以及相关符号的地址说明
- Linux下共享库(SO)有关的几个环境变量
- 关于项目中记录日志的几个相关jar报slf4j-api、slf4j-log4j12以及log4j之间什么关系?
- 【工作中学到的小技巧】 Linux 添加账户,SAMBA添加账户,以及配置共享目录相关步骤
- 在 Linux 系统中,有几个目录是特别需要注意的,以下提供几个需要注意的目录,以及预设相关的用途:
- Linux下共享库(SO)有关的几个环境变量
- 关于用mysql创建视图以及union all 和group by相关介绍
- 关于ASP .NET Core在跨平台的linux ubuntun,SUSE ,Mac OS的发布的相关平台操作
- 关于su 命令的的几个相关报错
- Android SELinux 的认知以及 init 的相关知识,Linux 环境利用这2个模块进行白名单测试 -- 架构分析
- 关于linux下的嵌入式文件系统以及flash文件系统选择
- linux中关于目录的几个特殊知识点-
- RMS技术QQ群(24893581)共享:关于RMS相关软件的下载资料
- 5、linux安装分区以及挂载目录的使用来实现ssh公钥的共享
- Linux下lamp以及php开发环境相关配置
- linux中关于账户的几个特殊的目录
- 关于嵌入式和安卓以及linux的区别
- 关于vim的一些使用以及Dos文件到Linux、Mac下问题
- linux环境下调试嵌入式设备时出现Aborted、segmentation fault、卡死的问题以及关于指针使用的一点想法