本论文研究了hello.c这一简单c语言文件在Linux系统下的整个生命周期,以其原始程序开始,依次深入研究了编译、链接、加载、运行、终止、回收的过程,从而了解hello.c文件的“一生”。该论文以hello.c文件为研究对象,结合《深入理解计算机系统》书中的内容与课上老师的讲授,在Ubuntu系统下对hello程序的整个生命周期进行了研究,通过对hello.c程序的深入研究,得以把计算机系统整个的体系串联在一起,真正做到了学以致用,融会贯通。
关键词:计算机系统;计算机体系结构;程序生命周期;底层原理;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
6.2 简述壳Shell-bash的作用与处理流程... - 32 -
6.3 Hello的fork进程创建过程... - 32 -
7.2 Intel逻辑地址到线性地址的变换-段式管理... - 39 -
7.3 Hello的线性地址到物理地址的变换-页式管理... - 40 -
7.4 TLB与四级页表支持下的VA到PA的变换... - 40 -
7.5 三级Cache支持下的物理内存访问... - 41 -
7.6 hello进程fork时的内存映射... - 42 -
7.7 hello进程execve时的内存映射... - 42 -
第1章 概述
1.1 Hello简介
Hello的P2P是指hello.c文件从可执行程序(Program)变为运行时进程(Process)的过程。在Linux系统下,hello.c 文件依次经过cpp预处理、ccl(C Compiler,C编译器) 编译、汇编、ld链接最终成为可执行目标程序hello。打开shell,输入命令./hello后,shell 通过fork产生子进程,hello 便从可执行程序(Program)变成为进程(Process),即实现程序的P2P。
“From 0 to 0”指初始时内存中并无hello文件的相关内容,这便是“From 0”。通过在Shell下调用execve函数,系统会将hello文件载入内存,执行相关代码;“to 0”是指当程序运行结束后, hello进程被回收,并由内核删除hello相关数据。
1.2 环境与工具
硬件:Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
16GB RAM
软件:Windows11 64位
Ubuntu 22.04.4 64位,(DELL服务器)
调试工具:Visual Studio 2016 64-bit;
gcc, readelf, objdump, edb
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
表格 1 中间结果
| 文件名 | 功能 |
| hello.i | 经预处理得到的文本文件 |
| hello.s | 编译后得到的汇编语言文件 |
| hello.o | 汇编后得到的可重定位目标文件 |
| hello.elf | 用readelf读取hello.o得到的ELF格式信息 |
| hello.asm | 反汇编hello.o得到的反汇编文件 |
| hello2.elf | 由hello可执行文件生成的.elf文件 |
| hello2.asm | 反汇编hello可执行文件得到的反汇编文件 |
1.4 本章小结
本章简要介绍了hello 的P2P,020的具体含义,同时列出了论文研究时采用的具体软硬件环境和中间结果。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
- 预处理的概念
预处理步骤,指程序运行时预处理器根据“#”开头的命令,修改原始的C程序的过程。比如hello.c中的#include 命令会告诉预处理器读取系统头文件stdio.h等的内容,并把这些内容直接插入到程序文本中。并用实际值替换用#define定义的字符串。此外,预处理过程还会删除程序中的注释和多余的空白字符。此过程结束后,通常得到另一个以.i作为拓展名的C程序,如hello.i。
- 预处理的作用
预处理过程将“#“后继的头文件内容直接插入程序文本中,完成字符串的替换方便后续处理。预处理过程中并未直接解析程序源代码的内容,而是对源代码进行相应的分割、处理和替换,总的来说预处理是一个文本插入与替换的过程。
2.2在Ubuntu下预处理的命令
在Ubuntu系统下,进行预处理的命令为:
cpp hello.c > hello.i
运行截图如下:
图 1 预处理过程
2.3 Hello的预处理结果解析
打开hello.i文件,可以发现行数比起hello.c文件大幅增加。其中, hello.c中的main函数相关代码在hello.i程序中对应着3044行到3057行。
图 2 预处理结果部分展在main函数内代码出现之前是大段的三个头文件的依次展开。展开的具体流程概述如下(以stdio.h为例)
2.4 本章小结
本章主要介绍了预处理的概念及作用、并结合Linux系统下hello.c文件实际预处理之后得到的hello.i程序对预处理结果进行了解析。
(第2章0.5分)
第3章 编译
- 编译的概念
编译,指C编译器ccl通过词法分析和语法分析,将合法指令翻译成等价汇编代码的过程。通过编译过程,编译器将文本文件 hello.i 翻译成汇编语言,以文本的形式描述了一条条低级机器语言指令,形成hello.s。
- 编译的作用
将文本文件翻译成汇编语言程序,为后续将其转化为二进制机器码做准备。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
参考PPT,在Ubuntu系统下,进行预处理的命令为:
gcc –m64 –no-pie –fno-PIC -S -o hello.i hello.s
运行截图如下:
图 3 编译过程
3.3 Hello的编译结果解析
- 文件结构分析

对hello.s文件整体结构分析如下:
表格 2 hello.s文件结构
| 内容 | 含义 |
| .file | 源文件 |
| .text | 代码段 |
| .global | 全局变量 |
| .data | 存放已经初始化的全局和静态C 变量 |
| .section .rodata | 存放只读变量 |
| .align | 对齐方式 |
| .type | 表示是函数类型/对象类型 |
| .size | 表示大小 |
| .long .string | 表示是long类型/string类型 |
- 数据类型
在hello.s中,涉及的数据类型主要包括整数,字符串,数组三种。下面对每种数据类型依次进行分析。
- 整数
在hello.s中,涉及的整数有:
- int i
局部变量存储在寄存器或者栈空间中。i作为函数内部的局部变量,并不占用文件实际节的空间,只存在于运行时栈中。

- int argc
argc是main函数的参数之一,保存在堆栈中,64位编译下,由寄存器传入。
3.立即数5
立即数5在汇编语句中直接以5的形式出现
- 字符串
程序中保存了两个字符串,分别为:

图 4 字符串的情况
两者均为字符串常量,储存在.text数据段中。\XXX为UTF-8编码,一个汉字对应三个字节。
- 数组
程序中涉及的数组为char *argv[],即函数的第二个参数。在hello.s中,其首地址保存在栈中。访问时,通过寄存器寻址的方式访问。


- 算数操作
汇编语言中,算数操作的指令包括:
表格 算数操作指令
| 指令 | 效果 |
| leaq s,d | d=&s |
| inc d | d+=1 |
| dec d | d-=1 |
| neg d | d=-d |
| add s,d | d=d+s |
| sub s,d | d=d-s |
| imulq s | r[%rdx]:r[%rax]=s*r[%rax](有符号) |
| mulq s | r[%rdx]:r[%rax]=s*r[%rax](无符号) |
| idivq s | r[%rdx]=r[%rdx]:r[%rax] mod s(有符号) r[%rax]=r[%rdx]:r[%rax] div s |
| divq s | r[%rdx]=r[%rdx]:r[%rax] mod s(无符号) r[%rax]=r[%rdx]:r[%rax] div s |
在hello.s中,具体涉及的算数操作包括:
- subq $32, %rsp:开辟栈帧
- addq $24, %rax:修改地址偏移量
- addl $1, -4(%rbp):实现i++


图 5 hello.s中涉及的算数操作
- 关系操作
在hello.s中,具体涉及的关系操作包括:
- argc!=5:
检查argc是否不等于5。在hello.s中,使用cmpl $5,-20(%rbp),比较 argc与3的大小并设置条件码,为下一步je利用条件码进行跳转作准备。


图 6 检查argc!=5
- i<10:
检查i是否小于10。在hello.s中,使用cmpl $9, -4(%rbp)比较i与9的大小,然后设置条件码,为下一步jle利用条件码进行跳转做准备。


图 7 检查i<10
- 控制转移
图 10 call指令的情况
3.4 本章小结
编译是将文本文件翻译成汇编语言程序,为后续将其转化为二进制机器码做准备的过程,本章介绍了编译器如何处理各个数据类型以及各类操作,汇编代码如何实现各种操作。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
- 汇编的概念
汇编,指汇编器(assembler)将以.s结尾的汇编程序翻译成机器语言指令,并把这些指令打包成可重定位目标程序格式,最终结果保存在.o 目标文件中的过程
- 汇编的作用
将汇编语言翻译为机器语言,并将相关指令以可重定位目标程序格式保存在.o文件中
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
在Ubuntu下汇编的命令为:
gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o
汇编过程如下:
图 11 汇编的命令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
首先,在shell中输入readelf -a hello.o > hello.elf 指令获得 hello.o 文件的 ELF 格式.
其结构分析如下:
- ELF 头(ELF Header):
以 16字节序列 Magic 开始,其描述了生成该文件的系统的字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包括 ELF 头大小、目标文件类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量等相关信息。

图 12 ELF头的情况
图 13 节头的情况
- 重定位节.rela.text
一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。
.rela.text节包含如下信息:
表格.rela.text节包含的信息
| 偏移量 | 代表需要进行重定向的代码在.text或.data节中的偏移位置 |
| 信息 | 包括symbol和type两部分,其中symbol占前半部分,type占后半部分,symbol代表重定位到的目标在.symtab中的偏移量,type代表重定位的类型 |
| 类型 | 重定位到的目标的类型 |
| 加数 | 计算重定位位置的辅助信息 |

图 14.rela.text节
4.4 Hello.o的结果解析
使用objdump -d -r hello.o > hello.asm 分析hello.o的反汇编,并与第3章的 hello.s文件进行对照分析。
图 17 生成hello.asm文件
通过对比hello.asm与hello.s可知,两者在如下地方存在差异:
- 分支转移:


在hello.s中,跳转指令的目标地址直接记为段名称;而在反汇编得到的hello.asm中,跳转的目标为具体的地址,在机器代码中体现为目标指令地址与当前指令下一条指令的地址之差。
图 18 分支转移
- 函数调用:
在hello.s文件中,call之后直接跟着函数名称,而在反汇编得到的hello.asm中,call 的目标地址是当前指令的下一条指令。因为 hello.c 中调用的函数都是共享库中的函数,最终需要通过动态链接器作用才能确定函数的运行时执行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,将其 call 指令后的相对地址设置为全0然后在.rela.text 节中为其添加重定位条目,在链接时确定。



图 19 函数调用
4.5 本章小结
本章介绍了汇编的概念与作用,在Ubuntu下通过实际操作将hello.s文件翻译为hello.o文件,并生成hello.o的ELF格式文件hello.elf,研究了ELF格式文件的具体结构。通过比较hello.o的反汇编代码(保存在hello.asm中)与hello.s中代码,
了解了汇编语言与机器语言的异同之处。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
- 链接的概念
链接是指通过链接器,将程序编码与数据块收集并整理成为一个单一文件,生成完全链接的可执行的目标文件的
- 程序头
程序头部分是一个结构数组,描述了
- 跳转指令参数发生变化。在链接过程中,链接器解析了重定位条目,并计算相对距离,修改了对应位置的字节代码为PLT 中相应函数与下条指令的相对地址,从而得到完整的反汇编代码。



图 28 跳转指令的参数
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址(为使用edb,在ubantu终端里运行)。
5.7 Hello的动态链接分析
编译器需要添加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。动态链接器使用过程链接表PLT+全局偏移量表实现函数的动态链接,在GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数,在加载时,动态链接器会重定位GOT中的每个条目,使得它包含目标的正确的绝对地址。
在调用后,其内容变为:

图 29 调用后的情况
可以得知,全局偏移量表GOT[1]和GOT[2]的内容发生了变化。GOT[1]保存的是指向已经加载的共享库的链表地址。GOT[2]是动态链接器在ld-linux.so模块中的入口。这样,接下来执行程序的过程中,就可以使用过程链接表PLT和全局偏移量表GOT进行动态链接。
5.8 本章小结
本章中介绍了链接的概念与作用、并得到了链接后的hello可执行文件的ELF格式文本hello2.elf,据此分析了hello2.elf与hello.elf的异同;之后,根据反汇编文件hello2.asm与hello.asm的比较,加深了对重定位与动态链接的理解。
(第5章1分)
第6章 hello进程管理
- 进程的概念
进程是一个正在运行的程序的实例,系统中的每一个程序都运行在某个进程的上下文中。
- 进程的作用
图 35 用pstree命令查看所有进程
输入kill命令,则可以杀死指定(进程组的)进程:
- 不停乱按
不停乱按的情况
6.7本章小结
针对进程,在这一章中根据hello可执行文件的具体示例研究了fork,execve函数的原理与执行过程,并给出了hello带参执行情况下各种异常与信号处理的结果。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
7.6 hello进程fork时的内存映射
8.5本章小结
本章主要介绍了linux的IO设备管理方法和及其接口和函数,对printf函数和getchar函数的底层实现有了进一步了解,通过分析系统函数write和syscall等进一步清晰了函数实现的底层逻辑。
(第8章1分)
结论
hello程序的一生经历了如下过程:
- 预处理
将hello.c中include的所有外部的头文件头文件内容直接插入程序文本中,完成字符串的替换;
- 编译
通过词法分析和语法分析,将合法指令翻译成等价汇编代码。通过编译过程,编译器将hello.i 翻译成汇编语言文件 hello.s;
- 汇编
将hello.s汇编程序翻译成机器语言指令,并把这些指令打包成可重定位目标程序格式,最终结果保存在hello.o 目标文件中;
- 链接
通过链接器,将hello的程序编码与动态链接库等收集整理生成完全链接的可执行的目标文件hello;
- 加载运行
打开Shell,在其中键入 ./hello 2023111740 杜佳美 17884143363 3,终端为其fork新建进程,并通过execve把代码和数据加载入虚拟内存空间,程序开始执行;
- 执行指令
在该进程被调度时, hello看似享有CPU全部资源,PC寄存器一步一步地更新,CPU不断地取指,顺序执行自己的控制逻辑流;
- 访存
内存管理单元将逻辑地址映射成物理地址,进而通过三级高速缓存系统访问物理内存/磁盘中的数据;
- 信号处理
进程时刻等待着信号,如果运行途中键入ctr-c ctr-z 则调用shell 的信号处理函数分别进行停止、挂起等操作,对于其他信号也有相应的操作;
- 终止并被回收
Shell父进程等待并回收hello子进程,内核删除为hello进程创建的所有数据结构。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
| 文件名 | 功能 |
| hello.i | 经预处理得到的文本文件 |
| hello.s | 编译后得到的汇编语言文件 |
| hello.o | 汇编后得到的可重定位目标文件 |
| hello.elf | 用readelf读取hello.o得到的ELF格式信息 |
| hello.asm | 反汇编hello.o得到的反汇编文件 |
| hello2.elf | 由hello可执行文件生成的.elf文件 |
| hello2.asm | 反汇编hello可执行文件得到的反汇编文件 |
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] Randal E.Bryant, David O'Hallaron. 深入理解计算机系统[M]. 机械工业出版社.2018.4
.
(参考文献0分,缺失 -1分)
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2401_82876825/article/details/147770807



