標籤:
朱宇軻 + 原創作品轉載請註明出處 + 《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
linux中主要的可執行檔為ELF檔案,我們可以將它裝載到自己的程式中,這次我們就將分析linux裝載可執行程式的過程。
首先明確一點,裝載可執行程式有兩種方式:靜態連結與動態連結。所謂靜態連結,就是在程式執行之前完成所有連結工作,組成一個可執行檔,放到記憶體執行。這樣做的缺點是,當有多個檔案要連結同一份可執行檔時,記憶體中會有多份這個可執行檔的拷貝,這在一定程度上就是一種對記憶體的浪費。因此,人們又發明了動態連結的概念,它指的是程式執行前並不將所有的模組組裝在一起,而是在需要用到這個模組的時候再完成連結工作,這樣相比靜態連結就更加靈活,也節省了記憶體。
動態連結分為裝載時動態連結和運行時動態連結,大家可有興趣可以進一步瞭解一下。
基礎知識普及完畢,接著我們來分析linux具體是如何裝載可執行程式的。
linux裝載可執行程式的系統調用是execve,它和fork函數一樣,在執行的過程中會更改執行完畢後返回的程式碼片段。
它的工作是首先讀入傳入的檔案名稱、參數和環境變數,然後調用解析鏈表尋找解析該可執行檔的結構:
list_for_each_entry(fmt, &formats, lh) { if (!try_module_get(fmt->module)) continue; read_unlock(&binfmt_lock); bprm->recursion_depth++; retval = fmt->load_binary(bprm); read_lock(&binfmt_lock);
比如我們讀入了ELF檔案,那它就要在鏈表中尋找ELF檔案的解析器。這裡注意運用了觀察者模式:linux會將各種解析器預先註冊,當添加了新的解析器後,就會更改解析的鏈表。比如ELF檔案中是這樣的:
static struct linux_binfmt elf_format = { .module = THIS_MODULE, .load_binary = load_elf_binary, .load_shlib = load_elf_library, .core_dump = elf_core_dump, .min_coredump = ELF_EXEC_PAGESIZE,};
在這裡,它定義load_lef_binary為解析器load_binary的具體實現(其實就是一種多態),之後將該結構體註冊到解析器的鏈表中,從此再遇到ELF檔案,搜尋解析器鏈表,就可以找到專門解析這種檔案的解析器了。
static int __init init_elf_binfmt(void){ register_binfmt(&elf_format); return 0;}
在ELF自己的解析函數load_elf_binary中,對於靜態連結和動態連結,處理過程是不一樣的。
if (elf_interpreter) {unsigned long interp_map_addr = 0;elf_entry = load_elf_interp(&loc->interp_elf_ex, interpreter, &interp_map_addr, load_bias);if (!IS_ERR((void *)elf_entry)) {/* * load_elf_interp() returns relocation * adjustment */interp_load_addr = elf_entry;elf_entry += loc->interp_elf_ex.e_entry;}if (BAD_ADDR(elf_entry)) {retval = IS_ERR((void *)elf_entry) ?(int)elf_entry : -EINVAL;goto out_free_dentry;}reloc_func_desc = interp_load_addr;allow_write_access(interpreter);fput(interpreter);kfree(elf_interpreter);} else {elf_entry = loc->elf_ex.e_entry;if (BAD_ADDR(elf_entry)) {retval = -EINVAL;goto out_free_dentry;}}
...
start_thread(regs,elf_entry,bprm->p);
對於動態連結,這段代碼直接執行elf_interpreter的部分,此時會裝載一個動態連結器,由它再進行具體的記憶體管理,這裡暫且不討論。
對於靜態連結,則直接執行else的部分,此時會將ELF程式碼片段的入口地址付給elf_entry變數。
之後會執行start_thread函數,該函數將進程上下文壓棧,同時將elf_entry賦給ip,對於靜態連結來說,也就是使代碼跳出核心態後執行的第一條代碼就是ELF的入口處代碼。
這樣一來,就可以實現裝載可執行程式的功能了。
總結:
本部落格討論了linux裝載可執行程式的過程,裝載的可執行程式分為靜態和動態連結兩種方式。在解析可執行檔時,linux利用了多態機制和觀察者模式,並在解析過程中改變核心堆棧的EIP地址,從而實現將執行的下一條代碼更改到可執行程式的作用。
實驗過程是基於實驗樓的系統,這裡簡單顯示幾張:
linux裝載可執行程式簡析