2020-2021-1 20209317《Linux内核原理与分析》第八周作业

《linux内核原理与分析》第八周作业

这个作业属于哪个课程 2020-2021-1 Linux内核原理与分析
这个作业要求在哪里 2020-2021-1 Linux内核原理与分析第八周作业
这个作业的目标 通过ELF文件和exec函数了解可执行程序工作原理
作业正文 本博客链接

一、实验过程

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

使用如下代码将exec添加进MenuOs中

rm -rf menu
git clone https://github.com/mengning/menu.git
cd menu
mv test_exec.c test.c
make rootfs


查看刚添加的exec的代码如下图:

启动并执行exec,添加成功:


使用qemu -kernel ../linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -S -s冻结内核并进行调试

使用终端双窗口打开gdb,添加断点


退出调试之后,使用readelf命令查看elf文件头

2.实验分析

ELF文件学习

ELF文件:ELF(Excutable and Linking Format)是一个文件格式的标准。通过readelf-h hello查看可执行文件hello的头部(-a查看全部信息,-h只查看头部信息),头部里面注明了目标文件类型ELF32。
ELF文件的三种类型:
可重定位文件:属于中间文件,需要继续处理。由编译器和汇编器创建。一个源代码会生成一个可重定位文件。用来和其他目标文件一起来创建一个可执行文件、静态库文件或者共享目标文件。可重定位文件后缀为.o ,最后所有.o文件会链接为一个文件。
可执行文件:由多个可重定位文件结合生成,完成了所有重定位工作和符号解析的文件。文件中保存着一个用来执行的程序。
共享目标文件:共享库,是指被可执行文件或其他库文件使用的目标文件。其后缀为.so
ELF文件的作用:
ELF文件参与程序链接(建立一个程序)和程序的执行(运行一个文件)。
如果用于编译和链接(可重定位文件),则编译器和链接器将把elf文件看作是节头表描述的节的集合,程序头表可选。
如果用于加载执行(可执行文件),则加载器则将把elf文件看作是程序头表描述的段的集合,一个段可能包含多个节,节头表可选。
如果是共享文件,则两者都含有。

ELF头分析

typedef struct{
  unsigned char e_ident[EI_NIDENT];    //最开头是16个字节的e_ident, 其中包含用以表示ELF文件的字符,以及其他一些与机器无关的信息。开头的4个字节值固定不变,为0x7f和ELF三个字符。
  Elf32_Half e_type;       //该文件的类型 2
  Elf32_Half e_machine;    //该程序需要的体系结构 2
  Elf32_Word e_version;    //文件的版本 4
  Elf32_Addr e_entry;      //程序的入口地址 4
  Elf32_Off e_phoff;       //Program header table 在文件中的偏移量 4
  Elf32_Off e_shoff;       //Section header table 在文件中的偏移量 4
  Elf32_Word e_flags;      //对IA32而言,此项为0。 4
  Elf32_Half e_ehsize;     //表示ELF header大小 2
  Elf32_Half e_phentsize;  //表示Program header table中每一个条目的大小 2
  Elf32_Half e_phnum;      //表示Program header table中有多少个条目 2
  Elf32_Half e_shentsize;  //表示Section header table中的每一个条目的大小 2
  Elf32_Half e_shnum;      //表示Section header table中有多少个条目 2
  Elf32_Half e_shstrndx;   //包含节名称的字符串是第几个节 2
}Elf32_Ehdr;

Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
7f 、45、4c、46分别对应ascii码的Del(删除)、字母E、字母L、字母F。这四个字节被称为ELF文件的魔数,操作系统在加载可执行文件时会确认魔数是否正确,如果不正确则拒绝加载。
第五个字节标识ELF文件是32位(01)还是64位(02)的。
第六个字节标识该ELF文件字节序是小端(01)还是大端(02)的。
第七个字节指示ELF文件的版本号,一般是01。
后九个字节ELF标准未做定义。一般为00.

文件类型:
类型: REL (可重定位文件)
e_type成员标识文件类型,ELF文件有三种类型,如下
ET_REL 1 可重定位文件,一般位.o文件
ET_EXEC 2 可执行文件
ET_DYN 3 共享目标文件,一般位.so文件

系统架构:
系统架构: Intel 80386
e_machine成员标识系统架构(机器类型),ELF定义了以下多种系统架构。可在“/usr/include/elf.h”头文件中查看

ELF文件结构

在ELF文件里面,每一个section内都装载了性质和属性都一样的内容。有以下几种情况:
.text:已编译程序的机器代码。
.rodata:只读数据,比如printf语句中的格式串和开关(switch)语句的跳转表。
.data:已初始化的全局C变量。局部C变量在运行时被保存在栈中,既不出现在.data中,也不出现在.bss节中。
.bss:未初始化的全局C变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。目标文件格式区分初始化和未初始化变量是为了空间效率在:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。
.symtab:一个符号表(symbol table),它存放在程序中被定义和引用的函数和全局变量的信息。一些程序员错误地认为必须通过-g选项来编译一个程序,得到符号表信息。实际上,每个可重定位目标文件在.symtab中都有一张符号表。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的表目。
.rel.text:当链接噐把这个目标文件和其他文件结合时,.text节中的许多位置都需要修改。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面调用本地函数的指令则不需要修改。注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非使用者显式地指示链接器包含这些信息。
.rel.data:被模块定义或引用的任何全局变量的信息。一般而言,任何已初始化全局变量的初始值是全局变量或者外部定义函数的地址都需要被修改。
.debug:一个调试符号表,其有些表目是程序中定义的局部变量和类型定义,有些表目是程序中定义和引用的全局变量,有些是原始的C源文件。只有以-g选项调用编译驱动程序时,才会得到这张表。
.line:原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译驱动程序时,才会得到这张表。
.strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串序列。

总结

1.程序编译连接过程

程序从源代码到可执行文件的步骤:预处理、编译、汇编、衔接--以hello.c为例。
-预处理: gcc -E hello.c -o hello.i -m32
-编译:gcc -S hello.i -o hello.s -m32
-汇编:gcc -c hello.s -o hello.o -m32
-默认衔接(动态库):gcc hello.o -o hello -m32
-衔接静态库:gcc hello.o -o hello.static -m32 -static

2.链接器,符号解析与重定位

链接器必须对这些可重定位目标文件完成两个主要任务:
符号解析。将每个符号引用刚好和一个符号定义联系起来。
重定位。链接器把每个符号定义与一个虚拟地址联系起来,然后修改所有对这些符号的引用,使得它们指向这个存储位置,从而重定位这些节。
符号分为四类: 导出符号(export,本地符号), 导入符号(import,外部符号), 静态符号(本地符号), 局部符号(本地符号,不出现在符号表中)。
导出符号, 在本模块定义, 能够被其他模块引用的符号。 非static全局函数, 非static全局变量。
导入符号, 在其他模块定义,被本模块引用的符号。 extern 修饰的全局非static变量声明(extern int a), 其他模块的函数引用
静态符号, 在本模块定义, 只能被本模块引用的符号。 static函数, static全局变量。
局部符号, 在函数内部定义的非static变量。不出现在符号表,由栈管理。链接器不care这类符号
汇编器生成可重定位目标文件后, 内部符号都已被正确地符号解析, 外部符号可能会引用了非本模块的符号定义,汇编器无法找到符号定义, 因此无法解析。 汇编器把外部符号放入”符号表“.symtab,同时把如何解析该符号的方法放入”重定位表“。
链接器符号解析:
使用的表: 符号表
强符号,若符号: 函数和初始化的全局变量叫强符号, 未初始化的全局变量叫弱符号。(extern int a是一个弱符号定义, int a 也是弱符号)
符号解析规则:
1, (定义多个强符号) 当引用符号时,该符号的符号定义有不止一个强符号定义时,会出现符号重定义错误。
2, (定义一个强符号和一个或者多个软符号)当引用符号时, 该符号的符号定义有个强符号定义和一个和多个弱符号定义, 使用强符号定义。
3, (定义多个弱符号)当引用符号时, 该符号的符号定义都是弱符号时, 选择任意一个定义。
链接器符号重定位:
使用的表: 重定位表,符号表
重定位表记录要修改的符号引用的位置,以及如何修改。
一旦链接器完成了符号解析这一步,它就把代码中的每个符号引用和确定的一个符号定义联系起来。此时,链接器就知道了每个模块(文件)代码节和数据节的大小,就可以开始重定位了。
重定位由三步组成:
合并可重定位目标文件中相同的节,
重定位节和符号定义,修改符号表。为节和符号定义分配虚拟地址。修改符号表中符号定义的值为刚分配的虚拟地址。
重定位节中的符号引用,修改代码段和数据段符号引用。使用重定位表.rel.text .rel.data, 修改text,data中符号引用的地址
简述为, 合并节, 重定位符号定义(修改符号表),重定位符号引用(修改数据段代码段)
汇编器遇到对存储位置未知(在可重定位目标文件中,汇编器都不知道数据和代码会存放在存储器的什么位置)的符号引用时,它也会将这些符号的信息存于.rel.text和.rel.data表中。告诉链接器将可重定位目标文件合并成可执行目标文件时如何修改符号引用。

3.linux下的exec函数学习

系统调用exec是以新的进程去代替原来的进程,但进程的PID保持不变。因此,可以这样认为,exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。
一个进程主要包括以下几个方面的内容:
(1)一个可以执行的程序
(2)与进程相关联的全部数据(包括变量,内存,缓冲区)
(3)程序上下文(程序计数器PC,保存程序执行的位置)
执行exec系统调用,一般都是这样,用fork()函数新建立一个进程,然后让进程去执行exec调用。我们知道,在fork()建立新进程之后,父进各与子进程共享代码段,但数据空间是分开的,但父进程会把自己数据空间的内容copy到子进程中去,还有上下文也会copy到子进程中去。而为了提高效率,采用一种写时copy的策略,即创建子进程的时候,并不copy父进程的地址空间,父子进程拥有共同的地址空间,只有当子进程需要写入数据时(如向缓冲区写入数据),这时候会复制地址空间,复制缓冲区到子进程中去。从而父子进程拥有独立的地址空间。而对于fork()之后执行exec后,这种策略能够很好的提高效率,如果一开始就copy,那么exec之后,子进程的数据会被放弃,被新的进程所代替。

posted @ 2020-11-24 22:56  20209317李明帅  阅读(85)  评论(0编辑  收藏  举报