哈工大CSAPP大作业:程序人生-Hello’s P2P

Posted having_salt

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈工大CSAPP大作业:程序人生-Hello’s P2P相关的知识,希望对你有一定的参考价值。

 

 

 

计算机系统

 

大作业

 

 

题     目  程序人生-Hellos P2P  

专       业       计算学部             

学     号       120L021818           

班    级        2003006             

学       生        秦梓涵        

指 导 教 师          吴锐          

 

 

 

 

 

 

计算机科学与技术学院

2021年5月

摘  要

本文从一个简单的hello.c文件入手,依次按照预处理、编译、汇编、链接、进程管理、存储管理和IO管理的顺序对一个程序的一生进行分析,从编写的文本文件,一直到到在硬件系统上的执行。

关键词:预处理;编译;汇编;链接                            

 

 

 

 

 

 

 

 

 

目录

计算机系统

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

5.3 可执行目标文件hello的格式

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 hello进程管理

6.1 进程的概念与作用

6.2 简述壳Shell-bash的作用与处理流程

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

第7章 hello的存储管理

7.1 hello的存储器地址空间

7.2 Intel逻辑地址到线性地址的变换-段式管理

7.3 Hello的线性地址到物理地址的变换-页式管理

7.4 TLB与四级页表支持下的VA到PA的变换

7.5 三级Cache支持下的物理内存访问

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

7.8 缺页故障与缺页中断处理

7.9动态存储分配管理

7.10本章小结

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件

参考文献



第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

程序人生-Hello’s P2P(CSAPP大作业)

哈工大 计算机系统大作业 程序人生-Hello’s P2P From Program to Process

哈工大计算机系统大作业——程序人生-Hello’s P2P

哈工大计算机系统大作业 程序人生-Hello’s P2P From Program to Process

(c)2006-2024 SYSTEM All Rights Reserved IT常识