这个作业属于哪个课程 | 2020-2021-1 Linux内核原理与分析 |
这个作业要求在哪里 | 2020-2021-1Linux内核原理与分析第八周作业 |
这个作业的目标 | 通过ELF文件和exec函数了解可执行程序工作原理 |
作业正文 | 本博客链接 |
一、基础知识
1、ELF概述
- “目标文件”指编译器生成的文件。
- 最古老的目标文件格式是a.out,后来发展成COFF格式,现在常用的格式有PE(Windows)和ELF(Linux)。
- ELF(Executable and Linkable Format)即可执行并可链接的格式,是一个目标文件格式的标准,这种格式的文件用于存储Linux程序。。
- ELF是一种对象文件的格式,用于定义不同类型的对象文件中都有什么内容,以什么样的格式放这些内容。
- ELF在首部会描绘整个文件的组织结构,还包括了很多系统定义的以及用户自定义的节。
(1)ELF文件的三种类型
- 可重定位文件(Relocatable File)
保存着代码和适当的数据,用来和其它的目标文件一起来创建一个可执行文件、静态库文件或者是一个共享目标文件(主要是.o文件)。
- 可执行文件(Executable File)
保存着一个用来执行的程序,一般由多个可重定位文件结合生成,是完成了所有重定位工作和符号解析(除了运行时解析的共享库符号)的文件。
- 共享目标文件(Shared Object File)
保存着代码和合适的数据,用来被两个链接器链接。
第一个是链接编辑器(静态链接),可以和其它的可重定位和共享目标文件来创建其它的object;
第二个是动态链接器,联合一个可执行文件和其它的共享目标文件来创建一个进程映象。
(2)ELF文件的作用
- 如果用于编译和链接(可重定位文件),则编译器和链结器将把ELF文件看作节的集合,所有节由节头表描述,程序头表可选。
- 如果用于加载执行(可执行文件),则加载器将把ELF文件看作程序头表描述的段的集合,一个段可能包含又多个节和节头表可选。
- 如果是共享文件,则两者都含有。
2、ELF格式
(1)ELF文件的索引表
(2)ELF Header结构
ELF表头首先会给出很多关于本ELF文件的属性信息,如前文提到过的3种ELF类型就是通过e_type来实现的。e_type值1、2、3、4分别代表可重定位文件、可执行文件、共享目标文件、核心文件。节头表基本定义了整个ELF文件的组成,可以说是整个ELF就是由若干个节组成的。
(3)Section Header结构
用于链接的目标文件必须包含节区头部表,其他目标文件有没有这个表皆可。
(4)program Header结构
段头表是和创建进程相关的,描述了连续的几个节在文件中的位置、大小以及它被放进内存后的位置和大小。
3、相关操作指令
- man elf:查看elf详细的格式定义
- readelf:用于显示一个或多个elf格式的目标文件的信息,可以通过它的选项来控制显示哪些信息。
- objdump:显示二进制文件信息,用于查看目标文件或者可执行的目标文件的构成的gcc工具。
4、程序编译
(1)预处理:处理代码中的宏定义和 include 文件,并做语法检查
gcc -E hello.c -o hello.i
预处理时编译器完成的具体工作如下:
- 删除所有的注释
- 删除所有的“#define”,展开所有的宏定义
- 处理所有的条件预编译指令
- 处理“#include”预编译指令
- 添加行号和文件名标识
(2)编译:生成汇编代码
gcc -S hello.i -o hello.s -m32
- 编译时,gcc首先检查代码的规范性、是否有语法错误。以确定代码实际要做的工作
- 在检查代码无误后,gcc讲代码翻译成汇编语言
(3)汇编:生成 ELF 格式的目标代码
gcc -C hello.s -o hello.o -m32
- -m32表示生成32位的目标文件
- 汇编后形成的.o格式的文件已经是ELF格式文件。
- 程序编译后生成的目标文件至少含有3个节区(.text .data .bss)
(4)链接:生成可执行代码
gcc hello.o -o hello -m32 -static
- 链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可以被加载到内存种并执行
- 上述代码是将编译出的.o文件与libc库文件进行链接,生成最终的可执行文件
- 链接从过程上讲分为符号解析和重定位两部分
- 根据链接时机的不同,又分为静态链接和动态连接两种
5、链接与库
(1)符号与符号解析
- 符号
符号包含全局变量和全局函数。例如printf就是一个符号,hello程序需要在函数库中找到这个符号。
- 符号表
供编译器用于保存有关源程序构造的各种信息的数据结构。
符号表的功能是找未知函数在其他库文件中的代码段的具体位置。
查看方式是objdump -t xxx.o或readelf -s xxx.o
- 符号表中的函数
链接前后,内存地址和符号对应的节区编号会发生改变。
链接前后,符号表个数的差异会很大。
(2)重定位
重定位是把程序的逻辑地址空间变换成内存中的实际物理地址空间的过程,即装入时对目标程序中指令和数据的修改过程,它是实现多道程序在内存中同时运行的基础。
(3)静态链接与动态链接
- 静态链接:在编译链接时直接将需要的执行代码复制到最终可执行文件中
优点:代码的装载速度快,执行速度也比较快,对外部的环境依赖度低。
缺点:编译时它会把需要的所有代码都链接进去,应用程序相对比较大。
- 动态链接:在编译时不直接复制可执行代码,而是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统
1)装载时动态链接
这种用法的前提是在编译之前已经明确知道要调用DLL中的哪几个函数,编译时在目标文件中只保留必要的链接信息,而不含DLL函数的代码。当程序执行时,调用函数的时候利用链接信息加载DLL函数代码并在内存中将其链接入调用程序的执行空间中(全部函数加载进内存),其主要目的是便于代码共享。
2)运行时动态链接
这种方式是指在编译之前并不知道将会调用哪些DLL函数,完全是在运行过程中根据需要决定应调用哪个函数,将其加载到内存中(只加载调用的函数进内存),并标识内存地址,其他程序也可以使用该程序,并用LoadLibrary和GetProcAddress动态获得DLL函数的入口地址。
6、fork和execve的区别与联系
fork()
- 子进程复制父进程的所有进程内存到其内存地址空间中。父、子进程的“数据段”,“堆栈段”和“代码段”完全相同,即子进程中的每一个字节都和父进程一样。
- 子进程的当前工作目录、umask掩码值和父进程相同,fork()之前父进程打开的文件描述符,在子进程中同样打开,并且都指向相同的文件表项。
- 子进程拥有自己的进程ID。
exec()
- 进程调用exec()后,将在同一块进程内存里用一个新程序来代替调用exec()的那个进程,新程序代替当前进程映像,当前进程的“数据段”,“堆栈段”和“代码段”被新程序改写。
- 新程序会保持调用exec()进程的ID不变。
- execve在执行时陷入内核态,用execve中加载的程序把当前正在执行的进程覆盖掉,当系统调用返回时也就返回到新的可执行程序起点,即返回的已经不是原来的那个可执行程序了。
二、实验——使用gdb跟踪分析execve系统调用内核处理函数sys_execve
1、将menu目录删除,利用git命令克隆一个新的menu目录,然后用test_exec.c将test.c覆盖,然后make rootfs启动menuos。
cd LinuxKernel rm -rf menu git clone https://github.com/mengning/menu.git cd menu mv test_exec.c test.c make rootfs
2、在QEMU中利用help命令查看,发现增加了exec命令,执行exec命令,发现比fork指令增加了一条输出“hello world !”。实际上是新加载了一个可执行程序来输出了一行语句。
3、调试内核
cd ../ # 返回到LinuxKernel目录下 qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -S -s
4、重新打开一个shell窗口,启动gdb,通过端口1234建立连接,在sys_exec、load_elf_binary、start_thread处设置断点
cd LinuxKernel/menu/
gdb file ../linux-3.18.6/vmlinux target remote:1234
//设置断点
b sys_exec
b load_elf_binary
b start_thread
5、“c”继续执行到sys_exec时输入exec命令,结果运行到sys_exec停住
6、退出调试状态后,查看hello的EIF头部信息
readelf -h hello
三、总结
在本实验中,我首先了解到ELF文件的类型,有可重定位文件(一般是中间文件,还需要继续处理。由编译器和汇编器创建,一个源代码文件会生成一个可重定位文件)、可执行文件(一般由多个可重定位文件结合生成,是完成了所有重定位工作和符号解析的文件)、共享目标文件(共享库,指可以被可执行文件或其他库文件使用的目标文件,例如标准C的库文件libc.so)。程序从源代码到可执行文件经过四个步骤,分别是预处理(负责把include的文件包含进来,宏替换)、编译( –S调用ccl,编译成汇编代码)、汇编(调用as,得到二进制文件)、链接(调用ld形成目标可执行文件)。