HIT计统大作业——程序人生
Posted sltbsxxs
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HIT计统大作业——程序人生相关的知识,希望对你有一定的参考价值。
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 2021113008
班 级 2103102
学 生 石隆泰
指 导 教 师 刘宏伟
计算机科学与技术学院
2022年5月
多少人的代码生涯,都是从那一句“Hello World!”开始的?多少菜鸟手敲的第一个程序就是hello?可渐渐的,随着程序猿们知识越加丰厚,曾经的伙伴hello早已被抛之脑后。但是CS知道它的生,知道它的死,知道它来过。本文通过一个入门级程序hello.c,来剖析它坎坷的一生:从源代码到经过预处理、编译、汇编、链接最终生成可执行目标文件hello,再通过在shell 中键入启动命令后,shell 为其fork,产生子进程,内核为新进程创建数据结构, hello从可执行程序变成为进程。
关键词:Hello;预处理;编译;汇编;链接;进程
目 录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P(From Program to Process):用户通过编辑器编写人类可读的高级语言代码,得到一个hello.c程序(program)。为了在系统上运行hello程序,首先通过预处理器(cpp)得到修改了的源程序——ASCII码的中间文件hello.i,然后利用编译器(ccl)得到汇编程序——ASCII汇编语言文件hello.s,随后,再利用汇编器(as)生成可重定位目标程序hello.o(二进制)。接下来,经过链接器(ld),将调用的标准C库中的函数(如printf等)对应的预编译好了的目标文件以某种方式合并到hello.o文件中,得到可执行目标程序hello。此时即可把可执行文件加载到内存中,由系统执行,即成为一个进程(process)。
图1:编译系统
020(From Zero-0 to Zero-0):当输入“./hello”后,shell调用fork()函数创建子进程,子进程通过execve加载并执行该程序时,映射虚拟内存,程序开始时载入物理内存,进入CPU处理。CPU为执行文件hello分配时间片,进行取指、译码、执行等流水线操作。内存管理器和CPU在执行过程中通过L1、L2、L3三级缓存和TLB多级页表在物理内存中取的数据,通过I\\O系统根据代码指令进行输出。程序运行结束后,shell父进程会回收这个僵死进程,内核会从把它从系统内清除,这是hello由0转换为0,完成020的过程。
1.2 环境与工具
硬件环境
X64 CPU;3GHz;16G RAM;256GHD Disk 以上;
软件环境
Windows10 64位;VirtualBox/Vmware 16;kali linux;
开发工具
Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 20.04 LTS 64位;edb;gdb;gcc;ld;objdump;readelf;HexEdit。
1.3 中间结果
hello.c 源程序:能够被人读懂的c语言程序,以字节序列的方式储存
hello.i 预处理后文件:读取了相关头文件的内容
hello.s 编译后的汇编文件:包含了用汇编语言表示的main函数定义
hello.o 汇编后的可重定位目标执行文件:可被链接以待后续加载执行
hello 链接后的可执行文件:可以被加载到内存中由系统执行
elf.txt hello.o的ELF
hello1.elf hello的ELF
hello_asm.txt hello的反汇编文件
1.4 本章小结
本章简单介绍了Hello的P2P,020的过程,随后介绍了本文用到的环境与工具,以及撰写本文之后会生成的中间结果文件,为下文做铺垫。
第2章 预处理
2.1 预处理的概念与作用
概念:预处理是指预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如hello.c中第一行中的 #include <stdio.h> 命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中。结果是得到了另一个C程序,通常以.i作为文件扩展名。
作用:主要作用有宏定义、文件包含、条件编译。
宏定义:将所有的#define删除,并且展开所有的宏定义,将宏名替换为文本。.
文件包含:预处理程序中的#include,将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件。
条件编译:处理所有条件预编译指令,根据#if以及#endif和#ifdef以及#ifndef来判断执行编译的条件。
2.2在Ubuntu下预处理的命令
Ubuntu下预处理的命令是:cpp hello.c > hello.i 或者 gcc -E hello.c -o hello.i
图2:Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
打开hello.i后,发现原本普普通通仅仅四行的代码被拓展到了3000多行。比较hello.i的尾部文件和hello.c,会发现“#”宏定义消失了。事实上,预处理是遵从对于资源的等价交换,占据hello.i大部分空间的代码实际上是对头文件的展开,对引用目录的标注,诸如”/usr/include/stdio.h”。
接着比较尾部文件和.c文件会发现,除上述变化以外,函数主体部分并没有改变。
图3:hello.i与hello.c的内容对比
2.4 本章小结
本章主要围绕预处理进行介绍,在明白预处理概念与作用之后学习Ubuntu下预处理的命令。并以hello.c为例操作生成hello.i。打开解析hello.i文件,对比原.c文件发现不同,加深了对预处理的理解。
第3章 编译
3.1 编译的概念与作用
概念:编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序.该程序包含函数main的定义,这个过程称为编译。
作用:将高级语言源程序翻译成等价的目标程序,并且进行语法检查、调试措施、修改手段、覆盖处理、目标程序优化等步骤。此阶段编译器会完成对代码的语法和语义的分析,生成汇编代码,并将这个代码保存在hello.s文件中。
3.2 在Ubuntu下编译的命令
Ubuntu下编译的命令是:cc1 hello.i -o hello.s 或者gcc -S hello.i -o hello.s
图4:Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1文件声明
图5:hello.s头部文件
所有以‘.’开头的行都是指导汇编器和链接器工作的伪指令。
.file:声明源文件
.text:代码节
.rodata:只读代码段
.align 8:数据或者指令的地址对齐方式
.string:声明一个字符串(.LC0,.LC1)
.global:声明全局变量(main)
.type:声明一个符号是数据类型还是函数类型
3.3.2数据
(1)整型数据
图6:main()的参数传递
如图6可见main()函数的两个参数。
(2)字符串与立即数
图7:hello.s一部分头部文件
如图7,源程序中的两个字符串信息都被存储在.rodata段,而立即数会直接作为代码的一部分保存在.text section中。
(3)局部变量
图8:局部变量保存
如图8,int i当前被保存在-4(%rbp)中,由图5可知%rbp是指向栈的寄存器,所以i被保存在栈上,其在初始化时是被保存在寄存器中的。
3.3.3赋值操作
图9:赋值操作
赋值操作主要有mov指令实现,有以下几种:
movb:一个字节
movw:两个字节
movl:四个字节
movq:八个字节、
如图9,i=0这条赋值操作就是利用的movl指令来实现。
3.3.4算术操作
图10:算术运算
汇编中有add,sub,imul,xor等算术操作对应着c语言程序中的+、-、*、异或等操作,在hello源程序中有对循环变量进行+1的操作,对应到汇编中是通过addl指令完成的,如图10的第二行和最后一行。
3.3.5控制转移
图11:控制转移
其中cmpl比较了立即数4与-20(%rbp)处值的大小,为一个关系操作其会通过一次计算来设置条件码,而je通过前一次操作设置的条件码实现当-20(%rbp)中的值也就是第一个参数argc不等于4时,跳转到.L2处,其为控制转移。
3.3.6关系操作
图12:关系操作
cmpl同样比较了立即数7与-4(%rbp)中值的大小并设置条件码,根据条件码判断,当-4(%rbp)中的值小于等于7时,程序会跳转到.L4处,即C程序中的i<8时执行for循环中的内容。
3.3.7指针数组
图13:指针数组
如图13,main的第二个参数*argv[]是一个指针数组的首地址,它被保存在了寄存器%rsi中。
3.3.8函数操作
在hello.s中涉及的函数操作有:
main函数,printf,exit,sleep ,getchar函数;
main函数的参数是argc和argv;两次printf函数的参数恰好是那两个字符串;
exit参数是1,sleep函数参数是atoi(argv[3]);
函数的返回值存储在%eax寄存器中。
图14:if中printf与exit函数调用
图15:主循环中printf函数调用
图16:atoi函数调用
hello.c中涉及的类型转换函数是:atoi(),将字符串类型转换为整数类。
图17:sleep函数调用
图18:getchar函数调用
3.4 本章小结
本章主要介绍了编译的的概念以及编译的作用与功能,以及在Ubuntu下将hello.i文件编译生成hello.s文件的命令。
按照C语言的不同数据与操作类型,分析了源程序hello.c文件中的语句是怎样转化为hello.s文件中的语句的。其中数据类型包括数字常量、字符串常量和局部变量;操作类型包括赋值、类型转换、算术操作、关系操作、数组\\指针\\结构操作控制转移以及函数调用。
第4章 汇编
4.1 汇编的概念与作用
概念:汇编是指经过编译后,汇编器as将汇编语言翻译成机器语言,得到可重定位目标文件的过程。
作用:将汇编进一步翻译为计算机可以理解的二进制机器语言,这是机器可直接识别执行的代码文件。
4.2 在Ubuntu下汇编的命令
Ubuntu下汇编的命令 as hello.s -o hello.o 或者 gcc -c hello.s -o hello.o
图19:Ubuntu下的汇编命令
4.3 可重定位目标elf格式
图20:hello.o的elf头
ELF首先由一个16B的magic数,该数描述了生成该文件的系统的字的大小和字节顺序,当魔数出现异常时,操作系统会停止加载该程序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息:包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。
图21:ELF节头部表
不同节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定大小的条目。节头记录每个节的名称、偏移量、大小、位置等信息。
.text节:已编译程序的机器代码以编译的机器代码。
.rela.text节:一个.text节中的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。
.data节:已初始化的静态和全局C变量。
.bss节:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量,在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。
.rodata节:存放只读数据。
.comment节:包含版本控制信息。
.symtab:一个符号表,存放在程序中定义和引用的函数和全局变量的信息。
.strtab节:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部的节名字。
.shstrtab节:该区域包含节的名称。
图22:重定位节
在.rela.text节中存放着代码的重定位条目。当链接器把这个目标文件和其他文件进行链接时,会结合这个节,修改.text节中相应位置的信息。
而重定位条目常见共2种:
R_X86_64_32:重定位绝对引用。重定位时使用一个32位的绝对地址的引用,通过绝对寻址,CPU直接使用在指令中编码的32位值作为有效地址,不需要进一步修改。
R_X86_64_PC32:重定位PC相对引用。重定位时使用一个32位PC相对地址的引用。一个PC相对地址就是据程序计数器的当前运行值的偏移量。
可以看出,对于字符串的都是绝对引用。每个重定位条目包含如下信息:该节包括的内容是:偏移量,信息,类型,符号值,符名称和加数。
图23:符号表
“.symtab”是一个符号表,它给出了存放在程序中定义和引用的函数和全局变量的信息,但是它不会保存本地非静态数据,因为这些数据会在程序运行的时候被保存在栈或者寄存器中。在进行符号解析的时候需要用到符号表。
4.4 Hello.o的结果解析
图24:hello.o反汇编内容
对比hello.o的反汇编代码,与hello.s的内容进行比较,发现以下几点的不同:
- 数字进制不同
hello.s中操作数都是十进制的,而在反汇编代码中以十六进制表示,这对应着在机器中以二进制的形式存在。
- 对字符串常量的引用不同
hello.s中是用的全局变量所在的那一段的名称加上%rip的值,而hello.o中用的是0加%rip的值,因为当前为可重定位目标文件,之后还需经过重定位方可确定其具体位置,所以这里都用0来代替。
- 分支转移不同
hello.s中列出了每个段的段名,分支转移时,跳转指令后用对应的段的名称表示跳转位置;而在hello.o的反汇编代码中每个段都有明确的地址,跳转指令后用相应的地址表示跳转位置。
- 函数调用不同
在hello.s中调用函数时,在call指令之后直接引用函数名称,而在hello.o的反汇编代码中,在call指令后加上下一条指令的地址来表示。观察机器语言,发现其中操作数都为0,这是因为通过重定位信息,再链接生成可执行文件后才会生成其确定的地址,所以这里的相对地址都用0代替。
4.5 本章小结
本章介绍了汇编的含义与作用,将hello.o转化成elf格式,主要分析了ELF头、节头部表、重定位节和符号表这几个节。之后通过objdump进行hello.o的反汇编与其hello.s进行对比,发现了其结构大致相同,但有些许差别,反汇编代码更加接近于底层,而汇编代码可读性更强。
第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组合成一个文件的过程,这个文件可被加载到内存执行。链接可以执行于编译时、加载时、运行时。
作用:把预编译好了的若干目标文件合并成为一个可执行目标文件。使得分离编译成为可能,不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为可独立修改和编译的模块。
5.2 在Ubuntu下链接的命令
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
图25:Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
图26:hello的ELF头
图27:hello的节头部表
图28:hello的程序头部表
图29:hello的重定位节内容
5.4 hello的虚拟地址空间
图30:edb打开hello的Data Dump窗口
edb的Data Dump窗口。窗口显示虚拟地址由0x400000开始,从开始到结束这之间的每一个节对应5.3中的每一个节头表的声明。
图31:edb打开hello的Loaded Symbols窗口
证实从虚拟地址从0x400000开始和5.3节中的节头表是一一对应的。
5.5 链接的重定位过程分析
运用指令objdump -d -r hello > hello-asm.txt得到hello的反汇编文件。
图32:反汇编的命令
将hello.o与hello文件的反汇编代码进行对比分析,得到如下几方面的不同:
- 链接后函数数量增加。多出了puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt等函数的代码。
图33:反汇编文件部分内容(一)
- 函数调用指令call的参数发生变化。
图34:反汇编文件部分内容(二)
- 跳转指令参数发生变化。
图35:反汇编文件部分内容(三)
5.6 hello的执行流程
5.7 Hello的动态链接分析
当程序调用一个由共享库定义的函数时,编译器无法预测这个函数运行时的地址,因为定义它的共享模块在运行时可以加载到任何位置。这时,编译系统提供了延迟绑定的方法,将过程地址的绑定推迟到第一次调用该过程时。他通过GOT和过程链接表PLT的协作来解析函数的地址。在加载时,动态链接器会重定位GOT中的每个条目,使它包含正确的绝对地址,而PLT中的每个函数负责调用不同函数。
在dl_init调用之前,对于每一条PIC函数调用,调用的目标地址都实际指向PLT中的代码逻辑,GOT存放的是PLT中函数调用指令的下一条指令地址。
通过节头表找到GOT起始位置0x404000
图36:got.plt起始位置
调用dl_init之前的情况:
图37:调用dl_init之前的内容
调用dl_init之后的情况:
图38:调用dl_init之后的内容
5.8 本章小结
本章结合实验中的hello可执行程序依此介绍了链接的概念及作用以及命令,并对hello的elf格式进行了详细的分析对比。以及hello的虚拟地址空间知识,并通过反汇编hello文件,将其与hello.o反汇编文件对比,详细了解了重定位过程,遍历了整个hello的执行过程,整理了过程中的子函数,在最后对hello进行了动态链接分析,对链接有了更深的理解。
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器地内容、程序计数器、环境变量以及打开文件描述符的集合。
作用:进程提供独立的逻辑控制流,好像我们的程序独占地使用处理器;也提供一个私有的地址空间,好像我们的程序独占地使用内存系统;使CPU被科学有效地划分成多个部分以并行地运行多个进程。
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell为用户提供命令行界面,使用户可以在这个界面中输入shell命令,然后shell执行一系列的读/求值步骤,读步骤读取用户的输入的命令行,求值步骤则解析命令行,并运行程序。完成后重复上述步骤,直到用户退出shell。从而完成用户与计算机的交互来操作计算机。
处理流程:Shell打印一个命令行提示符,等待用户输入指令。在用户输入指令后,从终端读取该命令并进行解析,若该命令为shell的内置命令,则立即执行该命令;若不是内置命令,是一个可执行目标文件,则shell创建会通过fork创建一个子进程,并通过execve加载并运行该可执行目标文件,用waitpid命令等待执行结束后对其进行回收,从内核中将其删除;若将该文件转到后台运行,则shell返回到循环的顶部,等待下一个命令行。完成上述过程后,shell重复上述过程,直到用户退出shell。
6.3 Hello的fork进程创建过程
终端程序通过调用fork()函数创建一个子进程,子进程得到与父进程完全相同但是独立的一个副本,包括代码段、段、数据段、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,父进程和子进程最大的不同时他们的PID是不同的。父进程与子进程是并发运行的独立进程,内核能够以任意方式交替执行它们的逻辑控制流的指令。在子进程执行期间,父进程默认选项是显示等待子进程的完成。
图39:创建子程序
以 ./hello 2021113008 shilongtai 1 为例,首先shell对我们输入的命令进行解析,由于我们输入的命令不是一个内置的shell命令,因此shell会调用fork()创建一个子进程,如图39。
6.4 Hello的execve过程
execve函数在当前进程的上下文中加载并运行一个新程序。
execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境遍历列表envp。只有当出现错误时,execve才会返回到调用程序。所以,execve调用一次并从不返回。在execve加载了filename后,调用启动代码,启动代码设置栈,并将控制转移传递给新程序的主函数。
子进程通过execve函数系统调用启动加载器。加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零。通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件的内容。最后加载器调到_start处,最终调用应用程序的main函数。
6.5 Hello的进程执行
当开始运行hello时,内存为hello分配时间片,如一个系统运行着多个进程,那么处理器的一个物理控制流就被分成了多个逻辑控制流,逻辑流的执行是交错的,它们轮流使用处理器,会存在并发执行的现象。其中,一个进程执行它的控制流的一部分的每一时间段叫做时间片。然后在用户态下执行并保存上下文。
如果在此期间内发生了异常或系统中断,则内核会休眠该进程,并在核心态中进行上下文切换,控制将交付给其他进程。
当hello 执行到 sleep时,hello 会休眠,再次上下文切换,控制交付给其他进程,一段时间后再次上下文切换,恢复hello在休眠前的上下文信息,控制权回到 hello 继续执行。
hello在循环后,程序调用 getchar() , hello 从用户态进入核心态,并再次上下文切换,控制交付给其他进程。最终,内核从其他进程回到 hello 进程,在return后进程结束。
6.6 hello的异常与信号处理
(以下格式自行编排,编辑时删除)
图40:异常的类别
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
图41:运行时不停乱按结果图
可以看到,虽然乱按不会影响程序的运行,但是会在程序运行结束后对shell发送许多无效指令,不过要注意因为hello程序最后有一个getchar,因此第一个乱按的指令被getchar给读走了,不会成为发送给shell的无效指令。因为我们可以判断,我们乱按的内容被放入缓冲区,等待程序执行结束被shell当作命令读走。
图42:运行时Ctrl-Z结果图
可以看到,ctrl+z后程序被放入后台并暂停运行。
图43:运行时Ctrl-C结果图
可以看到,ctrl+c后程序直接结束运行,回到shell等待输入下一条指令。
图44:Ctrl-Z后输入ps结果图
可以看到,输入ps,可以查看当前所有进程的相关信息,可以发现此时hello程序仍然存在。
图45:Ctrl-Z后输入jobs结果图
可以看到,输入jobs可以查看前台作业号。
图46:Ctrl-Z后输入pstree结果图
可以看到,输入pstree命令,以树形结构显示了程序和进程间的关系。
图47:Ctrl-Z后输入fg结果图
可以看到,输入fg,能使被挂起的hello程序变成前台程序继续运行。
图48:Ctrl-Z后输入kill结果图
对比kill前后两次ps显示的进程信息,可以知道,输入kill -9 4488会将SIGKILL信号发送给进程4488,使得它被终止。
6.7本章小结
本章主要介绍了进程的概念与作用。同时介绍了壳Shell-bash作用与处理流程。明确了hello的fork进程创建过程与execve过程,通 以上是关于HIT计统大作业——程序人生的主要内容,如果未能解决你的问题,请参考以下文章