计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 2021112155
班 级 2103103
学 生 谷佳熠
指 导 教 师 刘宏伟
计算机科学与技术学院
2022年11月
本篇文章详细的阐述了hello的程序人生,从预处理,汇编,编译,链接,到进入shell执行的全过程,并阐述了其中的细节信息,利用gdb和edb等程序进行了一些深入的分析,让我对计算机系统课程的知识有更好的掌握。
关键词:预处理;汇编;编译;链接;进程管理
目 录
第1章 概述
1.1 Hello简介
P2p:hello.c经过预处理器cpp,汇编器cc1,编译器as,链接器ld,先从hello.c变成修改后的C代码,hello.i;之后变成汇编代码hello.s;继续进行编译,获得可重定位目标文件hello.o。最后经过链接器获得可执行目标文件Hello。
020:shell的子进程hello通过函数execve执行经过异常、信号、虚拟内存管理等等步骤,最终运行结束,被父进程回收,hello的运行结束。
1.2 环境与工具
软件环境:Windows10,VWmare Tolls,Ubuntu虚拟机,edb和gdb
硬件环境:ASUS个人笔记本
1.3 中间结果
hello.i:修改过后的C代码,解析#指令。
hello.s:hello的汇编代码,可以把hello.i变成汇编语言。
hello.o:可重定位目标文件:可以把hello.s变为二进制文件。
hello:可执行目标文件,用来执行
1.4 本章小结
本章介绍了我完成本次作业的工具信息,也是hello程序人生的开始。
第2章 预处理
2.1 预处理的概念与作用
预处理阶段,由预处理器cpp根据以字符#开头的命令,修改原始的C程序。如把#include<stdio.h>直接插入到程序文本中,或者替换程序中的#define宏定义。最后得到的是另外一个C程序,一般以.i为后缀。
作用:修改#include,替换#define,确定执行条件#ifndef
2.2在Ubuntu下预处理的命令
图2.2.1 生成预处理文件hello.i的指令
2.3 Hello的预处理结果解析
hello.i的部分代码如下图所示
图2.3.1 hello.i的部分代码
此处完成了对#include<stdio.h>的展开,插入了许多行代码
2.4 本章小结
预处理是程序处理#代码的过程,本章解析了预处理的过程和原理。预处理是hello程序人生的第一步
第3章 编译
3.1 编译的概念与作用
编译阶段,由编译器ccl将文本文件hello.i编译成hello.s。得到的是一个汇编程序,以.s为后缀
作用:生成汇编程序,进行一些编译器优化与语法分析。
3.2 在Ubuntu下编译的命令
图3.2.1 生成hello.s的指令
3.3 Hello的编译结果解析
图3.3.1 hello.s的内容
此部分中有.file表示文件名是hello.c,.text表示代码段,.section和.rodata表示只读数据段,其中.rodata包括格式串跳转表等。.align段存有对数据和指令的地址的对齐要求。.string包括字符串类型的数据,每3位对应一个UTF-8编码的汉字,以及另外一段字符串。.global表示全局变量或函数,对应于其中的main。.type包括函数和对象,是符号。
图3.3.2 hello.s的内容
图3.3.3 hello.s的内容
数据:在hello.c中使用了三种数据,int型的argc和i,char*字符串型的argv[i]和其他的字符串,以及一个数组,即为argv。Argc是main函数的第一个参数,解析程序中的movl %edi, -20(%rbp)可知,%rdi中的内容作为第一个参数,赋给argc并被存放在-20(%rbp)中,直接引用即可。局部变量i被保存在栈地址-4(%rbp)中。其它常量被保存在.text节中。*argv是命令行参数,来自于键盘I/O设备输入。引用是通过其中的代码movq (%rax) %rax引用。对于数组的每个字符串元素已经在前文中解析。其它字符串则存放在.string和.rodata段
赋值:对argv的赋值通过I/O进行并存储于(%rax)。
类型转换:通过调用函数atoi函数将字符串类型转换为整数类型。对应指令为call atoi@plt
算术操作:此程序中绝大多数的算术操作均被用来对栈进行操作,其他则是用来在调用中使用,如addq $16,%rax。
逻辑和关系操作:此程序中几乎没有此类操作,主要的用处使用在跳转,如jle .L4
数组/指针/结构操作:*argv是命令行参数,来自于键盘I/O设备输入。通过间接寻址的处理。指针处理主要是针对栈指针,如sub $32,%rsp。
控制转移:.L3是一个循环,用-4(%rbp)和8对比控制跳出条件,把循环用跳转表示了。其它控制大多是是利用比较决定跳转。如.L2的内容。
函数操作:程序hello中使用了printf/exit/sleep/getchar/puts/main等多种函数。调用方式比较类似,均是call。其中sleep执行之前把%rax传给%edi作为第一个参数。
3.4 本章小结
本章节比较详细的阐述了编译的过程和其中的符号含义,体现了许多hello.s的信息。汇编是hello程序人生的第二步。
第4章 汇编
4.1 汇编的概念与作用
汇编阶段,由汇编器as将汇编程序汇编成可重定位目标文件
作用:将文本文件转换二进制文件,即翻译成机器语言程序,并且使得生成的文件可以被重定位。
4.2 在Ubuntu下汇编的命令
图4.2.1 生成hello.o的指令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
图4.3.1 hello.o的elf节
开头为一个16字节数据,分别是ELF字长、字节序、文件版本等等
图4.3.2 hello.o的其他节信息
.text节,存放已编译程序的机器代码。
.data节,存放已初始化的全局和静态C变量
.bss节,是未初始化的全局和静态C变量,不占用真正的空间
.rodata节,只读数据,比如这段程序中printf里的格式串和跳转表
.symtab节,是一个符号表,存放在程序中定义和引用的函数和全局变量的信息。
.strtab节,是一个包括.symtab和.debug节的符号表以及节头部表的节名字
节头部表,包括节名称,节的类型,节的属性(读写权限),节在ELF文件中所占的长度以及节的对齐方式和偏移量。
4.4 Hello.o的结果解析
图4.4.1 hello.o的反汇编内容
图4.4.2 hello.o的反汇编内容
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
机器语言由二进制数构成,每条汇编语言对应一条汇编语言
Hello.o由于尚未重定位,因此函数调用call不直接对应地址,而是对应main+偏移地址的形式。此外,对于全局变量,hello.o的反汇编结果没有具体地址,而是被存放在.rela.text中的重定位信息中。
对于分支转移,hello.s直接通过.L1.L2等地址进行转移,反汇编直接通过jmp语句到段名称来转移。
4.5 本章小结
本章通过展示汇编的结果和概念,并且展示出来其中的内容,显示了可重定位目标文件,体现了很多可重定位目标文件的具体信息。汇编是hello程序人生的第三步。
第5章 链接
5.1 链接的概念与作用
链接阶段,由链接器ld将一些.o文件和动态库静态库链接,从可重定位目标文件生成一个可执行目标文件,可以被加载到内存中由系统执行。
5.2 在Ubuntu下链接的命令
图5.2.1 生成hello可执行程序的指令
5.3 可执行目标文件hello的格式
图5.3.1 hello的elf节
开头为一个16字节数据,分别是ELF字长、字节序、文件版本等等
图5.3.2 hello的其它节信息
5.4 hello的虚拟地址空间
图5.4.1 hello的虚拟地址空间信息
利用edb可以查看到地址为401000到40404c
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
5.5 链接的重定位过程分析
图5.5.1 反汇编生成hello3.s的指令
图5.5.2 hello3.s的部分指令
hello得到的反汇编文件相较于原hello.o增加了许多节的信息
此外,hello中的地址是虚拟地址,并非实际地址
Hello中有一些外部库的代码,如printf@plt
hello中是通过间接寻址算法获取全局变量的信息。
5.6 hello的执行流程
图5.6.1 hello中具体的执行流程
利用gdb设置断点的当时可以寻找到其中的每一步
5.7 Hello的动态链接分析
利用readelf -S能够查询到hello的文件信息获取got信息
图5.7.1 hello中动态链接的查询
Init前的内容如图所示
图5.7.2 init前的内容
Init后的内容改用edb查看,如图所示
图5.7.3 init后的内容
5.8 本章小结
本章详细阐述了链接的过程和链接前后程序的变化,链接是hello程序人生的第四步,也是代表它成为可执行程序可以被执行的开始。
第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中的程序实例。
作用:进程给应用程序提供一个关键抽象,其中包括
一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。
一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell是用户与操作系统之间完成交互式操作的一个接口程序,它为用户提供简化了的操作。而NU组织又进一步开发Borne Again Shell,简称bash,它是Linux系统中默认的shell程序。
处理流程:(1)将用户输入的命令行进行解析,分析是否是内置命令;
(2)若是内置命令,直接执行;若不是内置命令,则bash在初始子进程的上下文中加载和运行它。
6.3 Hello的fork进程创建过程
Shell本身就是一个进程,当它运行指令./hello时,相当于fork了一个hello子进程。
二者并发执行,并且使用了相同但是独立的地址空间,并且共享stdin和stdout文件。二者的最大差别在于PID不同。
6.4 Hexecve函数在当前进程的上下文加载并运行可执行目标文件Helloello的execve过程
execve函数在当前进程的上下文加载并运行可执行目标文件Hello,在execve加载了Hello之后,它调用启动代码。启动代码设置栈,并将控制传递给新程序的主函数,这个主函数带有环境变量envp。
6.5 Hello的进程执行
进程的上下文信息包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构。
一个进程执行他的控制流的一部分的每一个时间段叫做时间片。
在进程执行的过程中,内核可以决定抢占当前进程,并且恢复一个之前被抢占的进程,这一过程称为内核调度了此进程。
在hello的程序人生中,先是由shell在用户模式执行,之后hello进程被创建,进入内核模式,调度hello进程,之后恢复用户模式执行hello进程。执行结束之后触发磁盘中断,进入内核模式,由内核调度shell,再次恢复用户模式。
执行过程shell和hello享有相同的时间片。
6.6 hello的异常与信号处理
异常是指控制流的突变,用来响应某种变化,分为4类。
(1)中断:异步异常,来自处理器外部的I/O设备。异常处理后会执行下一条指令;
(2)陷阱:同步异常,是执行系统调用函数的结果。函数调用结束后会执行下一条指令;
(3)故障:同步异常,由错误情况引起,如缺页,浮点异常等等。异常处理成功则重新执行该指令,否则程序终止;
(4)终止:同步异常,由致命错误造成。该异常将终止程序。
从键盘随意输入数据不影响结果输出
图6.6.1 随意输入数据的执行结果
回车:会停止进程。
图6.6.2 利用回车停止进程的执行结果
Ctrl-Z:挂起当前进程,信号是SIGTSTP,内核选择挂起进程
图6.6.3 利用Ctrl-Z挂起进程的执行结果
对应的ps检查如下
图6.6.4 利用ps检查进程的执行结果
Ctrl-C:中止当前进程,信号是SIGINT,内核选择中止进程
图6.6.5 利用Ctrl-C中止进程的执行结果
Jobs:列出当前被暂停的进程
图6.6.6 利用Jobs列出暂停进程的执行结果
Pstree:列出各个进程的树状图
图6.6.7 利用pstree列出进程的树状图的执行结果
fg:继续执行被挂起的进程
图6.6.8 利用fg继续执行被挂起进程的执行结果
Kill:杀死进程,信号是SIGKILL,内核选择杀死进程
图6.6.9 利用kill杀死进程的执行结果
6.7本章小结
本章主要介绍了四种异常和信号处理,以及异常控制流。异常和信号都是计算机运行程序的重要概念,也是理解系统的重要概念,还是hello的程序人生的执行层面上的重要环节。
结论
Hello的程序人生始于hello.c,经过预处理,汇编,编译,链接变成可执行文件,在shell上被执行,起于./hello的指令,终止于shell的kill指令或结束后被shell回收
通过本次的大作业,我深刻的体会了一个程序的运行过程,对半年以来计算机系统课程学习的内容有了一个深刻的掌握。在本次大作业中,我使用了edb和gdb结合的创新之处。在二者的结合之下,能够发挥二者的最大效益。
附件
hello.c 源代码
hello.i 对#预处理指令做初步处理
hello.s 汇编代码文件
hello.o 可重定位目标程序,二进制文件
hello 可执行目标程序,可用于执行
参考文献
[1] 大卫.R.奥哈拉伦 兰德尔.E.布赖恩特 深入理解计算机系统 北京:机械工业出版社, 2016.7
[2] (7条消息) Linux下 可视化 反汇编工具 EDB 基本操作知识_hahalidaxin的博客-CSDN博客
[3] HIT-ICS2022大作业要求