计算机系统
大作业
题目程序人生-Hello’s P2P
专业计算机科学与技术
学 号2021112386
班 级2103102
学生杜壹华
指导教师刘宏伟
计算机科学与技术学院
2022年5月
摘要
hello是我们学习过的第一个程序,可以说hello是最基础的也是最有助于我们理解底层计算机知识的文件,于是我们从hello.c到hello可执行文件对hello的“一生”进行解析,加深我们对课本知识的认识,并从计算机系统底层对我们所编写的程序进行认识。
关键词:进程,编译,链接,汇编,预处理。
目录
第1章概述………………………………………………………………………………………………….- 4 –
1.1 Hello简介……………………………………………………………………………………………- 4 –
1.2环境与工具…………………………………………………………………………………………..- 4 –
1.3中间结果………………………………………………………………………………………………- 4 –
1.4本章小结………………………………………………………………………………………………- 5 –
第2章预处理………………………………………………………………………………………………- 6 –
2.1预处理的概念与作用…………………………………………………………………………….- 6 –
2.2在Ubuntu下预处理的命令…………………………………………………………………..- 6 –
2.3 Hello的预处理结果解析………………………………………………………………………- 7 –
2.4本章小结………………………………………………………………………………………………- 7 –
第3章编译………………………………………………………………………………………………….- 8 –
3.1编译的概念与作用………………………………………………………………………………..- 8 –
3.2在Ubuntu下编译的命令……………………………………………………………………..- 8 –
3.3 Hello的编译结果解析………………………………………………………………………….- 9 –
3.4本章小结…………………………………………………………………………………………….- 11 –
第4章汇编………………………………………………………………………………………………..- 12 –
4.1汇编的概念与作用………………………………………………………………………………- 12 –
4.2在Ubuntu下汇编的命令……………………………………………………………………- 12 –
4.3可重定位目标elf格式……………………………………………………………………….- 12 –
4.4 Hello.o的结果解析……………………………………………………………………………- 14 –
4.5本章小结…………………………………………………………………………………………….- 15 –
第5章链接………………………………………………………………………………………………..- 16 –
5.1链接的概念与作用………………………………………………………………………………- 16 –
5.2在Ubuntu下链接的命令……………………………………………………………………- 16 –
5.3可执行目标文件hello的格式…………………………………………………………….- 16 –
5.4 hello的虚拟地址空间………………………………………………………………………..- 19 –
5.5链接的重定位过程分析……………………………………………………………………….- 19 –
5.6 hello的执行流程……………………………………………………………………………….- 21 –
5.7 Hello的动态链接分析………………………………………………………………………..- 21 –
5.8本章小结…………………………………………………………………………………………….- 22 –
第6章HELLO进程管理……………………………………………………………………………- 23 –
6.1进程的概念与作用………………………………………………………………………………- 23 –
6.2简述壳Shell-bash的作用与处理流程………………………………………………..- 23 –
6.3 Hello的fork进程创建过程……………………………………………………………….- 23 –
6.4 Hello的execve过程………………………………………………………………………….- 23 –
6.5 Hello的进程执行……………………………………………………………………………….- 24 –
6.6 hello的异常与信号处理…………………………………………………………………….- 25 –
6.7本章小结…………………………………………………………………………………………….- 28 –
结论- 29-
附件…………………………………………………………………………………………………………….- 30 –
参考文献……………………………………………………………………………………………………..- 31 –
第1章概述
1.1Hello简介
(1)P2P:(Program to Process)从源文件hello.c到可执行文件hello的过程,期间,经历了cpp预处理得到hello.i文件,再经过ccl编译器得到汇编程序hello.s
文件,再经过汇编器as得到可重定位目标文件hello.o,最后再通过链接器ld得到hello可执行文件。
图1.1编译系统流程图
(2)020:(Zero to Zero)轻轻的来,轻轻的走。在程序要运行时,shell会在fork函数和execve函数的作用下进行执行,通过内核的调度得到hello的进程。当程序运行结束时,hello进程又会被回收,所占内存和相关进程都会被删除
1.2环境与工具
(1)硬件工具:Inteli5@2.40GHz,512Gdisk,16G内存,x86-64
(2)软件环境:window 10,ubuntu 18.04
(3)开发工具:gcc,gdb,edb,gedit
1.3中间结果
hello.i
预处理得到的文件
hello.s
编译后得到的文件
hello.o
汇编后的可重定位目标文件
hello
链接之后得到的可执行文件
hello_etf.txt
hello.o文件的反汇编文件
hello.c
程序源文件
hello_etf2.txt
hello文件的反汇编文件
1.4本章小结
小结:第一章我们了解了hello.c文件在计算机系统中运行的“一生”,了解了它一路上所经历的演化,且在各个文件之间互相联系,互相依赖,共同作用下让.c文件得以在计算机上展现风采,除此我们也了解了hello运行时的软硬件环境。
第2章预处理
2.1预处理的概念与作用
(1)概念:预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。例如hello.c中第一行的#include命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中,结果就得到了另一个C程序,通常是以.i作为文件扩展名。
(2)作用:1`宏定义:#define指令以及#undef指令
2`条件包含:#include指定的文件内容被包含到程序中。
3`条件编译:#if,#ifdef,#else等指令的运用
2.2在Ubuntu下预处理的命令
预处理指令为:gcc -E hello.c -o hello.i
图2.1预处理指令
图2.2 hello.i文件内容
2.3 Hello的预处理结果解析
文件内容解析:我们很容易发现代码变长了许多,也没有了#引用的内容,其实.i文件是将原来#包含的内容展开,将代码添入到我们的程序文件中去,内容增加,但我们不难发现最底下的main函数部分仍然为我们所编写的c源程序,所以其本质还是可读的文本文件。
图2.3hello.i最底下的main函数部分
2.4本章小结
小结:在本章中,我们了解了如何在Linux环境下输入指令得到预处理文件,并对得到的hello.i进行分析,了解了预处理过程的作用。
第3章编译
3.1编译的概念与作用
(1)概念:编译器ccl将文本文件hello.i翻译成文本文件hello.s,它包含了一个汇编语言程序,同时利用编译程序从源语言编写的源程序产生目标程序,最终得到的是经过优化的汇编文件。
(2)作用:每条语句都以一种文本格式描述了一条低级机器语言指令,并且为不同高级语言的不同编译器提供了通用的输出语言。
3.2在Ubuntu下编译的命令
编译指令为:gcc -S hello.c -o hello.s
图3.1编译指令
图3.2 hello.s文件的内容
3.3 Hello的编译结果解析
3.3.1:数据:
- .rodata数据
图3.3数据节内容
我们可以看到:①.LC0:这其中储存的是在main函数中printf中的字符串内容,这里是通过编码的方式存储。
②.LC1:这里存储的是在printf函数中输出所采用的格式。
- 全局变量
在这里我们可以发现函数名main是唯一出现在global节中的符号。
- 局部变量
①main函数中局部变量i,不难发现i并不是存在寄存器中
而是被存储在栈中,其位置为-4(%rbp),这一点可以通过i在各个部分参加的指令看得出来(包括一些比较的指令以及循环处的跳转)
图3. 4局部变量i
图3. 5局部变量i
②main函数的参数argc以及argv分别存放在栈中,第一个参数在-20(%rsp)处,第二个参数在-32(%rsp)处。
图3. 6 main函数参数位置
3.3.2:操作:
- 赋值操作:一共是用到了mov指令和leap指令来进行赋值操作,mov
类指令丰富且灵活,如局部变量i则通过mov指令被赋值。
图3. 7局部变量i被赋值
- 类型转换:并未发现类型转换(显式或隐式)
- 算数操作:x86-64指令集提供了大量简单可执行的操作,在hello.c
文件中使用的算数操作是add(加法操作)
图3. 8 add算数操作
- 关系操作:我们常见的关系操作为cmp,TEST,这些操作会利用加法和减法操作完成需要的比较,并最终对标记码进行更改,不对寄存器的进行改变,常见的条件码有CF:进位标志,ZF:零标志,SF:符号标志OF:溢出标志。在我们的hello文件中使用的是cmp操作。
图3. 9 cmp关系操作
- 数组操作:在我们的hello文件运行时会对我们输入的字符串数组进行使用,于是就是用到对数组,指针的操作,我们不难发现利用argv数组的首地址,再利用指针的8个字节的长度大小,可以解决函数对argv[1]以及argv[2]的引用。
图3. 10对argv数组当中值的引用
- 控制转移操作:我们常见的控制转移常常利用的是跳转操作,需要用到jmp等指令,在这里我们用的指令有如下类:
图3. 10 hello中对控制转移的使用argv数组当中值的引用
同时这里采用的是for循环语句中的jump to middle的格式,先初始化,然后再判断,最后再是进入循环体。
- 函数操作:在hello操作中调用了printf函数以及sleep,getchar,exit函数,而在引用这些指令用到的都是call指令,调用时使用寄存器对参数进行传送,返回时也是通过寄存器进行操作。
图3. 11 hello中call指令使用
3.4本章小结
小结:在本章节中,我们对汇编文件内容进行解读,得到了有关hello文件中数据和操作的内容,深入理解了底层编码的使用,对理解hello程序更深一步。
第4章汇编
4.1汇编的概念与作用
(1)概念:汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成可重定位目标程序,并将结果保存在hello.o文件中。此时我们便完成了文本文件到二进制机器文件的转换。
(2)作用:真正得到机器可以读懂的二进制文件。
4.2在Ubuntu下汇编的命令
汇编指令为:gcc -c hello.s -o hello.o
图4. 1汇编指令使用
4.3可重定位目标elf格式
得到各节的信息:
图4. 2 ELF头结点信息
头结点:这里解释了生成该文件的系统的字的大小和字节顺序,剩下的部分包括ELF头的大小,目标文件的类型,机器类型,节头部表中条目的大小和数量。
文件剩下一般包含.txt,.rodata,.data,.bss,.symtab,.rel.text,.rel.data,.debug
节头表
图4. 3节头表信息
符号表
图4. 4符号表信息
重定位信息表
图4. 5重定位信息表
我们不难发现重定位在图中只有两种,事实上,一般我们所常见的重定位信息大致为相对地址寻址以及绝对地址寻址,例如信息为00050000002的重定位方式就为PC相对地址引用,而其计算方法则需要用到重定位条目的信息
图4. 6重定位条目
再结合算法便可得到重定位地址引用的情况。
图4.7相对寻址算法
4.4 Hello.o的结果解析
反汇编指令:objdump -d -r hello.o
图4. 8反汇编文件
对比分析,发现不一样的地方主要为以下几点:
- hello.s文件中操作数为10进制,而反汇编文件中操作数为16进制。
- 跳转语句采用的是相对地址的跳转,而不是寻找跳转的节点信息。
- 调用函数时,hello.s采取的是对函数名的调用,而反汇编文件中采取的是相对地址寻址。
- 反汇编对栈的利用率更高。
4.5本章小结
小结:在本章中,我们实现对hello.s的汇编以及hello.o的反汇编,并且实现了对可重定位目标文件hello.o的信息读取,包块ELF头,头节表,符号表,且认识重定位信息的处理过程。最终还比较了hello.s文件和hello.o得到的反汇编文件之间的差异。
第5章链接
5.1链接的概念与作用
(1)概念:链接是指在电子计算机程序的各模块之间传递参数和控制命令,并把它们组成一个可执行的整体的过程。即将一个或多个由编译器或汇编器生成的目标文件外加库链接为一个可执行文件的过程。
(2)作用:将目标文件,库文件(静态库,共享库)等连接成可执行的目标文件
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
图5.1链接指令
5.3可执行目标文件hello的格式
可重定位条目:
图5.2可重定位条目表
图5.3程序头表
图5.4 dynamic section
图5.5符号表
5.4 hello的虚拟地址空间
使用edb的情况为:
图5.6虚拟地址内存
不难发现,此处的虚拟地址与我们在条目中所读取的信息一致
图5.7 edb读取的.intrep信息(一致)
图5.8 edb读取的.text信息(一致)
其他条目也是如此,可以对应上。
5.5链接的重定位过程分析
objdump -d -r hello
图5.9 hello反汇编文件
- 将hello与hello.o文件进行比较,不难发现
首先,hello中增加了一些节,类似于.init节以及.plt节,这些节都为可执行目标文件所用。其次,hello中无hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。
图5.10两反汇编文件的跳转对比
还有的是链接增加了新的函数如exit、printf等。
- 还有就是关于重定位的计算,上面第四章有进行描述,其核心在于我们要先根据重定位条目选择读取地址的方式,然后再是根据条目列举出的信息对其进行计算,从而得到有关重定位信息。
图5.11重定位地址的计算
5.6 hello的执行流程
需要经过_init、_start、_libc_start_main、_main、_printf、_exit、_sleep、_getchar、exit
访问的地址分别为0x4004c,0x400550,0x400582,0x400500,0x400540,0x400510,0x400530
5.7 Hello的动态链接分析
首先找到.got的位置
图5.12 .got表的位置
于是我们通过edb进行前后的比对,不难发现其值发生了改变,动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
图5.13调用前后对比图(有发生变化)
5.8本章小结
小结:在本章中我们对链接加深了认识,同时调用edb等工具实现对静态库和动态链接的分析,并将可执行的目标文件的反汇编文件与可重定位目标文件的反汇编文件进行比对,得出差异。
第6章hello进程管理
6.1进程的概念与作用
(1)概念:进程的一个经典定义就是一个执行中程序的实例,是计算机科学中最深刻、最成功的概念之一,是OS对CPU执行的程序的运行过程的一种抽象。
(2)作用:进程为程序提供了一种假象,好像是在独立的占用着系统资源,有着独立的逻辑控制流,且有着私有的地址空间。
6.2简述壳Shell-bash的作用与处理流程
(1)Shell是一个交互型应用级程序,代表用户运行其他程序(是命令行解释器,以用户态方式运行的终端进程),其基本功能是解释并运行用户的指令。
(2)处理流程:1终端进程读取用户由键盘输入的命令行。2分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量3检查第一个(首个、第0个)命令行参数是否是一个内置的shell命令4如果不是内部命令,调用fork( )创建新进程/子进程5在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。6如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid等待作业终止后返回。7如果用户要求后台运行(如果命令末尾有&号),则shell返回。
6.3 Hello的fork进程创建过程
父进程通过调用fork()函数来创建一个新的运行的子进程,新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的一份副本,包括代码和数据段、堆、共享库以及用户栈。父进程和子进程之间的最大区别是他们有不同的PID。
Fork()函数调用一次,返回两次,实行并发执行,批次之间有着相同但是独立的地址空间,并且共享已经打开的文件。
6.4 Hello的execve过程
execve函数在当前进程的上下文中加载并运行一个程序,加载并运行目标文件filename,且只有找不到对应的调用程序才会返回,否则,调用一次从不返回。
当加载结束之后,便会先调用启动代码,启动代码设置栈,并将控制权传递给新程序的主函数,当映射完成之后则设置PC值,对程序进行运行。
图6.1参数以及环境变量组织结构
图6.2新程序的栈的典型组织结构
6.5 Hello的进程执行
进程执行过程中的一些概念:①并发流:一个逻辑流的执行在时间上与另一个流重叠,X与Y互相并发当且仅当X在Y开始之后和Y结束之前开始。
②用户模式和内核模式:一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置。没有设置模式位时,进程运行在用户模式中,此时不允许执行特权指令,也不允许直接引用地址空间中内核区的代码和数据。
③上下文切换:内核使用上下文这种异常控制流来实现多任务,内核为每一个进程维持一个上下文,上下文的内容由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。内核可以通过调度的方式实现上下文的切换。
④时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
在hello的执行过程中有在进程调用execve函数之后,进程为hello程序分配了虚拟地址空间,程序开始时hello运行在用户模式下,输出信息,然后调用sleep函数进程进入内核模式,运行信号处理程序,之后再返回用户模式。运行过程中,cpu不断切换上下文,不断实现调度,hello的时间片之间被阻隔开。
图6.3 hello的运行结果
图6.4进程的上下文切换
6.6 hello的异常与信号处理
图6.5异常的类别
图6.6各种异常出现的原因
在上述图片中我们讨论了异常的种类以及出现的原因,现在我们来看看异常处理的结果如何。
①:疯狂乱按
图6.7乱输入的情形
不难发现,在我们乱输入的时候,并不会发生停止的情况,但是伴随着程序结束,我们又不难发现突然会多出一堆无法被执行的指令,这是因为无关输入被缓存执行,而后Linux下无此些指令,则无法执行。
②:按下Ctrl+C
图6.8按下Ctrl+C的情形
此时进城结束,作业任务结束。
③:按下Ctrl+Z
图6.9按下Ctrl+Z的情形
我们不难发现显示进程已停止,但是我们输入
④:jobs
图6.10按下jobs的情形
图6.11按下ps的情形
图6.12按下pstree的情形
我们又不难发现这个程序仍然在,并没有被清除,其实是因为Ctrl+Z将该进程默认挂起在后台,于是我们输入
⑤:fg
图6.13按下fg的情形
图6.14按下kill的情形
此时我们又不难发现程序在前台中继续执行完成。(或者使用kill函数将其杀死)
6.7本章小结
小结:在本章中我们了解了Shell的工作原理,并且对fork()函数和execve函数()进行深入了解,分析了hello的执行过程之后,还对程序处理异常信号和任务进行模拟,受益匪浅。
总结
在此次大作业实验中,我们从一个底层的原程序文件hello.c文件开始,结合一个学期我们所学习的内容,得到了这个大作业的内容。
首先,我感觉这是意义颇深的一次作业,在做完了四次课内实验后,我认为正是这种上手操作的经历加深了我对课本知识的理解,在完成此次大作业时,我翻课本,并且将课本从第1面翻阅到第546面,这对我而言完全是一次知识的回顾,虽然过程是痛苦的,好早有同学的相助,在再一次翻阅课本的时候又学到了新的知识。
其次,hello程序的一生是计算机对程序员的回应,是程序员与机器之间沟通的过程,从一个人编写的文本文件到机器可以识别执行的二进制文件,这个过程在我们看来是快速,甚至于是一瞬间的事,但是在机器底层,却要走过一步一步的指令,得到一个又一个的文件,这个过程的探索就显得那么有趣。
再要说的是,本次的大作业虽然很好,但赖于个人能力,有些指令以及GDB的操作并没有得到完美实现,希望可以在后面的学习过程中将其补上。
大作业已经告一段落,hello的一生还在继续,希望在未来的道路上熟悉掌握计算机系统知识,编写适合计算机的程序,与机器进行更好的交流。
附件
hello.i | 预处理得到的文件 |
hello.s | 编译后得到的文件 |
hello.o | 汇编后的可重定位目标文件 |
hello | 链接之后得到的可执行文件 |
hello_etf.txt | hello.o文件的ELF文件 |
hello.c | 程序源文件 |
hello_etf2.txt | hello文件的ELF文件 |
参考文献
[1]https://blog.csdn.net/zhiai_/article/details/12483448
[2]兰德尔E.布莱恩特.深入理解计算机系统.第三版
[3]一篇教会你写90%的shell脚本-知乎(zhihu.com)