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. XStream别名;元素转属性;去除集合属性(剥皮);忽略不需要元素

    city package xstream; public class City { private String name; private String description; public St ...

  2. LeetCode-Water and Jug Problem

    You are given two jugs with capacities x and y litres. There is an infinite amount of water supply a ...

  3. 利用CSS3制作淡入淡出动画效果

    CSS3新增动画属性“@-webkit-keyframes”,从字面就可以看出其含义——关键帧,这与Flash中的含义一致. 利用CSS3制作动画效果其原理与Flash一样,我们需要定义关键帧处的状态 ...

  4. 树形结构数据存储方案的选择和java list转tree

    树形结构数据存储方案 Adjacency List:每一条记录存parent_idPath Enumerations:每一条记录存整个tree path经过的node枚举Nested Sets:每一条 ...

  5. node 同异步处理

    同步:序列执行,需等待 异步:非序列执行,无需等待 node同步处理:读取->输出->完毕(队列式执行) node异步处理:读取->完毕(回调输出)(后两步同时进行,谁先到谁先输出) ...

  6. HDU 4348 To the moon(可持久化线段树)

    To the moon Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Tota ...

  7. Spring Data 增删改查事务的使用(七)

    @Modifying 注解使用 @Modifying 结合 @Query注解进行更新操作 咱们单单的一个查询注解是没有办法完成事务的操作的 我们还要结合一点就是@Transaction 在spring ...

  8. 剑指Offer——数组中只出现一次的数字

    题目描述: 一个整型数组里除了两个数字之外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字. 分析: 数组中一共有偶数个数.两个数字只出现过一次. 相同数异或在一起等于0,那么将所有数异或 ...

  9. leetcode_Basic Calculator

    题目: Implement a basic calculator to evaluate a simple expression string. The expression string may c ...

  10. 如何缩减手游app安装包的大小?

    包体过大对手游的影响更是诟病已久,有具体数据证明,游戏包体越大,在游戏运营推广过程中游戏用户的转化率就越低:反之,游戏包体越小,游戏用户的下载转化率就越高(如下图),所有的手机app.游戏在大版本更新 ...