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

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

一、知识点

1、ELF(Executable and Linkable Format)概述:

  • “目标文件”指编译器生成的文件,“目标”指类似x86或x64的目标平台,它决定编译器使用的机器指令集。
  • “目标文件”也叫ABI,它和“目标平台”是二进制兼容的。
  • 最古老的目标文件格式是a.out,后来发展为COFF格式,现在linux常用的格式为ELF。
  • ELF(Executable and Linkable Format)即可执行并可链接的格式,是一个目标文件格式的标准。
  • ELF是一种对象文件的格式,用于定义不同类型的对象文件中都有什么内容,以什么样的格式放这些内容。
  • ELF在首部会描绘整个文件的组织结构,还包括了很多系统定义的以及用户自定义的节。

2、ELF可执行和可链接文件,其包含了以下三类:

  • 可重定位文件(Relocatable File):保存着代码和适当的数据,用来和其它的目标文件一起来创建一个可执行文件、静态库文件或者是一个共享目标文件(主要是.o文件)
  • 可执行文件(Executable File):保存着一个用来执行的程序,一般由多个可重定位文件结合生成,是完成了所有重定位工作和符号解析(除了运行时解析的共享库符号)的文件。
  • 共享目标文件(Shared Object File):保存着代码和合适的数据,用来被两个链接器链接。第一个是链接编辑器(静态链接),可以和其它的可重定位和共享目标文件来创建其它的object。第二个是动态链接器,联合一个可执行文件和其它的共享目标文件来创建一个进程映象。

3、ELF文件由4部分组成:

  • ELF头(ELF header)
    • ELF Header在文件最开描述了该文件的组织情况, 他的其他部分主要说明了其他文件内容的位置、大小等信息。
    • 节头表基本定义了整个ELF文件的组成,可以说是整个ELF就是由若干个节(Section)组成的。
  • 程序头表(Program header table)
    • 段头(Program Header)表示和创建进程相关的,描述了连续的几个节在文件的位置,大小以及它被放进内存后的大小和位置,告诉系统如何创建进程映象,可执行文件加载器就可以按这个说明将可执行文件搬到内存中。
    • p_type 当前描述的段类型
    • p_offset 段在文件中的偏移
    • p_vaddr 段在内存中的虚拟地址
    • p_paddr 在物理内存定位相关的系统中,此项为物理地址保留
    • p_filesz 段在文件中的长度
    • p_memsz 段在内存中的长度
    • p_align 确定段在文件及内存中如何对齐
  • 节(Section)
  • 节头表(Section header table)
    • 节头表是由Section Header组成的表,包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称,节区大小这类信息。
    • sh_name 节名,是在字符串中的索引
    • sh_addr 该节对应的虚拟地址
    • sh_offset 该节在文件中的位置
    • h_size 该节的大小
    • sh_link 与该节连接的其他节
    • sh_addralign 对齐方式
  • 注:实际上,一个文件中不一定包含全部内容,而且他们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息有ELF头中的各项值来决定。

4、ELF文件的作用:

ELF文件参与程序链接(建立一个程序)和程序的执行(运行一个文件)。

  • 如果用于编译和链接(可重定位文件),则编译器和链接器将把elf文件看作是节头表描述的节的集合,程序头表可选。
  • 如果用于加载执行(可执行文件),则加载器则将把elf文件看作是程序头表描述的段的集合,一个段可能包含多个节,节头表可选。
  • 如果是共享文件,则两者都含有。

5、fork和execve区别与联系:

  • fork():

    • 子进程复制父进程的所有进程内存到其内存地址空间中。父、子进程的“数据段”,“堆栈段”和“代码段”完全相同,即子进程中的每一个字节都和父进程一样。
    • 子进程的当前工作目录、umask掩码值和父进程相同,fork()之前父进程打开的文件描述符,在子进程中同样打开,并且都指向相同的文件表项。
    • 子进程拥有自己的进程ID。
  • exec():

    • 进程调用exec()后,将在同一块进程内存里用一个新程序来代替调用exec()的那个进程,新程序代替当前进程映像,当前进程的“数据段”,“堆栈段”和“代码段”背新程序改写。
    • 新程序会保持调用exec()进程的ID不变。
    • 调用exec()之前打开打开的描述字继续打开(好像有什么参数可以令打开的描述字在新程序中关闭)

二、实验

1.删除menu目录,克隆一个新的menu目录,然后用test_exec.c将test.c覆盖。

cd LinuxKernel
rm menu -rf  
git clone https://github.com/mengning/menu.git


2.用test_exec.c将test.c覆盖,重新编译

mv test_exec.c test.c 
make rootfs


3.查看test.c文件,可以看到新增加了exec系统调用。


4.在QEMU中执行exec命令。


5.冻结内核,启动GDB调试。

6.启动gdb,通过端口1234建立连接,在sys_exec、load_elf_binary、start_thread处设置断点。



7.退出调试状态,输入readelf -h hello可以查看hello的EIF头部。

总结

通过本实验,我了解到了ELF文件的类型。并且了解到在Linux中,一个程序的执行是做为一个新的进程,使用execve系统调用完成的。
程序从源代码到可执行文件的步骤:预处理、编译、汇编、衔接--以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

posted on 2019-11-10 21:59  刘北圣  阅读(169)  评论(0编辑  收藏  举报