..1. 目录
..1.1. 动态链接过程
..1.1.1. 基础宏定义
1 | //宏访问link_map成员并计算地址 |
..1.1.2. 重定位定义
1 | // Elf64_Rela |
..1.1.3. 符号表定义
1 | /Elf64_Sym |
..1.1.4. 动态段定义
1 | typedef struct { |
..1.1.5. r_scope_elem 定义
1 | /* Structure to describe a single list of scope elements. The lookup |
..1.1.6. link_map
1 | //link_map用于描述可加载共享目标文件的结构 l_next,l_prev是一个链接了已加载的所有共享目标文件的单链表结构 |
..1.1.7. _dl_fixup
函数定义和分析
libc/elf/dl-runtime.c
1 | __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE _dl_fixup ( |
1 |
|
static inline Elf64_Addr
elf_machine_fixup_plt (struct link_map *map, lookup_t t,
const Elf64_Rela *reloc,
Elf64_Addr *reloc_addr, Elf64_Addr value)
{
return *reloc_addr = value;
}
1 |
|
首先获得该scope下的link_map个数r_nlist和数组r_list 然后进行遍历
省略的部分是检查当前link_map是否有符合查找的条件 没有就继续遍历
再往下取出当前link_map的符号表symtab和字符串表strtab
接下来的if和else条件语句部分都是通过哈希值找到符号表中对应符号列表的索引
如果找到 就通过check_match函数比对符号表中的函数名symtab[symidx]和待查找的函数名undef_name是否相等 如果相等 就找到了该符号并跳转到found_it语句 否则返回null
找到了该符号后对符号类型进行判断
如果找到的是弱符号STB_WEAK 则保存第一次找到的结果 然后继续循环查找
如果后面没有找到可以覆盖该结果的符号 则返回的就是该第一次保存的结果。
如果找到的是全局符号STB_GLOBAL 则直接返回该结果。
如果找到的符号是其他类型的符号 则继续循环查找
最后 如果什么都没找到 则返回0
/elf/dl-lookup.c::check_match
1 | const ElfW(Sym) *__attribute_noinline__ |
这里主要有一个版本检查
..1.1.8. 符号版本
由于符号版本不是C语言的标准用法 所以它使用了汇编器的一个特殊指示 也就是.symver 指示
而GCC中通过内嵌汇编完成 如下:
1 | __asm__(".symver original_foo,foo@"); |
这个例子中定义了foo的四个版本
其中的symver的第一个参数为源代码中真正定义的实现 而之后的foo则为对外公开的版本 也就是可以有不同版本的符号
其中@@则表示这个是一个默认版本(简单来说 如果一个可执行文件链接的时候foo还没有任何版本控制 但是在运行时foo已经引入了多个版本 则此时的可执行文件可以选择这个@@表示的默认版本符号
这里的old_foo就是foo的VERS_1.1版本 如果在so中定义了old_foo 那么这个old_foo就是这个foo的VERS_1.1版本的实现
如果没有定义 可以通过old_foo来引用这个特殊版本的foo符号的定义
..1.1.9. 强弱符号
针对强弱符号的概念 链接器就会按照如下规则处理与选择被多次定义的全局符号
- 规则1: 不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号)
- 如果有多个强符号定义 则链接器报符号重复定义错误
- 规则2: 如果一个符号在某个目标文件中是强符号 在其他文件中都是弱符号 那么选择强符号
- 规则3: 如果一个符号在所有目标文件中都是弱符号 那么选择其中占用空间最大的一个
- 比如目标文件A定义全局变量global为int型 占4个字节
- 目标文件B定义global为doulbe型 占8个字节
- 那么目标文件A和B链接后 符号global占8个字节(尽量不要使用多个不同类型的弱符号,否则容易导致很难发现的程序错误)
..1.1.10. 强弱引用
我们所看到的对外部目标文件的符号引用在目标文件被最终链接成可执行文件时 它们必须要被正确决议
如果没有找到该符号的定义 链接器就会报符号未定义错误 这种被称为强引用(Strong Reference)
与之相对应还有一种弱引用(Weak Reference)
在处理弱引用时 如果该符号有定义 则链接器将该符号的引用决议 如果该符号未被定义 则链接器对于该引用不报错
链接器处理强引用和弱引用的过程几乎一样 只是对于未定义的弱引用 链接器不认为它是一个错误
一般对于未定义的弱引用 链接器默认其为0 或者是一个特殊的值 以便于程序代码能够识别
..1.1.11. 符号的作用域
C++中non-member function template模板的代码是具有不同于普通的函数定义的
C++标准里对于在global scope声明对象的链接描述:
[ISO/IEE 14882:2011]A name declared in a namespace scope without a storage-class-specifier has external linkage unless it has internal linkage because of a previous declaration and provided it is not declared const. Objects declared const and not explicitly declared extern have internal linkage.
例如声明int max(int,int)是具有外部链接的符号
C++中非成员函数模板(non-member function template)的链接不同于普通的函数
[ISO/IEE 14882:2014]A template name has linkage (3.5). A non-member function template can have internal linkage; any other template name shall have external linkage. Specializations (explicit or implicit) of a template that has internal linkage are distinct from all specializations in other translation units.
即通过模板实现的max(const T&,const T&)是具有内部链接internal linkage的符号
所以在链接时对于max的调用查找只能够找到自己目标文件中的符号名(内部链接外部不可见) 不会和外部链接产生重定义冲突
..2. 动态库装载过程
..2.1. ELF的辅助向量 AUXV
main函数的第三个参数 char* envp[]
..2.2. ELF的装载有三种方法
函数 | 描述 |
---|---|
load_binary | 通过读存放在可执行文件中的信息为当前进程建立一个新的执行环境 |
load_shlib | 用于动态的把一个共享库捆绑到一个已经在运行的进程, 这是由uselib()系统调用激活的 |
core_dump | 在名为core的文件中, 存放当前进程的执行上下文. 这个文件通常是在进程接收到一个缺省操作为”dump”的信号时被创建的, 其格式取决于被执行程序的可执行类型 |