哈工大2022计算机系统大作业---程序人生
Posted stlavender
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈工大2022计算机系统大作业---程序人生相关的知识,希望对你有一定的参考价值。
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机类
学 号 120L021923
班 级 2003006
学 生 甄自镜
指 导 教 师 吴锐
计算机科学与技术学院
2021年5月
本文对hello.c在linux下的运行的整个生命周期进行分析,从编写完成开始到程序回收结束,介绍了hello从预处理、编译、汇编、链接直到被回收的整个过程及原理,同时介绍了与运行相关的进程管理、存储管理、I/O管理的内容,描绘了hello波澜壮阔的一生
关键词:预处理;编译;汇编;链接;进程管理;存储管理;linux;I/O
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 5 -
3.2 在Ubuntu下编译的命令............................................................................. - 6 -
4.2 在Ubuntu下汇编的命令............................................................................. - 7 -
5.2 在Ubuntu下链接的命令............................................................................. - 8 -
5.3 可执行目标文件hello的格式.................................................................... - 8 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 10 -
6.3 Hello的fork进程创建过程..................................................................... - 10 -
6.6 hello的异常与信号处理............................................................................ - 10 -
7.1 hello的存储器地址空间............................................................................ - 11 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................ - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理....................................... - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换............................................. - 11 -
7.5 三级Cache支持下的物理内存访问.......................................................... - 11 -
7.6 hello进程fork时的内存映射.................................................................. - 11 -
7.7 hello进程execve时的内存映射.............................................................. - 11 -
7.8 缺页故障与缺页中断处理........................................................................... - 11 -
8.1 Linux的IO设备管理方法.......................................................................... - 13 -
8.2 简述Unix IO接口及其函数....................................................................... - 13 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
1.建立hello.c文件
2. 预处理器将其进行预处理生成hello.i文件。
3. 编译器编译得到hello.s文件
4. 汇编器汇编得到hello.o
5. 链接器与库函数链接生成一个可执行程序
6.通过shell输入./shell,shell通过fork函数创建了一个新的进程
7. 调用execve映射虚拟内存,通过mmap为hello程序开创了一片空间。
8. CPU从虚拟内存中的.text,.data节取代码和数据,调度器为进程规划时间片,有异常时触发异常处理子程序。
9. .程序运行结束,父进程回收hello进程和它创建的子进程,内核删除相关数据结构。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
1.2.1 硬件环境
X64 CPU;2GHz;2G RAM;256GHD Disk 以上
1.2.2 软件环境
Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上;
1.2.3 开发工具
Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
文件名 | 功能 |
hello.c | 源程序 |
hello.i | 预处理后的文件 |
hello.s | 汇编文件 |
hello.o | 可重定位目标执行文件 |
hello | 可执行文件 |
hello.elf | hello.o的ELF格式 |
hello.txt | hello.o的反汇编语言 |
hello1.txt | hello的反汇编语言 |
hello1.elf | hello的ELF格式 |
1.4 本章小结
本章对hello的一生进行了简要的介绍和描述,介绍了P2P,O2O的整个过程,介绍了计算机硬件环境、软件环境、开发与调试工具,介绍了中间文件的名称即其作用。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。
作用:预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或者编译。
2.2在Ubuntu下预处理的命令
图2.2.1
在linux 系统中用gcc -E -o hello.i hello.c进行预处理的命令
2.3 Hello的预处理结果解析
图2.3.1 Hello.i文件截图
分析:hello.c预处理后变为hello.i文件,文件内容如上图所示,文件扩展为三千多行,将原程序中的宏进行了宏展开,对函数的声明,变量定义等信息放在中间,程序的源代码被放到了最后。
2.4 本章小结
本章介绍了预处理的概念与作用,学习了用gcc对hello.c文件进行预处理,将其重定向到hello.i中,并且分析了hello.i文件中的内容
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
作用:
1.语法分析:编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位。
2.中间代码:源程序的一种内部表示,或称中间语言。中间代码的作用是可使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现中间代码。
3.代码优化:指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。
4.目标代码:生成是编译的最后一个阶段。目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。此处指汇编语言代码,须经过汇编程序汇编后,成为可执行的机器语言代码。
3.2 在Ubuntu下编译的命令
图3.2.1
在linux 系统中用gcc -S hello.i -o hello.s进行编译的命令
3.3 Hello的编译结果解析
3.3.1汇编第一部分:
图3.3.1
file:声明源文件
text:代码节
section:指示把代码划分成若干个段(Section)
rodata:只读代码段,后面是printf()与scanf()中存储的字符串。
align:数据或者指令的地址对其方式
string:声明一个字符串LC0,LC1
global:声明全局变量(main)
type:声明一个符号是数据类型还是函数类型
3.3.2数据
1.字符串:即只读代码段中的两个字符串LC0,LC1。二者作为printf(),scanf()的参数,如图:
图3.3.2
、
2.整数:
在程序中一共有两个int变量,分别为i与argc
i是一个局部变量,编译器进行编译的时候将局部变量i会放在堆栈中。如图所示,局部变量i放在栈上-4(%rbp)的位置。
Argc是传给main的参数,进入了堆栈
3.立即数,形式为$+常数
3.3.3赋值操作:
利用mov实现,类如:
PS:movb:一个字节
movw:“字”
movl:“双字”
movq:“四字”
3.3.4关系操作和控制转移
Jle:判断cmpl产生的条件码,小于等于7跳转L4
Je:判断cmpl产生的条件码,不等于4则跳转.L2、
3.3.5算术操作:
Add 即i++,图片对应解释为:对i自加,栈上存储变量i的值加1
3.3.6数组/指针/结构操作
主函数main的参数中有指针型数组char *argv[],对此数组的操作通常由mov指令实现。取到argv数组的首地址,然后对首地址加相应字节得到对应的地址,argv[0]指向输入程序的路径和名称,argv[1]和argv[2]分别表示两个字符串。char* 数据类型占8个字节
3.3.7函数操作:
Main函数;传入参数argc和argv[],分别用寄存器%rdi和%rsi存储。被系统启动函数调用。设置%eax为0并且返回,对应return 0 。
Printf:第一处调用由于只是输入一串字符串,所以被优化成puts函数,之后通过call来调用puts,而第二处调用printf,有三个参数,因此我们需要取出参数
Exit:传入参数1后调用函数退出。
Atoi:将字符串类型转换为整型。
Sleep:atoi被调用完后,会将其返回值作为sleep函数的参数,调用sleep。
Getchar:直接调用
3.4 本章小结
介绍了编译的概念以及过程。通过hello函数分析了c语言如何转换成为汇编代码。介绍了汇编代码如何实现变量、常量、传递参数以及分支和循环。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:汇编器将汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o 目标文件中。
作用:将汇编代码转换为机器指令,使其在链接后能被机器识别并执行。
4.2 在Ubuntu下汇编的命令
图4.2.1
在linux 系统中用gcc -c -o hello.o hello.s进行汇编的命令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
典型ELF可重定位目标文件:
图4.3.1
图4.3.2
在linux 系统中用readelf -a hello.o > hello.elf进行生成elf格式文件的命令
ELF文件格式分析:
1.ELF头:ELF头(ELF header)以一个16B的序列Magic开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。
Hello.elf文件中ELF头:
图4.3.3
2.节头目表:(节头):记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。 因为是可重定位目标文件,所以每个节都从0开始,用于重定位。
图4.3.4
3.重定位节:各个段引用的外部符号等在链接时需要通过重定位对这些位置的地址进行修改。链接器会通过重定位节的重定位条目计算出正确的地址。
需要重定位有:.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar等符号。
图4.3.5
4.符号表:存放在程序中定义和引用的函数和全局变量的信息。编译器中的符号表不同,.symtab符号表不包含局部变量的条目。
图4.3.6
4.4 Hello.o的结果解析
图4.4.1
利用objdump -d -r hello.o >hello.txt 进行反汇编。
机器语言的构成:机器语言是机器能直接识别的程序语言或指令代码,无需经过翻译,每一操作码在计算机内部都有相应的电路来完成它,或指不经翻译即可为机器直接理解和接受的程序语言或指令代码。机器语言使用绝对地址和绝对操作码。不同的计算机都有各自的机器语言,即指令系统。从使用的角度看,机器语言是最低级的语言。
与汇编语言的映射关系:机器语言是用于控制计算机中处理器的实际位,通常被视为十六进制数字序列(通常为字节).处理器从程序存储器中读取这些位,这些位表示下一步操作的"指令".因此,机器语言提供了一种将指令输入计算机的方式
与hello.s的不同:
1.控制转移:
Hello.s
Hello.txt
Hello.s中使用L2这样的段名称进行跳转,而在反汇编中,跳转到函数+偏移量这样的形式。因为段名称在汇编语言中为便于编写的助记符,所以在汇编成机器语言之后就不存在了,而是确定的地址。
2.函数调用
汇编代码中函数调用时直接使用函数名称,而在反汇编的文件中call之后定位到call的下一条指令,即用具体的地址表示。例如:
3.数的表示
hello.s中的操作数表现为十进制,而hello.o反汇编代码中的操作数为十六进制。
4.5 本章小结
本章介绍了汇编。经过汇编器,将汇编语言转化为机器语言,hello.s文件转化为hello.o可重定位目标文件。分析了ELF的文件格式,了解了ELF头等相关概念,分析了汇编语言与机器语言的不同。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
(以下格式自行编排,编辑时删除)
注意:这儿的链接是指从 hello.o 到hello生成过程。
概念:链接是将各种不同文件(主要是可重定位目标文件)的代码和数据综合在一起,通过符号解析和重定位等过程,最终组合成一个可以在程序中加载和运行的单一的可执行目标文件的过程。
作用:链接令分离编译成为可能,方便了程序的修改和编译:无需重新编译整个工程,而是仅编译修改的文件。
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.2.1
5.3 可执行目标文件hello的格式
利用readelf -a hello > hello1.elf看ELF Header的具体信息
图5.3.1
1.ELF头
图5.3.2
2.节头
图5.3.3
3.重定位节
图5.3.4
4.符号表
图5.3.5
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
从data dump中发现虚拟地址从0x401000开始到0x401ff0结束,根据5.3节头部表,可以找到各个节的信息
图5.4.1
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
通过objdump -d -r hello >hello1.txt得到hello的反汇编文件hello1.txt
二者的不同:
1.hello1.txt中多了许多节
比如下图中的.int .plt等
图5.5.1
2.文件内容有所不同
Hello已经完成了重定位,因此在调用函数时使用的地址已经是函数的虚拟地址,同时在跳转时也是使用虚拟地址。
图5.5.2
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
过程:
0x4010f0<_start>
0x4011f1 <in __libc_csu_init>
0x401000<_init>
0x401125 <main>
0x4010a0 printf@pl
0x4010c0 atoi@plt
0x4010e0 sleep@plt
0x4010b0 getchar@plt
0x7ffff7e06a70 <exit>
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
延迟绑定是通过GOT和PLT实现的
由ELF文件可知,got起始表位置为0x403ff0
图5.7.1
调用dl_init前0x403ff0为空
图5.7.2
调用后:
图5.7.3
我们可以发现,0x403ff0出现了变化,存入地址。
5.8 本章小结
本章研究了链接的过程。通过edb查看hello的虚拟地址空间,对比hello与hello.o的反汇编代码,深入研究了链接的过程中重定位的过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:一个执行程序中的实例,系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
作用:进程提供给应用程序的关键抽象:一个独立的逻辑控制流,如同程序独占处理器;一个私有的地址空间,如同程序独占内存系统。它提供了一个假象,好像我们的程序独占地使用内存系统,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。多进程可以完成多任务,每个进程就好比一家独立的公司,每个公司都各自在运营,每个进程也各自在运行,执行各自的任务。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell是一个交互型应用程序,把用户输入的命令翻译给操作系统。提供了一个界面,用户可以通过这界面访问操作系统内核。
处理流程:shell首先检查命令是否是内部命令,若不是再检查是否是一个应用程序(这里的应用程序可以是Linux本身的实用程序,如ls和rm,也可以是购买的商业程序,如xv,或者是自由软件,如emacs)。然后shell在搜索路径里寻找这些应用程序(搜索路径就是一个能找到可执行程序的目录列表)。如果键入的命令不是一个内部命令并且在路径里没有找到这个可执行文件,将会显示一条错误信息。如果能够成功找到命令,该内部命令或应用程序将被分解为系统调用并传给Linux内核。
6.3 Hello的fork进程创建过程
在我们输入./hello 120L021923 甄自镜 1后,shell将会对我们的命令进行解析,由于我们输入的不是一个内置命令,shell会调用fork(),创建一个子进程,子进程得到与父进程用户级虚拟地址空间相同的但是独立的一份副本,然后开始执行程序。
6.4 Hello的execve过程
子进程创建完毕后,会去调用execve()来加载可执行文件到当前进程。然后删除已存在的用户区域。删除之前进程在用户部分中已存在的结构。创建新的区域结构:通过虚拟内存机制将可执行文件hello中的各个段映射到对应的代码段、数据段等地址空间。然后通过跳转到hello程序的第一条指令或入口点来运行该程序,由此将控制传递给新程序的主函数。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
进程上下文信息:当进程调度一个新的进程运行后,会使用上下文切换来将控制转移到新的进程。上下文切换会:1.保存当前进程的上下文。2.恢复某个先前被抢占进程的被保存的上下文。3.将控制传递给新进程。系统调用、中断可能引起上下文切换。
进程时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
进程调度的过程:在对进程进行调度的过程,操作系统主要做两件事:加载保存的寄存器,切换虚拟地址空间。
用户态与核心态转换:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中, 用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。
具体过程如下:输入命令./hello 120L021923 甄自镜 ,然后调用sleep()函数进入内核模式,进行信号处理,然后返回用户模式,运行过程中,cpu不断切换上下文,使运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度。
图6.5.1
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
1.异常
异常和信号异常可以分为四类:中断、陷阱、故障、终止
中断:处理器外部I/O设备引起,异步异常,例如:时钟中断,键盘上敲击Ctrl-C.
陷阱:有意的异常,执行指令产生的结果,发生时间可预知,同步异常,例如:系统调用。
故障:不是有意的,但可能被修复,同步异常,例如:缺页故障,保护故障。
终止:非故意,不可恢复非致命错误造成,例如:非法指令,奇偶校验错误。
2.各种键盘操作导致的异常:
正常:
图6.6.1
回车
图6.6.2
Ctrl Z 默认结果是挂起前台的作业,hello进程并没有回收,而是运行在后台下
图6.6.3
Ps
图6.6.4
Jobs
图6.6.5
Pstree
图6.6.6
Fg
图6.6.7
Kill 挂起的进程被终止,在ps中无法查到到其PID。
图6.6.8
Ctrl C
图6.6.9
PS:运行了两个hello,因此显示有两个
6.7本章小结
本章介绍了进程的概念和作用,以及壳Shell-bash的作用与处理流程,调用 fork 创建新进程,调用 execve函数执行<哈工大2022年春季学期计算机系统大作业——程序人生
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 人工智能(未来技术)
学 号 7203610716
班 级 20WJ102
学 生 孙铭蔚
指 导 教 师 刘宏伟
计算机科学与技术学院
2021年5月
本文以一个简单的程序hello.c为例,分析了程序执行的过程,包括预处理、编译、汇编、链接、进程管理、存储管理等过程,对《深入理解计算机系统》(第三版)中的相关知识进行简要概括。
关键词:预处理、编译 、汇编、链接、进程、存储
目 录
6.2 简述壳Shell-bash的作用与处理流程 - 10 -
7.2 Intel逻辑地址到线性地址的变换-段式管理 - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理 - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 11 -
7.7 hello进程execve时的内存映射 - 11 -
第1章 概述
1.1 Hello简介
hello程序起源于一个能被人类读懂的高级C语言程序。当我们刚学习一门编程语言后,作为程序员的共识,我们总是尝试着在IDE中打印出”hello!”,然而,当我们没有学习计算机底层时,会对这产生很浓厚的好奇心。事实上,我们编写的每一条C语言程序都必须先被转化为一系列的机器语言。下面,我们一起看看编写的hello程序是如何被打印出来的。
Hello的P2P过程:在我们的电脑中,源文件到目标文件的转化分为四个步骤:预处理、编译、汇编和链接。其中分别需要编译器驱动程序调用语言预处理器、编译器、汇编器和链接器。当我们在IDE中编写完hello程序,就得到了源文件。hello.c文件经过预处理器(cpp)得到修改了的源程序,即一个ASCII码的中间文件(hello.i)。之后,通过编译器(cc1)将这个中间文件翻译成汇编程序(hello.s)。然后汇编器(as)将汇编程序翻译成可重定位目标的程序(二进制)叫做hello.o。最后由链接器(ld)创建一个可执行目标二进制程序(hello),这个文件可以被加载到内存中。此后,在shell中输入./hello即可命令系统调用fork函数以及execve等使程序变成进程进而运行。这里的p2p是指程序向进程转化的过程。
Hello的020过程:这个过程对应的内存相关操作,首先加载把hello代码以及数据载入内存,shell为子进程映射虚拟内存,在软硬件结合的情况下,运行直至代码运行结束。在程序运行结束或,内核回收hello进程,删除内存中的相关代码以及数据,一切“归零”。
1.2 环境与工具
硬件环境:硬件1概览:
型号名称: MacBook Air
型号标识符: MacBookAir10,1
芯片: Apple M1
核总数: 8(4性能和4能效)
内存: 8 GB
系统固件版本: 7429.81.3
操作系统加载程序版本: 7429.81.3
序列号(系统): C02H479LQ6L5
硬件UUID: B9297F28-81B7-5DB0-8760-89063F63E0E5
预置UDID: 00008103-001205960108801E
激活锁状态: 已启用
硬件2概览:
64 位操作系统, 基于 x64 的处理器
AMD Ryzen 7 4800H with Radeon Graphics 2.90 GHz
LAPTOP-CSSCDDGT
软件环境:Windows 10 64位;Virtualbox; Ubuntu 20.04
开发工具:gcc; gdb; objdump
1.3 中间结果
hello.i: ASCII码的中间文件(预处理器产生),用于分析预处理过程。
hello.s: ASCII汇编语言文件(预处理器产生),用于分析编译的过程。
hello.o:可重定位目标程序(汇编器产生),用于分析汇编的过程。
hello:可执行目标文件(链接器产生),用于分析链接的过程。
hello.txt:hello.o的反汇编文件,用于分析可重定位目标文件hello.o。
hellold.txt:hello的反汇编文件,用于分析可执行目标文件hello。
helloelf.txt:hello.o的ELF格式,用于分析可重定位目标文件hello.o。
helloldelf.txt:hello的ELF格式,用于分析可执行目标文件hello。
1.4 本章小结
本章介绍了Hello的P2P、020的整个过程并介绍了实验的环境、工具以及实验的中间结果。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预编译又称为预处理,作用是代码文本替换。预处理指令是以“#”开头的代码为编译做准备工作。C语言在编译之前会进行一步处理——预处理,例如#include<stdio.h>命令告诉处理器读取系统头文件stdio.h的内容,并把它直接插入到程序文本中。进而得到另外一个C语言程序,同城以.i作为拓展名。预处理包括宏定义、文件包含、条件编译等。大多数预处理器指令属于下面3种类型:
- 宏定义:#define 指令定义一个宏,#undef指令删除一个宏定义。
- 文件包含:#include指令导致一个指定文件的内容被包含到程序中。
- 条件编译:#if,#ifdef,#ifndef,#elif,#else和#dendif指令可以根据编译器可以测试的条件来将一段文本包含到程序中或排除在程序之外。
作用如下:
- 将源文件中以”include”格式包含的文件复制到编译的源文件中。
- 用实际值替换用“#define”定义的字符串。
- 根据“#if”后面的条件决定需要编译的代码。
2.2在Ubuntu下预处理的命令
首先,在乌班图终端输入命令gcc -E hello.c -o hello.i
预处理过程如下:
图2-2-1预处理命令
图2-2-2预处理结果
2.3 Hello的预处理结果解析
经过我们查看预处理生成的hello.i文件,可以发现,主函数中定义的变量没有发生变化,注意到以#include开头的代码发生了很大改变,被替换成头文件中的内容,同时,源文件中的注释也被删除了。说明预处理对我们的源程序进行了文本性质的处理,进而生成hello.i文件。
2.4 本章小结
本小结介绍了预处理的概念给出了相关实例,分析了程序运行过程,给出了详细说明,例如预处理指令、宏定义命令、文件包含以及条件编译等。1、
大致总结为:预处理阶段删除所有的#define,展开所有宏定义;处理条件预编译指令#if、#ifdef、#else;处理#include指令,引入递归头文件;删除注释,添加行号和文件名标识用于编译时产生调试信息;保留#pragma 编译器指令。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译就是把代码转化为汇编指令的过程,汇编指令只是CPU相关的。也就是由hello.i转化为hello.s的过程。编译会对于处理文件进行词法分析(运用一种有限状态机的算法将源代码的字符序列分割成一系列的记号,将记号分成关键字、标识符、字面量、运算符)、语法分析(对语法分析中产生内容进行语法分析,从而产生语法树,以表达式为节点的数)、语义分析(只分析静态语义,如类型之间的赋值转换,而动态语义是运行时的出现的问题,如0做除数)、源代码优化、代码生成、目标代码优化(产生优化后的汇编代码)。
作用如下:
便于计算机“理解”程序,因为高级语言是便于人理解的语言,而机器只能(或者说更容易)理解汇编语言,通常汇编程序比C语言效率更高,而且它们采用兼容的指令体系,今儿提高兼容性。
3.2 在Ubuntu下编译的命令
编译命令为gcc -S hello.i -o hello.s
编译过程如下:
图3-2-1编译命令
hello.s文件内容如下:
.file "hello.c"
.text
.section .rodata
.align 8
.LC0:
.string "\\347\\224\\250\\346\\263\\225: Hello \\345\\255\\246\\345\\217\\267 \\345\\247\\223\\345\\220\\215 \\347\\247\\222\\346\\225\\260\\357\\274\\201"
.LC1:
.string "Hello %s %s\\n"
.text
.globl main
.type main, @function
main:
.LFB6:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
cmpl $4, -20(%rbp)
je .L2
leaq .LC0(%rip), %rdi
call puts@PLT
movl $1, %edi
call exit@PLT
.L2:
movl $0, -4(%rbp)
jmp .L3
.L4:
movq -32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
leaq .LC1(%rip), %rdi
movl $0, %eax
call printf@PLT
movq -32(%rbp), %rax
addq $24, %rax
movq (%rax), %rax
movq %rax, %rdi
call atoi@PLT
movl %eax, %edi
call sleep@PLT
addl $1, -4(%rbp)
.L3:
cmpl $7, -4(%rbp)
jle .L4
call getchar@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE6:
.size main, .-main
.ident "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
3.3 Hello的编译结果解析
3.1.1数据
1)常量
hello.c文件中的常量为printf函数的参数(字符串),“用法: Hello 学号 姓名 秒数!\\n”和“Hello %s %s\\n”。
图 3-3-1hello.c源程序
.LC0:
.string "\\347\\224\\250\\346\\263\\225: Hello \\345\\255\\246\\345\\217\\267 \\345\\247\\223\\345\\220\\215 \\347\\247\\222\\346\\225\\260\\357\\274\\201"
.LC1:
.string "Hello %s %s\\n"
对比代码可知,两个printf中的字符串是常量,这两个字符串常量被.LC0和.LC1标识。
2)局部变量
这个源程序中只有一个局部变量,即循环变量int i = 0
在hello.s中,这个局部变量存储在栈上相关代码为:
.L2:
movl $0, -4(%rbp)
jmp .L3
3)函数参数
函数参数有argc和argv,它们被保存在栈上,通过偏移地址进行访问,相关代码如下:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
3.3.2 操作
1)赋值操作
源程序在给局部变量i赋值时,由于i的数据类型是int占4个字节,只用movl指令,将栈指针移动4,下面操作左侧为立即数0.
movl $0, -4(%rbp)
jmp .L3
在赋值操作中,mov后面的字母代表数据类型不同大小。例如,b,w,l,q分别代表1,2,4,8个字节。
2)算数操作
在for循环中的++操作,每次云环都会给局部变量i自增1,相应的汇编代码为:
addl $1, -4(%rbp)
此外,还有分配栈帧时的加减操作,例如:
subq $32, %rsp
还有改变数组的偏移操作,例如:
addq $16, %rax
3)关系操作和控制转移操作
if循环中的关系操作和控制转移,例如:
cmpl $4, -20(%rbp)
je .L2
对应与源程序的代码为:
if(argc!=4)
printf("用法: Hello 学号 姓名 秒数!\\n");
exit(1);
这段汇编代码会检查argc是否为4,进而设置条件码,如果相等则会跳转到.L2位置。
for循环中的关系操作和控制转移,例如:
cmpl $7, -4(%rbp)
jle .L4
这两句汇编代码对应于源程序:
for(i=0;i<8;i++)
printf("Hello %s %s\\n",argv[1],argv[2]);
sleep(atoi(argv[3]));
每次循环结束判断循环条件,进而决定是否跳出循环。
4)数组、指针、结构操作
数组操作:
在主函数中有指针数组char* argv[]
在argv数组中,argv[0],argv[1],argv[2],argv[3]分别对应指向输入程序的路径和名称、学号、姓名、休眠秒数。
movq -32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
以及:
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
leaq .LC1(%rip), %rdi
从上述汇编代码可以看出,访问函数参数argv[0],argv[1],argv[2],argv[3]时候的寻址方法是基址-变址的方法,使用寄存器指向数组的首地址,再通过便宜的地址寻找所需变量.
movq %rsi, -32(%rbp)
从以上代码可知,rsi存储着argc的首地址。
5)函数操作
通过以下代码以及源程序可知,主函数调用了exit,puts,getchar,print,atoi,sleep函数。
leaq .LC1(%rip), %rdi
movl $0, %eax
call printf@PLT
movq -32(%rbp), %rax
addq $24, %rax
movq (%rax), %rax
movq %rax, %rdi
call atoi@PLT
movl %eax, %edi
call sleep@PLT
addl $1, -4(%rbp)
现在逐个函数进行分析:
main函数:传入参数argc和argv[],分别用寄存器%rdi和%rsi存储,并且返回使用movl $0, %eax,由所学知识可知,rax寄存器存储的是返回值,代表返回0.
printf函数:利用leaq .LC0(%rip)将第一个参数地址传递给%rip,其他参数传递方法类似。使用call puts@PLT调用printf函数。
exit函数:使用movl $1, %edi命令传给参数寄存器,当argc!=4时调用该函数,相关汇编指令为call exit@PLT。
sleep函数:相关汇编指令为 call atoi@PLT以及movl %eax, %edi,将atoi函数的返回结果作为参数传入sleep函数,也即是将%eax中的值复制给%edi。该函数通过汇编指令call sleep@PLT调用。
atoi函数:相关汇编指令为addq $24, %rax、movq (%rax), %rax以及movq %rax, %rdi将argv[3]的字符串地址传入%rdi.通过汇编指令call atoi@PLT调用。
getchar函数:通过汇编指令call getchar@PLT调用。并返回读取字符的ASCII.
3.4 本章小结
本章首先介绍了编译的过程,接下来给出在乌班图下编译的命令。结合hello程序实例分析说明了编译结果,依次介绍了常量、局部变量、函数参数、赋值操作、函数操作、关系操作和控制转移操作、数组、指针、结构操作函数操作,并逐一分析了用到的函数以及对应的汇编指令。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:概念:汇编器(as)将 hello.s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中。
作用:将汇编代码转变为机器指令,生成目标文件。
4.2 在Ubuntu下汇编的命令
命令:gcc hello.s -c -o hello.o
图4-2-1 汇编命令
4.3 可重定位目标elf格式
4.3.1 ELF头
用命令readelf -h hello.o查看
查看后结果如下:
图4-3-1 hello.o中的ELF内容
从上图可以得出以下结论:
hello.o是64位文件;数据的存储形式为补码、小端序存储;文件的类型为REL(可重定位文件),入口点地址为0x0,程序头起点为0,节头表的起始位置为1256;文件共有13节。
4.3.2 头节表
使用命令readelf -S hello.o查看
查看结果如下图所示:
图4-3-2hello.o头节表内容
头节表中包含了很多信息,例如节的类型为之和大小等。具体信息可见上图。
4.3.3 符号表
用命令readelf -s hello.o查看,查看结果如下图所示:
图4-3-3hello.o中符号表内容
可以看到,符号表中存储了很多变量名和函数名,具体内容见上图。
4.3.4 重定位节
用命令readelf -r hello.o查看,查看结果如下图所示:
图4-3-4hello.o中重定位节的内容
从上图可以看到,重定位节中包含了偏移量、信息、类型、符号值和符号名称等信息。本程序需要重定位的信息有:.rodata,puts,exit,printf,atoi,sleep,getchar这些符号同样需要与相应的地址进行重定位。
4.4 Hello.o的结果解析
使用命令objdump -d -r hello.o > a.s将hello.o存入a.s中,便于查看
结果如下所示:
hello.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 83 ec 20 sub $0x20,%rsp
c: 89 7d ec mov %edi,-0x14(%rbp)
f: 48 89 75 e0 mov %rsi,-0x20(%rbp)
13: 83 7d ec 04 cmpl $0x4,-0x14(%rbp)
17: 74 16 je 2f <main+0x2f>
19: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 20 <main+0x20>
1c: R_X86_64_PC32 .rodata-0x4
20: e8 00 00 00 00 callq 25 <main+0x25>
21: R_X86_64_PLT32 puts-0x4
25: bf 01 00 00 00 mov $0x1,%edi
2a: e8 00 00 00 00 callq 2f <main+0x2f>
2b: R_X86_64_PLT32 exit-0x4
2f: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
36: eb 48 jmp 80 <main+0x80>
38: 48 8b 45 e0 mov -0x20(%rbp),%rax
3c: 48 83 c0 10 add $0x10,%rax
40: 48 8b 10 mov (%rax),%rdx
43: 48 8b 45 e0 mov -0x20(%rbp),%rax
47: 48 83 c0 08 add $0x8,%rax
4b: 48 8b 00 mov (%rax),%rax
4e: 48 89 c6 mov %rax,%rsi
51: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 58 <main+0x58>
54: R_X86_64_PC32 .rodata+0x22
58: b8 00 00 00 00 mov $0x0,%eax
5d: e8 00 00 00 00 callq 62 <main+0x62>
5e: R_X86_64_PLT32 printf-0x4
62: 48 8b 45 e0 mov -0x20(%rbp),%rax
66: 48 83 c0 18 add $0x18,%rax
6a: 48 8b 00 mov (%rax),%rax
6d: 48 89 c7 mov %rax,%rdi
70: e8 00 00 00 00 callq 75 <main+0x75>
71: R_X86_64_PLT32 atoi-0x4
75: 89 c7 mov %eax,%edi
77: e8 00 00 00 00 callq 7c <main+0x7c>
78: R_X86_64_PLT32 sleep-0x4
7c: 83 45 fc 01 addl $0x1,-0x4(%rbp)
80: 83 7d fc 07 cmpl $0x7,-0x4(%rbp)
84: 7e b2 jle 38 <main+0x38>
86: e8 00 00 00 00 callq 8b <main+0x8b>
87: R_X86_64_PLT32 getchar-0x4
8b: b8 00 00 00 00 mov $0x0,%eax
90: c9 leaveq
91: c3 retq
分析hello.o的反汇编,并与第3章的 hello.s进行对照分析如下:
经过比较,可以知道,hello.o的反汇编和hello.s大体上比较相似。
有区别的地方如下:
- 操作数表示:hello.o反汇编代码中的操作数是以16进制表示,而hello.s中的操作数是以10进制表示。
- 分支转移:hello.o反汇编文件的分支转移是通过给出地址进行跳转实现的;ernhello.s文件中的分支转移是通过使用段名称实现的。
- 函数调用:hello.s文件中call指令后面的是函数名,而hello.o反汇编文件中call指令后面的是指令的地址。
- 全局变量访问:hello.s中全局变量访问是段的名称+寄存器,反汇编文件中全局变量访问是0+寄存器,因为全局变量数据的地址在运行时已经确定,访问也需要重定位。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
4.5 本章小结
本章介绍了汇编的过程,并且分析了机器语言的构成,与汇编语言的映射关系。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接的概念:链接是将一个或多个由编译器或汇编器生成的目标文件外加库链接为一个可执行文件。例如,hel1o程序调用了 printf 函数,它是每个C编译器都提供的标淮C库中的一个函数。printf 两数存在于一个名为 printf.o的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到我们的hel1o.o程序中。链接器(ld)就负责处理这种合并。结果就得到hel10文件,它是一个可执行目标文件(或者简称为可执行文件),可以被加载到内存中,由系统执行。
链接的作用: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-2-1 链接命令
5.3 可执行目标文件hello的格式
5.3.1 ELF头
在ubuntu终端输入readelf -a hello > all.txt,可以将内容输出到all.txt中,方便查看信息。ELF头信息如下图所示。
图5-3-1hello ELF头内容
从上图可以得出以下结论:
hello的数据的存储形式为补码、小端序存储;文件的类型为ELF64,入口点地址为0x1100,程序头起点为64,节头表的起始位置为14208,表明重定位已经完成;文件共有27节,多于hello.o文件数目。
5.3.2 节头表
图5-3-2hello中头节表部分内容
从上图可以得出以下结论:
hello中头节表包含了很多信息,例如节的类型为之和大小等,hello中节头表的条目数多于hello.o中节头表的条目数。特别地,每一节都有实际的地址而非hello.o中地址全是0。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。
5.3.3 符号表
查看符号表,结果如下图所示:
图5-3-3 hello符号表内容
由上图可知,hello 符号表内容包括很多符号引用和定义的信息,例如目标文件的符号表包含定位和重定位程序的符号定义和符号引用所需的信息。每个符号表是一个条目的数组,每个条目包括value:距定义目标的节的起始位置的偏移;size:目标的大小;type:指明数据还是函数;bind:表示符号是本地的还是全局的等等。
5.3.4动态区域
查看结果如下图所示
图5-3-4动态区域内容
5.4 hello的虚拟地址空间
在乌班图中运行命令edb –-run hello运行hello。
运行结果如下图所示:
图5-4-1edb运行结果图
经过初步分析,程序被载入至地址0x401000~0x402000中。在该地址范围内,每个节的地址都与对应的 Address 相同。根据edb查看的结果,在地址空间0x401000~0x401fff中存放着与地址空间0x401000~0x402000相同的程序,在0x401fff之后存放的是.dynamic到.shstrtab节的内容。
5.5 链接的重定位过程分析
使用命令objdump -d -r hello > hello.txt将hello文件返汇编代码存储到hello.txt便于查看。
图5-5-1获取反汇编代码命令
对hello进行反汇编部分结果如下:
图5-5-1 hello的反汇编部分结果
hello和hello.o相比,有很多地方发生变化。
首先多了很多经过重定位之后的函数,如多出了.plt,puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt,atoi@plt等,hello.o在.text段之后只有一个main函数。相关事例如下:
图5-5-2相关示例
其次,跳转指令参数发生变化。在链接过程中,链接器解析了重定位条目,并计算相对距离,修改了对应位置的字节代码为PLT 中相应函数与下条指令的相对地址,从而得到完整的反汇编代码。相关例子如下所示:
图5-5-3相关示例
最后,hello.o的地址是从0开始的,是相对地址,而hello的地址是从0x401000(_init的地址)开始的,是已经进行重定位之后的虚拟地址;在hello的main函数中,条件跳转指令和call指令后均为绝对地址,而hello.o中是相对于main函数的相对地址。相关例子如下所示:
图5-5-4相关示例
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
_dl_start、_dl_init
_start
_libc_start_main
_main
_printf
_exit
_sleep_getchar
_dl_runtime_resolve_xsave
_dl_fixup
_dl_lookup_symbol_x
Exit
ld-2.27.so!_dl_start
ld-2.27.so!_dl_init
hello!_start
lib-2.27.so!__libc_start_main
hello!puts@plt
5.7 Hello的动态链接分析
(以下格式自行编排,编辑时删除)
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
编译器在运行时会添加重定位记录,等待动态链接器处理,目的是避免运行时修改调用模块的代码段。动态链接器使用过程链接表+全局偏移量表实现函数的动态链接,在全局偏移量表中存放函数目标地址,过程链接表使用全局偏移量表中地址跳转到目标函数,在加载时,动态链接器会重定位全局偏移量表中的每个条目,使得它包含目标的正确的绝对地址。
.got与.plt节保存着全局偏移量表,其内容从地址0x404000开始。通过edb查看,在dl_init调用前,其内容如下:
exit@plt的GOT条目 调用前的.got.plt
图5-7-1调用前的情况
在调用后,其内容变为:
调用后的.got.plt
图5-7-2调用后的情况
比较可以得知,0x404008~0x404017之间的内容,对应着全局偏移量表的内容发生了变化。其一保存的是指向已经加载的共享库的链表地址,其二是动态链接器在ld-linux.so模块中的入口。这样,接下来执行程序的过程中,就可以使用过程链接表和全局偏移量表进行动态链接。
5.8 本章小结
本章介绍了链接相关概念,介绍了乌班图中链接器链接的命令,通过可执行目标文件hello,分析了hello虚拟地址空间,借助edb软件分析了重定位过程以及执行流程。
第6章 hello进程管理
6.1 进程的概念与作用
进程被认为是计算机科学中最深刻、最成功的概念之一。在现代系统上运行一个程序时,我们会得到一个假象,就好像我们的程序是系统中当前运行的唯一的程序一样。我们的程序好像是独占地使用处理器和内存。处理器就好像是无间断地一条接一条地执行我们程序中的指令。
以上是关于哈工大2022计算机系统大作业---程序人生的主要内容,如果未能解决你的问题,请参考以下文章