现在的程序几乎都是动态链接了 + 延迟绑定了. 这样可以节省宝贵的内存空间还能提升运行时的效率. 之前也零零散散地看了很多相关的文章. 今天就系统地总结一遍吧.
got表和plt表
首先从got表和plt表讲起. 以一个调用libc中write()函数为例. 来分析一下流程:
基础知识
- got表属于数据段, 是可写的. 表中存储的是指针. plt属于代码段, 其中每一项都存储了三个汇编指令.
- 当系统根据一个文件创建一个进程的时候, dynamic linker会把got表的第二项和第三项初始化为特殊的值, 具体是什么后面会解释.
- 对于每个重定位的函数, 其在got表和plt表中分别有一项存储该函数动态链接时需要使用的程序. 我们假设 PLT[2], 和GOT[4]存储libc 中 write 的对应信息
动态链接流程
- 当这个程序里面调用write()的时候就会跳到plt表中write对应的项.这儿就是PLT[2], 其中有三行汇编码
jmpq *GOT[4] # write 函数在got表中对应的项
pushq $0x01 # write()对应的编号, 记为reloc_arg.程序中的每个重定位函数都有一个独一无二的编号, 根据这个编号可以计算这个函数对应的got表的偏移, 动态链接需要的信息等等.
jmpq 4005a # PLT[0]
而GOT[4]表中初始值都是指向PLT[2]的第二行代码, 当程序刚加载完毕的时候, 每个重定位函数对应的got表中的地址都指向对应的plt表的第二个指令.
- 进入 PLT[0], 其中代码如下:
pushq *GOT[1] #一个特殊的地址, 指向动态链接所需要的信息, 后面会解释, 记为 link_map
jmpq *GOT[2] # dynamic linker 的地址
GOT[2] 中存储的就是_dl_runtime_resolve
函数的地址了.
就相当于执行了_dl_runtime_resolve(link_map, reloc_arg)
.
-
_dl_runtime_resolve()
会根据reloc_arg计算出got表地址, 需要重定位函数的名称等信息, 然后根据这些信息找到函数的真实运行地址. 最后把got表中这个函数对应的项got[2]修改为真实地址. -
dynamic linker()
执行结束之后就会跳转到write()
函数里面. -
之后在调用
write()
的时候仍然先跳到plt[2], 然后跳到*got[4]
, 此时got[4]
中的地址就是write()
函数的真实地址了. 因为只在第一次执行的时候才绑定真实的地址, 所以叫做延迟绑定(lazy binding)
dynmic linker工作流程
看完前面的内容, 对动态链接的过程已经大致了解了. 接下来我们深入分析一下最关键的一步:调用dynmic linker修改got表内容.
我画了如下示意图来更直观地表达动态链接的过程, 具体过程后面解释.
.dynamic是elf文件中的一个section, 其中包含了动态链接所需要的信息. 比如一些指向其它section的指针. 可以参考这个文档
.dynstr是elf文件中的一个section. 其包含的需要重定位的函数的名称. dynamic linker可以根据这些名称找到真实的运行时地址进而修改got表
.dynsym section是一个结构体数组, 结构体定义如下:
typedef struct
{
Elf32_Word st_name; /* Symbol name (index in .synstr) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility under glibc>=2.2 */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;
每个重定位函数在其中有一项, 可以根据这个找到重定位函数的名称.
.rel.plt section也是一个结构体数组, 结构体定义如下:
typedef uint32_t Elf32_Addr;
typedef uint32_t Elf32_Word;
typedef struct
{
Elf32_Addr r_offset; /* 该项对应的got表的项的地址 */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;
#define ELF32_R_SYM(val) ((val) >> 8) //计算该项在.dynsym中的index
#define ELF32_R_TYPE(val) ((val) & 0xff)
.dynmic .dynstr
+-------> +-----------------+ +-------------------------------> +------------+
| ................| | | ........ |
| | | +------------+
| | | +----------> "read" |
+-----------------+ | | +------------+
| STRTAB +--------+ .dynsym | | ......... |
+-----------------+ +--------------+ | +------------+
| SYSTAB +---------> | ......... | |
+-----------------+ +--------------+ |
| PLTRELSZ | | name_index +----+
+-----------------+ +--------------+
| PLTREL | | | index = Elf32_R_SYM(r_info)
+-----------------+ +--------------+ <-------------------+
| RELENT | | | |
+-----------------+ +--------------+ |
| JMPREL +---+ | ......... | |
+-----------------+ | +--------------+ |
| | | .rel.plt |
| | +-----------------------> +--------------+ |
| | +----+ r_offset | |
| | .got.plt | +--------------+ |
| | +-------------+ | | r_info +---+
| | | ...... | | +--------------+
| | +-------------+ | | ........... |
| | | read@got | <+ | |
| | +-------------+ | |
+-----------------+ | ....... | | |
| | +--------------+
| |
| |
+-------------+
最后结合动态链接器的源码分析具体链接过程(源码可见: glibc/elf/dl-runtime.c: _dl_fixup 函数)
_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg)
{
// 首先通过参数reloc_arg计算重定位入口,这里的JMPREL即.rel.plt,reloc_offset即reloc_arg
const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
// 然后通过reloc->r_info找到.dynsym中对应的条目
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
// 这里还会检查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
// 接着通过strtab+sym->st_name找到符号表字符串,result为libc基地址
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
// value为libc基址加上要解析函数的偏移地址,也即实际地址
value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);
// 最后把value写入相应的GOT表条目中
return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
}
参考博客:
ROP之return to dl-resolve
Executable and Linkable Format (ELF)
网友评论