可执行程序的装载和启动---linux内核学习笔记(七)

内容一:实验报告相关说明

 

所学课程:《Linux内核分析》MOOC课程  

链接:http://mooc.study.163.com/course/USTC-1000029000

 

内容二:可执行文件的创建(自己本身对这块不熟,是通过查资料来学习的,篇幅有点多)

  2.1 预处理阶段

  预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行相应的转换,预处理过程还会删除程序中的注释和多余的空白字符。

其中预处理指令主要包括以下四个方面:

     2.1.1 宏定义指令

       预处理过程会把源代码中出现的宏标识符替换成宏定义时的值,常用的两种宏定义: 

1 //声明一个标识符,全部用大写字母来定义宏
2 #define MAX_NUM 10
3 
4 //带参数的#define指令(宏函数)
5 #define Cube(x)  ((x)*(x))
6 int i,num=1;
7 i=Cube(num);

    2.1.2 条件编译指令

      定义不同的宏来决定编译程序对哪些代码进行处理,条件编译指令将决定哪些代码被编译,而哪些是不被编译的。

    2.1.3 头文件包含指令

      #include预处理指令的作用是在指令处展开被包含的文件。

    程序中包含头文件有两种格式:#include <my.h>

                  #include "my.h"

    2.1.4 特殊符号

      预编译程序可以识别一些特殊的符号。预编译程序对于在源程序中出现的这些串将用合适的值进行替换,__FILE__,__LINE__,__TIME__ 等。

    以下代码分别打印所在文件名和当前时间。

1 int main(int argc, char *argv[]) 
2 {
3     printf("%s
",__FILE__);
4     printf("%s
",__TIME__);
5     return 0;
6 }

    上述阶段对应 gcc -E -o hello.cpp hello.c -m32

  2.2 编译阶段

  在这个阶段中,Gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc把代码翻译成汇编语言。

对应gcc -x cpp-output -S -o hello.s hello.cpp -m32

   2.3 汇编阶段

  把编译阶段生成的”.s”文件转成目标文件,得到一个二进制文件,对应 gcc -x assembler -c hello.s -o hello.o -m32

  2.4 链接阶段

  函数库一般分为静态库和动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。动态库在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库。动态库一般后缀名为”.so”,Gcc在编译时默认使用动态库。

  gcc -o hello hello.o -m32 (动态编译)

  gcc -o hello.static hello.o -m32 -static(静态编译)

内容三:ELF可执行文件

   3.1 目标文件有三类:

  可重定位文件:文件保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。

    可执行文件:一个可执行(executable)文件保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象。

    共享object文件:一个共享object文件保存着代码和合适的数据,用来被两个链接器链接,分别是连接编辑器[请参看ld(SD_CMD)]、动态链接器

    

    3.2 elf文件格式

  用readelf -h hello看看elf文件内容: xingzhe@ubuntu:~$ readelf -h hello,右边为ELF的文件格式

   ELF文件默认从0x8048000开始加载,上图的ELF头中Entry point address的内容为程序实际入口,当启动一个刚加载过可执行文件的进程时,就从此处执行。

内容四:可执行程序的执行环境,动态链接方式(从老师课件处摘抄的)

   4.1 可执行环境

  • 命令行参数和shell环境,一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。

    • $ ls -l /usr/bin 列出/usr/bin下的目录信息

    • Shell本身不限制命令行参数的个数, 命令行参数的个数受限于命令自身

      • 例如,int main(int argc, char *argv[])

      • 又如, int main(int argc, char *argv[], char *envp[])

    • Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数

      • int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

      • 库函数exec*都是execve的封装例程

  命令行参数和环境串都放在用户态堆栈中:

 

    动态链接分为可执行程序装载时动态链接和运行时动态链接。
       装载时动态链接是在将功能模块读入内存时把动态库中调用到的相关模块的内容载入内存。
           运行时动态链接是在执行程序调用到模块内容时再将动态库中的相应模块载入到内存。


内容五:
使用gdb跟踪分析一个execve系统调用
gdb调试前的准备:
 1 $ cd LinuxKernel/
 2 
 3 $ rm menu -rf
 4 
 5 $ git clone https://github.com/mengning/menu.git
 6 
 7 $ move test_exec.c test.c
 8 
 9 //查看Makefile文件可知道实验采用的是静态编译
10 rootfs:
11         gcc -o init linktable.c menu.c test.c -m32 -static -lpthread
12         gcc -o hello hello.c -m32 -static
启动内核后,执行exec,执行效果:


打断点

执行到第一个断点

继续执行,到了load_elf_binary

第三个断点:

查看new_ip指向:

使用$ readelf -h hello
对比下图中的黄色部分:
地址相同,可以验证这是静态编译的可执行文件

紧接着之后修改了IP,SP。


内容六:小结

1:可执行文件的创建包括预处理、编译、汇编、链接四个阶段。
2:学习并了解了ELF文件的格式,了解了ELF文件头、段头表、text节等各个组成部分。
 3:通过对源码的追踪分析,理解了可执行程序加载的大致流程。当执行到execve系统调用时,陷入内核态,用execve加载的可执行文件覆盖当前进程的可执行程序,当execve系统调用返回时,返回新的可执行程序的执行起点。
  如果是静态链接,elf_entry指向可执行文件规定的头部(main函数对应的位置0x8048***)
  如果需要依赖动态链接库,elf_entry指向动态链接器的起点。动态链接主要是由动态链接器ld来完成的。
 4:对动态链接这一块说实话是不是很明白,还需要学习。



原文地址:https://www.cnblogs.com/esxingzhe/p/4442632.html