zoukankan      html  css  js  c++  java
  • Linux内核如何装载和启动一个可执行程序

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

    符钰婧 原创作品转载请注明出处 

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

     这一周的主要内容是可执行程序的装载。

    一、那么首先来看一下编译链接的过程和ELF可执行文件格式

    1、这张图简明扼要的说明了可执行程序的产生。

     

    大概过程是这样的:

    ​.c文件汇编成汇编代码.asm,

    然后再汇编成目标码.o,

    然后链接成可执行文件a.out,

    这时可执行文件就可以加载到内存中执行了。

    2、举个例子(对hello world .c文件进行编译链接):

    具体过程如图:

    ​3ELF可执行文件格式

    1ELF中的三种主要的目标文件

     

    可重定位文件:.o文件

    共享目标文件:.so文件(链接编辑器=静态链接器)

    2)文件格式

     (3)查看可执行文件的头部(readelf指令

     

    指明了版本号、OS/ABIABI的版本、是可执行文件还是目标文件、入口地址(程序的起点)。

    4)静态链接的ELF可执行文件和进程的地址空间

     

    入口地址为0x8048*00(不唯一);

    x86的系统有4G的进程地址空间(前面的1G:内核用;之后:用户态可访问);

    当一个ELF可执行文件要加载到内存中时:

    先把代码段和数据段加载到当中(默认从0x8048000位置开始加载);

    开始加载时,前面的都是ELF格式的头部信息,大小不尽相同,根据头部大小可确定程序的实际入口;

    当启动一个刚加载过可执行文件的进程时,就可从这个位置开始执行。

    二、接下来是使用exec*库函数加载一个可执行文件的过程

    注:对于静态链接,只要传递命令行参数和环境变量等就可以正常工作了;但是对于绝大多数的可执行程序来讲,还是有一些对动态链接库的依赖。

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

    1)引用视频中的例子

     

    main函数中调用动态加载共享库时需要用到dlopen

    声明了一个函数指针【*func】;

    找到函数名,根据函数名赋给指针。

    这样就可以使用共享库中定义的函数了。

    2)怎样编译执行

     

    三、在跟踪分析之前稍微提一下关于可执行程序的装载的关键问题

    ​1sys_execve内核处理过程:

    do_execve-> do_execve_common ->  exec_binprm  //调用顺序

    2、观察者和被观察者

     

    3sys_execve的内部处理过程

    这是系统调用的入口:

     

    do_execve

     

    1550行为用户态的指针;

    1553行把命令行参数变成一个结构。

    do_execve_common

     

    其中有几个关键的地方:

    1do_open_exec:

     

    打开要加载的可执行文件,还会加载它的文件头部。

    创建了一个结构体bprm

     

    15051509行是把环境变量和命令行参数都copy到结构体中;

    1513行开始是可执行文件的处理过程。

    2exec_binprm

     

    其中关键的代码为search binary handler(寻找此可执行文件的处理函数):

    在其中更关键的代码:

     

    在这个循环中寻找能够解析当前可执行文件的代码。

    1374行为加载可执行文件的处理函数,实际调用的是load_elf_binary函数:

    查找一下可发现:

     

    48行为此函数原型;

    84行为赋值语句;

    571行为函数的实现。

    赋值的代码:

     

    这是一个结构体变量。

    此结构体变量是怎么进入内核的处理模块中的:

     

    此函数中将这个结构体变量注册进了链表中,

    如此当出现了一个ELF格式的文件时,在链表中寻找就能发现此结构体变量。

    函数实现代码中较关键的代码:

    注:ELF可执行文件会被默认映射到0x8048000这个地址。

     

    如果进入到这个语句,即表示它需要依赖其他的动态库(不是静态链接的可执行文件);

    它就会加载load_elf_interp(动态链接库动态链接文件),动态链接器的起点。

    如果它是一个静态链接,可直接进行以下赋值:

     

    可发现在start_thread处会有两种可能:

     

    小结:

    如果是静态链接,elf_entry就指向了可执行文件中规定的头部,即main函数对应的位置,是新程序执行的起点;

    如果是需要依赖其他动态库的动态链接,elf_entry是指向动态链接器的起点。

    四、最后就是使用gdb跟踪分析一个execve系统的调用内核处理函数sys_execve

    ​(1)先把menu删掉,重新clone一份之后进入menu,覆盖test.c之后进入查看

     

    2test.c

     

    可发现增加了一句menuconfigexec;

     

    fork源代码不同只在于增加了一句execlp

    3​Makefile

     

     可发现其中编译了hello.c

    然后在生成根文件系统的时候把inithello都放到rootfs.img中了。

    4make rootfs

     

    发现增加了exec

    5)开始使用gdb跟踪

     

    6)设断点

     

    7)开始执行

     

    发现按了3c之后程序才执行结束。

    8)执行exec​

     

    发现它执行到这个地方就停止了。

    注:不知道是什么原因,每次执行到这一步实验楼就完全卡住。重新开始实验也是这样的结果。所以后面的步骤配图来自视频。(截图过多,适当省略了一点)

    9list,然后继续s跟踪

     

    发现跟踪到了do_execve处;

    10c继续执行

     

    到了load_elf_binary处;

    list,然后继续n跟踪:

     

    此时追踪到start_thread处。

    问题:new_ip到底指向哪里?

     

    11)再次水平分割,然后执行readelf命令

     

    可发现new_ip指向入口点地址

    12)关掉新窗口之后按n执行然后一直s执行

     

    可以发现这里将new_ipnew_sp赋值,并设了一个新堆栈。

    13)最后按c执行结束

     

    五、总结动态链接的过程中内核做了什么?

    1、动态链接库的依赖关系会形成一个图

    2、问题:是由内核负责加载可执行程序依赖的动态链接库吗?

          答:否。由动态连接器(libc的一部分)完成,是用户态做的事情。

    ​3、动态链接库的装载过程是一个图的遍历

    4动态链接:由ld来动态链接可执行程序,完成各种工作之后再把控制权移交给可执行程序的入口,可执行程序然后执行。

  • 相关阅读:
    数值数据类型
    如何提高数据迁移和复制的速度
    dns解析
    cdn加速
    集群
    JavaScript初学者应注意的七个细节
    CXF 5参考资料
    深入理解Spring MVC 思想
    【深入理解Java内存模型】
    牛人论
  • 原文地址:https://www.cnblogs.com/fuyujing/p/5358368.html
Copyright © 2011-2022 走看看