动态符号链接的细节 (转)

    Linux支持动态连接库,不仅节省了磁盘、内存空间,而且可以提高程序运行效率[1]。不过引入动态连接库也可能会带来很多问题,例如动态连接库的调试[4]、升级更新[5]和潜在的安全威胁[6][7]。这里主要讨论符号的动态链接过程,即程序在执行过程中,对其中包含的一些未确定地址的符号进行重定位的过程[3][8]。    本篇主要参考资料[3]和[8],前者侧重实践,后者侧重原理,把两者结合起来就方便理解程序的动态链接过程了。另外,动态连接库的创建、使用以及调用动态连接库的部分参考了资料[1][2]。    下面先来看看几个基本概念,接着就介绍动态连接库的创建、隐式和显示调用,最后介绍符号的动态链接细节。1、基本概念1.1 ELF    ELF是Linux支持的一种程序文件格式,本身包含重定位、执行、共享(动态连接库)三种类型。(man elf)代码:Code:[Ctrl+A Select All]演示:

$ gcc -c test.c    #通过-c生成可重定位文件test.o,这里不会进行链接$ file test.otest.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped$ gcc -o test test.o  #链接后才可以执行$ file test       test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), not stripped//也可链接成动态连接库,不过一般不会把main函数链接成动态连接库,后面再介绍$ gcc -fpic -shared -W1,-soname,libtest.so.0 -o libtest.so.0.0 test.o$ file libtest.so.0.0libtest.so.0.0: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped

    虽然ELF文件本身就支持三种不同的类型,不过它有一个统一的结构。这个结构是:文件头部(ELF Header)    程序头部表(Program Header Table)    节区1(Section1)    节区2(Section2)    节区3(Section3)    …    节区头部表(Section Header Table)    无论是文件头部、程序头部表、节区头部表,还是节区,它们都对应着C语言里头的一些结构体(elf.h中定义)。文件头部主要描述ELF文件的类型,大小,运行平台,以及和程序头部表和节区头部表相关的信息。节区头部表则用于可重定位文件,以便描述各个节区的信息,这些信息包括节区的名字、类型、大小等。程序头部表则用于描述可执行文件或者动态连接库,以便系统加载和执行它们。而节区主要存放各种特定类型的信息,比如程序的正文区(代码)、数据区(初始化和未初始化的数据)、调试信息、以及用于动态链接的一些节区,比如解释器(.interp)节区将指定程序动态装载/连接器ld-linux.so的位置,而过程链接表(plt)、全局偏移表(got)、重定位表则用于辅助动态链接过程。1.2  符号    对于可执行文件除了编译器引入的一些符号外,主要就是用户自定义的全局变量,函数等,而对于可重定位文件仅仅包含用户自定义的一些符号。

$ gcc -c test.c  #生成可重定位文件test.o//包含全局变量、自定义函数以及动态连接库中的函数,但不包含局部变量;发现这个三个符号的地址都没有确定$ nm test.o      #nm可以用来查看ELF文件的符号表信息         00000000 B global00000000 T main         U printf$ gcc -o test test.o  #生成可执行文件//经过链接后,global和main的地址都已经确定了,但是printf却还没有,因为它是动态连接库glibc中定义函数,需要动态链接,而不是这里的“静态”链接$ nm test | egrep "main$| printf|global$"080495a0 B global08048354 T main         U printf@@GLIBC_2.0

1.3 重定位:"是将符号引用与符号定义进行连接的过程"[8]    从上面的演示可以看出,重定位文件test.o中的符号地址都是没有确定的,而经过“静态"链接(gcc默认调用ld进行链接)以后有两个符号地址已经确定了,这样一个确定符号地址的过程实际上就是链接的实质。链接过后,对符号的引用变成了对地址(定义符号时确定该地址)的引用,这样程序运行时就可通过访问内存地址而访问特定的数据。    我们也注意到符号printf在可重定位文件和可执行文件中的地址都没有确定,这意味着该符号是一个外部符号,可能定义在动态连接库中,在程序运行时需要通过动态链接器(ld-linux.so)进行重定位,即动态链接。    通过这个演示可以看出printf确实在glibc中有定义。

$ nm /lib/libc.so.6 | grep  "/ printf$"000457b0 T printf

1.4 动态链接    动态链接就是在程序运行时对符号进行重定位,确定符号对应的内存地址的过程。    Linux下符号的动态链接默认采用Lazy Mode方式[3],也就是说在程序运行过程中用到该符号时才去解析它的地址。这样一种符号解析方式有一个好处:只解析那些用到的符号,而对那些不用的符号则永远不用解析,从而提高程序的执行效率。    不过这种默认是可以通过设置LD_BIND_NOW为非空来打破的(下面会通过实例来分析这个变量的作用),也就是说如果设置了这个变量,动态链接器将在程序加载后和符号被使用之前就对这些符号的地址进行解析。1.5 动态连接库    上面提到重定位的过程就是对符号引用和符号地址进行链接的过程,而动态链接过程涉及到的符号引用和符号定义分别对应可执行文件和动态连接库,在可执行文件中可能引用了某些动态连接库中定义的符号,这类符号通常是函数。    为了让动态链接器能够进行符号的重定位,必须把动态连接库的相关信息写入到可执行文件当中,这些信息是什么呢?

而在于当时的那份心情。可是旅行的彼时那刻我的心情一直是好的吗?

动态符号链接的细节 (转)

相关文章:

你感兴趣的文章:

标签云: