zoukankan      html  css  js  c++  java
  • Linux内核实验作业七

    实验作业:Linux内核如何装载和启动一个可执行程序

    20135313吴子怡.北京电子科技学院

    【第一部分】理解编译链接的过程和ELF可执行文件格式

    1.编译链接的过程

    2.ELF可执行文件格式

    一个可重定位(relocatable)文件保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。
    一个可执行(executable)文件保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象。
    一个共享object文件保存着代码和合适的数据,用来被不同的两个链接器链接。

    3.流程图:(execve–> do——execve –> search_binary_handle –> load_binary)

    4.堆栈变化图(参数块和环境块如何被传到新进程):

    【第二部分】编程使用exec*库函数加载一个可执行文件,动态链接分为可执行程序装载时动态链接和运行时动态链接

    这个部分的实践我是迷茫的。但是根据我查找的资料和学习的内容。可以借他山之石来填充我的学习记录。我整理如下:

    两种动态库实现文件比较起来没有过多不同。差异关键在调用端:main函数

    ①比较库的实现


    发现:没有功能差异,右侧仅仅多个额外的宏定义。

    ②比较库头文件


    分析如上。

    ③验证

    • 编译库
    gcc -shared shlibexample.c -o libshlibexample.so -m32
    gcc -shared dllibexample.c -o libdllibexample.so -m32

    分析:和常规的可执行文件比较,这里注意的就是要加上 -shared标志

    • 编译Main
    gcc main.c -o main -L/media/sda_m/SharedLibDynamicLink -lshlibexample -ldl -m32
    

    额外需要注意的就是这里library路径需要指明当前的路径。直接-L是不可行的。

    • 出现典型的找不到动态库的错误
    #错误情形
    /usr/bin/ld: cannot find -lshlibexample
    collect2: ld returned 1 exit status
    • 设置动态库查找的位置
    export LD_LIBRARY_PATH=$PWD

    如果不设置,将会报找不到动态库

    #错误情形
    ./main: error while loading shared libraries: libshlibexample.so: cannot open shared object file: No such file or directory
    • 成功调用动态库和运行时动态库的函数 

    【第三部分】使用gdb跟踪分析execve系统调用内核处理函数sys_execve ,验证对Linux系统加载可执行程序所需处理过程的理解

    ①设置以下断点:

    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程序的入口地址。

    【第四部分】总结

    “Linux内核装载和启动一个可执行程序”

    linux通过sys_execve()系统调用从文件系统中读取、识别并加载elf。

    调用sys_execve后,执行过程:

    do_execve -> do_execve_common -> exec_binprm->load_elf_binary()->sys_close

    根据elf的库类型,elf_entry是不一样的。load_elf_binary通过解析器将不同的入口地址写入。

    【第五部分】附录

    作者:吴子怡

    学号:20135313

    原创作品转载请注明出处

    《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

  • 相关阅读:
    LeetCode 1032. Stream of Characters
    LeetCode 872. Leaf-Similar Trees
    LeetCode 715. Range Module
    LeetCode 353. Design Snake Game
    LeetCode 509. Fibonacci Number
    LeetCode 632. Smallest Range Covering Elements from K Lists
    LeetCode 963. Minimum Area Rectangle II
    LeetCode 939. Minimum Area Rectangle
    LeetCode 727. Minimum Window Subsequence
    LeetCode 844. Backspace String Compare
  • 原文地址:https://www.cnblogs.com/paperfish/p/5361211.html
Copyright © 2011-2022 走看看