作者:左少华
时间:2015-05-24
转载请注明出处:
http://blog.csdn.net/shaohuazuo/article/details/45957971
每个可重定位的目标模块多有一个符号表,它包含模块所定义和引用的符号信息
再链接器上下文中,有三种不同的符号:
有模块定义并可以被其他模块引用的全局符
全局连接器符号对应于非静态的c函数和不带
static变量修饰全局变量.
由其他模块定义,并被本模块引用全局符号
这些符号称为外部符号,对应于定义C中的extends修饰的全局变量的函数.
只被本模块m定义和引用的本地符号,有的本地链接器符号对应带static属性的c函数的全局变量,
本地链接器符号和本地程序变量不同,.symtab中符号不包括对应于本地非静态程序变量和符号,c中的static变量的位置在.data段或者是在.bss段
符号表是由汇编器构造的,使用汇编器输出到汇编语言.s文件中的符号. symtab节中包含elf符号表,符号表是一个条目数组.每个条目项格式如下:
typedef struct{
int name; name是字符串表中的字节偏移.指向符号的以null结尾的字符串名字.
int value; value是符号的地址.
int size; 目标的大小.(单位字节)
char type:4, 要么是数据,要么是函数.
binding:4; 表示是全局的,还是本地的.
char reserved; 保留字段.
char section; 到节头部.
}Elf_Symbol;
链接器解析符号引用的方法
是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义联系起来.
对那些引用在本地模块中的本地符号引用,符号解析非常简单明了.编译器只允许每个模块中每个本地
符号只有一个定义.编译器还确保静态本地变量.它们也会由本链接符号.拥有唯一的名字.
编译c代码是如果main.c的main函数中应用一个printfx()函数.当编译时遇到一个不是本模块的函数.
他会假设这个函数在其他的模块中被定义,他会生成一个符号链接表条目.
并把它交给连接器处理.但是如果链接器再所有输入的目标文件中没由找到他的定义
链接器将会出错.
在早期库的组织形式相对简单,里面的目标代码只能够进行静态链接,所以我们称为“静态库”,静态库的结构比较简单,其实就是把原来的目标代码放在一起,链接程序根据每一份目标代码的符号表查找相应的符号(函数和变量的名字),找到的话就把该函数里面需要定位的进行定位,然后将整块函数代码放进可执行文件里,若是找不到需要的函数就报错退出。
静态库的两个特点:
如果有多个(调用相同库函数的)进程在内存中同时运行,
内存中就存有多份相同的库函数代码,因此占用内存空间较多。
链接方法如下 gcc main.c /usr/lib/libm.a /usr/lib/libc.a
重定位由两步组成:
以下来自:http://docs.oracle.com/cd/E26926_01/html/E25910/chapter3-29.html
运行时链接程序在装入应用程序所需的全部依赖项之后,将会处理每个目标文件并执行所有必需的重定位。
在目标文件的链接编辑过程中,随输入可重定位目标文件提供的任何重定位信息均会应用于输出文件。但是,在创建动态可执行文件或共享目标文件时,许多重定位无法在链接编辑时完成。这些重定位需要仅在目标文件装入内存时才知道的逻辑地址。在这种情况下,链接编辑器将在输出文件映像中生成新的重定位记录。然后,运行时链接程序必须处理这些新的重定位记录。
有关许多重定位类型的更详细说明,请参见SPARC: 重定位。重定位存在两个基本类型。
非符号重定位
符号重定位
使用 elfdump(1) 可以显示目标文件的重定位记录。在以下示例中,文件 libbar.so.1 包含两条重定位记录,用于指示必须更新全局偏移表或 .got 节。
$ elfdump -r libbar.so.1
Relocation Section: .rel.got:
type offset section symbol
R_SPARC_RELATIVE 0x10438 .rel.got
R_SPARC_GLOB_DAT 0x1043c .rel.got foo
第一个重定位是一个简单的相对重定位,这可通过重定位类型以及没有符号引用看出。此重定位需要使用将目标文件装入内存的基本地址来更新关联的 .got 偏移。
第二个重定位需要符号 foo 的地址。要完成此重定位,运行时链接程序必须从动态可执行文件或其依赖项之一查找该符号。
重定位符号查找
运行时链接程序负责搜索目标文件在运行时所需的符号。通常,用户应该熟悉应用于动态可执行文件及其依赖项的缺省搜索模型,以及应用于通过 dlopen(3C) 获取的目标文件的缺省搜索模型。但是,目标文件的符号属性,或是具体的绑定要求会导致符号查找结果具有更复杂的特性。
目标文件有两个属性会影响符号查找。第一个属性是请求目标文件的符号搜索作用域。第二个属性是进程中每个目标文件提供的符号可见性。
装入目标文件时,可将这些属性作为缺省属性应用。此外,也可将这些属性作为 dlopen(3C) 的特定模式提供。在某些情况下,可在生成目标文件时将这些属性记录在目标文件中。
目标文件可以定义一个 world 搜索作用域和/或一个 group 搜索作用域。
world
目标文件可以在进程的任何其他全局目标文件中搜索符号。
group
目标文件可以在同一组的任何目标文件中搜索符号。通过使用 dlopen(3C) 获取的目标文件或使用链接编辑器的 -B group 选项生成的目标文件创建的依赖项树构成一个唯一的组。
目标文件可以定义目标文件的导出符号是全局可见还是局部可见。
global
可在具有 world 搜索作用域的任何目标文件中引用该目标文件的导出符号。
local
只能在构成同一组的其他目标文件中引用该目标文件的导出符号。
运行时符号搜索也可由符号可见性指定。指定了 STV_SINGLETON 可见性的符号不受任何符号搜索作用域的影响。所有的单件符号引用都绑定到进程中第一次出现的单件定义。请参见表 12-20。
符号查找的最简单形式将在下一节缺省符号查找中进行概述。通常,符号属性由多种形式的 dlopen(3C) 使用。这些情况将在符号查找中进行讨论。
动态目标文件使用直接绑定时,将提供替代的符号查找模型。此模型指示运行时链接程序直接在链接编辑时提供符号的目标文件中搜索符号。请参见第 9 章。
缺省符号查找
动态可执行文件及随其装入的所有依赖项都被指定了 world 搜索作用域和 global 符号可见性。针对动态可执行文件或随其装入的任何依赖项的缺省符号查找会导致搜索每个目标文件。运行时链接程序将从动态可执行文件开始,并按目标文件的装入顺序搜索每个依赖项。
ldd(1) 将按依赖项的装入顺序列出动态可执行文件的依赖项。例如,假定动态可执行文件 prog 将 libfoo.so.1 和 libbar.so.1 指定为其依赖项。
$ ldd prog
libfoo.so.1 => /home/me/lib/libfoo.so.1
libbar.so.1 => /home/me/lib/libbar.so.1
需要符号 bar 来执行重定位,运行时链接程序首先在动态可执行文件 prog 中查找 bar。如果找不到符号,运行时链接程序随后会在共享目标文件 /home/me/lib/libfoo.so.1 中查找,最后在共享目标文件 /home/me/lib/libbar.so.1 中查找。
注 - 符号查找操作的开销可能很大,尤其是在符号名称大小和依赖项数目增加的情况下。这方面的性能将在性能注意事项中详细介绍。有关替代查找模型,请参见第 9 章。
缺省重定位处理模型还允许转换为延迟装入 (lazy loading) 环境。如果在当前装入的目标文件中找不到某符号,则会处理所有暂挂的延迟装入目标文件,以尝试查找该符号。此装入是对尚未完整定义其依赖项的目标文件的补偿。但是,该补偿可能会破坏延迟装入的优点。
运行时插入
缺省情况下,运行时链接程序首先在动态可执行文件中搜索符号,然后在每个依赖项中进行搜索。使用此模型时,第一次出现的所需符号满足搜索要求。因此,如果同一符号存在多个实例,则会在所有其他实例中插入第一个实例。
简单解析中概述了插入如何影响符号解析。缩减符号作用域中提供了有关更改符号可见性,从而减少意外插入几率的机制。
注 - 指定了 STV_SINGLETON 可见性的符号提供了一种插入形式。所有的单件符号引用都绑定到进程中第一次出现的单件定义。请参见表 12-20。
如果目标文件被显式标识为插入项,则可以对每个目标文件强制执行插入。使用环境变量 LD_PRELOAD 装入或通过链接编辑器的 -z interpose 选项创建的任何目标文件都会标识为插入项。运行时链接程序搜索符号时,将在应用程序之后、任何其他依赖项之前搜索标识为插入项的任何目标文件。
仅当在进行任何进程重定位之前装入了插入项的情况下,才能保证可以使用插入项提供的所有接口。在重定位处理开始之前,将装入使用环境变量 LD_PRELOAD 提供的插入项,或作为应用程序的非延迟装入依赖项建立的插入项。启动重定位之后,引入进程中的插入项会降级为正常依赖项。如果插入项是延迟装入的,或者是由于使用 dlopen(3C) 而装入的,则插入项可能会降级。可使用 ldd(1) 来检测前一种类别。
$ ldd -Lr prog
libc.so.1 => /lib/libc.so.1
foo.so.2 => ./foo.so.2
libmapmalloc.so.1 => /usr/lib/libmapmalloc.so.1
loading after relocation has started: interposition request \
(DF_1_INTERPOSE) ignored: /usr/lib/libmapmalloc.so.1
注 - 如果链接编辑器在处理延迟装入的依赖项时遇到显式定义的插入项,则插入项将被记录为非延迟可装入依赖项。
可以使用 INTERPOSE mapfile 关键字将动态可执行文件中的单个符号定义为插入项。该机制使用 -z interpose 选项,因而更具选择性,并且针对随着依赖项发展而发生的逆向插入提供更好的隔离。请参见定义显式插入。
执行重定位的时间
根据重定位的执行时间,重定位可分为两种类型。产生这种区别是由对已重定位偏移进行的引用的类型所致。
即时引用
延迟引用
即时引用指的是必须在装入目标文件后立即确定的重定位。这些引用通常是目标文件代码使用的数据项、函数指针,甚至是通过与位置相关的共享目标文件进行的函数调用。这些重定位无法向运行时链接程序提供有关何时引用重定位项的信息。因此,必须在装入目标文件时,并在应用程序获取或重新获取控制权之前执行所有即时重定位。
延迟引用指的是在目标文件执行时可确定的重定位。这些引用通常是通过与位置无关的共享目标文件进行的全局函数调用,或者是通过动态可执行文件进行的外部函数调用。在对提供这些引用的任何动态模块进行编译和链接编辑的过程中,关联的函数调用将成为对过程链接表项的调用。这些项构成 .plt 节。每个过程链接表项都成为包含关联重定位的延迟引用。
在首次调用过程链接表项时,控制权会移交给运行时链接程序。运行时链接程序将查找所需符号,并重写关联目标文件中的项信息。将来调用此过程链接表项时,将直接转至相应函数。使用此机制,可以推迟此类型的重定位,直到调用函数的第一个实例。此过程有时称为延迟绑定。
运行时链接程序的缺省模式是在每次提供过程链接表重定位时执行延迟绑定。通过将环境变量 LD_BIND_NOW 设置为任意非空值,可以覆盖此缺省模式。此环境变量设置将导致运行时链接程序在装入目标文件时,同时执行即时引用和延迟引用重定位。这些重定位在应用程序获取或重新获取控制权之前执行。例如,根据以下环境变量来处理文件 prog 及其依赖项中的所有重定位。在将控制权转交给应用程序之前处理这些重定位。
$ LD_BIND_NOW=1 prog
此外,也可使用 dlopen(3C) 来访问目标文件,并将模式定义为 RTLD_NOW。还可使用链接编辑器的 -z now 选项来生成目标文件,以指示该目标文件需要在装入时进行完整的重定位处理。此重定位要求还将在运行时传播至所标记目标文件的所有依赖项。
注 - 前面的即时引用和延迟引用示例都很典型。但是,过程链接表项的创建最终受用作链接编辑输入的可重定位目标文件提供的重定位信息控制。R_SPARC_WPLT30 和 R_386_PLT32 等重定位记录指示链接编辑器创建过程链接表项。这些重定位由与位置无关的代码公用。
但是,通常会通过与位置相关的代码创建动态可执行文件,该代码可能不会指示需要过程链接表项。由于动态可执行文件具有固定位置,因此链接编辑器可在将引用绑定到外部函数定义时创建过程链接表项。无论原始重定位记录如何,都会创建此过程链接表项。
重定位错误
如果找不到符号,则会发生最常见的重定位错误。此情况将会产生相应的运行时链接程序错误消息并终止应用程序。在以下示例中,找不到在文件 libfoo.so.1 中引用的符号 bar。
lddproglibfoo.so.1=>./libfoo.so.1libc.so.1=>/lib/libc.so.1libbar.so.1=>./libbar.so.1libm.so.2=>/lib/libm.so.2 prog
ld.so.1: prog: fatal: relocation error: file ./libfoo.so.1: \
symbol bar: referenced symbol not found
$
在对动态可执行文件进行链接编辑的过程中,此类别的任何潜在重定位错误都会标记为致命未定义符号。有关示例,请参见生成可执行输出文件。但是,如果运行时找到的依赖项与链接编辑过程中引用的原始依赖项不兼容,则可能会发生运行时重定位错误。在前面的示例中,根据包含 bar 的符号定义的 libbar.so.1 共享目标文件的版本生成了 prog。
在链接编辑过程中使用 -z nodefs 选项,将抑制验证目标文件运行时重定位要求。抑制验证还可能会导致运行时重定位错误。
如果由于找不到用作即时引用的符号而发生重定位错误,则会在进程初始化期间立即出现该错误状态。对于延迟绑定的缺省模式,如果找不到用作延迟引用的符号,则会在应用程序获取控制权后出现该错误状态。后一种情况可能需要几分钟、几个月,也可能从不发生,具体情况视整个代码中使用的执行路径而定。
为防止发生此类错误,可使用 ldd(1) 来验证任何动态可执行文件或共享目标文件的重定位要求。
如果在使用 ldd(1) 时指定 -d 选项,将显示每个依赖项并处理所有即时引用重定位。如果无法解析引用,则会生成诊断消息。在前面的示例中,-d 选项将导致以下错误诊断。
$ ldd -d prog
libfoo.so.1 => ./libfoo.so.1
libc.so.1 => /lib/libc.so.1
libbar.so.1 => ./libbar.so.1
libm.so.2 => /lib/libm.so.2
symbol not found: bar (./libfoo.so.1)