linux课程第七周实验及总结

实验及学习总结

1. 编译链接的过程和ELF可执行文件格式(以hello为例)

GNU编译系统编译源码:

  • 首先,运行C预处理器(cpp),将.c文件翻译成.i文件——gcc -E -o hello.cpp hello.c -m32
  • 接着,运行C编译器(cc1),将.i文件翻译成ASCII汇编语言文件.s文件——gcc  -S -o hello.s hello.cpp -m32
  • 然后,运行汇编器(as),将.s文件翻译成可重定位目标文件.o文件——gcc -c hello.s -o hello.o -m32
  • 最后,运行链接器(ld),将各.o文件组合起来,创建一个可执行目标文件——ld -o hello hello.o xxx.o
  • 流程图:

ELF可执行文件格式

ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。
                                                           
 
  • 我们使用readelf -h命令查看hello的ELF文件头,可以看到:(除此外objump或readelf可以直接查看ELF文件各节点信息)
  • ELF文件头代码项含义:

    最开头是16个字节的e_ident, 其中包含用以表示ELF文件的字符,以及其他一些与机器无关的信息。开头的4个字节值固定不变,为0x7f和ELF三个字符。
    e_type 它标识的是该文件的类型。
    e_machine 表明运行该程序需要的体系结构。
    e_version 表示文件的版本。
    e_entry 程序的入口地址。
    e_phoff 表示Program header table 在文件中的偏移量(以字节计数)。
    e_shoff 表示Section header table 在文件中的偏移量(以字节计数)。
    e_flags 对IA32而言,此项为0。
    e_ehsize 表示ELF header大小(以字节计数)。
    e_phentsize 表示Program header table中每一个条目的大小。
    e_phnum 表示Program header table中有多少个条目。
    e_shentsize 表示Section header table中的每一个条目的大小。  
    e_shnum 表示Section header table中有多少个条目。
    e_shstrndx 包含节名称的字符串是第几个节(从零开始计数)

2. gdb调试execve系统调用及新的可执行程序起点

新的可执行程序起点

一般是地址空间为0x8048000或0x8048300;
  • 与此相对的,再返回用户态时new_ip保存返回用户态的第一条指令的地址

execve系统调用

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

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

      • execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
    • 库函数exec*都是execve的封装例程

execve和fork都是特殊一点的系统调用:一般的都是陷入到内核态再返回到用户态。

fork父进程和一般进程调度一样,子进程返回到一个特定的点ret_from_fork,子进程是从ret_from_fork开始执行然后返回到用户态;

execve特殊:执行到可执行程序--陷入内核--构造新的可执行文件--覆盖掉原可执行程序--返回到新的可执行程序,作为起点(也就是main函数) ,需要构造他的执行环境;
  • 我们可以看一下execve系统调用在实验中的入口及返回值

  • sys_execve()进行一些参数的检查复制之后,调用do_execve()。
 do_execve()会首先检查被执行文件,当do_execve()读取了128个字节的文件头部之后,然后调用search_binary_handle()【retval = search_binary_handler(bprm,regs)】
去搜索和匹配合适的可执行文件装载处理过程。Linux中所有被支持的可执行文件格式都有相应的装载处理过程,search_binary_handle()会通过判断文件头部的魔数确定文件的格式,
并且调用相应的装载处理过程。如ELF用load_elf_binary(),a.out用load_aout_binary(),脚本用load_script()。其中ELF装载过程的主要步骤是:
  
   ①检查ELF可执行文件格式的有效性,比如魔数、程序头表中段(Segment)的数量。
  
  ②寻找动态链接的”.interp”段(该段保存可执行文件所需要的动态链接器的路径),设置动态链接器路径。
   
 ③根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码、数据、只读数据。
    
④初始化ELF进程环境,比如进程启动时EDX寄存器的地址应该是DT_FINI的地址(结束代码地址)。
 
   ⑤将系统调用的返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的链接方式,

  • 静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时:

    execve系统调用将系统调用的返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的链接方式,      
对于静态链接的ELF可执行文件,这个程序入口就是ELF文件的文件头中e_enEry所指的地址;对于动态链接的ELF可执行文件,程序入口点是动态链接器。
  当ELF被load_elf_binary()装载完成后,函数返回至do_execve()在返回至sys_execve()。在load_elf_binary()中系统调用的返回地址已经被改成ELF程序的入口地址了。
所以当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到了ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件装载完成。

3. exec*函数对应的系统调用处理过程

内核通过以下步骤实现该系统调用:

(1) exec系统调用要求以参数形式提供可执行文件名,并存储该参数以备将来使用。连同文件名一起,还要提供和存储其他参数,例如如果shell命令是"ls -l",那么ls作为文件名,-l作为选项,一同存储起来以备将来使用。

(2) 现在,内核解析文件路径名,从而得到该文件的索引节点号。然后,访问和读取该索引节点。内核知道对任何shell命令而言,它要先在/bin目录中搜索。

(3) 内核确定用户类别(是所有者、组还是其他)。然后从索引节点得到对应该可执行文件用户类别的执行(X)权限。内核检查该进程是否有权执行该文件。如果不可以,内核提示错误消息并退出。

(4) 如果一切正常,它访问可执行文件的头部。

(5) 现在,内核要将期望使用的程序(例如本例中的ls)的可执行文件加载到子进程的区域中。但"ls"所需的不同区域的大小与子进程已经存在的区域不同,因为它们是从父进程中复制过来的。因此,内核释放所有与子进程相关的区域。这是准备将可执行镜像中的新程序加载到子进程的区域中。在为仅仅存储在内存中的该系统调用存储参数后释放空间。进行存储是为了避免"ls"的可执行代码覆盖它们而导致它们丢失。根据实现方式的不同,在适当的地方进行存储。例如,如果"ls"是命令,"-l"是它的参数,那么就将"-l"存储在内核区。/bin目录中"ls"实用程序的二进制代码就是内核要加载到子进程内存空间中的内容。

(6) 然后,内核查询可执行文件(例如ls)镜像的头部之后分配所需大小的新区域。此时,建立区域表和页面映射表之间的链接。

(7) 内核将这些区域和子进程关联起来,也就是创建区域表和P区表之间的链接。

(8) 然后,内核将实际区域中的内容加载到分配的内存中。

(9) 内核使用可执行文件头部中的寄存器初始值创建保存寄存器上下文。

(10) 此时,子进程("ls"程序)已经运行。因此,内核根据子进程优先级,将其插到"准备就绪"进程列表的合适位置。最终,调度这个子进程。

(11) 在调度该子进程后,由前述(9)中介绍的保存寄存器上下文生成该进程的上下文。然后,PC、SP等就有了正确的值。

(12) 然后,内核跳转到PC指明的地址。这就是要执行的程序中第一个可执行指令的地址。现在开始执行"ls"这样的新程序。 内核从步骤(5)中存储的预先确定的区域中得到参数,然后生成所需的输出。如果子进程在前台执行,父进程会一直等到子进程终止;否则它会继续执行。

(13) 子进程终止,进入僵尸状态,期望使用的程序已经完成。现在,内核向父进程发送信号,指明"子进程死亡",这样现在就可以唤醒父进程了。

如果这个子进程打开新文件,那么这个子进程的用户文件描述符表、打开文件列表和inode表结构就和父进程的不同。如果该子进程调用另一个子程序,就会重复执行/分支进程。这样就会创建不同深度层次的进程结构。

4. 总结——“Linux内核装载:动态链接和静态链接”

  • 静态链接库创建:
 gcc -c fun.c

 ar cqs libfun.a fun.o
 
gcc call.c -static -L. -lfun -o fun_static_call ./fun_static_call
  • 动态链接库创建:
/*生成动态链接库*/
 gcc fun.c -fPIC -shared -o libfun.so
/*-L指定查找动态链接库的路径,-lfun实际就是查找libfun.so*/
  • 当ELF被load_elf_binary()装载完成后,函数返回至do_execve()在返回至sys_execve()。
  • ELF可执行文件的入口点取决于程序的链接方式:

1. 静态链接:elf_entry就是指向可执行文件里边规定的那个头部,即main函数处。

2. 动态链接:可执行文件是需要依赖其它动态链接库,elf_entry就是指向动态链接器的起点。

参考资料:

20135302魏静静——linux课程第七周实验及总结的更多相关文章

  1. 20135302魏静静——linux课程第三周实验及总结

    linux课程第三周实验及总结 一.实验:跟踪分析Linux内核的启动过程 使用gdb跟踪调试内核从start_kernel到init进程启动 使用实验楼的虚拟机打开shell cd LinuxKer ...

  2. 20135302魏静静——linux课程第五周实验及总结

    linux课程第五周实验及总结 一.学习总结 给MenuOS增加time和time-asm命令(四步操作命令) rm menu -rf 强制删除git clone http://github.com/ ...

  3. 20135302魏静静——linux课程第六周实验及总结

    linux课程第六周实验及总结 实验及学习总结 1.进程描述符task_struct数据结构 进程的作用: 将信号.进程间通信.内存管理和文件系统联系起来 操作系统的三大功能: 进程管理.内存管理.文 ...

  4. 20135302魏静静——linux课程第八周实验及总结

    linux课程第八周实验及总结 实验及学习总结 1. 进程切换在内核中的实现 linux中进程切换是很常见的一个操作,而这个操作是在内核中实现的. 实现的时机有以下三个时机: 中断处理过程(包括时钟中 ...

  5. 20135302魏静静——Linux课程期中总结

    Linux期中总结 Linux课程第一周实验及总结:[http://www.cnblogs.com/20135302wei/p/5218607.html] 冯诺依曼体系结构的核心思想是存储程序计算机. ...

  6. 20135302魏静静——linux课程第四周实验及总结

    linux课程第四周实验及总结 一.实验 我选择的是第20号系统调用,getpid 代码如下: /* getpid.c */ #include <unistd.h> #include &l ...

  7. 20135302魏静静Linux内核分析第二周学习总结

    操作系统是如何工作的 1. 小结:计算机是怎样工作的 三个法宝 存储程序计算机.函数调用堆栈.中断机制 两把宝剑 中断上下文.进程上下文的切换 2. 堆栈 堆栈是C语言程序运行时必须的一个记录调用路径 ...

  8. Linux 第七周实验 及总结

    姬梦馨 原创作品 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 第七周 Linux内核如何装载和启动一 ...

  9. 第七周&实验报告五

    实验四 类的继承 •实验目的 •理解抽象类与接口的使用: •了解包的作用,掌握包的设计方法. •实验要求 •掌握使用抽象类的方法. •掌握使用系统接口的技术和创建自定义接口的方法. •了解 Java ...

随机推荐

  1. spring的@Transactional注解详细用法(转载)

    概述 事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性.Spring Framework对事务管理提供了一致的抽象,其特点如下: 为不同的事务API提供一致的编程模型, ...

  2. mysql 容灾 灾备 备份

    一.数据备份 1.使用mysqldump命令备份 mysqldump命令将数据库中的数据备份成一个文本文件.表的结构和表中的数据将存储在生成的文本文件中. mysqldump命令的工作原理很简单.它先 ...

  3. Codeforces Round #118 (Div. 1) A. Plant

    A. Plant 题目链接:http://codeforces.com/problemset/problem/185/A 题意:一个植物会长,一开始是一个正三角形,每过一年,一个向上的正三角形会变成三 ...

  4. 【转载】为什么不常见include .c文件

    备:对于#include  <filename.h> ,编译器从标准库路径开始搜索 filename.h       对于#include  “filename.h” ,编译器从用户的工作 ...

  5. tomcat启动报错:Injection of autowired dependencies failed

    Error creating bean with name 'backPrintPaperController': Injection of autowired dependencies failed ...

  6. LeetCode题目_Reverse Integer

    最近在LeetCode上做题,写点东西记录一下,虽然自己做的都是些很水的题目,但是重在练手. 题号7:Reverse Integer,题目描述: Reverse digits of an intege ...

  7. 使用Standford coreNLP进行中文命名实体识别

    因为工作需要,调研了一下Stanford coreNLP的命名实体识别功能. Stanford CoreNLP是一个比较厉害的自然语言处理工具,很多模型都是基于深度学习方法训练得到的. 先附上其官网链 ...

  8. SSM框架关于后台返回JSON数据中显示很多不需要的字段为NULL

    xml 配置 spring mvc 的 json 返回忽略 null 字段 <mvc:annotation-driven> <mvc:message-converters regis ...

  9. 可能引起「We Were Unable to Load Disqus」错误的一种情况分析

    今天想往Octopress里加评论,之前看了网上的教程,应该是简单的事情,因为Octopress本来为你准备好了评论插件Disqus的代码,只需要去Disqus注册个帐号,然后在_config.yml ...

  10. 关于android:windowNoTitle不起作用的解决办法

    今天测试一个新功能的时候,在styles.xml设置<item name="android:windowNoTitle">true</item>并没有生效, ...