現(xiàn)在的程序幾乎都是動態(tài)鏈接了 + 延遲綁定了. 這樣可以節(jié)省寶貴的內(nèi)存空間還能提升運(yùn)行時的效率. 之前也零零散散地看了很多相關(guān)的文章. 今天就系統(tǒng)地總結(jié)一遍吧.
got表和plt表
首先從got表和plt表講起. 以一個調(diào)用libc中write()函數(shù)為例. 來分析一下流程:
基礎(chǔ)知識
- got表屬于數(shù)據(jù)段, 是可寫的. 表中存儲的是指針. plt屬于代碼段, 其中每一項(xiàng)都存儲了三個匯編指令.
- 當(dāng)系統(tǒng)根據(jù)一個文件創(chuàng)建一個進(jìn)程的時候, dynamic linker會把got表的第二項(xiàng)和第三項(xiàng)初始化為特殊的值, 具體是什么后面會解釋.
- 對于每個重定位的函數(shù), 其在got表和plt表中分別有一項(xiàng)存儲該函數(shù)動態(tài)鏈接時需要使用的程序. 我們假設(shè) PLT[2], 和GOT[4]存儲libc 中 write 的對應(yīng)信息
動態(tài)鏈接流程
- 當(dāng)這個程序里面調(diào)用write()的時候就會跳到plt表中write對應(yīng)的項(xiàng).這兒就是PLT[2], 其中有三行匯編碼
jmpq *GOT[4] # write 函數(shù)在got表中對應(yīng)的項(xiàng)
pushq $0x01 # write()對應(yīng)的編號, 記為reloc_arg.程序中的每個重定位函數(shù)都有一個獨(dú)一無二的編號, 根據(jù)這個編號可以計算這個函數(shù)對應(yīng)的got表的偏移, 動態(tài)鏈接需要的信息等等.
jmpq 4005a # PLT[0]
而GOT[4]表中初始值都是指向PLT[2]的第二行代碼, 當(dāng)程序剛加載完畢的時候, 每個重定位函數(shù)對應(yīng)的got表中的地址都指向?qū)?yīng)的plt表的第二個指令.
- 進(jìn)入 PLT[0], 其中代碼如下:
pushq *GOT[1] #一個特殊的地址, 指向動態(tài)鏈接所需要的信息, 后面會解釋, 記為 link_map
jmpq *GOT[2] # dynamic linker 的地址
GOT[2] 中存儲的就是_dl_runtime_resolve
函數(shù)的地址了.
就相當(dāng)于執(zhí)行了_dl_runtime_resolve(link_map, reloc_arg)
.
_dl_runtime_resolve()
會根據(jù)reloc_arg計算出got表地址, 需要重定位函數(shù)的名稱等信息, 然后根據(jù)這些信息找到函數(shù)的真實(shí)運(yùn)行地址. 最后把got表中這個函數(shù)對應(yīng)的項(xiàng)got[4]修改為真實(shí)地址.dynamic linker()
執(zhí)行結(jié)束之后就會跳轉(zhuǎn)到write()
函數(shù)里面.之后在調(diào)用
write()
的時候仍然先跳到plt[2], 然后跳到*got[4]
, 此時got[4]
中的地址就是write()
函數(shù)的真實(shí)地址了. 因?yàn)橹辉诘谝淮螆?zhí)行的時候才綁定真實(shí)的地址, 所以叫做延遲綁定(lazy binding)
dynmic linker工作流程
看完前面的內(nèi)容, 對動態(tài)鏈接的過程已經(jīng)大致了解了. 接下來我們深入分析一下最關(guān)鍵的一步:調(diào)用dynmic linker修改got表內(nèi)容.
我畫了如下示意圖來更直觀地表達(dá)動態(tài)鏈接的過程, 具體過程后面解釋.
.dynamic是elf文件中的一個section, 其中包含了動態(tài)鏈接所需要的信息. 比如一些指向其它section的指針. 可以參考這個文檔
.dynstr是elf文件中的一個section. 其包含的需要重定位的函數(shù)的名稱. dynamic linker可以根據(jù)這些名稱找到真實(shí)的運(yùn)行時地址進(jìn)而修改got表
.dynsym section是一個結(jié)構(gòu)體數(shù)組, 結(jié)構(gòu)體定義如下:
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;
每個重定位函數(shù)在其中有一項(xiàng), 可以根據(jù)這個找到重定位函數(shù)的名稱.
.rel.plt section也是一個結(jié)構(gòu)體數(shù)組, 結(jié)構(gòu)體定義如下:
typedef uint32_t Elf32_Addr;
typedef uint32_t Elf32_Word;
typedef struct
{
Elf32_Addr r_offset; /* 該項(xiàng)對應(yīng)的got表的項(xiàng)的地址 */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;
#define ELF32_R_SYM(val) ((val) >> 8) //計算該項(xiàng)在.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 | <+ | |
| | +-------------+ | |
+-----------------+ | ....... | | |
| | +--------------+
| |
| |
+-------------+
最后結(jié)合動態(tài)鏈接器的源碼分析具體鏈接過程(源碼可見: glibc/elf/dl-runtime.c: _dl_fixup 函數(shù))
_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg)
{
// 首先通過參數(shù)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中對應(yīng)的條目
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基址加上要解析函數(shù)的偏移地址鼠证,也即實(shí)際地址
value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);
// 最后把value寫入相應(yīng)的GOT表?xiàng)l目中
return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
}
參考博客:
ROP之return to dl-resolve
Executable and Linkable Format (ELF)