Linux内核如何装载和启动一个可执行程序

此文仅用于MOOCLinux内核分析作业

张依依+原创作品转载请注明出处+<Linux内核分析>
MOOC课程http://mooc.study.163.com/course/USTC-1000029000


ELF

可执行和可链接格式 (Executable and Linkable Format,缩写为ELF),常被称为ELF格式,在计算机科学中,是一种用于执行档、目的档、共享库和核心转储的标准文件格式。

1999年,被86open项目选为x86架构上的类Unix操作系统的二进制文件格式标准,用来取代COFF。因其可扩展性与灵活性,也可应用在其它处理器、计算机系统架构的操作系统上。

ELF文件由 ELF header和文件数据组成.文件数据包括:

  • Program header table, 程序头:描述段信息
  • Section header table, Section头:链接与重定位需要的数据
  • Data referred to by entries in the program header table or section header table 如:.text .data
Elf-layout--en.svg.png

动态&静态

当用户启动一个应用程序时,它们正在调用一个可执行和链接格式映像。
Linux中ELF支持两种类型的库,每一种库都有各自的优缺点:

  • 静态库包含在编译时静态绑定到一个程序的函数。
  • 动态库则不同,它是在加载应用程序时被加载的,而且它与应用程序是在运行时绑定的。
libraries.gif

而动态链接分为可执行程序装载时动态链接和运行时动态链接.

GDB

  • 设置以下断点:
sysexecve1.png
  • 在MenuOS执行exec后,中断情况如下:
sysexecve2.png
sysexecve3.png
sysexecve4.png
  • 进入search_binary_handler后可以查看一些变量情况:比如fmt,bprm
sysexecve5.png
sysexecve6.png
  • 进入start_thread后使用po命令,可以看到new_ip处:
sysexecve9.png
  • 在改变regs前后查看regs:
sysexecve7.png
sysexecve8.png
  • 继续跟踪可以看到在执行do_notify_resume后,进入0x08048d0a处,即之前的new_ip处(hello的入口地址):
sysexecve10.png
sysexecve11.png

分析sys_execve

sys_execve被调用后,涉及的主要函数为:

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)
        {   //真正执行程序的功能exec.c文件中的do_execve函数中实现
            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 };
            //调用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;

            ..

            //打开要执行的文件,并检查其有效性
            file = do_open_exec(filename);
            retval = PTR_ERR(file);
            if (IS_ERR(file))
                goto out_unmark;

            sched_exec();
            // 填充linux_binprm结构
            bprm->file = file;
            bprm->filename = bprm->interp = filename->name;

            ...

            //将文件名、环境变量和命令行参数拷贝到新分配的页面中
            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;
            //调用exec_binprm,保存当前的pid并且调用 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;

        }
  • 关于linux_binprm保存要执行的文件相关的参数,包括argc,envc,filename,interp等
  • exec_binprm在保存了bprm后调用该函数来进一步操作,这个函数除了保存pid以外,还执行了ret = search_binary_handler(bprm);来查询能够处理相应可执行文件格式的处理器,并调用相应的load_binary方法以启动进程。

search_binary_handler

            int search_binary_handler(struct linux_binprm *bprm)
        {
            ...
            //循环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格式执行的位置
                retval = fmt->load_binary(bprm);
                read_lock(&binfmt_lock);
                
            ...
        }
  • 这里的fmt是linux_binfmt格式,该结构用来load the binary formats
  • 经由search_binary_handler函数呼叫load_elf_binary函数
  • ELF格式的二进制映像的认领、装入和启动是由load_elf_binary()完成的。在/fs/binfmt_elf.c中,定义了如下结构:
            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;
        ...

    //读取可执行文件的解析器
    for (i = 0; i < loc->elf_ex.e_phnum; i++) {
        if (elf_ppnt->p_type == PT_INTERP) {
            ...
    }

    ...

    //如果需要装入解释器,并且解释器的映像是ELF格式的,就通过load_elf_interp()装入其映像,并把将来进入用户空间时的入口地址设置成load_elf_interp()的返回值,那显然是解释器的程序入口。而若不装入解释器,那么这个地址就是目标映像本身的程序入口。
    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;
        }
    }

load_elf_binary()执行完毕,返回至do_execve()在返回至sys_execve()时,系统调用的返回地址已经被改写成了被装载的ELF程序的入口地址了。

可执行文件开始执行的起点在哪里?

当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到ELF程序的入口地址。

总结

  1. linux通过sys_execve()系统调用从文件系统中读取、识别并加载elf
  2. 调用sys_execve后,执行过程:do_execve -> do_execve_common -> exec_binprm->load_elf_binary()->sys_close
  3. 根据elf的库类型,elf_entry不一样.load_elf_binary通过解析器将不同的入口地址写入.

参考

  1. Linkers and Loaders
  2. ELF在Linux下的加载过程
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容