前置知识:
ELF可执行文件由ELF头部,程序头部和其对应的段,节头部表和其对应的节组成。
ELF文件结构如果想深入了解的话可以看下《程序员的自我修养》的第三章 P70
如果一个可执行文件要进行动态链接,它的程序头部表将包含类型为PT_DYNAMIC的段,它包含.dynamic节结构如下
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
其中tag对应着每个节
JMPREL对应 .rel.plt 用来保存运行时重定位表
SYMTAB对应着符号表.dymsym 它是一个Elf32_Sym结构的数组
STRTAB对应着字符串表.dymstr 用来存放动态链接时需要用到的字符串
ELF中节中包涵目标文件的所有信息,节的结构如下
typedef struct {
Elf32_Word sh_name; // 节头部字符串表节区的索引
Elf32_Word sh_type; // 节类型
Elf32_Word sh_flags; // 节标志,用于描述属性
Elf32_Addr sh_addr; // 节的内存映像
Elf32_Off sh_offset; // 节的文件偏移
Elf32_Word sh_size; // 节的长度
Elf32_Word sh_link; // 节头部表索引链接
Elf32_Word sh_info; // 附加信息
Elf32_Word sh_addralign; // 节对齐约束
Elf32_Word sh_entsize; // 固定大小的节表项的长度
} Elf32_Shdr;
.rel.plt节是用于函数重定位 ,.rel.dyn节是用于变量重定位
typedef struct {
Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址
Elf32_Word r_info; // 符号表索引
} Elf32_Rel;
#define ELF32_R_SYM(info) ((info)>>8) #符号在符号表中的索引,占r_offset的高24位
#define ELF32_R_TYPE(info) ((unsigned char)(info))重定位类型 占r_offset的低8位
#define ELF32_R_INFO(sym, type) (((sym)<<8)+(unsigned char)(type))
.rel.plt 中的offset 对应着r_offset 是函数在.got.plt表中的位置, Info对应着r_info的高24位,Type对应着r_info的低8位
GOT表是一个简单数组,存放各种绝对地址。
GOT[0] 存放的是动态链接数组_dynamic[]的地址,GOT[1]存放的是一个link_map类型的指针
GOT[2]存放的是动态链接器的解析函数
PLT表是由一小段一小段代码组成的(几条控制跳转的汇编语句)
PLT0:
push GOT[1];
jmp GOT[2];
.......
PLTn:
jmp GOT[x+n];
push n; #relocation offset of symbol ,the second argument of _dl_runtime_resolve
jmp PLT0; call the rtld
程序第一次调用位于.got.plt表中的函数时 ,会跳转回plt表中
然后将参数压入栈中 ,这个参数是reloc_offset 是函数在got表中的偏移
接着会跳转到PLT0,将link_map压入栈中,在调用_dl_runtime_resolve函数来对函数进行重定位
这个函数原型是_dl_runtime_resolve(link_map,reloc_offset)
调用_dl_runtime_resolve函数后
先根据reloc_offest 找到要解析符号的重定位项结构信息reloc
ELF32_Rel *rel_entry = JMPREL + reloc_offset
然后在根据reloc->r-info 找到符号在符号表中的索引
ELF32_Sym *sym_entry = SYMTAB[ELF32_R_SYM(rel_entry->r_info)]
其中还会对重定向符号的类型进行检测
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
接着在通过.dymsym项中的st_name找到符号在字符串表.dymstr中的偏移
char *sym_name = STRTAB + sym_entry->st_name
最后通过fixup()函数遍历各个库的符号表 找到函数地址再将地址写入GOT表对应的位置中
当程序第二次调用这个函数时,就会直接调用这个函数
如果想对动态链接更进一步了解的话
可以看《Linux动态链接机制研究及应用》、《程序员的自我修养》