哈工大CSAPP大作业:程序人生-Hello’s P2P
Posted having_salt
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈工大CSAPP大作业:程序人生-Hello’s P2P相关的知识,希望对你有一定的参考价值。
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算学部
学 号 120L021818
班 级 2003006
学 生 秦梓涵
指 导 教 师 吴锐
计算机科学与技术学院
2021年5月
摘 要
本文从一个简单的hello.c文件入手,依次按照预处理、编译、汇编、链接、进程管理、存储管理和IO管理的顺序对一个程序的一生进行分析,从编写的文本文件,一直到到在硬件系统上的执行。
关键词:预处理;编译;汇编;链接
目录
第1章 概述
1.1 Hello简介
P2P过程:P2P即From Program to Process。Hello程序在一开始只是我们编写的一个文本文件,系统调用编译器的预处理器将文本文件中的注释删除,将文件内容包含进源程序并执行宏替换,生成.i文件。之后系统调用编译器将c语言转化成汇编语言指令,生成.s文件。再调用汇编器将汇编指令转换为机器指令,生成.o文件。最后使用链接器将若干文件链接,得到一个可执行文件,这就是我们说的Program。当我们在shell里运行这个程序时,系统将这个程序加载至内存,然后为这个程序fork一个新的进程,称为Progress,在子进程中,使用execve执行这个程序。这就实现了Program to Progress。
020过程:020即From Zero-0 to Zero-0。程序从一片空白开始,经过P2P的过程,我们得到了一个ELF格式的可执行文件。shell在子进程中使用execve执行这个程序时,系统将会把这个程序加载到内存,等待进程结束时,它的父进程将会回收hello,而系统内核将会对内存进行清理,最后什么也没有留下,即020。
1.2 环境与工具
硬件环境:AMD Ryzen 7 5800H; RAM 32G; NVIDIA GeForce RTX3050 Ti Laptop GPU。
软件环境:Windows 10; VirtualBox; Ubuntu-20.04.4。
开发与调试工具:gcc; vim; edb; readelf; HexEdit
1.3 中间结果
文件名 | 文件作用 |
hello.c | hello程序的源代码 |
hello.i | 源代码经过预处理后的结果 |
hello.s | 编译后生成的汇编程序 |
hello.o | 汇编后生成的可重定位文件 |
hello | 链接之后生成的可执行文件 |
a.out | 添加相关编译参数的可执行文件 |
1.4 本章小结
本章简要介绍了P2P、020的含义,分析了程序产生到执行过程中的几个主要过程,以及需要的环境、产生的中间文件等,后面的章节将会围绕这几个部分展开进一步的分析。
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:预处理器将会展开以#起始的行,并试图解释为预处理指令(包括条件编译指令#if/#ifdef/#ifndef/#else/#elif/#endif;宏定义#define;源文件包含指令#include;行控制指令#line;错误指令#error以及指定编译器功能指令#pragma)。执行完所有的预处理指令后,预处理器生成一个.i文件。
2.2在Ubuntu下预处理的命令
在bash里输入gcc -E hello.c -o hello.i :
产生hello.i文件:
2.3 Hello的预处理结果解析
原hello.c文件如下图:
对比两个文件的内容我们可以看到,预处理器完成了如下过程:首先,预处理器将stdio.h、unistd.h和stdlib.h中的内容读入到了hello.i中,其中包括一些类型的声明定义和一些函数的声明。然后预处理器将所有用#define定义的宏替换为了对应的值并且删除了注释内容。
2.4 本章小结
本章介绍了预处理过程相关的内容,从预处理的概念与内容入手,通过比较hello.c和hello.i中内容的不同之处,对预处理的过程有了更加深入的认识。
第3章 编译
3.1 编译的概念与作用
编译的概念:编译器将目标源程序hello.i翻译成汇编语言程序hello.s。
编译的作用:为不同的源程序语言提供了统一的汇编语言输出,便于机器以相同的标准执行。
3.2 在Ubuntu下编译的命令
在bash里输入gcc -S hello.i -o hello.s:
产生hello.s文件:
3.3 Hello的编译结果解析
3.3.1 数据
该程序中包含两个字符串常量:
分别对应着源程序中printf函数的两个格式化控制串,即"用法: Hello 学号 姓名 秒数!\\n"和"Hello %s %s\\n"。
main的参数变量argc和argv:
参数argc和argv分别为int型和char*[]型,在参数传递时分别保存在了%edi和%rsi寄存器中,之后作为局部变量,被程序保存在了栈中。
循环变量i:
循环变量i同样以局部变量的形式存在,保存在程序栈中,在每次循环结束时,对循环变量i进行更新。
3.3.2 赋值
程序中出现了对局部变量i的赋值:
使用movl将4字节的立即数0赋值到-4(%rbp)处,即在程序栈中储存临时变量的位置。
3.3.3 算数操作
汇编程序中显式地使用add指令来实现了i++操作:
3.3.4 关系操作
第一处操作是在判断argc是否为4时,即:
对应的汇编语言实现为:
cmpl对应着局部变量argc是一个int型的数据。
第二处操作是在循环时判断i的大小,即:
在汇编语言实现为:
因为i是整型,所以这里把i<8的操作转换为了i<=7。
3.3.5 数组/指针/结构操作
在程序中有3次数组的访问如下:
首先,程序计算-32(%rbp)即argv的地址,保存在%rax中。argv为char*[]型,%rax + 16相当于计算了argv[2],将argv[2]的值保存在%rdx中,这里argv[2]是一个指针,所以需要8个字节的寄存器保存。同理,程序计算argv[1],并将其保存在%rsi中。
在执行完printf后,程序计算argv[3],将其保存至%rdi中。
3.3.6 控制转移操作
在源程序中,程序有循环和分支语句:
在汇编指令中,使用跳转指令来完成这一功能:
对于分支语句,程序首先比较-20(%rbp)处的变量(即argc)和4的大小关系,如果不相等,则直接执行下面的语句,如果相等,则跳转到.L2标签。
对于循环语句,本程序采用了先赋初值,然后jump to middle的策略,程序声明了一个局部变量i,赋初值为0,然后跳转到.L3位置,比较i的值与7的大小关系,如果i<=7,则跳转到.L4位置处,否则继续向下执行。
3.3.7 函数操作
printf函数:
程序第一次调用printf函数输出了一个字符串,在汇编程序中实现如下:
其中,.LC0为:
这里printf输出一个以\\n结尾的字符串,因而编译器直接将其优化成了puts函数,这里常量字符串的寻址使用了通过程序计数器(PC)的相对偏移实现。
程序第二次调用printf时输出了该程序的两个参数,汇编程序实现如下:
这里程序将argv[1]和argv[2]分别保存在%rsi和%rdx中,然后将格式化字符串的地址保存在%rdi中,最后调用printf函数输出内容。
其中,.LC1为:
exit函数:
程序将立即数1保存在%edi寄存器中,然后把这个参数传递给exit函数。
atoi函数:
程序将argv[3]的值取出保存到寄存器%rdi中,然后调用atoi函数将值传入。
sleep函数:
程序将atoi的返回值保存到%edi寄存器中,然后调用sleep函数将参数传入。
getchar函数:
直接调用getchar函数。
3.4 本章小结
本章讨论了编译相关的内容,编译器将一个经过预处理的文件hello.i通过编译器转换为hello.s文件的过程,对汇编程序的具体执行过程做的深入的分析,尤其对数据、变量赋值、类型转换、算术操作、关系操作、控制转移、指针数组操作和函数操作和C语言中的语句做了详细对比,学习了编译器如何将抽象的高级语言转化成低级的汇编语言。
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:将汇编语言程序翻译成机器语言并保存在.o文件中的过程。
汇编的作用:将汇编语言的指令转化成机器指令。
4.2 在Ubuntu下汇编的命令
在bash下输入:gcc hello.s -c -o hello.o。
生成hello.o文件,使用readelf查看:
4.3 可重定位目标elf格式
ELF头:
ELF头的开始是一个16字节的魔数,其中前4个字节7f 45 4c 46描述了该文件是否满足ELF格式,第5个字节02描述了该系统架构为x86-64,第6个字节01表示使用小端法,第7个字节01表示ELF的主版本号。下面是有关该ELF文件的一些其他信息。
节头部表:
程序的节头部表描述了每一个节的名称、大小、类型、地址、偏移量、旗标、链接、信息、对齐等信息。
在节头部表中注明了本文件中没有节组、程序头和动态节。
重定位节:
其中在类型部分,以PC32结尾的是静态链接的内容,而以PLT32结尾的是动态链接内容。在最后有一个与重定位相关的异常处理单元.rela.eh_frame。
符号表:
符号表中存放了所有引用的函数和全局变量的信息。
4.4 Hello.o的结果解析
在Bash中输入objdump -d -r hello.o 得到hello.o的反汇编程序:
将其与hello.s文件对比可以发现,反汇编程序主要发生了三种变化:
条件分支变化:
跳转指令由标签转变成了具体的相对偏移地址,我们可以知道标签在经过汇编过程时被删除,在实际机器指令中并不存在。
函数调用变化:
在.s程序中,call的后面紧跟着函数名,而在反汇编程序中,call的后面紧跟着下一条指令的地址。这是因为该程序调用的函数是共享库中的函数,最终需要通过动态链接器才能确定函数的运行时执行地址。对于这一类函数调用,call指令中的相对偏移量暂时编码为全0,然后在.rela.text节添加重定位条目,等待链接时的进一步确定。
数据访问变化:
对于同一全局变量的访问,反汇编文件中需要通过链接时的重定位确定地址,而在.s文件中仍以.LC0占位符表示。
4.5 本章小结
本章介绍了汇编的过程,hello程序从汇编语言hello.s到机器指令hello.o,通过查看hello.o的ELF格式文件以及利用objdump进行反汇编查看汇编代码并与hello.s比较的方式,加深了对汇编过程的理解。
第5章 链接
5.1 链接的概念与作用
链接的概念:链接是将各种代码和数据片段收集并组合称为一个单一文件的过程,这个文件可被加载到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。链接是由链接器的程序自动执行。
链接的作用:链接器使分离编译成为可能;一个大型应用程序不再需要组织巨大的源文件,而是可以分解为更小,更好管理的模块,可以独立修改和编译这些模块,当我们改变这些模块中的一个时,只需简单编译,重新链接使用,不必重新编译其他文件。
5.2 在Ubuntu下链接的命令
在bash中输入: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产生hello文件:
5.3 可执行目标文件hello的格式
使用readelf打开hello文件如下:
节头部表:
各段的起始地址,大小等信息如图所示。
5.4 hello的虚拟地址空间
使用edb打开hello程序:
下面,开始查看hello的虚拟空间各个段的信息:
首先,我们用Memory Regions功能查看第一个部分:
由前16个字节可以看到,这一部分就是我们ELF文件的头。
下一个内存区域是可读可执行的,我们查看该区域可知:
这一部分是我们指令的装载地址。
第三个内存区域是只读数据域,我们查看这一部分:
这一部分编码了我们代码中字符串常量等数据。
下一部分是可读可写的:
这一部分是运行时堆,由malloc函数管理内存分配,同时作为全局变量的数组也会保存在这一部分。
最后一部分是用户栈:
5.5 链接的重定位过程分析
在bash中输入objdump -d -r hello,结果如下:
与hello.o反汇编的结果对比可知,hello程序反汇编结果有如下变化:
含有汇编代码的段增多:
hello.o反汇编的代码只有.text段有汇编代码:
hello反汇编的代码除了.text段有汇编代码外,还有.init段、.plt段、.fini段。
增加了一些函数:
其中包括main的入口函数_start等:
以及一些外部库函数:
进行重定位:
这里我们增加-no-pie参数重新进行编译:
在hello.o的反汇编文件中,调用函数时的偏移地址为0:
只留下了若干重定位条目等待重定位,重定位时,链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。
在这个过程中,这些重定位条目告诉链接器32位PC相对地址或32位绝对地址进行重定位,这些重定位条目通过计算地址或直接调用保存的绝对地址,达到重定位的目的。
在重定位后,这些部分都被替换为了准确的地址:
5.6 hello的执行流程
使用edb打开hello程序,观察右侧的rip可知程序调用的子程序及其地址:
hello运行过程中,调用的子程序如下:
子程序名 | 程序地址 |
ld -2.31.so_dl_start | 7ff3394d8ea0 |
ld-2.31.so_dl_init | 7ff3394e7630 |
hello_start | 400500 |
libc-2.31.so__libc_start_main | 7ff339100ab0 |
Hello_printf@plt | 4004c0 |
hit csapp大作业 程序人生-Hello’s P2P 哈工大计算机系统大作业 程序人生-Hello‘s P2P 020 |