哈工大计算机系统大作业2022
摘要
本文介绍了hello.c文件编写完成后在Linux下运行的完整生命历程,对预处理、编译、汇编、链接、进程管理、存储管理、I/O管理这些hello程序的生命历程进行详细、清楚地解释。通过运用一些工具,如gdb、readelf等,清晰地观察hello程序完整的周期,直观地表现了程序从开始到结束的生命历程。
关键词:预处理;编译;汇编;链接;进程;存储;I/O
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P(From Program to Process):
- 通过文本编辑器编写C语言文本文件hello.c
- 通过预处理器cpp预处理,生成文本文件hello.i
- 通过编译器ccl生成汇编程序hello.s
- 通过汇编器as生成二进制可重定位文件hello.o
- 通过链接器ld将其与引用到的库函数连接生成二进制可执行文件hello
- 在shell中输入命令./hello,shell fork一个子进程,再调用execve把程序加载到进程中,开始运行
O2O(From Zero-0 to Zero-0):
- execve系统调用启动加载器,加载器创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零
- 通过将虚拟地址空间中的页映射到可执行文件的页大小的片(chunk),新的代码和数据段被初始化为可执行文件的内容
- 加载器跳转到_start地址,它会调用应用程序的main函数
- 程序运行结束后,shell回收进程,释放虚拟地址空间,删除有关内容,所以进程从0开始,最后又回到了0
1.2 环境与工具
硬件环境:
12th Gen Intel Core i9-12900H
软件环境:
OS: Ubuntu 20.04 focal(on the Windows Subsystem for Linux)
Kernel: x86_64 Linux 5.10.60.1-microsoft-standard-WSL2
开发调试工具:
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
1.3 中间结果
hello.c 源程序
hello.i 预处理后文件
hello.s 编译后的汇编文件
hello.o 汇编后的可重定位目标执行文件
hello.o.txt hello.o的反汇编
hello 链接后的可执行文件
hello.txt hello的反汇编代码
1.4 本章小结
本章总体介绍了hello程序“一生”的过程,以及进行实验时的软硬件环境及开发与调试工具等基本信息。
第2章 预处理
2.1 预处理的概念与作用
2.1.1 预处理的概念
在计算机科学中,预处理器是程序中处理输入数据,产生能用来输入到其他程序的数据的程序。输出被称为输入数据预处理过的形式,常用在之后的程序比如编译器中。所作处理的数量和种类依赖于预处理器的类型,一些预处理器只能够执行相对简单的文本替换和宏展开,而另一些则有着完全成熟的编程语言的能力。
C语言预处理cpp用于在编译器处理程序之前预扫描源代码,完成头文件的包含, 宏扩展, 条件编译, 行控制等操作。
2.1.2 预处理的作用
主要作用:处理关于“#”的指令
- 删除#define,展开所有宏定义
- 处理条件预编译#if, #ifdef, #if, #elif, #endif
- 处理“#include”预编译指令,将包含的“.h”⽂件插⼊对应位置
- 删除所有注释
- 添加⾏号和⽂件标识符。⽤于显⽰调试信息:错误或警告的位置
- 保留#pragma编译器指令
2.2 在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
处理后的hello.s变为3000多行,合并了所有.h内容,删除了注释、宏等,添加了行号和文件标识符。
2.4 本章小结
本章介绍了预处理的相关概念和作用,并进行实际操作查看hello.i文件。
第3章 编译
3.1 编译的概念与作用
3.1.1 编译的概念
编译器是一种计算机程序,它会将某种编程语言写成的源代码(原始语言)转换成另一种编程语言(目标语言)。
C语言编译器ccl将文本文件hello.i转换成汇编文件hello.s,里面是hello.c对应的汇编语言程序。
3.1.2 编译的作用
对预处理文件进行语法分析、语义分析、优化,将其转换为汇编程序,是高级语言向机器代码转换中重要的中间过程。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
3.3.1 数据
1) 字符串
程序中有两个输出字符串,它们都存在.rodata节
这两个字符串作为参数传递给printf
函数(其中一个printf
被优化为puts
)
2) 参数argc、argv
参数argc
和数组argv
地址分别在寄存器rdi
、rsi
中,它们接着被放入堆栈,argv
随后在printf
时被使用。
3) 局部变量
main
函数声明了一个局部变量i,赋值为0,放在帧指针rbp
指向位置的下方4个字节中。
4) 立即数
立即数直接体现在汇编代码中。
3.3.2 全局函数
hello.c声明了一个全局函数int main(int argc,char *argv[])
3.3.3 赋值
for循环中,i=0
对应汇编movl $0, -4(%rbp)
3.3.4 类型转换
程序中使用了一个类型转换函数atoi
,从栈中取出字符串地址放入参数rdi
,atoi
将其转为int型数存入rax
。
3.3.5 算数操作
for循环中,i++
对应汇编addl $1, -4(%rbp)
3.3.6 关系操作、控制转移
判断argc!=4,如果argc等于4,就跳转到L2,否则继续执行下一条指令
判断i<=7,如果成立,就跳到L4继续执行循环内容,如果不成立,则循环结束继续执行下面的指令
3.3.7 数组操作
见3.3.1 2)
3.3.8 函数操作
1) main
参数传递:edi
保存argc
,rsi
保存argv
;见3.3.1 2)
局部变量:见3.3.1 3)
函数返回:将返回值0存入eax
; mov rsp rbp
; ret
2) exit
函数调用、参数传递:参数1存入edi
中
3) puts
函数调用、参数传递:参数1字符串地址存入rdi
中
4) printf
函数调用、参数传递:参数1字符串地址存入rdi
中,参数2、3字符串地址从栈中取出存入rsi
、rdx
中
5) atoi:见3.3.4
6) sleep
函数调用、参数传递:将atoi
结果eax
作为参数1存入edi
7) getchar
函数调用:call getchar@PLT
,无参数
3.4 本章小结
本章主要介绍了编译器将c语言预处理代码转换为汇编代码的过程,并分别从c语言的数据,赋值语句,类型转换,算术操作,逻辑/位操作,关系操作,控制转移与函数操作等方面进行分析。
第4章 汇编
4.1 汇编的概念与作用
4.1.1 汇编的概念
汇编器(as)将汇编文件转换为二进制可重定位文件的过程。
4.1.2 汇编的作用
将汇编代码转换为机器指令,并将其打包为重定位目标程序的格式,生成.o文件,为程序的链接与运行做准备。
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o
4.3 可重定位目标elf格式
4.3.1 ELF头
该部分包含了ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量,其中Magic描述了生成该文件的系统的字的大小和字节序。
4.3.2 节头表
该部分包含了各节的名称、大小、类型、地址、偏移量。每个节有不同的读写权限、对齐大小。因为这是用来重定位的文件,所以每个节的起始地址都是0。
4.3.3 符号表
符号表.symtab包含了程序中的函数、全局变量的名称、类型、大小、vis等信息。
4.3.4 重定位节
重定位节.rela.text包含了对.text节进行重定位的信息。当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。