题 目 程序人生-Hello’s P2P
专 业 计算学部
学 号 120L020112
班 级 2003003
学 生 赖正勤
指 导 教 师 史先俊
hello程序的生命周期来自高级语言——c从语言开始,本文阐述了预处理、编译、汇编和链接最终生成可执行目标程序的整个过程,并介绍了它hello流程管理、存储管理和IO如何让管理hello.c从程序到过程。通过这样对hello探索生命周期,计算机系统如何使用软件Editor Cpp Compiler AS LD OS和硬件上的 CPU/RAM/IO在我们面前完成一个程序的创建和一个过程的结束。
预处理;编译;汇编;链接;信号;
1.1 Hello简介... - 4 -
1.2 环境与工具... - 4 -
1.3 中间结果... - 4 -
1.4 本章小结... - 5 -
2.1 预处理的概念和作用... - 6 -
2.2在Ubuntu下一个预处理命令... - 6 -
2.3 Hello的预处理结果解析................................................................................. - 7 -
2.4 本章小结............................................................................................................ - 7 -
3.1 编译的概念与作用............................................................................................ - 9 -
3.2 在Ubuntu下编译的命令................................................................................ - 9 -
3.3 Hello的编译结果解析..................................................................................... - 9 -
3.4 本章小结.......................................................................................................... - 10 -
4.1 汇编的概念与作用.......................................................................................... - 14 -
4.2 在Ubuntu下汇编的命令.............................................................................. - 14 -
4.3 可重定位目标elf格式.................................................................................. - 14 -
4.4 Hello.o的结果解析........................................................................................ - 17 -
4.5 本章小结.......................................................................................................... - 19 -
5.1 链接的概念与作用.......................................................................................... - 20 -
5.2 在Ubuntu下链接的命令.............................................................................. - 20 -
5.3 可执行目标文件hello的格式...................................................................... - 20 -
5.4 hello的虚拟地址空间................................................................................... - 23 -
5.5 链接的重定位过程分析.................................................................................. - 24 -
5.6 hello的执行流程........................................................................................... - 24 -
5.7 Hello的动态链接分析................................................................................... - 26 -
5.8 本章小结.......................................................................................................... - 26 -
6.1 进程的概念与作用.......................................................................................... - 28 -
6.2 简述壳Shell-bash的作用与处理流程........................................................ - 28 -
6.3 Hello的fork进程创建过程......................................................................... - 28 -
6.4 Hello的execve过程..................................................................................... - 29 -
6.5 Hello的进程执行........................................................................................... - 29 -
6.6 hello的异常与信号处理............................................................................... - 30 -
6.7本章小结.......................................................................................................... - 34 -
7.1 hello的存储器地址空间............................................................................... - 35 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................... - 35 -
7.3 Hello的线性地址到物理地址的变换-页式管理......................................... - 35 -
7.4 TLB与四级页表支持下的VA到PA的变换................................................ - 37 -
7.5 三级Cache支持下的物理内存访问............................................................. - 39 -
7.6 hello进程fork时的内存映射..................................................................... - 40 -
7.7 hello进程execve时的内存映射................................................................. - 41 -
7.8 缺页故障与缺页中断处理.............................................................................. - 42 -
7.9动态存储分配管理.......................................................................................... - 42 -
7.10本章小结........................................................................................................ - 42 -
8.1 Linux的IO设备管理方法............................................................................. - 45 -
8.2 简述Unix IO接口及其函数.......................................................................... - 45 -
8.3 printf的实现分析........................................................................................... - 45 -
8.4 getchar的实现分析....................................................................................... - 47 -
8.5本章小结.......................................................................................................... - 48 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:首先我们创造c语言程序,例如hello.c,这是Hello的生命起点,而后经过cpp预处理,将头文件加入hello.c并且对宏进行替换。这样,我们得到了hello.i文件。Hello.i文件经过ccl的编译,生成hello.s,也就是汇编语言程序,再经由汇编器as将汇编语言转为机器语言。这样,hello.s就变成了可重定位目标文件hello.o,再经过ld与其他的可重定位目标文件进行链接和重定位等操作,最终得到可执行目标程序。它是由二进制组成的,通过shell终端可以执行它,shell会创建一个子进程专门执行该文件,这种操作叫做“fork”。这样一系列操作下来,hello就从无到有,从一个程序(program),变成了一个进程(process)。此所谓P2P。
O2O:shell通过execve加载并执行可执行目标文件,为其分配虚拟空间;由于该可执行目标文件一开始并不存在于物理内存中,而shell需要引用该文件,于是触发了缺页,而后操作系统将其加载入物理内存中,再次执行程序。执行将以main函数作为入口,CPU配合分配时间片执行逻辑控制流。程序执行结束后shell将hello进程回收并从内存中删除相关数据。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:AMD 4800U CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境:Windows10 64位; /Vmware 11以上;Ubuntu 20.04 LTS 64位
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
文件名称 |
文件作用 |
hello.c |
高级语言编写的c源码 |
hello.i |
hello.c预处理之后的文本文件 |
hello.s |
hello.i编译后的汇编文件 |
hello.o |
hello.s汇编之后的可重定位目标文件 |
hello |
hello.o与其他可重定位目标文件链接之后的可执行目标文件 |
hello.out |
hello反汇编之后的可重定位文件 |
1.4 本章小结
本章系统地介绍了hello的P2P,O2O过程,列出了本次实验的环境,中间结果,简要介绍了hello的所有生命进程。
第2章 预处理
2.1 预处理的概念与作用
预处理概念:预处理器cpp根据以字符#开头的命令(宏定义、条件编译),修改原始的C程序,将引用的所有库展开合并成为一个完整的文本文件。
预处理阶段作用:
1.处理宏定义指令预处理器根据#if和#ifdef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外,通常把排除在外的语句转换成空行。
2. 处理条件编译指令
条件编译指令如#ifdef,#ifndef,#else,#elif,#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。
3.处理头文件包含指令头文件包含指令如#include "FileName"或者#include 等。 该指令将头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。
4.处理特殊符号
预编译程序可以识别一些特殊的符号。 例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。
2.2在Ubuntu下预处理的命令
linux shell下的命令:gcc hello.c -E -o hello.i
2.3 Hello的预处理结果解析
经过cpp预处理后,可以看到hello.i中不仅包括hello.c主函数中的c代码,还包括源文件中的宏,并对其进行了展开,头文件中的声明函数、结构体、变量、宏等内容也被加入到了hello.i中。
2.4 本章小结
这一章介绍了预处理的概念:修改原始的C程序(hello.c),将引用的头文件中的所有内容展开和合并为一个完整的文本文件,该文本文件仍然是C语言风格的。
还介绍了预处理的作用:处理条件编译指令、宏定义、头文件、一些特殊的符号。
还给出了一个用cpp进行预处理的范例,并查看了cpp预处理后的文件中的内容,将hello.o和hello.i对比能够更好的理解预处理的概念以及作用。
第3章 编译
3.1 编译的概念与作用
编译的概念:编译器将文本文件 hello.i 翻译成文本文件 hello.s,它包含一个汇编语言程序。其以高级程序设计语言书写的源程序作为输入,而以汇编语言或机器语言表示的目标程序作为输出。 这个过程称为编译,同时也是编译的作用。
编译程序的基本功能是把源程序(高级语言)翻译成目标程序。除了基本功能之外,编译程序还具备语法检查、调试措施、修改手段、覆盖处理、目标程序优化、不同语言合用以及人际联系等重要功能。
3.2 在Ubuntu下编译的命令
linux shell下的命令:gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
3.3.0 表头信息
.file:声明源文件
.text:代码节
.section:
.rodata:只读代码段
.align:数据或者指令的地址对其方式
.string:声明一个字符串(.LC0,.LC1)
.global:声明全局变量(main)
.type:声明一个符号是数据类型还是函数类型
3.3.1 数据
在代码的头部申明中说明了该程序有两个常量,两个string常量如上图所示,且能够看出这两个常量都是在只读数据段中的,观察hello.c源代码可以看出这两个字符串常量都是printf函数的参数。
观察汇编代码可以看到在main函数中将局部变量放到了栈中,如图所示
都是在栈中通过rbp对存储的局部变量进行操作。
在hello.s中所有的立即数都直接在汇编代码中出现,用$+数表示(该数可能是16进制也可能是10进制,如果是16进制需要在数字前再加上0x)。
在hello.c中main函数的第二个参数是一个字符串数组char *argv[],在hello.s汇编代码中可以看出该字符串数组是存储在栈中的,并在之后作为参数传递给printf函数。
3.3.2 全局函数
在hello.c中全局函数只有一个,那就是主函数main,这个可以在表头声明中看到。函数的参数有两个一个是int argc,一个是char *argv[],两者都存在栈中。
3.3.3 赋值
程序中的赋值主要是寄存器和内存所存的数据通过movb、movw、movl、movq等mov指令来完成的,主要区别就是移动的数的字节大小不同使用不同后缀的mov指令。例如程序中的“=”就是通过将等于号右边数据所存的寄存器或者内存的数据放到等号左边数据所存的寄存器或者内存中去,完成一次赋值操作。
3.3.4 算数操作
hello.c中出现的算数操作是循环时的i++,对于这个操作hello.s使用的汇编指令是addl $1, -4(%rbp),i是存储在栈中即-4(%rbp)中存的值就是i的值。因为i是int类型的,即i是四个字节大小的,故使用的是addl指令。对于其他算数操作的指令如下图
3.3.5 关系操作
在hello.c中有两个关系操作,一个是argc!=4,一个是i<8.
首先是argc!=3,hello.s中是使用cmpl $4, -20(%rbp)来进行比较,cmpl的作用是通过两个比较数的大小来设置条件码寄存器OF,DF,IF,TF,SF,ZF,AF,PF,CF等。反映cpu执行的算术和逻辑操作结果的状态标志。
然后是i<8,hello.s是使用cmpl $7, -4(%rbp)来比较i和7的大小来设置条件码。
3.3.6 控制转移
控制转移指令与关系操作的指令是相辅相成的,只有在关系操作设置了条件码之后,才能够通过控制转移指令跳转到相应的位置(但是jmp指令不需要条件码相当于c语言中的goto语句)。
在hello.c中伴随cmpl也有两个控制转移指令,分别是如下图所示
cmpl $4, -20(%rbp)+je .L2是完成argc!=4的判断的,而cmpl $7, -4(%rbp)+jle .L4是完成i<8的判断的。
3.3.7函数操作
在汇编中调用函数一般使用call指令,call指令的作用是将该指令的下一条指令的地址压入栈中然后令rip指向call的那个指令。而传递函数的参数有时是使用寄存器(第一个参数在%rdi中,第二个参数在%rsi中,第三个参数在%rdx中,第四个参数在%rcx中,第五个参数在%r8中,第六个参数在%r9中)其余参数用栈来存,或者直接就是用栈来保存所有的函数参数,而函数的返回值一般都是放在%rax中,当函数返回后如果向使用函数的返回值一般就是使用%rax。并且在调用函数的时候可能需要为这个函数分配它局部变量的空间,所以需要使用%rbp寄存器来存储它的栈帧,并且在返回前要释放这些空间。
在hello.c中的函数有main、printf、exit、atoi、sleep、getchar。
main函数的参数分别是int argc和char *argc[]
printf函数的参数是两个字符串常量
exit的参数是1
atoi的参数是argv[3]
sleep的参数是atoi(argv[3])
所有函数的返回值都在%eax中。
3.3.8 类型转换
hello.c中有一个类型转换atoi(argv[3]),该类型转换是将字符串转化为整型,通过调用头文件中定义的atoi函数完成这部分功能。
3.4 本章小结
本章主要讲述了编译阶段中编译器如何处理各种数据和操作,以及c语言中各种类型和操作所对应的的汇编代码。通过理解了这些编译器编译的机制,我们可以很容易的将汇编语言翻译成c语言。
第4章 汇编
4.1 汇编的概念与作用
汇编器(as)将汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o 目标文件中,.o 文件是一个二进制文件,它包含程序的指令编码。
4.2 在Ubuntu下汇编的命令
linux shell下的命令:gcc hello.s -c -o hello.o
4.3 可重定位目标elf格式
readelf <option(s)> elf-file(s)
-a –all 等同于同时使用:-h -l -S -s -r -d -V -A -I
-h --file-header 显示ELF文件头
-l --program-headers 显示程序头(执行文件才有)-W 宽显
-S --section-headers 显示节头
-t --section-details 显示节详细信息
-s –syms 显示符号表(symbol table)
-r –relocs 显示重定位信息
-d –dynamic 显示动态节(dynamic section)
-x --hex-dump=<number|name> readelf -x.data xxxx.o
以字节形式显示输出<number|name>指定节的内容
-p --string-dump=<number|name>
以字符串形式显示输出<number|name>指定节的内容
-R --relocated-dump=<number|name>
以重定位后的字节形式显示输出<number|name>指定节内容
查看ELF头:readelf -h hello.o,如下图所示。
ELF头:以16B的序列 Magic开始,Magic描述了生成该文件的系统的字的大小和字节顺序,ELF头剩下的部分包含帮助链接器语法分析和解 ## 标题释目标文件的信息,其中包括 ELF 头的大小、目标文件的类型、机器类型、字节头部表(section header table)的文件偏移,以及节头部表中条目的大 小和数量等信息。
从图中可以看到我们的hello.o是小端序,且类型是REL(可重定位文件),架构是x86-64。
查看节头命令:readelf -S hello.o
节头部表:包含了文件中出现的各个节的语义,包括节的类型、位置和大小等信息。由于是可重定位目标文件,所以每个节都从0开始,用于重定位。在文件头中得到节头表的信息,然后再使用节头表中的字节偏移信息得到各节在文件中的起始位置,以及各节所占空间的大小,同时可以观察到,代码是可执行的,但是不能写;数据段和只读数据段都不可执行,而且只读数据段也不可写。如下图所示。
查看符号表.symtab命令:readelf -s hello.o
.symtab: 存放程序中定义和引用的函数和全局变量的信息。name是符号名称,对于可冲定位目标模块,value是符号相对于目标节的起始位置偏移,对于可执行目标文件,该值是一个绝对运行的地址。size是目标的大小,type要么是数据要么是函数。Bind字段表明符号是本地的还是全局的。如下图所示
查看重定位节信息:readelf -r hello.o
重定位节:一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。
重定位节.rela.text中各项符号的信息:
Offset:需要被修改的引用节的偏移Info:包括symbol和type两个部分,symbol在前面四个字节,type在后面四个字节,
symbol:标识被修改引用应该指向的符号,
type:重定位的类型
Type:告知链接器应该如何修改新的应用
Attend:一个有符号常数,一些重定位要使用它对被修改引用的值做偏移调整Name:重定向到的目标的名称。
hello.o的重定位节如下图所示:
4.4 Hello.o的结果解析
(以下格式自行编排,编辑时删除)
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
通过对照hello.o的反汇编和hello.s,可以看出两者的差别不是很大,主要的差别就是hello.o的反汇编代码中立即数都是以16进制数表示的,相反在hello.s中立即数都是10进制表示的(这里可以看出机器语言中的操作数和汇编语言中的不一致:机器语言中的操作数都是16进制的,而汇编语言中的操作数可以是不同的数制,在将汇编翻译为机器语言时需要将这些操作数都转化为16进制,还有一些不一致是出现在),而且在hello.o的反汇编中不仅仅有汇编语言还有汇编语言对应的机器语言和一些重定位信息。机器语言程序的是二进制机器指令的集合,是纯粹的二进制数据表示的语言,是电脑可以真正识别的语言。这些机器语言(二进制编码)和所有的汇编语言有着一一对应的关系,所以可以根据汇编语言得到对应的机器语言也可以根据机器语言得到对应的汇编语言。
分支转移:在汇编语言中分支转移的汇编指令是jmp、jl、jg、jle、jge、ja、jb、jae、jbe等这些跳转指令,这些跳转指令的操作数一般是段名称或者跳转位置与目前位置的相对偏移量(又称为相对寻址),在hello.o的反汇编中可以看到其中对汇编代码进行了分段:L1,L2,L3,然后就可以使用如jmp .L3这样的汇编指令跳转到.L3段的起始位置运行。
函数调用:函数调用的汇编指令是call,后面跟的要么是一个函数名,要么是一个相对偏移量,在hello.s中call后跟着的是函数名,而在hello.o的反汇编中call后跟的就是相对偏移量,但是在hello.o的反汇编机器语言中显示的都是全0,因为在hello.c中调用的都是共享库中的函数,要最终使用链接器链接重定位(在rela.text节中的重定位条目)才能够计算出这些函数调用操作数的重定位值。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
4.5 本章小结
本章对hello.s进行了汇编,生成了hello.o可重定位目标文件,并且分析了可重定位文件的ELF头、节头部表、符号表和可重定位节,比较了hello.s和hello.o反汇编代码的不同之处,分析了从汇编语言到机器语言的一一映射关系。
第5章 链接
5.1 链接的概念与作用
链接是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接是由叫做链接器的程序执行的。链接器使得分离编译成为可能。
5.2 在Ubuntu下链接的命令
ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o
链接hello.o之后生成的hello反汇编代码如下:
5.3 可执行目标文件hello的格式
首先看ELF头的信息:通过ELF头能够看出hello和hello.o的不同点在于hello的类型是EXEC(可执行文件),而hello.o的类型是REL(可重定位文件),且hello的入口点地址、程序头起点、程序头部的大小和数量、节头部的数量都 与hello.o不同。
然后看节头的信息:Section Headers 对 hello中所有的节信息进行了声明,其 中包括大小 Size 以及在程序中的偏移量 Offset,因此根据 Section Headers 中的信息我们就可以用 HexEdit 定位各个节所占的区间(起始位置,大小)。其中 Address 是程序被载入到虚拟地址的起始地址。分析节头的信息可以看出hello的节头和hello.o的节头也是有一些不一样的,一些主要的不一样就是hello的节头没有.bss段和.rela.text段,hello.o中的节头就有,但是hello的节头有很多hello.o没有的段。
最后是符号表.dynsym:从符号表中可以看到hello中所有的符号条目,在下图中可以看到所有符号(包括全局符号、外部符号、局部符号)的偏移量、类型、所存的段(.data,.text等)。
5.4 hello的虚拟地址空间
根据hello中记录的.rodata相对虚拟地址起始地址的偏移量能够算出其虚拟地址为0x402000大小为0x3b个字节,进入EDB中查看相应位置的数据可以得到如下图
可以看出存储的刚好是程序中的两个字符串常量。
还可以从节头信息中看出hello的.text的起始位置是0x4010f0大小为0x145个字节,从EDG中查看0x4010f0出所存的数据,可以看出刚好是hello的代码段的二进制机器语言代码。
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
上图为hello反汇编后的main函数汇编代码,从这段汇编代码中可以看到
hello.o中的je,call,jmp后面跟的操作数是全0,而hello中是已经计算出来的相应段或函数的地址。根据这个不同可以分析出hello.o链接成为hello的过程中需要对重定位条目进行重定位,对相应的条目进行计算得到地址。并且再hello的反汇编代码中除了main函数以外还有很多其他的函数,例如puts,printf,getchar,atoi,exit,sleep,_start等函数的汇编代码,从这个不同可以分析出链接会将共享库中函数的汇编代码加入hello.o中。
hello重定位的过程:
(1)重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。
(2)重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。执行这一步,链接器依赖于可重定位目标模块中称为的重定位条目的数据结构。
(3)重定位条目当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目。代码的重定位条目放在.rela.txt
重定位算法:
foreach section s{
foreach relocation entry r{
refptr = s + r.offset;/*ptr to reference to be relocated*/
/*Relocate a PC-relative reference*/
if(r.type == R_X86_64_PC32){//PC相对寻址的引用
refaddr = ADDR(s) + r.offset;/*ref's run-time address*/
*refptr = (unsigned) (ADDR(r.symbol) + r.addend - refaddr);
}
/*Relocate an absolute reference*/
if( r.type == R_X86_64_32)//使用32位绝对地址
*refptr = (unsigned)(ADDR(r.symbol) + r.addend);
}
}
例如exit()相对地址引用定义exit的重定位条目为r,则
通过图5.5.6的公式可知:
ADDR(s)=0x4010f0refaddr=ADDR(s) +r.offset= (unsigned)(0xffffff7c),查看反汇编中的机器语言可以看出计算正确。
5.6 hello的执行流程
5.7 Hello的动态链接分析
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。虽然动态链接把链接过程推迟到了程序运行时,但是在形成可执行文件时(注意形成可执行文件和执行程序是两个概念),还是需要用到动态链接库。比如我们在形成可执行程序时,发现引用了一个外部的函数,此时会检查动态链接库,发现这个函数名是一个动态链接符号,此时可执行程序就不对这个符号进行重定位,而把这个过程留到装载时再进行。
在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。GNU编译系统使用延迟绑定(lazybinding),将过程地址的绑定推迟到第一次调用该过程时。
延迟绑定是通过GOT和PLT实现的。GOT是数据段的一部分,而PLT是代码段的一部分。两表内容分别为:
PLT:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。
GOT:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。
查看hello的ELF可以看到GOT起始表位置为0x404000
进入EDB中查看0x404000中的数据如下图
可以看出GOT表位置在调用dl_init之前0x404008后的16个字节均为0.
调用_start之后再次查看GOT表中的内容可以看到如下图:
从中可以看出在调用_start之后0x404008后的16个字节分别为0x7f10afe06190和0x7f10afdefbb0,其中GOT[0](0x403e50)和GOT[1](0x7f10afe06190)包含了动态链接器在解析函数地址时会使用的信息。GOT[2](0x7f10afdefbb4)是动态链接器在ld-linux,so模式中的入口点。其余每个条目都对应一个被调用的函数。下图是0x7f10afdefbb4作为共享库模块的入口点。
5.8 本章小结
在本章中主要介绍了链接的概念与作用,并且详细阐述了hello.o是怎么链接成为一个可执行目标文件的过程,详细介绍了hello.o的ELF格式和各个节的含义,并且分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。
第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中的程序的实例,系统中的每个程序都运行在某个进程上下文中。上下文是由程序正确运行所需的状态组成的。存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
每次用户通过向shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文中运行它们自己的代码或者其他应用程序。
作用:进程提供给应用程序的关键抽象:
(1) 一个独立的逻辑控制流,我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存。
(2) 一个私有的地址空间,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
shell:shell是一个用c语言编写的程序,它是用户使用Linux的桥梁,shell既是一种命令语言,又是一种程序设计语言,shell是一种应用程序。
功能:shell应用程序提供了一个界面,用户通过访问这个界面访问操作系统内 核的服务。
处理流程:
1)从终端读入输入的命令。
2)将输入字符串切分获得所有的参数
3)如果是内置命令则立即执行
4)否则调用相应的程序执行
5)shell 应该接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
父进程(在这里指的是shell)通过调用fork函数创建一个新的运行的子进程。
新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,这就意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们不同的PID。
fork函数只被调用一次但是会返回两次,一次在调用进程(父进程)中,一次是在新创建的子进程中。
在shell中输入./hello 120L020112 赖正勤 1 ,之后shell会识别到该命令并不是内置命令,因此shell会调用fork函数创建一个新的子进程,然后在该子进程中运行该可执行二进制目标程序。
6.4 Hello的execve过程
execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量envp。具体数据结构如下图所示:
argv变量指向一个以null结尾的指针数据,其中每个指针都指向一个参数字符串,按照惯例,argv[0]是可执行目标文件的名字。环境变量的列表是由一个类似的数据结构表示的envp也指向一个以null结尾的指针数组,其中每个指针都指向一个环境变量字符串,每个串都是形如“name=value”的名字-值对。
只有当出现错误时,例如找不到filename,execve才会返回到调用程序,所以execve一般是调用一次并不返回的。
在execve加载了filename之后,它调用启动代码,启动代码设置栈,并将控制传递给新程序的主函数。
6.5 Hello的进程执行
时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
用户模式和内核模式::处理器通常使用一个寄存器提供两种模式的区分,该寄 存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中, 用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的 代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任 何命令,并