程序人生-Hello’s P2P
Posted ZZZZZZZeno
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序人生-Hello’s P2P相关的知识,希望对你有一定的参考价值。
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 2021110908
班 级 2103103
学 生 朱宸慷
指 导 教 师 刘宏伟
计算机科学与技术学院
2022年5月
本文主要通过结合本学期计算机系统这门课程所学习的知识,以及教材《深入理解计算机系统》相关内容,分析研究了hello.c文件的生命周期。本文从hello.c这个c源文件开始,深入分析在linux系统下c源文件经过的预处理、编译、汇编、链接、执行、终止以及最后被回收的过程。通过对于这一过程的学习,得以深入理解linux系统下程序的构造、执行、回收过程等等,从而回顾本学期所学习到的内容的知识要点,从而得以梳理知识框架,做到学以致用、融会贯通。
关键词: Hello’s P2P;程序人生;计算机系统;程序生命周期
目 录
第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的预处理结果解析.................................................... - 6 -
2.4 本章小结............................................................................... - 7 -
第3章 编译................................................................................... - 9 -
3.1 编译的概念与作用............................................................... - 9 -
3.2 在Ubuntu下编译的命令.................................................... - 9 -
3.3 Hello的编译结果解析........................................................ - 9 -
3.4 本章小结............................................................................. - 13 -
第4章 汇编................................................................................. - 14 -
4.1 汇编的概念与作用............................................................. - 14 -
4.2 在Ubuntu下汇编的命令.................................................. - 14 -
4.3 可重定位目标elf格式...................................................... - 14 -
4.4 Hello.o的结果解析........................................................... - 16 -
4.5 本章小结............................................................................. - 17 -
第5章 链接................................................................................. - 18 -
5.1 链接的概念与作用............................................................. - 18 -
5.2 在Ubuntu下链接的命令.................................................. - 18 -
5.3 可执行目标文件hello的格式......................................... - 18 -
5.4 hello的虚拟地址空间....................................................... - 21 -
5.5 链接的重定位过程分析..................................................... - 22 -
5.6 hello的执行流程............................................................... - 23 -
5.7 Hello的动态链接分析...................................................... - 24 -
5.8 本章小结............................................................................. - 25 -
第6章 hello进程管理.......................................................... - 26 -
6.1 进程的概念与作用............................................................. - 26 -
6.2 简述壳Shell-bash的作用与处理流程........................... - 26 -
6.3 Hello的fork进程创建过程............................................ - 26 -
6.4 Hello的execve过程........................................................ - 27 -
6.5 Hello的进程执行.............................................................. - 27 -
6.6 hello的异常与信号处理................................................... - 28 -
6.7本章小结.............................................................................. - 32 -
参考文献....................................................................................... - 36 -
第1章 概述
1.1 Hello简介
1.1.1 P2P
P2P是指From Program to Process,也即从一个c源文件hello.c程序(Program)到一个可执行的程序(Process)的过程。Hello首先是一个存储在磁盘上的文本文件,使用预处理器处理这个文件,将头文件插入,得到hello.i。之后将hello.i输入编译器,编译器生成汇编语言文件hello.s。之后,通过汇编器,我们讲文本文件hello.s转换为二进制机器码格式的可重定位文件hello.o。此时使用链接器,就可以将hello.o链接生成可执行文件hello,再调用shell运行hello为其创建进程(process),就完成了p2p
1.1.2 020
020 是指 from zero to zero。Hello 程序执行前,不占用内存空间,这就是第一个0。使用shell执行hello后,首先调用execve,依次进行虚拟内存映射、物理内存载入等等步骤,再进程的上下文中运行hello。执行时,这一进程被分配进入流水线,执行完毕后,由父进程回收进程,之后内核清除相关信息,就完成了第二个0.
1.2 环境与工具
硬件环境:
处理器:AMD Ryzen 7 5800H 3.2GHz
内存:16GB(3200MHz)
软件环境:
Windows10;Ubantu
开发与调试工具:gcc,cpp,as,ld,edb,gdb,readelf
1.3 中间结果
文件名 | 功能 |
hello.i | hello.c经过预处理得到的文本文件 |
hello.s | 编译后得到的汇编文件 |
hello.o | 汇编后得到的可重定向文件 |
hello | 链接得到的可执行文件 |
hello1_elf.txt | 用readelf读hello.o得到的elf文件 |
hello1_dis.txt | 反汇编hello.o得到的文件 |
hello2_elf.txt | hello的elf格式文件 |
hello2_dis.txt | hello的反汇编文件 |
1.4 本章小结
本章首先介绍了hello中p2p和020的概念,之后介绍了所用到的实验环境,最后介绍了本文中所涉及到的中间文件的名称及功能。
第2章 预处理
2.1 预处理的概念与作用
2.1.1概念:
预处理是在编译文件前对文本文件进行的处理,修改原始的c程序。这一过程中预处理器cpp会根据文件头部#开头的语句进行操作。
2.1.2作用:
预处理器会读取到#include<>相关内容,并且把头文件插入到文本文件中。此外,预处理器还会根据#define的内容进行宏替换,将代码中使用宏定义的地方替换为定义的实际值。预处理器还有可能根据#ifdef等决定是否插入某些内容。在这一过程中,预处理器会忽略所有的注释以及空白,最终生成hello.i这个预处理后的文件。
2.2在Ubuntu下预处理的命令
cpp hello.c>hello.i
使用预处理器将hello.c的结果输出到hello.i中
运行截图如下:
图2-1预处理命令
2.3 Hello的预处理结果解析
图2-2 预处理文件部分内容
可以发现,原本很短的程序被扩展到了3091行,而原先的主函数对应着从3078行开始的内容。之前的三千余行,都是cpp在预处理过程中插入的头文件。cpp删除掉了原本文本中的#include,并且在ubantu的环境中寻找stdio.h等,并且将它的内容插入文本文件中。除了原本明确#include的三个头文件外,cpp还插入了其他的头文件,这些头文件是原本头文件中#include的内容,而如果这些文件中有宏定义,预处理器也会递归地将他们表现出来。除此之外,预处理器还删除了文件中的注释和多余的空白等。
图2-3 预处理文件中main函数与原始代码对比
图2-4 插入的头文件
图2-5 部分类型的定义
2.4 本章小结
本章展示了将原始c文件进行预处理的方法,以及预处理过程中预处理器的工作。同时,结合ubantu系统,展示了预处理的过程和预处理文件的部分内容。这一过程中预处理器的工作为之后的编译等流程提供了前提。
第3章 编译
3.1 编译的概念与作用
3.1.1概念:
编译是指编译器ccl在通过对于hello.i内容的检查之后,将其翻译成汇编语言文件hello.s的过程
3.1.2作用:
编译的作用可以通过以下几步来体现。(1)词义分析,编译器通过逐个扫描输入的字符 ,产生一个个的单词,从而把源程序改造为一个由单词组成的中间程序。(2)语法分析,编译器通过基于词义分析的每个单词,生成语法分析树。之后进行判断每个指令是否是符合c语言指令的合法指令。(3)代码优化,编译器根据用户选择的不同优化等级,保守地对于代码进行不同程度的优化,这种优化是等价且安全的。(4)生成代码,编译器可以将优化后的代码转换为汇编语言代码,生成hello.s文件
3.2 在Ubuntu下编译的命令
gcc -S hello.i hello.s
运行结果如下:
图3-1 编译命令运行
3.3 Hello的编译结果解析
3.3.1数据
(1)常量
数字:通过观察我们可以得知,在程序中我们使用了许多的数字常量,这些常量都存储在程序的.text节中,直接体现在代码部分。部分截图如下:
图3-2 代码中的数字常量
我们可以看到,许多的运算都使用了数字常量。
字符串常量:程序中使用了printf()函数,这一输出中涉及了一些格式串,而格式串一般存储在.rodata节中,截图如下:
图3-3 代码中的字符串常量
(2)全局变量
程序中的全局变量只有main,我们知道这是一个函数型的全局变量,而且是一个强符号。
图3-4 全局变量main
(3)局部变量
程序中的局部变量都存储在堆栈段或者寄存器中,本程序中的局部变量有int argc,char *argv[],int i等等。
其中argc和argv都是运行时从寄存器传入,之后保存在运行时栈中。我们可以发现argc存储在%rbp-20的地方,它用来和4比较来决定程序的输入是否正确
图3-5 传入的argc与argv
而循环变量i保存在%rbp-4,它用来比较决定何时结束循环
图3-6 循环变量i
3.3.2 赋值
通过观察,我们可以知道程序中主要有两个赋值
(1)对循环变量i进行赋值,每次加1,截图如下:
图3-7 对循环变量i进行赋值
(2)在调用sleep函数之前,对传入的参数进行赋值,我们可以发现eax中的值是由atoi()函数得来,这就是我们所输入的argv[3]中的秒数,作为参数被从eax中复制到rdi中,截图如下:
图3-8 对sleep()传入的参数进行赋值
3.3.3 类型转换
(1)字符串变量转为整型数
程序中通过调用atoi()函数,来讲我们输入的字符串给转换为一个整型数字,截图如下:
图3-9 字符串转换为整型数字
3.3.4 算术操作
程序中最主要的算术操作就是计算循环变量i的值,每次循环递增,截图如下:
图3-10 循环变量递增
3.3.5 关系操作
程序中主要有两处关系操作,分别对应着两种条件下的跳转指令。
(1)argc!=4时的跳转,如果输入不符合要求,就打印正确做法并退出。
图3-11 第一次跳转
(2)循环变量达到9时结束循环
图3-12 第二次跳转
3.3.6 数组/指针/结构操作
程序中只有一处对于数组进行的操作,也就是传入的argv数组,通过观察我们可以知道,argv数组的4部分都存在栈中
图3-13 数组操作
3.3.7 控制转移
程序中控制转移相关操作与关系操作中相同
3.3.8 函数调用
程序主要存在三处函数调用,分别是main函数,atoi函数和 sleep函数
(1)main函数
参数传入:argc存放在rdi中,argv存放在rsi中,这两个参数通过寄存器传入后被压入运行时栈中
图3-14 main函数调用
(2)atoi函数
我们可以看到参数从rax中被复制入rdi,传入atoi函数调用
图3-15 atoi函数调用
(3)sleep函数
我们可以看到参数从eax中被复制入rdi,传入sleep函数调用
图3-16 sleep函数调用
3.4 本章小结
本章主要介绍了将预处理后的文件转化为汇编语言文件时发生的变化,并且展示了汇编代码中的一些主要操作,验证了这些操作的实现。
第4章 汇编
4.1 汇编的概念与作用
4.1.1概念:
汇编是指汇编器将hello.s的汇编语言程序翻译为机器语言程序,并且将机器语言程序打包为可重定位文件hello.o的过程。
4.1.2作用:
汇编器将汇编代码根据特定的转换规则转换为二进制代码,也就是机器码,机器只能理解机器代码。
4.2 在Ubuntu下汇编的命令
命令 as hello.s -o hello.o
汇编命令截图如下:
图4-1 汇编命令截图
4.3 可重定位目标elf格式
使用readelf -a hello.o > hello1_elf.txt将内容导出
(1)ELF头
以一个16字节的序列开始,这个序列描述了生成该文件系统下的字的大小以及一些其他信息。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息:包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。具体ELF头的代码如下:
图4-2 ELF头
(2)节头:
这里包含了文件中各个节的信息,包括类型、位置和大小等等
(3)重定位节.rela.text:
这个节中包含.text节中需要进行重定位的信息,在链接的时候,连接器会根据这些信息重定位节中的地址。节中包含这些内容:偏移量、信息、类型、 符号值、符号名称 + 加数。
图4-4 重定位节.rela.text
(4)符号表:
.symtab是一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。本程序中的getchar等函数名都需要在这一部分体现,具体信息如下图所示:
图4-5 符号表
4.4 Hello.o的结果解析
命令:objdump -d -r hello.o >hello1_dis.txt
图4-6 反汇编命令
通过对比生成的文件和原文件,我们可以发现一些差异:
(1)分支转移:
在hello.s中,我们可以看到跳转的时候是直接指明段名称的,例如.L2,.L3等等,然而反汇编的得到的文件中,跳转主要是通过计算偏移量得到的
图4-7 跳转命令
(2)函数调用
在我们之前的汇编语言文件,call之后直接跟着函数名称,而在反汇编得到的文件,call 的目标地址是当前指令的下一条指令。这是因为 hello.c 中调用的函数都是共享库中的函数,最终需要通过链接器才能确定函数运行时的地址,在汇编的时候,对于这些不确定地址的函数调用,需要将其相对地址设置为全 0,然后在.rela.text节中为其添加重定位条目,等待链接时确定。
图4-8 call命令
(3)参数
在访问参数时,同样使用类似上述函数调用时的方式,将偏移地址暂时设为0,等待重定位时修改。
图4-9 访问参数
4.5 本章小结
本章主要介绍了汇编的过程,文件经过汇编器的转化,生成了一个可重定位的文件,为下一步的链接过程做准备。本章同时展示了可重定位文件的反汇编结果,,展示了暂时为0的偏移值,这些都是为了链接过程所做的准备。
第5章 链接
5.1 链接的概念与作用
5.1.1概念:
链接是将各种不同文件的代码和数据片段收集并组合成一个单一文件的过 程,这个文件可被加载到内存并执行。
5.1.2作用:
链接可以把预编译好了的若干目标文件合并成为一个可执行目标文件。使得分离编译成为可能,不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为可独立修改和编译的模块。当改变这些模块中的一个时,只需简单重新编译它并重新链接即可,不必重新编译其他文件。
5.2 在Ubuntu下链接的命令
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o hello.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o -o hello
图5-1 链接
5.3 可执行目标文件hello的格式
命令:readelf -a hello > hello2_elf.txt
打开文件,内容如下:
(1)ELF头:
基本信息未发生改变,但是类型发生了变化并且添加了程序入口的地址。
图5-2 ELF头
(2)节头包含的信息更加丰富
图5-3 节头
(3)程序头中出现了偏移量、虚拟地址和物理地址
图5-4 程序头
(4)Dynamic section
图5-5 Dynamic section
(5)符号表
图5-6 符号表
5.4 hello的虚拟地址空间
使用edb打开hello,查看datadump窗口,我们可以得到虚拟地址空间结果如下所示:
我们可以发现这部分代码的地址是从0x401000开始的,地址从401000到401280
通过对照符号表,我们可以发现对应关系。
图5-7 data dump窗口和符号表
5.5 链接的重定位过程分析
命令objdump -d -r hello >hello2_dis.txt
图5-8 反汇编代码(部分)
不同之处有以下几点:
(1)hello中存在程序所调用的一些库函数的实现,例如getchar等
(2)链接后的函数都有了虚拟地址
图5-9 虚拟地址
(3)对于变量的引用也有了偏移量
图5-10 引用变量
链接的过程:
链接主要分为两个过程:符号解析和重定位。
(1)符号解析:目标文件定义和引用符号,符号解析将每个符号引用和一个符号定义关联起来。(2)重定位:编译器和汇编器生成从0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。链接器使用汇编器产生的重定位条目的详细指令,不加甄别地执行这样的重定位。
5.6 hello的执行流程
通过对照符号表,以及使用edb跟踪执行,我们可以得到执行流程如下
图5-11 符号表和datadump
图5-11 符号表和datadump
子程序名 | 地址 |
hello! _start | 0x4010f0 |
Libc.so.6!__libc_start_main | 0x7ae62d588b0 |
hello!_init | 0x401000 |
hello!main | 0x401125 |
hello!printf@plt | 0x401040 |
hello!sleep@plt | 0x401080 |
hello!getchar@plt | 0x401050 |
Libc.so.6!exit | 0x7ae62d635f0 |
5.7 Hello的动态链接分析
当程序调用一个由共享库定义的函数时,由于编译器无法预测这时候函数的地址是什么,所以需要添加重定位记录,等待动态链接器处理。链接器采用延迟绑定的方法,将过程地址的绑定推迟到第一次调用该过程时。通过 GOT 和过程链接表 PLT 的协作来解析函数的地址。在加载时,动态链接器会重定位 GOT 中的每个条目,使它包含正确的地址。
首先查看elf中.got.plt节的内容
图5-12 .got.plt
图5-13 改变后的.got.plt
通过调用dl_init,我们可以发现.got.glt的内容发生了改变
5.8 本章小结
本章主要介绍了将可重定位目标文件链接生成可执行文件的过程。首先分析了链接的概念和作用,之后查看并验证了hello虚拟地址和节头部表的信息,最后分析了hello的执行流程并对动态链接进行了分析,加深了对于重定位和动态链接的理解。
第6章 hello进程管理
6.1 进程的概念与作用
6.1.1概念:
进程是一个正在运行的程序的实例,系统中的每一个程序都运行在某个进程的上下文中。
6.1.2作用:
进程为应用程序提供两个关键抽象(1)一个独立的逻辑控制流,提供一个假象,好像程序独立地使用处理器(2)一个私有地址空间,提供一个假象,好像程序独占地使用内存
6.2 简述壳Shell-bash的作用与处理流程
6.2.1作用:
shell是一个用C语言编写的交互型应用程序,代表用户运行其他程序。shell应用程序提供了一个界面,用户可以通过这个界面进行系统的基本操作,访问操 作系统内核的服务。shell执行一系列的读/求值步骤,然后终止。读步骤读取来自用户的一个命令行,求值步骤解析命令行,并根据解析结果运行程序。
6.2.2处理流程:
(1)shell从终端读入命令(2)shell将命令行拆分获得命令行参数,并且扫描识别所有参数(3)如果是一个内置命令,就执行(4)如果不是一个内置命令,那说明要执行一个程序,调用fork创建一个子进程(5)通过命令结尾处是否有&决定是否切入后台执行(6)执行任务
6.3 Hello的fork进程创建过程
父进程通过调用fork函数创建一个新的运行的子进程。调用fork函数后,新 创建的子进程几乎但不完全与父进程相同:子进程得到与父进程虚拟地址空间相同 的(但是独立的)一份副本,包括代码、数据段、堆、共享库以及用户栈,子进程获 得与父进程任何打开文件描述符相同的副本,这意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。fork被调用一次,却返回两次,子进程返 回0, 父进程返回子进程的PID。父进程和新创建的子进程之间最大的区别在于它 们有不同的 PID。
在本程序里,我们输入对应的指令调用hello,shell为为hello调用fork创建一个子进程。当子进程结束时,如果父进程尚未结束,就由父进程负责回收,否则由init回收。
图6-1 fork()创建进程
6.4 Hello的execve过程
exceve 函数在当前进程的上下文中加载并运行一个新程序。exceve 函数加载 并运行可执行目标文件,并带参数列表和环境变量列表。只有当出现错误时,exceve 才会返回到调用程序。所以,与 fork 一次调用返回两次不同,在 exceve 调用一次 并从不返回。当加载可执行目标文件后,exceve 调用启动代码,启动代码设置栈, 将可执行目标文件中的代码和数据从磁盘复制到内存中,然后通过跳转到程序的 第一条指令或入口点来运行该程序,由此将控制传递给新程序的主函数。
execve在加载了hello之后,会构造argc,argv和envp,并将控制传递给main函数。
6.5 Hello的进程执行
上下文:
内核重新启动一个被抢占的进程所需要恢复的原来的状态,由寄存器、程序计数器、用户栈、内核栈和内核数据结构等对象的值构成。
进程时间片:
一个进程执行它的控制流的一部分的每一个时间段叫做时间片。
进程调度:
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这种决策称为调度,是由内核中的调度器代码处理的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。
用户模式与核心模式的转换:
为了保证系统安全,需要限制应用程序所能访问的地址空间范围。因而存在用户态与核心态的划分,核心态拥有最高的访问权限,而用户态的访问权限会受到一些限制。处理器使用一个寄存器作为模式位来描述当前进程的特权。进程只 有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,一定程度上保证了系统的安全性。
图6-2 用户模式与核心模式的转换
6.6 hello的异常与信号处理
正常执行结果:
图6-3 正常执行
异常类型如下:
图6-4 异常类型
处理方法如下:
图6-5 异常的处理方法
异常:
(1)不停乱按:乱码会被认为是命令,不影响执行,但是会提示未找到命令
图6-6 不停乱按
(2)ctrl + z
图6-7 ctrl + z
通过使用ctrl + z命令,将进程挂起
(3)ps
图6-8 ps命令
在进程挂起后使用ps指令,打印pid
(3)jobs
图6-9 jobs命令
使用jobs命令,打印了被挂起的进程的jid
(4)pstree
图6-10 pstree命令
挂起后使用pstree命令
(5)fg
图6-11 fg命令
运行fg命令会把之前挂起在后台的hello重新调到前台并且执行,打印出剩余部分。
(6)kill
图6-12 kill命令
重新运行程序,我们可以找到pid为5558,通过使用kill -9 5558命令发送SIGKILL信号,可以杀死进程。
(7)ctrl + c
图6-13 ctrl + c命令
在程序运行时输入ctrl + c,向进程发送SIGINT信号,结束hello进程。这时再使用ps和jobs均查询不到hello。
6.7本章小结
本章主要介绍了 hello 可执行文件的执行过程,包括进程创建、加载和终止, 以及通过键盘输入等过程。之后介绍了异常以及异常的处理方法。 最后通过对hello进行操作直观地体现了进程的控制。在hello程序运行的过程中,内核对其进行进程管理,决定何时进行进程调度,在接收到不同信号时,还要进行对应的处理。
结论
hello所经历的历程:hello从一个c源文件,到成为一个可执行文件,最终成为进程,被执行并且回收,它的经历如下:
(1)预处理:hello.c源代码文件经过预处理器cpp 的处理,插入了头文件,结构得到调整,得到处理后的文本文件helli.i
(2)编译:hello.i经过编译器的编译,得到了汇编语言文件hello.s
(3)汇编:hello.s经过汇编器的汇编,得到了可重定向文件hello.o
(4)链接:hello.o经过链接器的链接,整合生成了可执行目标文件hello
(5)加载:在shell中输入命令,使用shell调用fork函数,为shell生成进程
(6)运行:使用execve加载并运行hello,把代码和数据加载进入虚拟内存空间
(7)执行:在该进程被调度时,CPU为 hello其分配时间片,在一个时间片中,hello进程独占CPU全部资源,CPU不断地取指,顺序执行逻辑流。hello在这一过程中会处理各种异常,除此之外,还有访问内存等等行为。进程还调用了printf和getchar等函数
程序人生-Hello’s P2P
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 120L021927
班 级 2003006
学 生 李欣桐
指 导 教 师 吴锐
计算机科学与技术学院
2021年5月
本文对hello.c程序编写成功后在Linux下如何顺利运行做出了详细的分析。hello.c源程序经过预处理、编译、汇编、链接、进程管理、存储管理、IO管理一系列操作完成了自己的生命周期。而我们可以通过一些工具清晰地观察它的一生,如gdb,edb等。
关键词:预处理;编译;汇编;链接;进程;存储;I/O;Linux
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:在编辑器中编写好程序创建hello.c源文件,经过cpp预处理生成hello.i文件,编译器ccl编译生成汇编文件hello.s,然后通过汇编器as生成二进制可重定位文件hello.o,最后链接器ld将hello.o与引用到的库函数组合起来生成二进制可执行文件hello。在shell中输入命令,shell通过fork函数创建了一个子进程,再调用execve映射虚拟内存,把程序加载到进程中,开始运行。程序运行结束时,父进程回收hello进程和它创建的子进程,操作系统内核删除相关数据结构。
020:shell为hello可执行程序进行执行,execve映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入main函数执行目标代码,CPU为运行的hello分配时间片执行逻辑控制流。程序运行结束后,shell回收进程,释放虚拟地址空间,删除有关进程上下文。
1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境:Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/ 优麒麟 64位以上
开发与调试工具:vi/vim/gedit+gcc;gdb;objdump;edb;readelf
1.3 中间结果
文件名 | 文件作用 |
hello.c | 源程序 |
hello.i | 预处理后的文件 |
hello.s | 编译后的汇编文件 |
hello.o | 汇编之后的可重定位目标文件 |
hello | 链接后的可执行目标文件 |
hello.elf | hello.o的ELF格式 |
hello1.elf | hello的ELF格式 |
hello1.txt | hello.o的反汇编 |
hello2.txt | hello的反汇编 |
1.4 本章小结
本章概括地介绍了hello的一生,介绍了P2P、020的过程,同时介绍了实验进行时使用的软硬件环境,以及开发与调试工具。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。
作用:将源文件中以”include”格式包含的文件复制到编译的源文件中;用实际值替换用“#define”定义的字符串;根据“#if”后面的条件决定需要编译的代码,得到另一个C程序.i。
2.2在Ubuntu下预处理的命令
命令:gcc -E hello.c -o hello.i
图2.2.1 Ubuntu预处理的命令
图2.2.2 预处理生成hello.i文件
2.3 Hello的预处理结果解析
图2.3.1 hello.i文件部分截图
图2.3.2 hello.i文件中main函数部分
打开预处理后生成的hello.i文件,我们可以观察到该文件共有3060行代码,较源程序内容大大增多,而源程序中的main函数出现在文件的最后部分,没有发生变化。这是因为在预处理的过程中预处理器对源程序的头文件#include <stdio.h>、#include <unistd.h> 、#include <stdlib.h>进行了展开,文件加入了许多宏定义,并且将其放在了文件前面。
2.4 本章小结
本章介绍了预处理的概念和作用,进行了在Ubuntu中预处理的操作,并浏览分析了生成的hello.i文件,通过代码分析了预处理的工作机制。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编辑器(ccl)将文本文件.i翻译成文本文件.s,它包含一个汇编语言程序。
作用:把源程序翻译成目标汇编程序,进行词法分析和语法分析,分析过程中发现有语法错误,给出提示信息(无法进行后续动作),是高级语言向机器代码转换中重要的中间过程,生成.s文件。
3.2 在Ubuntu下编译的命令
命令:gcc -S hello.i -o hello.s
图3.2.1 Ubuntu下编译的命令
图3.2.2 编译生成的hello.s文件
3.3 Hello的编译结果解析
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.3.1 文件
图3.3.1.1 hello.s文件部分代码
.file 声明源文件
.text 代码段
.section .rodata 只读数据段
.align 声明指令或者数据的存放地址的对齐方式
.string 字符串
.global 全局变量
.type 声明一个标识符是函数类型还是数据类型
3.3.2 数据
一、整型常量
图3.3.2.1 常量的保存
if(argc!=4)语句中,整型常量4的值被保存在.text中,作为指令的一部分。同理,for(i=0;i<8;i++)中的整型常量0、8也被保存在.text中,如下图所示。
图3.3.2.2 常量的保存
二、字符串
图3.3.2.3程序中字符串
printf()、scanf()中的字符串被存储在.rodata节中,如上图所示。hello.c中唯一的数组是main函数的第二个参数,数组中每个元素都是指向字符类型的指针。数组两次被调用将参数传递给printf函数。
图3.3.2.4 两字符串作为printf函数的参数
三、局部变量
局部变量存储在寄存器或栈中。
该程序中,局部变量i储存在-4(%rbp)中。
图3.3.2.5 局部变量
3.3.3 赋值
赋值操作利用mov语句。如果是局部变量不赋初值,则在汇编代码中没有体现,只在使用并赋值时才用寄存器等来存储;如果是全局或静态变量变量不赋初值,存放在.bss段;如果是已初始化的全局变量,存放在.data段。
mov指令根据操作数的字节大小分为:
movb:一个字节
movw:字
movl:双字
movq:四字
例如i = 0赋值,如下图所示。
图3.3.3.1 赋值操作
3.3.4 算术操作
在循环中,程序使用了++操作符:for(i=0;i<8;i++),对应的汇编代码如下图所示。
图3.3.4.1 算数操作
3.3.5 关系操作
- 程序中argc!=4是条件判断语句,汇编代码为cmpl $4,-20(%rbp),比较后设置条件码,根据条件码判断是否跳转。
- 程序中i < 8是循环判断条件,汇编代码为cmpl $7, -4(%rbp),比较后设置条件码,根据条件码判断是否跳转。
详情如下图所示。
图3.3.5.1 argc!=4
图3.3.5.2 i < 8
3.3.6 数组/指针/结构操作
主函数main的参数中含有指针数组char *argv[],int main(int argc,char *argv[])。该数组中,argv[0]指向输入程序的路径和名称,argv[1]和argv[2]分别指向printf()、scanf()中的字符串。
数组地址存储在栈中,可以利用栈指针的偏移量来读取。
图3.3.6.1 栈指针偏移
3.3.7 控制转移
汇编语言中先设置条件码,然后根据条件码来进行控制转移。
- if(argc!=4),判断argc是否等于4,如果等于4,则不执行if语句,等于4则执行if语句,对应的汇编代码如下:
图3.3.7.1 if(argc!=4)
2.for(i=0;i<8;i++),每次判断i是否小于8来判断是否退出循环,对应的汇编代码如下:
图3.3.7.2 for(i=0;i<8;i++)
图3.3.7.3 for(i=0;i<8;i++)
3.3.8 函数操作
调用函数时,有以下几个过程。首先要将程序计数器(%rip)设为被调用函数代码的起始地址,在返回时需要将%rip设为调用函数中调用被调用函数后面那条指令的地址。之后要进行数据传递,调用函数向被调用函数提供参数,被调用函数返回相应的返回值给调用函数,同时还要分配和释放相应内存。
该程序中的函数操作:
函数 | 参数 | 返回值 |
main() | int argc,char *argv[] | 0 |
printf() | argv[1],argv[2] | / |
exit() | 1 | / |
sleep() | atoi(argv[3]) | / |
getchar() | 无 | / |
atoi() | argv[3] | int值 |
3.4 本章小结
本章介绍了编译的概念和作用,进行了在Ubuntu中编译的操作,并细致地从几个方面分析了汇编代码以及其与源程序代码之间的联系,借此了解了编译器编译的机制。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:汇编器(as)将.s汇编程序翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件.o中。
作用:将汇编代码转换为机器指令,并将其打包生成.o文件,为后续的程序的链接和运行做准备。
4.2 在Ubuntu下汇编的命令
命令:gcc -c -o hello.o hello.s
图4.2.1 Ubuntu下汇编的命令
图4.2.2 汇编生成的hello.o文件
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
Linux下生成hello.o文件的elf格式命令:readelf -a hello.o > hello.elf
- ELF头
图4.3.1 ELF头
ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目。
2.节头
图4.3.2 节头
描述.o文件中出现的各节名称、类型、地址、偏移量、大小等信息。
3.重定位节
当汇编器生成一个目标模块时,它并不知道数据和代码在内存中具体位置,也不知道该模块引用的外部函数和全局变量的位置。所以当汇编器遇到最终位置未知的目标引用时,就会生成一个重定位条目,告诉链接器在合并目标文件时如何修改引用。代码的重定位条目放在.rel.text中。已初始化数据的重定位条目放在.rel.data中。
图4.3.3 重定位节
.rela.text中保存的是.text节中需要通过重定位被修改的信息:任何调用外部函数或者引用全局变量的指令,调用外部函数的指令,引用全局变量的指令。不需要重定位的信息:调用局部函数的指令,在可执行目标文件中不存在。hello.o中的.rodata中的模式串、puts、exit、printf、slepsecs、sleep,getchar等符号。
.rela.eh_frame节是.eh_frame节重定位信息。
4.符号表
图4.3.4 符号表
.symtab存放在程序中定义和引用的函数和全局变量的信息。
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
命令:objdump -d -r hello.o > hello1.txt
图4.4.1 反汇编命令
图4.4.2 hello.o的反汇编
与hello.s进行对比分析:
(1)数值表示 :hello.s中的操作数是以十进制表示的,而hello.o的反汇编代码中的操作数是以十六进制表示的,因为16进制与2进制之间的转换比十进制更加方便。
图4.4.3 hello.s数值表示
图4.4.4 hello1.txt数值表示
(2)控制转移:hello.s中使用.L2等段名称进行跳转,而hello.o的反汇编代码使用目标代码的虚拟地址,即主函数+段内偏移量。
以上是关于程序人生-Hello’s P2P的主要内容,如果未能解决你的问题,请参考以下文章