• 2018-2019-1 20189219《Linux内核原理与分析》第八周作业


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

    一.实验

    1.1理解编译链接的过程和ELF可执行文件格式。

    1.1.1编译链接过程

    能用图说明的问题,就少用文字描述:

    1.1.2ELF可执行文件

    ELF可执行文件中有三种主要的目标文件:

    一个可重定位文件保存着代码和适当的数据,用来和其他的object文件一起创建一个可执行文件或者是一个共享文件。主要是.o文件。
    一个可执行文件保存着一个用来执行的程勋;该文件指出了exec如何创建程序进程映像。
    一个共享object文件保存着代码和合适地数据,用来被下面的两个链接器链接。第一个是链接编辑器,可以和其他的可重定位的共享object文件来创建其他的object。第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映像。
    Object文件参与程序的链接(创建一个程序)和程序的执行(运行一个程序)。一个ELF头在文件的开始,保存了路线图(road map),描述该文件的组织情况。程序头表告诉系统如何创建一个进程的内存映像。section头表包含了描述文件sections的信息。每个section在这个表中有一个入口;每个入口给出了该section的名字、大小等信息。

    archemiya@mint:~/homework$ readelf -h main.c
    

    查看一个main目标文件的信息:

    1.2

    编程使用exec*库函数加载一个可执行文件,动态链接分为可执行程序装载时动态链接和运行时动态链接,编程练习动态链接库的这两种使用方式。

    1.2.1使用execlp加载上周GCC内联汇编的main程序

    关于execlp函数的介绍。

    int main(int argc,char *argv[]){
            int pid;
            pid = fork();
            if(pid < 0){
                    fprintf(stderr,"Fork failed!");
                    exit(-1);
            }
            else{
                    execlp("/home/sillysen/homework/11.14/main","./main","2","0","1","7","9","2","0","9",NULL);
                    exit(0);
            }
            return 0;
    }
    

    运行结果如下:

    execlp函数的第一个参数是可执行程序的路径,后面的参数是这个可执行程序运行时的参数,值得注意的是命令本身也算一个参数,所以第二个参数一般就是命令本身。至于这里为什么输出两遍,而且是这样的格式,我也不太清楚,欢迎大家指正。

    1.2.2编程练习动态链接库的这两种使用方式

    1.2.2.1可执行程序装载时动态链接

    我们测试的代码非常简单,能说明问题就行。把sharelib.c制作成动态库,然后在main函数中调用。源代码如下:

    /*
        main 函数
    */
    #include <stdio.h>
    extern int print();
    int main(int argc, char *argv[]){
            print();
            return 0;
    }
    /*
        sharelib
    */
    int print(){
            printf("This is share lib!
    ");
            return 0;
    }
    
    archemiya@mint:~/homework/11.16$ gcc -fPIC -shared -o libsharelib.so sharelib.c    //制作动态库libsharelib.so
    archemiya@mint:~/homework/11.16$ sudo cp libsharelib.so /usr/lib    //将生成的动态库拷贝到/usr/lib目录,只有这样生成的程序才能执行
    

    1.2.2.2可执行程序运行时动态链接

    修改main函数为如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <dlfcn.h>
    int main(int argc, char *argv[]){
            //print();
            void * handle = dlopen("libsharelib.so",RTLD_NOW);
            if(handle == NULL){
                    printf("Open Lib libsharelib.so Error:%s
    ",dlerror);
                    return -1;
            }
            int (*func)(void);
            char * error;
            func = dlsym(handle,"print");
            if((error = dlerror()) != NULL){
                    printf("print not found:%s
    ",error);
                    return -1;
            }
            func();
            dlclose(handle);
            return 0;
    }
    

    编译命令为

    archemiya@mint:~/homework/11.16$ gcc -o main main.c -ldl,
    

    使用dl系列函数除了要在头文件中包含dlfcn.h之外,在编译链接时还得加-ldl参数。

    1.3使用gdb跟踪分析一个execve系统调用内核处理函数sys_execve

    根据孟老师视频中讲解的execve系统调用的整个过程,可以把其中一些重要的函数摘录出来,画出如下的一个简易的流程图(不完整的流程图,只包含老师讲解过的过程)如下:

    为此我在关键位置设置断点(除了视频中讲解的三个外,又加了其他几个):

    开始GDB跟踪:



    分析:

    八个断点只跟踪到了四个exec命令就执行完了。其中第一个是sys_execve,截图中没有是因为我一开始就断在了sys_execve处,第二个是do_open_exec断点,第三个是load_elf_binary,第四个是start_thread。其中do_execve这个断点没出现是因为在sys_execve函数中最后return do_execve(getname(filename),argv,envp);时候我没有s进去看;而load_elf_interp断点没有出现是因为这里调用的fork为静态装载可执行程序,而只有动态装载才会调用这个函数,到此还有两个断点do_execve_common和exec_binprm没有出现。为此我还特意断了一下exec_binprm中search_binary_handler函数,gdb显示没有这个符号,这里就留下一个问题,为什么会出现这样的情况?根据视频中讲解这个函数的目的是装载bprm数据结构,但gdb跟踪过程中却没有这一步。。。
    新的可执行程序的起点根据程序的链接方式不同而不同,如果是静态链接,则起点为可执行文件里边规定的entry地址,也是main函数对应的位置;如果是动态链接,则elf_entry就是指向动态链接器的起点。
    execve返回后之所以能顺利执行是因为可执行程序在当前进程调用execve内核函数的时候已经部署就绪,返回用户态后之前的进程“苏醒”,开始执行程序。

  • 相关阅读:
    构建一个应用,并在docker compose里运行起来
    docker engine docker-compose部署
    django 返回数据的几种常用姿势
    fiddler+httprunner 零编码实现接口自动化DEMO
    选择排序
    曾经学的那些表示时间复杂度的公式怎么来的?
    python+Airtest+android使用AirtestIDE编写一个DEMO
    怎么计算时间复杂度?
    算法_冒泡排序python+java实现
    2020年1月16日(多进程)
  • 原文地址:https://www.cnblogs.com/archemiya/p/10056205.html
Copyright © 2020-2023  润新知