此文僅用于MOOC
Linux內(nèi)核分析作業(yè)張依依+原創(chuàng)作品轉(zhuǎn)載請注明出處+<Linux內(nèi)核分析>
MOOC課程http://mooc.study.163.com/course/USTC-1000029000
ELF
可執(zhí)行和可鏈接格式 (Executable and Linkable Format,縮寫為ELF),常被稱為ELF格式,在計(jì)算機(jī)科學(xué)中,是一種用于執(zhí)行檔、目的檔、共享庫和核心轉(zhuǎn)儲的標(biāo)準(zhǔn)文件格式。
1999年,被86open項(xiàng)目選為x86架構(gòu)上的類Unix操作系統(tǒng)的二進(jìn)制文件格式標(biāo)準(zhǔn),用來取代COFF。因其可擴(kuò)展性與靈活性,也可應(yīng)用在其它處理器、計(jì)算機(jī)系統(tǒng)架構(gòu)的操作系統(tǒng)上。
ELF文件由 ELF header和文件數(shù)據(jù)組成.文件數(shù)據(jù)包括:
- Program header table, 程序頭:描述段信息
- Section header table, Section頭:鏈接與重定位需要的數(shù)據(jù)
- Data referred to by entries in the program header table or section header table 如:.text .data

動(dòng)態(tài)&靜態(tài)
當(dāng)用戶啟動(dòng)一個(gè)應(yīng)用程序時(shí),它們正在調(diào)用一個(gè)可執(zhí)行和鏈接格式映像。
Linux中ELF支持兩種類型的庫,每一種庫都有各自的優(yōu)缺點(diǎn):
- 靜態(tài)庫包含在編譯時(shí)靜態(tài)綁定到一個(gè)程序的函數(shù)。
- 動(dòng)態(tài)庫則不同,它是在加載應(yīng)用程序時(shí)被加載的,而且它與應(yīng)用程序是在運(yùn)行時(shí)綁定的。

而動(dòng)態(tài)鏈接分為可執(zhí)行程序裝載時(shí)動(dòng)態(tài)鏈接和運(yùn)行時(shí)動(dòng)態(tài)鏈接.
GDB
- 設(shè)置以下斷點(diǎn):

- 在MenuOS執(zhí)行exec后,中斷情況如下:



- 進(jìn)入search_binary_handler后可以查看一些變量情況:比如fmt,bprm


- 進(jìn)入start_thread后使用po命令,可以看到new_ip處:

- 在改變r(jià)egs前后查看regs:


- 繼續(xù)跟蹤可以看到在執(zhí)行do_notify_resume后,進(jìn)入
0x08048d0a處,即之前的new_ip處(hello的入口地址):


分析sys_execve
當(dāng)sys_execve被調(diào)用后,涉及的主要函數(shù)為:
do_execve -> do_execve_common -> exec_binprm
syscall
SYSCALL_DEFINE3(execve,
const char __user *, filename,
const char __user *const __user *, argv,
const char __user *const __user *, envp)
{ //真正執(zhí)行程序的功能exec.c文件中的do_execve函數(shù)中實(shí)現(xiàn)
return do_execve(getname(filename), argv, envp);
}
do_execve
int do_execve(struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
//調(diào)用do_execve_common
return do_execve_common(filename, argv, envp);
}
do_execve_common
static int do_execve_common(struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp)
{
struct linux_binprm *bprm;
struct file *file;
struct files_struct *displaced;
int retval;
..
//打開要執(zhí)行的文件,并檢查其有效性
file = do_open_exec(filename);
retval = PTR_ERR(file);
if (IS_ERR(file))
goto out_unmark;
sched_exec();
// 填充linux_binprm結(jié)構(gòu)
bprm->file = file;
bprm->filename = bprm->interp = filename->name;
...
//將文件名、環(huán)境變量和命令行參數(shù)拷貝到新分配的頁面中
retval = copy_strings_kernel(1, &bprm->filename, bprm);
if (retval < 0)
goto out;
bprm->exec = bprm->p;
retval = copy_strings(bprm->envc, envp, bprm);
if (retval < 0)
goto out;
retval = copy_strings(bprm->argc, argv, bprm);
if (retval < 0)
goto out;
//調(diào)用exec_binprm,保存當(dāng)前的pid并且調(diào)用 search_binary_handler
retval = exec_binprm(bprm);
if (retval < 0)
goto out;
/* execve succeeded */
current->fs->in_exec = 0;
current->in_execve = 0;
acct_update_integrals(current);
task_numa_free(current);
free_bprm(bprm);
putname(filename);
if (displaced)
put_files_struct(displaced);
return retval;
}
- 關(guān)于linux_binprm保存要執(zhí)行的文件相關(guān)的參數(shù),包括argc,envc,filename,interp等
-
exec_binprm在保存了bprm后調(diào)用該函數(shù)來進(jìn)一步操作,這個(gè)函數(shù)除了保存pid以外,還執(zhí)行了ret = search_binary_handler(bprm);來查詢能夠處理相應(yīng)可執(zhí)行文件格式的處理器,并調(diào)用相應(yīng)的load_binary方法以啟動(dòng)進(jìn)程。
search_binary_handler
int search_binary_handler(struct linux_binprm *bprm)
{
...
//循環(huán)binary formats handler,直到找到
retry:
read_lock(&binfmt_lock);
list_for_each_entry(fmt, &formats, lh) {
if (!try_module_get(fmt->module))
continue;
read_unlock(&binfmt_lock);
bprm->recursion_depth++;
//解析elf格式執(zhí)行的位置
retval = fmt->load_binary(bprm);
read_lock(&binfmt_lock);
...
}
- 這里的
fmt是linux_binfmt格式,該結(jié)構(gòu)用來load the binary formats - 經(jīng)由search_binary_handler函數(shù)呼叫l(wèi)oad_elf_binary函數(shù)
- ELF格式的二進(jìn)制映像的認(rèn)領(lǐng)、裝入和啟動(dòng)是由load_elf_binary()完成的。在
/fs/binfmt_elf.c中,定義了如下結(jié)構(gòu):
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_elf_binary
static int load_elf_binary(struct linux_binprm *bprm)
{
...
//獲取頭
loc->elf_ex = *((struct elfhdr *)bprm->buf);
//讀取頭信息
if (loc->elf_ex.e_phentsize != sizeof(struct elf_phdr))
goto out;
if (loc->elf_ex.e_phnum < 1 ||
loc->elf_ex.e_phnum > 65536U / sizeof(struct elf_phdr))
goto out;
size = loc->elf_ex.e_phnum * sizeof(struct elf_phdr);
retval = -ENOMEM;
elf_phdata = kmalloc(size, GFP_KERNEL);
if (!elf_phdata)
goto out;
...
//讀取可執(zhí)行文件的解析器
for (i = 0; i < loc->elf_ex.e_phnum; i++) {
if (elf_ppnt->p_type == PT_INTERP) {
...
}
...
//如果需要裝入解釋器,并且解釋器的映像是ELF格式的,就通過load_elf_interp()裝入其映像,并把將來進(jìn)入用戶空間時(shí)的入口地址設(shè)置成load_elf_interp()的返回值,那顯然是解釋器的程序入口。而若不裝入解釋器,那么這個(gè)地址就是目標(biāo)映像本身的程序入口。
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)) {
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;
}
}
當(dāng)load_elf_binary()執(zhí)行完畢,返回至do_execve()在返回至sys_execve()時(shí),系統(tǒng)調(diào)用的返回地址已經(jīng)被改寫成了被裝載的ELF程序的入口地址了。
可執(zhí)行文件開始執(zhí)行的起點(diǎn)在哪里?
當(dāng)sys_execve()系統(tǒng)調(diào)用從內(nèi)核態(tài)返回到用戶態(tài)時(shí),EIP寄存器直接跳轉(zhuǎn)到ELF程序的入口地址。
總結(jié)
- linux通過sys_execve()系統(tǒng)調(diào)用從文件系統(tǒng)中讀取、識別并加載elf
- 調(diào)用sys_execve后,執(zhí)行過程:do_execve -> do_execve_common -> exec_binprm->load_elf_binary()->sys_close
- 根據(jù)elf的庫類型,elf_entry不一樣.load_elf_binary通過解析器將不同的入口地址寫入.