Linux內(nèi)核如何裝載和啟動(dòng)一個(gè)可執(zhí)行程序

此文僅用于MOOCLinux內(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
Elf-layout--en.svg.png

動(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í)綁定的。
libraries.gif

而動(dòng)態(tài)鏈接分為可執(zhí)行程序裝載時(shí)動(dòng)態(tài)鏈接和運(yùn)行時(shí)動(dòng)態(tài)鏈接.

GDB

  • 設(shè)置以下斷點(diǎn):
sysexecve1.png
  • 在MenuOS執(zhí)行exec后,中斷情況如下:
sysexecve2.png
sysexecve3.png
sysexecve4.png
  • 進(jìn)入search_binary_handler后可以查看一些變量情況:比如fmt,bprm
sysexecve5.png
sysexecve6.png
  • 進(jìn)入start_thread后使用po命令,可以看到new_ip處:
sysexecve9.png
  • 在改變r(jià)egs前后查看regs:
sysexecve7.png
sysexecve8.png
  • 繼續(xù)跟蹤可以看到在執(zhí)行do_notify_resume后,進(jìn)入0x08048d0a處,即之前的new_ip處(hello的入口地址):
sysexecve10.png
sysexecve11.png

分析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é)

  1. linux通過sys_execve()系統(tǒng)調(diào)用從文件系統(tǒng)中讀取、識別并加載elf
  2. 調(diào)用sys_execve后,執(zhí)行過程:do_execve -> do_execve_common -> exec_binprm->load_elf_binary()->sys_close
  3. 根據(jù)elf的庫類型,elf_entry不一樣.load_elf_binary通過解析器將不同的入口地址寫入.

參考

  1. Linkers and Loaders
  2. ELF在Linux下的加載過程
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容