CSAPP程序人生-Hello’s P2P

Posted LWuoa

tags:

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

摘要

Hello.c作为一个一个入门级的程序,它的执行包含着计算机系统各个环节的相互配合。本篇课程报告主要以linux系统下,hello.c从C语言程序经过预处理、编译、汇编、链接生成可执行目标文件hello的过程,以及通过进程管理、存储管理以及IO管理,分析了hello是如何在计算机系统中执行的。

关键词:计算机系统;P2P;020;


第1章 概述

1.1 Hello简介

根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。

1.1.1 P2P

hello程序的生命周期是从一个高级C语言程序开始的。为了在系统上运行hello.c程序,hello.c首先经过预处理器(cpp)得到修改了的源程序hello.i;接着,编译器(cc1)将其翻译为汇编程序hello.s;然后经过汇编器(as)翻译成机器语言指令,把这些指令打包成可重定位目标程序hello.o;接下来,经过链接器,将调用的标准C库中的函数(如printf等)对应的预编译好了的目标文件以某种方式合并到hello.o文件中,得到可执行目标程序hello。当我们运行时,在shell中利用fork()函数创建子进程,再用execve加载hello程序,这时,hello就由程序(program)变成了一个进程(process),完成了P2P的过程。

1.1.2 020

020:在shell中用fork()函数创建子进程,再用execve加载可执行目标程序hello,映射虚拟内存,程序开始时载入物理内存,进入CPU处理。CPU为执行文件hello分配时间片,进行取指、译码、执行等流水线操作。内存管理器和CPU在执行过程中通过L1、L2、L3三级缓存和TLB多级页表在物理内存中取的数据,通过I\\O系统根据代码指令进行输出。在程序运行结束后,父进程会对其进行回收,内核把它从系统中清除,这时hello就由0转换为0,完成了020的过程。

1.2 环境与工具

1.2.1 硬件环境

处理器 12th Gen Intel(R) Core(TM) i5-12500H   2.50 GHz

机带RAM 16.0 GB (15.7 GB 可用)

系统类型 64 位操作系统, 基于 x64 的处理器

硬盘 476GB SSD

1.2.2 软件环境

Windows 11 家庭中文版

Vmware 16.2.1;Ubuntu 20.04.3 LTS 64位;

1.2.3 开发工具

Visual Studio 2022 64位;CodeBlocks 64位;vi/vim/gedit+gcc;

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

文件名

作用

hello.c

原C语言文件

hello.i

预处理产生文件

hello.s

编译产生文件

hello.o

汇编产生文件

hello.out

链接产生可执行文件

hello_o_elf.txt

查看hello.o的elf格式对应的文本文件

hello_o_asm.txt

查看hello.o的反汇编对应的文本文件

hello_elf.txt

查看hello的elf格式对应的文本文件

hello_asm.txt

查看hello的反汇编对应的文本文件

 1.4 本章小结

本章主要介绍了hello程序的P2P(From Program to Process) 及020(From Zero-0 to Zero-0)的过程。

同时介绍了此次大作业完成的硬件环境、软件环境以及开发工具。

还列出了为完成本次大作业,生成的中间结果文件的名字以及文件的作用。


第2章 预处理

2.1 预处理的概念与作用

2.1.1 预处理的概念

预处理是指预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如hello.c中第一行中的 #include <stdio.h> 命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中。结果是得到了另一个C程序,通常以.i作为文件扩展名。

2.1.2 预处理的作用

预处理可以处理C语言中以#开头的语句,大致包括以下几类:

  1. #define:进行宏替换,用实际的常量或字符串常量来替换它的符号;
  2. #include:处理文件包含,将包含的文件插入到程序文本中;
  3. #if、#elif、#else等:条件编译,选择符合条件的代码送至编译器编译,实现有选择地执行相关操作
  4. 注释:删除C语言源程序中所有的注释;
  5. #error等:特殊控制指令。

 2.2在Ubuntu下预处理的命令

在Ubuntu下预处理的命令为:

cpp hello.c > hello.i  

预处理过程如下:

图2.2-1 预处理过程

预处理后生成的文件hello.i如下:

图2.2-2 预处理后生成的文件

 2.3 Hello的预处理结果解析

打开hello.i文件,将其与hello.c文件进行对比。发现hello.c文本仅有23行,而hello.i文件有3060行,代码量大大增加。经过对比发现,hello.i中的main函数(除注释与头文件)部分位于代码的最后,与源程序中的main函数相同,没有改变。

图2.3-1 hello.c 与 hello.i 对比的相同部分

对比发现在两个文本文件的不同部分中,首先是注释部分被删除了,并且插入了大量的代码。其中有在源程序中被引用的stdio.h,unistd.h,以及stdlib.h的代码,它们都是被直接插入了程序文本中。

图2.3-2 插入的stdio.h

图2.3-3 插入的unistd.h

图2.3-4 插入的stdlib.h

在hello.i文本文件中,还发现插入了一些未在hello.c中直接引用的其他文件。经过分析,这些文件应该是在stdio.h等文件中引用了,被递归插入了hello.i中。

图2.3-5 hello.i中插入的其他文件

也就是说在hello.c中引用的头文件,可能会引用另一些文件,他们在预处理的过程中都需要被展开插入到hello.i文件中,使得文件代码量相比源程序大大增加。

2.4 本章小结

本章主要介绍了预处理的概念以及预处理在五个方面的作用与功能,包括进行宏替换、处理文件包含,进行条件编译、删除注释以及处理#error等特殊控制指令。

在Ubuntu下将hello.c文件预处理生成了hello.i文件。

分析了hello.i文件与源程序hello.c文件的相同与不同之处。发现预处理的过程会保留注释与#开头语句之外的部分,删除注释部分,同时将头文件与头文件所包含的文件直接插入到代码中。


第3章 编译

3.1 编译的概念与作用

3.1.1 编译的概念

编译是指将便于人编写、阅读、维护的高级计算机语言所写作的源代码程序,翻译为计算机能解读、运行的低阶机器语言的程序。在这里指编译器(ccl)将文本文件hello.i翻译成汇编语言程序hello.s的过程。

3.1.2 编译的作用

编译的作用是将高级计算机语言所写作的源代码程序翻译为汇编语言程序,在这个过程中,会进行以词法分析、语法分析、语义分析来生成汇编语言程序,且编译器可能在这个过程中根据编译选项对程序进行一些适当的优化。

3.2 在Ubuntu下编译的命令

在Ubuntu下编译的命令为:

cc1 hello.i -o hello.s  

由于在我的Ubuntu中,cc1不在path中,这里使用命令:

/usr/lib/gcc/x86_64-linux-gnu/9/cc1 hello.i -o hello.s  

编译过程如下:

图3.2-6 编译过程

编译后生成的文件hello.s如下:

图3.2-7 编译后生成的文件

3.3 Hello的编译结果解析

此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析

3.3.1 数据

1. 数字常量

在hello.c中出现的数字常量在hello.s中都有相应的对应。编译器将数字常量以立即数的形式进行处理。

  • 将整型数argc与4比较,数字常量4在hello.s中以立即数$4的形式出现。

图3.3-1 hello.c中出现的第1处数字常量

图3.3-2 编译器将第1处数字常量处理为立即数

  • 在循环的判断条件中出现数字常量,用立即数表示。注意到由于循环的判断条件为i<8,编译器将其翻译为小于等于立即数$7。

图3.3-3 hello.c中出现的第2处数字常量

图3.3-4 编译器将第2处数字常量处理为立即数

2. 字符串常量

在hello.c文件中,有两个字符串,如下图所示:

图3.3-5 hello.c的两个字符串常量

编译器对这两句字符串进行处理时,将这两个字符串放入内存中的 .rodata节常量区中,如下图所示:

图3.3-6 两个字符串常量存放在 .rodata节中

打印字符串常量时,编译器将语句翻译为先将字符串存放的地址存入寄存器%rdi,再进行打印,具体过程如下图所示:

图3.3-7 打印字符串常量

3. 局部变量

编译器一般将局部变量存放在寄存器中或者栈中,存在寄存器中时可以看作是寄存器别名,节省从内存中读取数据的时间。

  • 传入参数int argc存放在寄存器%edi中。

图3.3-8 传入参数argc

  • 传入参数char *argv[]存放在栈中,其中首地址存放在栈中-32(%rbp)的位置,argv[1]地址为-32(%rbp)+$8,argv[2]地址为-32(%rbp)+$16,argv[3]地址为-32(%rbp)+$24。在hello.s中的使用如下图所示:

图3.3-9 传入参数char *argv[]

  • 局部变量int argc存放在栈中-20(%rbp)的位置。

图3.3-10 局部变量int argc

  • 局部变量int i存放在栈中-4(%rbp)的位置。

图3.3-11 局部变量int i

3.3.2 赋值

编译器将赋值的操作主编为对相应的寄存器或栈进行赋值。

在hello. c文件中,有对i的赋值如下:

图3.3-12 hello.c中对局部变量i赋值

经过编译器cc1的编译,在hello.s文件中,该语句转变为:

图3.3-13 hello.s中对局部变量i赋值

在3.3.1中,已经知道栈中-4(%rbp)的位置存放的是局部变量i,因此是将i赋值为0。

3.3.3 类型转换

Hello.c文件中, argv[3]的类型为字符型,经过函数atoi()转换为整型。在hello.c文件中的实现如下图所示:

图3.3-14 hello.c中调用函数实现类型转换

经过编译器编译后,步骤变为首先将argv[3]从栈中取出,赋值给%rdi,通过调用call atoi@PLT指令调用atoi函数,最终转换为整型数,存放在%eax中。

图3.3-15 hello.s中调用函数实现类型转换

3.3.4 算术操作

Hello.c文件中,算术运算有for循环中的i++:

图3.3-16 hello.c中的i++

经过编译器后,被翻译为:

图3.3-17 hello.s中的i++

在3.3.1中,已经知道栈中-4(%rbp)的位置存放的是局部变量i,每次执行这条指令,实现的是i自增1。

3.3.5 关系操作

对于关系操作,编译器一般会将关系操作翻译为cmp语句。在源文件hello.c中,有两处关系操作:

  • 比较argc与4是否相等

图3.3-18 hello.c中比较argc与4是否相等

经过编译,在hello.s文件中,该关系比较实现为:

图3.3-19 hello.s中对argc与4的关系操作

在3.3.1中,已经知道栈中-20(%rbp)的位置存放的是argc,因此这条指令就是在判断4与argc的关系。

  • 比较i与8的大小,i >= 8时跳出循环,在hello.c中的实现为:

图3.3-20 hello.c中比较i与8的大小

经过编译,在hello.s文件中,该关系比较实现为:

图3.3-21 hello.s中对i与8的关系操作

在3.3.1中,已经知道栈中-4(%rbp)的位置存放的是i,并且这里将i<8替换为判断i<=7,意义与原C语言程序相同。

3.3.6 数组/指针/结构操作

编译器对源代码中数组的操作往往翻译为对地址的加减操作,在hello.c的源代码中,存在对数组argv[]的访问:

图3.3-22 hello.c中的数组操作

经过编译器的翻译,在hello.s的文件中,对数组argv的访问变为:

图3.3-23 hello.s中的数组操作

其中首地址存放在栈中-32(%rbp)的位置,argv[1]地址为-32(%rbp)+$8,argv[2]地址为-32(%rbp)+$16,argv[3]地址为-32(%rbp)+$24即进行了地址的加减操作以访问数组。

3.3.7 控制转移

控制转移是指C语言源文件中的选择分支、循环结构等经过编译器的翻译,产生一些跳转的语句,编译器cc1编译后的文件hello.s中,控制转移有三处:

  • 比较argc是否等于4,如果相等,跳转.L2,否则顺序执行。

图3.3-24 hello.s中的第一处控制转移

  • 无条件跳转,将i初始化为0后,无条件跳转至循环中。

图3.3-25 hello.s中的第二处控制转移

  • 比较i是否小于等于7(小于8),如果小于等于7,则跳转至.L4,即满足循环条件,继续循环;如果大于7,则顺序执行,跳出循环。

图3.3-26 hello.s中的第三处控制转移

3.3.8 函数调用

函数调用一般会进行参数传递和返回值。在hello.s中,一共有六次函数调用。

  • 调用puts()函数。首先将调用函数所需要的参数,即需要打印的字符串常量的地址存放在寄存器%rdi中,然后执行call puts@PLT指令打印字符串。对应的C语言源程序如下:

图3.3-27 hello.c中第一处函数调用

可以看到,在C语言源程序中执行该命令用的是printf函数,由于打印的是一个单纯的字符串,因此编译器对它进行了优化,改用puts函数进行打印。相应的hello.s文件中的指令如下:

图3.3-28 hello.s中第一处函数调用

  • 调用exit()函数,参数为1。对应的C语言源程序如下图所示:

图3.3-29 hello.c中第二处函数调用

在hello.s中,首先进行参数的准备。将立即数1放入寄存器%edi中,然后执行call exit@PLT指令调用exit函数。

图3.3-30 hello.s中第二处函数调用

  • 调用含其他参数的printf()函数。对应的C语言源程序如下图所示:

图3.3-31 hello.c中第三处函数调用

在hello.s中,首先进行参数的准备。将argv[2]放入寄存器%rdx中,将argv[1]放入寄存器%rsi中,将字符串"Hello %s %s\\n"放入寄存器%rdi中,然后执行call printf@PLT指令进行打印。

图3.3-32 hello.s中第三处函数调用

  • 调用atoi()函数,参数为argv[3],以下是C语言原程序中的函数调用:

图3.3-33 hello.c中第四处函数调用

在hello.s中,首先进行参数的准备。将argv[3]放入寄存器%rdi中,然后执行call atoi@PLT指令调用atoi函数。

图3.3-34 hello.s中第四处函数调用

  • 调用sleep()函数,参数为atoi(argv[3])的返回值,以下是C语言原程序中的函数调用:

图3.3-35 hello.c中第五处函数调用

在hello.s中,首先进行参数的准备。将atoi(argv[3])的返回值放入寄存器%edi中,然后执行call sleep@PLT指令调用sleep函数。

图3.3-36 hello.s中第五处函数调用

  • 调用getchar()函数,没有参数,以下是C语言原程序中的函数调用:

图3.3-37 hello.c中第六处函数调用

在hello.s中,直接执行call getchar@PLT指令进行函数的调用。

图3.3-38 hello.s中第六处函数调用

3.4 本章小结

本章主要介绍了编译的的概念以及编译的作用与功能,包括将高级语言指令翻译为汇编语言指令,同时根据编译选项进行一些适当的优化。

在Ubuntu下将hello.i文件编译生成了hello.s文件。

按照C语言的不同数据与操作类型,分析了源程序hello.c文件中的语句是怎样转化为hello.s文件中的语句的。其中数据类型包括数字常量、字符串常量和局部变量;操作类型包括赋值、类型转换、算术操作、关系操作、数组\\指针\\结构操作控制转移以及函数调用。


第4章 汇编

4.1 汇编的概念与作用

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。

3.1.1 汇编的概念

汇编是指将汇编语言程序经过编译器(as)转化为二进制的机器语言指令,并把这些指令打包成可重定位目标程序的格式,并保存在目标文件.o中。在这里指汇编器器(as)将文本文件hello.s转换为可重定位目标程序hello.o的过程。

3.1.2 汇编的作用

汇编的作用是把汇编语言翻译成机器语言,用二进制码0、1代替汇编语言中的符号,即让它成为机器可以直接识别的程序。最后把这些指令打包成可重定位目标程序的格式,并保存在目标文件.o中。

4.2 在Ubuntu下汇编的命令

Ubuntu下汇编的命令为:

as hello.s -o hello.o  

汇编过程如下:

图4.2-1 汇编过程

汇编后生成的文件hello.o如下:

图4.2-2 汇编后生成的hello.o文件

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

首先查看hello.o的ELF格式,在终端中输入如下指令即可查看:

readelf -a hello.o  

也可以用以下的语句将输出内容保存为文本文件再进行查看:

readelf -a hello.o > hello_o_elf.txt  

4.3.1 ELF头

hello.o的ELF格式的开头是ELF头,它以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息:包括ELF头的大小、目标文件的类型、处理器体系结构、节头部表的文件偏移,以及节头部表中条目的大小和数量。

图4.3-1 hello.o的ELF格式的ELF头

4.3.2 节头部表

ELF文件格式中的节头部表描述了目标文件中不同节的类型、地址、大小、偏移等信息,以及可以对各部分进行的操作权限。

图4.3-2 hello.o的ELF格式的节头部表

4.3.3 重定位节

ELF文件格式中的重定位节包含两个部分:.rela.text节与.rela.eh_frame节。

图4.3-3 hello.o的ELF格式的重定位节

.rela.text节包含.text节中的位置的列表,含有该.text中所需要进行重定位操作的信息,当链接器(ld)将目标文件与其他文件由进行结合时,需要修改这些位置

.rela.eh_frame节包含了对en_frame节的重定位信息。

在.rela.text节与.rela.eh_frame节中,表头表头名称与对应的含义如下表所示:

表头名称

含义

偏移量

需要重定位的信息的字节偏移位置

(代码节/数据节)

信息

重定位目标在.symtab中的偏移量和重定位类型

类型

表示不同的重定位类型

(例如图中R_X86_64_PC32就表示重定位一个使用32位PC相对地址的引用)

符号名称

被重定位时指向的符号

加数

重定位过程中要使用它对被修改引用的值做偏移调整

4.3.3 符号表

ELF文件格式中的符号表中存放了程序中所定义和引用的的全局变量以及函数的信息。(不包含局部变量)

图4.3-4 hello.o的ELF格式的符号表

4.4 Hello.o的结果解析

objdump -d -r hello.o  分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

输入以下命令查看hello.o对应的反汇编代码:

objdump -d -r hello.o  

也可以用以下的语句将输出内容保存为文本文件再进行查看:

objdump -d -r hello.o > hello_o_asm.txt  

得到反汇编代码如下:

图4.4-1 hello.o对应的反汇编代码

可以看见hello.o对应的反汇编代码每一句都有一串相应的地址与二进制的指令,以十六进制的数值展示出来。

对比hello.o的反汇编代码,与hello.s的内容进行比较,发现以下几点的不同:

4.4.1 数字进制不同

hello.s中操作数都是十进制的,而在反汇编代码中以十六进制表示,这对应着在机器中以二进制的形式存在。

图4.4-2 hello.s中的十进制与hello.o反汇编代码中的十六进制

4.4.2 对字符串常量的引用不同

hello.s中是用的全局变量所在的那一段的名称加上%rip的值,而hello.o中用的是0加%rip的值,因为当前为可重定位目标文件,之后还需经过重定位方可确定其具体位置,所以这里都用0来代替。

图4.4-3 hello.s中字符串常量与hello.o反汇编代码中的字符串常量

4.4.3 分支转移的不同

hello.s中列出了每个段的段名,分支转移时,跳转指令后用对应的段的名称表示跳转位置;而在hello.o的反汇编代码中每个段都有明确的地址,跳转指令后用相应的地址表示跳转位置。

图4.4-4 hello.s与hello.o反汇编代码中分支转移的不同

值得注意的是,在机器代码中,采用的是相对寻址方式。如:

这行代码在机器的二进制代码中跳转的是0x48。因此实际跳转的位置是下一条指令的地址0x34+0x48=0x7c的位置,如果是后向跳转,则会加上一个负值。

4.4.4 函数调用的不同

在hello.s中调用函数时,在call指令之后直接引用函数名称,而在hello.o的反汇编代码中,在call指令后加上下一条指令的地址来表示。观察机器语言,发现其中操作数都为0,这是因为通过重定位信息,再链接生成可执行文件后才会生成其确定的地址,所以这里的相对地址都用0代替。

图4.4-5 hello.s与hello.o反汇编代码中函数调用的不同

4.5 本章小结

本章主要介绍了汇编的的概念以及汇编的作用,主要是将汇编语言程序经过编译器(as)转化为二进制的机器语言指令,并把这些指令打包成可重定位目标程序的格式,并保存在目标文件.o中,成为机器可以识别的程序。

在Ubuntu下将hello.s文件经过汇编器(as)生成了hello.o文件。

分析了hello.o的ELF格式,用readelf等列出了其各节的基本信息,包括ELF头、节头部表、重定位节以及符号表的功能与包含的信息。

对hello.o二进制文件进行反汇编,得到了反汇编程序,并分析了该反汇编程序与汇编语言程序hello.s中语句的对应关系。从数字进制、字符串常量的引用、分支转移以及函数调用的不同四个方面分析了二者的关系。


第5章 链接

5.1 链接的概念与作用

注意:这儿的链接是指从 hello.o 到hello生成过程。

5.1.1 链接的概念

链接(linking)是指将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。

5.1.2 链接的作用

链接的作用是将预编译好了的一个目标文件(hello.o)或若干目标文件外加链接库合并成为一个可执行目标文件(hello)。使得分离编译称为可能,不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为可独立修改和编译的模块。当改变这些模块中的一个时,只需简单重新编译它并重新链接即可,不必重新编译其他文件。

5.2 在Ubuntu下链接的命令

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件

在Ubuntu下链接的命令为:

ld  -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 /usr/lib/gcc/x86_64-linux-gnu/9/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello  

链接过程如下:

图5.2-1 链接过程

汇编后生成的可执行目标文件hello如下:

图4.2-2 汇编后生成的hello.o文件

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

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

首先查看hello的ELF格式,在终端中输入如下指令即可查看:

readelf -a hello.o  

也可以用以下的语句将输出内容保存为文本文件再进行查看:

readelf -a hello > hello_elf.txt  

通过查看hello文件的elf格式的节头,列出其各段的基本信息如下:

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

其中,它的第一列按地址顺序列出了各段的名称及大小,第三列列出来各段的起始地址,最后一列列出来各段的偏移量

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。  

首先用edb加载hello,在终端输入如下命令:

edb --run hello  

完成加载。

图5.4-1 用edb加载hello

通过readelf查看hello的程序头,可以发现其中的虚拟地址在edb的Data Dump中都能找到相应的位置,且大小也相对应。

  • 在edb的symbol窗口,可以查看各段对应的名称以及各段的起始位置与结束的位置,与5.3中所展示出来的elf格式展示出来的相对应。

图5.4-2 各段名称、起始位置和终止位置对应

  • Data Dump是从地址0x400000开始的,并且该处有ELF的标识,可以判断从可执行文件加载的信息(只读代码段,读/写段)是从地址0x400000处开始的。可以从程序头处读取相关信息。

图5.4-3 可执行文件加载信息起始位置

  • .PDHR起始位置为0x400040 大小为0x230。

图5.4-4 .PDHR 起始位置与大小

  • .INTERP起始位置为0x400270 大小为0x1c。

图5.4-5 .PDHR 起始位置与大小

  • .DYNAMIC起始位置为0x403e10 大小为0x1e0

图5.4-6 .DYNAMIC起始位置与大小

  • .GNU_RELRO起始位置为0x403e50 大小为0x1b0

图5.4-7.GNU_RELRO起始位置与大小

5.5 链接的重定位过程分析

objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

首先查看hello的反汇编代码,在终端中输入如下指令即可查看:

objdump -d -r hello   

也可以用以下的语句将输出内容保存为文本文件再进行查看:

objdump -d -r hello > hello_asm.txt  

将hello.o与hello文件的反汇编代码进行对比分析,得到如下几方面的不同。

5.5.1 代码量增加

打开反汇编代码的文本文件,查看两个文件代码量,发现hello_o_asm.txt只有52行,而hello_asm.txt有223行。

图5.5-1 经过链接后的反汇编代码量增加

5.5.2 插入C标准库中的代码

在hello.o的反汇编程序中,只有main函数,没有调用的函数段;经过链接过程后,原来调用的C标准库中的代码都被插入了代码中,并且每个函数都被分配了各自的虚拟地址。

图5.5-2 被插入的函数与其虚拟地址

5.5.3 指令分配虚拟地址

在hello.o的反汇编程序中, main函数中的所有语句前面的地址都是从main函数开始从0开始依次递增的,而不是虚拟地址;经过链接后,每一条语句都被分配了虚拟地址。

图5.5-3 链接前后的main函数语句的地址

5.5.4 字符串常量的引用

在hello.o的反汇编程序中,字符串常量的位置是用0加%rip的值来表示的,这是由于当时字符串常量并未分配虚拟内存;而在hello的反汇编程序中,因为字符串常量都有了相应的位置,所以用实际的相对下一条语句的偏移量加%rip(下一条语句的地址)的值来描述其位置。

 

图5.5-4 链接前后字符串常量的引用

csapp 程序人生 Hello’s P2P

 

大作业

题     目  程序人生-Hellos P2P  

专       业        计算学部          

学  号             

班       级               

学       生             

指 导 教 师         

计算机科学与技术学院

2022年5月

摘  要

文章通过对hello.c程序在Linux系统下的生命周期进行追踪,逐步分析其在预处理、编译、汇编、链接生成可执行文件中的变化表现,并在Ubuntu系统下执行,观察其加载、运行、终止、回收的过程,实现了对程序翻译的系统性了解,以及对其运行机制的系统性的分析与概述。

关键词:计算机系统;编译系统;进程管理;存储                           

目  录

第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:即From Program to Process。如下图1,hello程序从源文件hello.c开始,经过预处理器cpp的预处理,得到hello.i文件,这是一个经修改了的源程序;hello.i文件再经过编译器cc1处理,得到文本文件hello.s;hello.s再经过汇编器as处理,被翻译成机器语言指令,打包成二进制文件hello.o;再经过链接器ld,将hello.o文件和一些必要的系统目标文件组合起来,就得到了可执行目标文件hello。接着,用户在shell输入./hello即可执行该文件。Shell会通过一系列操作将代码加载到内存,并调用fork函数创建一个新的进程,再调用execve函数,将hello程序加载到该子进程中执行。

                                                             图1 从源文件到可执行文件

020:即From Zero to Zero。接上述过程,shell执行execve函数将程序加载到进程中,进行虚拟内存映射,将程序载入物理内存中执行,后开始执行目标代码,CPU为运行的hello分配时间片来执行逻辑控制流,运行结束后,父进程回收这个子进程,内核删除相关的数据结构,释放资源,hello归于zero。

1.2 环境与工具

硬件环境:X64 CPU;2GHz;4GRAM;256Disk;

软件环境:Windows10 64位;Vmware 16;Ubuntu 20.04 LTS 64位;

工具:gcc;edb;as;cpp;cc1;gedit;GNU READELF等

1.3 中间结果

hello.i 预处理后得到的文件;

hello.s   编译后的汇编文件;

hello.o 汇编得到的可重定位目标文件;

hello 链接得到的可执行文件;

hello.elf 用readelf读取hello.o得到的ELF格式信息;

hello.txt 反汇编hello.o得到的反汇编文件

helloprog.elf 由hello可执行文件生成的.elf文件

helloprog.txt 反汇编hello可执行文件得到的反汇编文件

1.4 本章小结

本章是对hello程序“一生”的过程的简述,主要围绕P2P(从程序到进程)和020(从无到有到无)的过程来进行简略介绍,并给出了实验时的软硬键环境、开发调试工具等基本信息。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

概念:程序设计领域中,预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。典型地,由预处理器对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位——预处理记号用来支持语言特性(如C/C++的宏调用)。

作用:预处理程序读入所有包含的文件以及待编译的源代码,然后生成源代码的预处理版本,会根据以字符#开头的命令来修改原始的C程序。比如,会读取头文件的相应内容,并将其直接插入到程序文本中;用实际值来替换掉“#define”定义的字符串;根据“#if”后的条件来决定需要编译的代码;此外还有#line(行控制)、#error(错误指令)、#pragma(和实现相关的杂注)等的处理。

2.2在Ubuntu下预处理的命令

命令:gcc hello.c -E -o hello.i或者cpp hello.c hello.i

                                                                图2.1 预处理命令

2.3 Hello的预处理结果解析

经预处理得到了hello.i文件,使用gedit查看hello.i文件部分内容,见下图:

 

                                                                图2.2.1 hello.i部分内容

                                                                图2.2.2 hello.i部分内容

源文件的预处理删去了hello.c文件中的注释内容,引入了头文件,对宏进行了展开,得到了一个三千多行的代码,该代码仍是C程序。

2.4 本章小结

本章主要介绍了预处理的相关概念和具体作用,并结合hello.c文件的预处理进行实际的结果分析:根据以字符#开头的命令来修改原始的C程序,比如宏定义的扩展、头文件的代码引入等。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:可通过编译器(cc1)将文本文件.i翻译成.s文本文件,它包含一个汇编语言程序。

作用:它是将高级语言程序转化为机器可直接识别处理执行的的机器码的中间步骤,会进行语法检查、目标程序优化等。

3.2 在Ubuntu下编译的命令

命令:gcc -S hello.i -o hello.s

图3.1 编译命令

3.3 Hello的编译结果解析

3.3.1汇编初始部分

 

图3.2.1 hello.s部分代码

图3.2.2 hello.s部分代码

文件的开头是一些声明:.file声明源文件;.test是代码段;.section.rodata是只读数据段;.align声明对指令或数据的存放地址的对齐方式;.string声明字符串;.globl声明全局变量;.type声明一个符号是函数类型还是数据类型;.size声明大小。

3.3.2数据

(1)数字常量:源代码中有将参数argc和常数4比较,4便以立即数的形式直接用于比较了;exit的1也同样以立即数的形式出现。

图3.3 数字常量

(2)局部变量:main函数里有声明一个局部变量i,它被存储在了栈中(图3.4.1);参数argc和argv也是局部变量,同样被存储在了栈里(图3.4.2)。

图3.4.1 局部变量i

图3.4.2 参数argc及argv

(3)字符串常量:源程序中的两个printf的参数都是字符串常量分别别为:"用法: Hello 学号 姓名 秒数!\\n"、"Hello %s %s\\n"。这两个字符串都存储在只读数据段中。

图3.5  符串常量

3.3.3赋值

在源代码中,for循环部分将i赋值0,对应汇编代码中的movl $0  -4(%rbp)指令(见图3.4.1),由于i为int型操作数,用movl来赋值“双字”。

3.3.4类型转换

源代码中有引用atoi函数,将argv[3]由字符串转换成了整型:

 

图3.6 类型转换

3.3.5算术操作与逻辑操作

在该代码文件中出现的算术操作有以下几种:

  1. subq $32, %rsp:对栈指针的减法操作,开辟了一个32字节的栈空间;
  2. addq $16, %rax:修改地址偏移量;
  3. addl $1, -4(%rbp):实现i++

其余整数算术操作见下图。

图3.7 整数算术操作

3.3.6 关系操作以及控制转移指令

关系操作即一些判断大于、等于、不小于之类的关系判断操作。在源代码中,有将参数argc与4比较,以及将i与8比较,其在汇编语言中的情况见下图:

图3.8 关系操作

先用CMP指令设置条件码,再结合不同的JXX控制转移指令得到比较的效果,若满足控制转移条件,则跳转到对应的跳转目标。控制转移指令一般出现在循环语句和条件语句中。

3.3.7数组

源程序的第二个参数argv[]是一个指针数组,被存在了栈中(见图3.4.2)。该数组的每个元素都是一个只想字符类型的指针,在汇编代码中被两次传递给了printf函数。

图3.9 数组

3.3.8函数操作

函数调用:假设P调用Q,Q执行后返回P,这个过程包含下面一个或多个机制:

(1)传递控制:进行过程 Q 的时候,程序计数器必须设置为 Q 的代码的起始地址,然后在返回时,要把程序计数器设置为 P 中调用 Q 后面那条指令的地址。

(2)传递数据:P 必须能够向 Q 提供一个或多个参数,Q 必须能够向 P 中返回一个值。

(3)分配和释放内存:在开始时,Q 可能需要为局部变量分配空间,而在返回前,又必须释放这些空间。

hello程序中的函数有main、puts、exit、printf、atoi、sleep、getchar函数。

在程序入口处调用了main函数,其余函数都是通过call指令来调用的。注意,在源代码中并未调用puts函数,它是由于第一个printf函数只用来输出一串字符串,而被系统优化成了puts函数。

3.4 本章小结

本章介绍了编译的概念和作用,编译将文本文件翻译成汇编语言程序,为后续将其转化为二进制机器码做了准备。此外,本章结合hello.s文件,对汇编代码中的一些C数据与操作进行了解析,介绍了编译器如何处理各个数据类型以及各类操作。

(第3章2分)

第4章 汇编

4.1 汇编的概念与作用

概念:汇编是指汇编器(as)将.s文件翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在.o目标文件的过程。作用:将编译器产生的汇编语言进一步翻译为计算机可以理解的二进制机器语言,生成.o文件。

4.2 在Ubuntu下汇编的命令

命令:as hello.s -o hello.o或 gcc -c hello.s -o hello.o

图4.1 汇编命令

4.3 可重定位目标elf格式

用readelf -a -W hello.o > hello.elf命令得到hello.elf文件:

图4.2 用readelf命令得到hello.o的ELF格式文件

接下来为其各节的基本信息:

4.3.1 ELF头

ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包括 ELF 头大小(64字节)、目标文件类型、机器类型(X86-64)、节头部表的文件偏移(1240字节),以及节头部表中条目的大小和数量等信息。

图4.3 ELF头

4.3.2节头部表

节头部表包含了文件中出现的各个节的名称、类型、属性、地址、偏移量、大小等信息,目标文件中每个节的都有一个固定大小的条目(entry)。

图4.4 节头部表

该文件的节头部表中包含的信息具体见图4.4,依次为:

(0)无效段,类型为NULL。

(1).text节:内容为已编译程序的机器代码。

(2).rela.text节:一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。

(3).data节:包含已初始化的全局和静态C变量。

(4).bss节:包含未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。

(5).rodata节:包含只读数据,如printf语句中的格式串和开关语句的跳转表等。

(6).comment节:存储的是编译器的版本信息。

(7).note.GNU_stack节:标记可执行堆栈。

(8).note.gnu.property节。

(9).eh_frame节:处理异常。

(10).rela.eh_frame节:保存.eh_frame节的重定位信息。

(11).symtab节:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。

(12).strtab节:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。

(13).shstrtab节:该节保存着节名称。

4.3.3重定位节

当汇编器生成一个目标模块时,它并不知道数据和代码最终将放在内存中的什么位置。它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。

图4.5 重定位节

所以,无论何时汇编器遇到对最终位置未知的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位条目放在.re1.text中。已初始化数据的重定位条目放在.rel.data中。

图4.5是重定位节的具体内容。需要重定位的是各个调用的函数和只读数据内容。其中,offset是需要被修改的引用的节偏移;symbol标识被修改引用应该指向的符号;type告知链接器如何修改新的引用;addend是一个有符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整。ELF定义了32种不同的重定位类型,两种最基本的重定位类型包括R_X86_64_PC32(重定位使用32位PC相对地址的引用)和R_X86_64_32(重定位使用32位绝对地址的引用)。

4.3.4 符号表

符号表是由汇编器构造的,使用编译器输出到汇编语言.s文件中的符号。

图4.6 符号表

name是字符串表中的字节偏移,指向符号的以nu11结尾的字符串名字。value 是符号的地址。对于可重定位的模块来说,value是距定义目标的节的起始位置的偏移。对于可执行目标文件来说,该值是一个绝对运行时地址。size是目标的大小(以字节为单位)。 type通常要么是数据,要么是函数。符号表还可以包含各个节的条目,以及对应原始源文件的路径名的条目。所以这些目标的类型也有所不同。binding字段表示符号是本地的还是全局的。

4.4 Hello.o的结果解析

用objdump -d -r hello.o > hello.txt 反汇编,得到hello.txt文件。

hello.o的反汇编得到的汇编代码基本与hello.s一致,每个操作代码都可以一一对应。此外,两者之间有一些细小的差别:

(1)立即数 在.s文件中,立即数直接以十进制数表示,而在反汇编得到的文件中,立即数以十六进制表示。

(2)指令表示 反汇编的指令mov、add、sub等后面没有对字大小的表示,如w,l,q等;但.s文件中有这些表示。而call指令在反汇编中表示为callq。

(3)分支转移 在.s文件中,跳转指令的目标地址直接记为段名称,如.L2,.L3等;而反汇编代码跳转指令的操作数使用的是确定的地址。

(4)函数调用 在hello.s文件中,call之后直接跟着函数名称,而在反汇编文件中,call后跟着的是直接的地址偏移。

 

                                                        图4.7 hello.o的反汇编结果

                                                        图4.8 hello.s代码段

4.5 本章小结

本章介绍了汇编的概念与作用,并依据实际操作将hello.s文件翻译为hello.o文件,并生成hello.o的ELF格式文件hello.elf,对ELF格式文件的具体结构进行了详细的分析。此外,将hello.o文件反汇编并与hello.s文件进行了对照分析,并就二者的差异进行说明。

(第4章1分)

5链接

5.1 链接的概念与作用

概念:链接是将各种代码和数据片段收集并合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。

作用:链接可以通过符号解析、重定位等工作将多个.o文件和静态库文件、动态链接库等合并得到一个执行文件,这使得分离编译成为可能,从而可以避免因某一个模块的小改动而需要重新编译所有文件的麻烦。

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.1 链接命令

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

用readelf -a -W hello > helloprog.elf命令得到helloprog.elf文件:

                                图5.2 用readelf命令得到hello.o的ELF格式文件

hello的ELF格式文件的各节的基本信息:

5.3.1 ELF头

图5.3.1 ELF头

5.3.2 节头部表

图5.3.2 节头部表

其比hello.o文件多了一些段,各段的基本信息均已在上图中列出,不再赘述。    

5.3.3 程序头

图5.3.3 程序头

5.3.4 动态节

图5.3.4 动态节

5.3.5 重定位表

 

图5.3.5 重定位表

5.3.6 符号表

 

 图5.3.6 符号表

5.4 hello的虚拟地址空间

使用edb加载hello,通过Data Dump可以查看到本进程的虚拟地址空间各段信息。结合5.3的内容,可以定位到各节的信息。

程序头表(图5.5)中load表明程序段开始的地址为0x400000,见下图。

图5.4.1 edb中Data Dump部分示图

下图是.text节和.got节的信息:

 

图5.4.2 .text节(地址0x4010f0)

 

图5.4.3 .got节(地址0x403ff0)

5.5 链接的重定位过程分析

用命令objdump -d -r hello > helloprog.txt得到helloprog.txt文件。

相比于hello.o的反汇编文件,helloprog.txt中除了main函数,还有其调用的库函数(图5.5.1)和一些新增节,如.init节、.plt节(图5.5.2)。

图5.5.1 hello反汇编中的库函数(部分)

链接的过程:链接主要分两个过程:符号解析和重定位。

符号解析:目标文件定义和引用符号,每个符号对应于一个函数、-个全局变量或一个静态变量(即 C语言中任何以static属性声明的变量)。符号解析可以将每个符号引用正好和一个符号定义关联起来。

重定位:编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义于一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。重定位由下面两步组成:

重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。例如,来自所有输入模块的.data节被全部合并成一个节,这个节成为输出的可执行目标文件的.data节。然后,链接器运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号.当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。

重定位节中的符号引用。在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于可重定位目标模块中的重定位条目。代码的重定位条目被放在.rel.text中,已初始化的 数据的重定位条目放在.rel.data中。

图5.5.2 hello反汇编中的新增节(部分)

重定位的算法见下图:

图5.5.3 重定位算法

对于hello在执行重定位,以hello.o反汇编文件第16行内容来举例说明。该行有一个重定位条目:类型为R_X86_64_PC32,offset为0x1c,addend为-4,symbol为.rodata(见图5.3.4);ADDR(main)和ADDR(.rodata)均可在hello的重定位表或者hello执行文件的反汇编文件中得到,再利用图5.3.3中6~8行算法即可得到更新的内容(一个相对地址)。

 

图5.5.4 重定位条目

5.6 hello的执行流程

子程序名

程序地址

hello!_start

0x4010f0

hello!main

0x401125

hello!puts@plt

0x401030

hello!printf@plt

0x401040

hello!atoi@plt

0x401060

hello!sleep@plt

0x401080

hello!getchar@plt

0x401050

hello!exit@plt

0x401070

表5.6 hello执行流程

5.7 Hello的动态链接分析

在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,于是需要一条重定位记录由动态链接加载时来处理。动态链接器使用PLT和GOT来延迟绑定。

由图5.3.2的节头部表可知,.got.plt的起始位置是0x404000,在调用dl_init前后,其值发生了改变(图5.7),即实现了在运行时的链接。

图5.7.1 .got.plt内容(改变前)

 

图5.7.2 .got.plt内容(改变后)

5.8 本章小结

本章主要介绍了链接的概念与作用,对链接得到的可执行文件得的ELF格式文件进行详细分析,并于hello.o的文件进行比较,从而更加具体地表现了链接的执行操作和功能,并具体地介绍、表现了其重定位的原理和过程。

(第5章1分)

6hello进程管理

6.1 进程的概念与作用

概念:进程是操作系统对一个正在运行的程序的一种抽象。其经典定义就是一个执行中程序的实例。

作用:进程作为一个执行中程序的实例,系统中每个程序都运行在某个进程的上下文中,上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。它提供给应用程序两个关键抽象:1)一个独立的逻辑控制流,提供一个程序独占处理器的假象;2)一个私有的地址空间,提供一个程序独占地使用内存系统的假象。

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

Shell是一个交互型应用级程序,代表用户运行其他程序。如Windows下的命令行解释器,cmd、powershell,图形界面的资源管理器。Linux下的Terminal/tcsh、bash等等,也包括图形化的GNOME桌面环境。Shell是信号处理的代表,负责各进程创建与程序加载运行及前后台控制,作业调用,信号发送与管理等。

处理流程:

1、读取用户由键盘输入的命令行。

2、分析命令,以命令名作为文件名,并将其它参数改造为系统调用execve( )内部处理所要求的形式。

3、终端进程调用fork( )建立一个子进程。

4、终端进程本身调用waitpid()来等待子进程完成(如果是后台命令,则不等待)。当子进程运行时调用execve(),子进程根据文件名到目录中查找有关文件,调入内存,执行这个程序。

5、如果命令末尾有&,表示后台执行,此时shell不会等待它完成;否则shell会等待其完成。当子进程完成工作后,向父进程报告,此时终端进程醒来,作必要的判别工作后,终端发出命令提示符,重复上述处理过程。

6.3 Hello的fork进程创建过程

在shell上输入./hello命令时,先判断该命令是否为内置命令,如果是内置命令则立即对其进行解释;不是否则将其看成一个可执行目标文件,再调用fork创建一个新进程并在其中执行。新创建的子进程鸡湖但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码段、段、数据段、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本。二者之间的最大的区别是它们有不同的PID。

6.4 Hello的execve过程

execve函数在当前进程的上下文中加载并运行一个新程序。execve函数加载并运行可执行目标文件,且带参数列表rgv和环境变量列表envp。只有当出现错误时,execve才会返回到调用程序。 

在子进程调用该函数时,它将删除该进程的代码和地址空间内的内容并将其初始化,然后通过跳转到程序的第一条指令或入口点来运行该程序;接着映射私有区域,为新程序的代码、数据等创建新的区域结构;然后映射共享区域,比如动态链接到程序中的标准C库libc.so等;再是设置程序计数器,使之指向代码区域的入口。

6.5 Hello的进程执行

在fork了一个子进程之后,该进程有了一个独立的逻辑控制流。在运行过程中&#x

以上是关于CSAPP程序人生-Hello’s P2P的主要内容,如果未能解决你的问题,请参考以下文章

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

hit csapp大作业 程序人生-Hello’s P2P

CSAPP程序人生-Hello’s P2P

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

CSAPP程序人生

程序人生-Hello’s P2P