CSAPP大作业——程序人生

打印 上一主题 下一主题

主题 1063|帖子 1063|积分 3189

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x

摘  要

    本文主要从hello.c这个小程序入手,对它从预处置惩罚、编译、汇编、链接、进程创建接纳等一步步生成过程进行了深入的剖析和阐明。结合CSAPP这本书和Ubuntu捏造机的相干知识进行了实践操纵,把盘算机体系整个体系串联在一起,做到了理论知识和动手实践的结合。

关键词:盘算机体系;汇编;链接;Ubuntu;csapp                           

(择要0分,缺失-1分,根据内容精彩称都酌情加分0-1分










目  录


第1章 概述.............................................................................................................. - 5-
1.1 Hello简介...................................................................................................... - 5 -
1.2 情况与工具..................................................................................................... - 6 -
1.3 中间结果......................................................................................................... - 6 -
1.4 本章小结......................................................................................................... - 6-
第2章 预处置惩罚......................................................................................................... - 7 -
2.1 预处置惩罚的概念与作用..................................................................................... - 7 -
2.2在Ubuntu下预处置惩罚的下令.......................................................................... - 7 -
2.3 Hello的预处置惩罚结果剖析.............................................................................. - 7 -
2.4 本章小结......................................................................................................... - 8 -
第3章 编译............................................................................................................. - 9 -
3.1 编译的概念与作用......................................................................................... - 9 -
3.2 在Ubuntu下编译的下令............................................................................. - 9 -
3.3 Hello的编译结果剖析.................................................................................. - 9 -
3.4 本章小结....................................................................................................... - 15 -
第4章 汇编........................................................................................................... - 17 -
4.1 汇编的概念与作用....................................................................................... - 17 -
4.2 在Ubuntu下汇编的下令........................................................................... - 17 -
4.3 可重定位目的elf格式............................................................................... - 17 -
4.4 Hello.o的结果剖析.................................................................................... - 21 -
4.5 本章小结....................................................................................................... - 22 -
第5章 链接........................................................................................................... - 23 -
5.1 链接的概念与作用....................................................................................... - 23 -
5.2 在Ubuntu下链接的下令........................................................................... - 23 -
5.3 可执行目的文件hello的格式.................................................................. - 24 -
5.4 hello的捏造地址空间................................................................................ - 26 -
5.5 链接的重定位过程分析............................................................................... - 27-
5.6 hello的执行流程........................................................................................ - 28 -
5.7 Hello的动态链接分析................................................................................ - 29 -
5.8 本章小结....................................................................................................... - 29 -
第6章 hello进程管理................................................................................... - 31 -
6.1 进程的概念与作用....................................................................................... - 31 -
6.2 简述壳Shell-bash的作用与处置惩罚流程..................................................... - 31 -
6.3 Hello的fork进程创建过程..................................................................... - 32 -
6.4 Hello的execve过程................................................................................. - 32 -
6.5 Hello的进程执行........................................................................................ - 32 -
6.6 hello的非常与信号处置惩罚............................................................................ - 33 -
6.7本章小结....................................................................................................... - 36 -
第7章 hello的存储管理............................................................................... - 37 -
7.1 hello的存储器地址空间............................................................................ - 37 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................ - 37 -
7.3 Hello的线性地址到物理地址的变换-页式管理...................................... - 38 -
7.4 TLB与四级页表支持下的VA到PA的变换............................................. - 39 -
7.5 三级Cache支持下的物理内存访问.......................................................... - 40 -
7.6 hello进程fork时的内存映射.................................................................. - 41 -
7.7 hello进程execve时的内存映射.............................................................. - 41 -
7.8 缺页故障与缺页停止处置惩罚........................................................................... - 42 -
7.9动态存储分配管理....................................................................................... - 43 -
7.10本章小结..................................................................................................... - 43 -
第8章 hello的IO管理................................................................................. - 44 -
8.1 Linux的IO设备管理方法.......................................................................... - 44 -
8.2 简述Unix IO接口及其函数....................................................................... - 44 -
8.3 printf的实现分析........................................................................................ - 44 -
8.4 getchar的实现分析.................................................................................... - 44 -
8.5本章小结....................................................................................................... - 44 -
结论......................................................................................................................... - 45 -
附件......................................................................................................................... - 46 -
参考文献................................................................................................................. - 47 -



第1章 概述

1.1 Hello简介

当我们在编写程序的IDE中写下hello程序的末了一个字符,点击“编译并运行”之后,hello便开启了它传奇而又精彩的一生。
首先是关于.c文件的预处置惩罚:预处置惩罚器(cpp)根据以字符#开头的下令,修改原始的C程序。比如hello.c中第一行的#include<stdio.h>下令告诉预处置惩罚器读取体系头文件stdio.h的内容,并把它直接插入程序文本中。如许就得到了另一个C程序通常是以.i作为文件拓展名。总的来说,cpp会将全部的头文件都合成为一个.i文件。
第二步是编译:编译器(ccl)将文本文件hello.i翻译为文本文件hello.s,它包括一个汇编语言程序。该程序包罗函数main的定义,其汇编语句我们之后会提到,在这里先略过。
第三步,汇编:汇编器(as)将hello.s翻译为呆板语言指令,把这些指令打包成一种叫做可重定位目的程序(relocatable object program)的格式,并将结果保存在文件hello.o中。hello.o文件是一个二进制文件,在windows下不能直接查看,但是在linux体系下可以通过链接得到可执行文件并执行。
第四步,链接:我们可以注意到,hello程序中调用了一个printf函数,它是每个C编译器提供的标准C库中的一个函数。Printf函数存在于一个名为printf.o的单独的预编译好了的目的文件中,而这个文件必须以某种方式归并到我们的hello.o程序中,链接器(ld)就负责处置惩罚这种归并。结果就得到hello文件,它是一个可执行目的文件(或简称可执行文件),可以被加载到内存中,由体系执行。
第五步,在Shell中运行:用户在Shell中输入指令./hello。在shell读入下令行之后,首先会判断此下令行是否为内部指令,若为内置下令则直接执行,否则shell会调用fork函数创建一个新的子进程并调用execve函数把hello的内容加载到子进程的地址空间。在hello成为进程之后,便会受到信号的控制,例如键盘输入ctrl+z或ctrl+c分别表示挂起(SIGSTP)和终止(SIGINT)的信号。在键盘输入这些信号之后,体系会根据信号默认行为对相应进程进行操纵。
最终,接纳进程:当Hello执行return语句后,它就将处于一种叫终止的状态(没有运行但仍旧占用内存),也叫僵死进程。为了接纳这些僵死进程,体系会让shell调用waitpid函数让父进程对子进程进行接纳。一旦hello的进程被接纳乐成,hello的生命周期便结束了,我们的hello传奇的一生也就到此终止。
1.2 情况与工具

1.硬件情况:处置惩罚器:AMD Ryzen 7 5800H
                     内存:16GB
                     主显卡:RTX 3060
2.软件情况:Windows 10 64位;Ubuntu20.04
3.工具:gcc,as,ld,vim,edb,gdb,readelf,VScode,VS

1.3 中间结果

文件

作用

hello.c

源文件

hello.i

预处置惩罚后生成的文件

hello.s

编译后生成的文件

hello.o

汇编后生成的可重定位目的程序

hello

最终生成的可执行目的文件

hello.asm

hello.o反汇编生成的文件

hello.elf

hello.o的elf格式文件

hello2.asm

hello反汇编生成的文件

hello2.elf

hello的elf格式文件


1.4 本章小结

本章对hello程序的产生过程从预处置惩罚、汇编到进程处置惩罚等一步步过程作了概括性介绍,并列举出了大作业实验的软硬件情况和使用的工具。之后会将hello生成过程的每一步进行更加详细更加专业性的阐明。

(第1章0.5分)




第2章 预处置惩罚

2.1 预处置惩罚的概念与作用

概念:
预处置惩罚是指程序开始运行时,预处置惩罚器(cpp)根据以字符#开头的下令,修改原始的C程序的过程。例如,hello.c文件中的#include 下令会告诉预处置惩罚器读取体系头文件stdio.h,unistd.h,stdlib.h 的内容,并把这些内容直接插入到程序文本中。别的,预处置惩罚过程还会删除程序中的注释和多余的空白字符。最终通常得到一个以.i作为拓展名的C程序,这个.i文件仍旧是一个文本文件。
作用:

  • 更换#define定义的字符串
  • 处置惩罚特别符号并进行更换
  • 根据#if 后面的条件决定需要编译的代码

2.2在Ubuntu下预处置惩罚的下令

下令格式:cpp hello.c > hello.i(如下图2-1)

(图2-1)


2.3 Hello的预处置惩罚结果剖析

在完成之前的预处置惩罚操纵之后,我们打开生成的hello.i文件可以看到文件内容明显增长,从最初的几行增长到了3060行(展示为下图2-2),在末了几行我们可以找到最初的hello程序。可以发现经过预处置惩罚操纵之后,.i文件的程序也和最初的.c文件有差异,详细体现为:


  • 删除空白和注释
  • 更换宏定义,对程序中的宏定义递归睁开
  • 增长for循环对#if判断语句进行优化

(图2-2)



2.4 本章小结

本章主要介绍了预处置惩罚的概念和作用,通过在Ubuntu下输入预处置惩罚指令将hello.c文件变成hello.i文件为例,详细详细地阐明白linux体系下.c文件经过cpp处置惩罚后生成.i文件的过程。


(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

概念:
编译是指汇编器(ccl)将高级语言程序文本文件(.i文件)翻译成为汇编语言文件(.s文件)的过程,为后续转化为二进制呆板码做预备。编译主要分为三个步调:

  • 词法与语法分析:将字符串转化为标记流,之后将此标记流生成语法树。
  • 转化目的代码:将语法树转化为目的代码
  • 代码优化:将代码进行面向体系的主动优化

作用:
将高级语言转化为汇编语言,为之后转化为二进制呆板码做预备

3.2 在Ubuntu下编译的下令

       下令格式:gcc -m64 -no-pie -fno-PIC -S hello.i -o hello.s(如下图3-1)

(图3-1)

3.3 Hello的编译结果剖析

3.3.1从主函数分析数据类型和数据段:
(图3-2)

(图3-3)

      从主函数的布局中(图3-2、3-3)我们可以看到一些数据类型,下面对主函数中出现的这些数据类型进行逐一阐明:
.file:源文件,后接源文件名
.data:数据段,存储已经初始化的全局变量和静态变量
.text:代码段
.rodata:只读代码段
.align:地址对齐格式
.string:字符串段
.global:全局变量
.size:数据空间巨细

下面我们将对各种数据类型和各种操纵进行详细阐明。

3.3.2:各种数据类型
      1.整数:
      (1)int i:局部变量,在程序运行中被存储在寄存器或者栈空间中,从下图(图3-4)可以看出局部变量i占用了四个字节的地址
(图3-4)



      (2)int argc :是main函数的参数,保存在堆栈中
      (3)3:立刻数3,在汇编语言中以$3给出

      2.数组:
      (1)char *argv[]:是主函数的第二个参数,由汇编代码(图3-5)可以看出此数组的首地址保存在栈中,通过地址偏移访问各个数组内元素。在访存时,使用%rax寄存器间接访存。
(图3-5)


3.字符串:通过查看汇编代码,我们可以发现hello.s中存储了两个字符串,
(如下图3-6),他们分别对应源程序中的第14行和第18行(如图3-7)




                     (图3-6)                              (图3-7)
       在图3-5中第6行,我们可以看到除了英文Hello和数字学号以外,其他的男人都以\xxx的形式给出,其表示的是UTF-8编码,一个汉字由三个字节表示。两个string字符串都存放在.rodata只读数据段中

3.3.3:赋值操纵
程序中有非常多地方涉及赋值操纵,其中绝大部分由mov指令完成,例如下图(图3-8)中对%edi赋值的操纵
(图3-8)

值得注意的是,mov指令后缀指的是操纵数的位数,不同的后缀字母表示不同位数,例如b—1B, w—2B, l—4B, q—8B。

3.3.4:算术操纵
      汇编代码中涉及最多的算术操纵就是add操纵,例如下图(图3-9)中末了一行addl,便是在每次循环结束后对栈顶指针进行+4的操纵。其次,汇编语言中另有很多本程序中没有涉及到的算术操纵,如图3-10所示。
(图3-9)
(图3-10)


3.3.5:关系控制转移操纵
对比原程序和生成的.s汇编代码,我们可以发现汇编代码中有两处条件转移操纵(图3-11、3-12),它们分别对应源代码中的if判断和for循环的判断(图3-13)

(图3-11)           
(图3-12)             
(图3-13)



      1.if判断语句的分析:上图中if判断语句的内容为if(argc!=4),即当argc不为4时执行printf和exit函数,否则执行下一行(进行for循环)。其汇编代码对应为图3-10,使用cmpl $4,-20(%rbp),比较 argc与4是否相称,若相称,则跳转至.L2,不执行后续部分内容;若不等则继续执行函数体内部对应的汇编代码。值得注意的是,在汇编代码的控制转移操纵中,为了到达控制的目的,cmp操纵通常和jmp操纵结合。例如本if判断语句的汇编代码:
cmpl  $4,  -20(%rbp)
je     .L2
第一行表示将%rbp栈顶指针-20的地址位置的内容与立刻数4进行32位数的对比,第二行je即指若二者相称则执行跳转指令,跳转到L2,否则继续执行下一行指令。Je的含义是相称时跳转,jmp操纵有特别多的类型,我们在下图3-14中列出。


(图3-14)

2.for循环操纵结束条件的判断:源程序中for循环语句为:for(i=0;i<9;i++),当i < 10时进行循环,每次循环i++。在hello.s中的汇编语句体现在图3-15中的cmpl与jle下令中。利用cmpl  $8,  -4(%rbp)比较i是否与8相称,当i<=8时则继续循环,进入.L4,i>8时跳出循环。
(图3-15)

3.3.6:函数操纵
      在C语言中,函数是一种过程,提供了一种封装代码的方式,用一组指定的参数和可选的返回值实现某种功能。函数调用主要涉及三个要点:(1)传递控制:在调用函数进行某个过程的时候,程序计数器必须设置为调用点代码的起始地址,便于调用完成之后的地址返回。(2)传递数据:在调用函数时,程序需向调用函数提供参数(偶尔也可不提供),调用函数大部分时候会返回一个值。(3)分配、释放内存:在调用函数时,体系需为函数局部变量分配空间,在函数return之后再释放这些空间。
通过观察汇编程序,我们可以发现函数参数传递使用的寄存器序次是一定的,64位体系设有6个函数传递寄存器,其序次依次是:%rdi, %rsi, %rdx, %rcx, %r8, %r9除此之后再进行参数传递就会通过栈空间。接下来我们将对.s文件中涉及的函数进行分析:


  • main函数:(汇编内容见上方图3-3)
main函数传入了两个参数,分别为argc和argv[],分别用寄存器%rdi和%rsi存储。在程序运行时,main函数被主动调用。末了几行表示出main函数的返回通过%eax实现,值为0,对应return 0 。



  • exit函数:图3-16
(图3-16)

      exit函数在main函数的末了被调用执行,其作用为传入参数1之后执行退出下令。



  • sleep函数:图3-17
(图3-17)

      sleep函数的汇编代码在.L4段中,对应源程序中的for循环中的sleep函数段。其传入的参数为atoi(argv[3]),在for循环中被调用,作用是使体系休眠。


  • getchar函数:图3-18
(图3-18)

      getchar函数在.L3段,即主函数段中被调用。作用为读入用户输入的内容。



  • printf函数:图3-19


(图3-19)
      printf函数详细体现为.LC段中的两个.string,.string中的内容即为pirntf函数打印的内容。其含义已在上笔墨符串中阐述过,此处略过。


3.3.7:类型转换操纵
      .s文件中有一处数据类型转换的操纵,详细体现在图3-20中
(图3-20)

      这里进行类型转换的原因是sleep函数的参数为int值,而argv为字符串数组,所以汇编时用atoi将字符串转化成int型。在.s中用call语句调用atoi函数强制处置惩罚该类型转换。


3.4 本章小结

本章的关键词是编译,.i文件通过编译器ccl生成汇编程序.s。汇编语言是C程序经过编译器处置惩罚后的一种语言形式,相对于高级程序语言,它更加底层,也更加贴近于呆板码。本章通过Ubuntu上详细的实例详细介绍了编译的概念和过程实现。展示了hello.c文件是怎样一步步转化为汇编代码的,阐明白源程序中的各代码段和汇编语言代码段中的对应关系。
对Hello的编译结果进行重点阐明,首先介绍了.s文件中出现的各种文件布局及其含义,例如.text、.global、.data等。之后分析了数字、字符串、数组等数据布局在汇编语言中的形式和含义。末了对.s文件中的各种操纵控制(如条件跳转、算术操纵、赋值操纵等)进行了分析,阐明白各个代码含义和流程。为下一章汇编做下了预备。
(第32分)


第4章 汇编

4.1 汇编的概念与作用

       概念:
       汇编器(as)将汇编语言(例如hello.s文件)翻译成呆板语言指令(例如hello.o文件)的过程称为汇编,其中这个.o文件还是一个可重定位目的程序,可以在下一步继续链接。
       作用:
       将汇编代码转换为盘算机能够明白并执行的二进制呆板代码,这个二进制呆板代码是程序在本呆板上的呆板语言的表示。

4.2 在Ubuntu下汇编的下令

下令格式:gcc hello.s -c -o hello.o 或 as hello.s -o hello.o (本章采取前者)
展示如下图4-1
(图4-1)



4.3 可重定位目的elf格式

    为了查看.o文件的elf格式,我们可以采取工具readelf,详细下令格式为:readelf -a hllo.o > hello.elf(图4-2)。如许我们就可以在新生成的hello.elf文件中直接查看elf格式内容了。

(图4-2)



hello.elf的内容详细内容分析如下:


  • elf头(elf header)(图4-3):elf头形貌了生成该文件的体系的字的巨细和字节序次,elf头包罗了资助链接器语法分析息争释目的文件的信息,其中包括elf头巨细、目的文件类型、呆板类型、节头部表的文件偏移,以及节头部表中条目的巨细和数量等相干信息。

(图4-3)




  • 节头部表(图4-4):包罗了文件中出现的各个节的语义、节的类型、位置和巨细等信息。节头中的代码是可执行的,但是不能写;数据段和只读数据段都不可执行,而且只读数据段也不可写。节头中包罗了很多节(也叫段),有的节之前在3.3.1中介绍过,这里我们再介绍更多之前未提及的节:
(1).rela.text节:一个.text节中位置的列表。
(2).bss节:未初始化的全局和静态C变量,以及全部被初始化为0的全局或静态变量存储在其中。在目的文件中这个节不占据实际的空间,它仅仅是一个占位符。
(3).comment节:显示版本控制信息。
(4).note节:注释节详细形貌。
(5).eh_frame节:处置惩罚非常。
(6).strtab节:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部的节名字。
(7).symtab节:本节用于存放符号表。
(8).shstrtab节:包罗节区名称。

(图4-4)




  • 重定位节(图4-5):显示各个段引用的外部符号等,在链接时,需要通过重定位节对这些位置的地址进行修改,这里链接器对elf文件的重定位节的操纵我们会在之后的下一章链接中介绍。
本程序需要重定位的信息有:.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar等符号。

(图4-5)




  • 符号表(图4-6):也是.symtab节,其作用是存放程序中定义和引用的函数和全局变量的信息。重定位需要引用的符号都在其中声明。

(图4-6)



4.4 Hello.o的结果剖析

根据要求,使用objdump -d -r hello.o > hello.asm 生成hello.o的反汇编文件hello.asm(图4-7),之后查看hello.asm,并与第3章的 hello.s进行对照分析。

(图4-7)


hello.asm这个反汇编文件的全部内容如下图(图4-8)

(图4-8)

通过与第三章生成的hello.s汇编语言文件进行对照分析,我们可以发现以下几点不同:

  • 反汇编语言中mov、add等操纵后方无q, b, l等字符后缀。
  • 反汇编中利用call调用函数时会直接体现出函数名,且call的目的地址是当前指令的下一条指令,而汇编语句直接利用地址进行call调用。
  • 反汇编代码中使用相对偏移地址取代了汇编语句中的标记位。
  • 反汇编与汇编在全局变量访问上不同,汇编语言对于全局变量的访问为LC0和sleepsecs(%rip),而在反汇编代码中是$0x0和0(%rip),原因与函数调用一样,全局变量的地址也是在运行时才确定,访问也需要经过重定位。


4.5 本章小结

本章对汇编这一操纵进行了详细的介绍,特别是对汇编产生的结果文件(elf等)进行了深入分析。在汇编过程中,hello.s被汇编器变为hello.o文件,此时hello.o已经是二进制文件,可以直接被呆板读懂,其中的可重定位条目也为下一阶段——链接做下了预备。
其次,我们剖析了可重定位目的elf格式,对它的elf头,节头,符号表等重要组成部分都进行了分析和阐明,阐述了其中各个符号、字段的含义。
末了,我们还将.o文件进行了反汇编,将生成的hello.asm文件与之前的.s文件进行对比,体现出了反汇编语句与汇编语句的区别与联系。
到此为止,我们隔断生成可执行文件还差末了一步,就是链接,其详细内容我们将在下一章讲到。


(第41分)


第5章 链接

5.1 链接的概念与作用

概念:
链接是将各种不同文件的代码和数据部分网络(符号剖析和重定位)起来并组合成一个单一文件的过程。

作用:
把可重定位目的文件和下令行参数作为输入,产生一个完全链接的,可以加载运行的可执行目的文件

例如:我们在最初的hello.c程序中调用了printf函数,其printf函数是存在于一个名叫printf.o的已经编译好的文件之中。为了能够正常的调用执行printf函数和生成可执行文件hello,我们就必须通过链接器ld将这个printf.o文件整合到hello.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-1)


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

在shell中输入下令:readelf -a hello > hello2.elf(用以区别之前生成的hello.elf文件),如许我们就生成了所需的elf文件,直接打开查看即可。


  • elf头(elf header):(图5-2)

(图5-2)


其格式与内容与上一章中的elf文件根本类似。但也有几处差异:

  • 类型:由REL(可重定向目的文件)变为EXEC(可执行文件);
  • 程序入口点:由0x0(未确定)变为了0x4010f0;
  • 程序和节的起始位置和巨细都有改变;节个数由14个变为了27个。


  • 节头:(由于节头较长,这里我们截取部分分析,见图5-3)

(图5-3)

由上图可见,hello2.elf的节头布局和内容与上一章的hello.elf根本相同。都展示了节的类型、位置、偏移量和巨细等信息。但是相对于hello.elf,我们这次新生成的elf文件内容明显增多了。节从14个变成了27个。



  • 符号表:(由于节头较长,这里我们截取部分分析,见图5-4)
(图5-4)


由上图可见,hello2.elf的符号表布局和内容与上一章的hello.elf根本相同。保存着定位、重定位程序中符号定义和引用的信息,全部重定位需要引用的符号都在其中声明。但hello2.elf相对而言内容更多。



  • Dynamic section:(图5-5)

(图5-5)

       Dynamic section中包罗很多信息,比如你需要的动态库,以及是否立刻加载以及符号表等。在got表的第一项指像.Dynamic,并且延迟绑定那个的时候需要传入.Dynamic的对象地址,因为里面包罗剖析函数地址的上下文。


  • 程序头:(图5-6)

(图5-6)

       程序头在程序执行的时候被使用,作用是告诉链接器运行时应该加载的内容并提供动态链接的信息,提供了各段在捏造地址空间和物理地址空间的巨细,位置,标记,访问权限和对齐等信息。



    • hello的捏造地址空间

使用edb打开我们生成的可执行文件hello,通过edb的Data Dump窗口查看加载到捏造地址中的hello程序。(图5-7)

(图5-7)

通过观察上图我们可以发现,hello文件的捏造地址空间是从0x401000-0x402000。我们通过查阅捏造空间相干资料可以知道,一个linux进程的捏造内存布局如下图(图5-8),由低地址0x4000000开始向高地址依次是.text, .data, .bss, 运行时堆,用户栈,内核代码数据等等。
(图5-8)


为了清晰每个段在捏造内存地址中的详细位置,我们还要结合图5-6中的VirtAddr查看,在图5-6中一共包罗7个段:
1. PHDR保存程序头表。
2. INTERP指定在程序已经从可执行文件映射到内存之后,必须调用的解释器(如动态链接器)。
3. LOAD表示一个需要从二进制文件映射到捏造地址空间的段。其中保存了常量数据(如字符串)、程序的目的代码等。
4. DYNAMIC保存了由动态链接器使用的信息。
5. NOTE保存辅助信息。
6. GNU_STACK:权限标记,标记栈是否是可执行的。
7. GNU_RELRO:指定在重定位结束之后那些内存地区是需要设置只读。

通过Data Dump查看捏造地址段0x600000~0x602000,在0~fff空间中,与0x400000~0x401000段的存放的程序相同,即在fff之后存放的是.dynamic~.shstrtab节




    • 链接的重定位过程分析

在Shell中输入下令objdump -d -r hello > hello2.asm生成反汇编文件hello2.asm(便于与hello.asm区分)。接下来我们将把hello2.asm与第四章中生成的hello.o.asm文件进行比较,并分析主要的不同之处:

       这里我们由于hello2.asm长度较长,我们截取一部分展示(图5-9):
(图5-9)

       经过比较,二者主要有以下不同:

  • 函数数量明显增长:在hello2.asm新增了plt,puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt等函数的代码。这是因为动态链接器将共享库中hello.c用到的函数参加可执行文件中。
  • 节数量明显增长:hello中增长了.init和.plt节,和一些节中定义的函数。
  • hello.asm中相对偏移地址变成了hello2.asm中的捏造内存地址。
  • hello2.asm中没有重定位条目,是因为在链接这一步已经完成文件重定位。

综上我们可以总结出重定位的主要过程步调:(1)归并相同的节:ld将全部相同类型的节归并成为同一类型的新节,例如全部文件的.data节归并成一个新的.data节,归并完成后该新节即为可执行文件hello的.data节。(2)确定地址:之后ld再分配内存地址赋给新的节以及符号。地址确定后全局变量、指令等均具有唯一的运行时地址。




    • hello的执行流程

使用gdb调试工具中的rbreak指令可以直接查看hello内部每个函数的断点,即能查看函数运行流程及其地址。(图5-10)
(图5-10)

5.7 Hello的动态链接分析

首先通过查看之前生成的hello2.elf表找到.got段(图5-11),发现got的地址为0x403ff0
(图5-11)

进入edb中找到该地址,并设置断点,在运行do_init前后查看got表的变化,可以发现got表在调用前后发生了改变,GOT[1]指向重定位表(.plt节需要重定位的函数运行时地址),作用是确定调用函数的地址,GOT[2]指向动态链接器ld-linux.so运行时地址。如许,接下来执行程序的过程中,就可以使用过程链接表PLT和全局偏移量表GOT进举措态链接。


5.8 本章小结

       本章主要介绍了链接的相干知识。链接是生成可执行目的文件的末了一步,它的主要步调分为符号剖析和重定位。本章就重定位睁开了深入的阐明,给出了hello程序中elf格式文本hello2.elf,据此分析了hello2.elf与hello.elf的异同,对重定位的过程和步调给予相识释阐明,介绍了hello文件的捏造地址空间组成。自从执行hello文件开始,链接的任务就结束了,接下来hello就是一个进程,我们也将从进程管理的角度深入探究hello程序的进程。

(第51分)



第6章 hello进程管理

6.1 进程的概念与作用

概念:
     进程的经典定义就是一个执行中程序的实例。在当代体系上运行一个程序时,我们会得到一个假象,就好像我们的程序是体系中当前运行的唯一的程序一样。我们的程序好像是独占地使用处置惩罚器和内存。处置惩罚器就好像是无间断地一条接一条地执行 我们程序中的指令。末了,我们程序中的代码和数据好像是体系内存中唯一的对象。这些假象都是通过进程的概念提供给我们的。

作用:
每次运行程序时,shell创建一新进程,在这个进程的上下文切换中运行这个可执行目的文件。应用程序也能够创建新进程,并且在新进程的上下文中运行它们自己的代码或其他应用程序。
进程提供给应用程序的关键抽象:一个独立的逻辑控制流,如同程序独占处置惩罚器;一个私有的地址空间,如同程序独占内存体系。


6.2 简述壳Shell-bash的作用与处置惩罚流程

作用:
Shell 是一个用C语言编写的交互型应用程序,代表用户运行其他程序。Shell 应用程序提供了一个界面,用户可以通过这个界面进行体系的根本操纵,访问操纵体系内核的服务。

Shell的处置惩罚流程:

  • 从Shell终端读入输入的下令。
  • 切分输入字符串,获得并辨认全部的参数
  • 若输入参数为内置下令,则立刻执行
  • 若输入参数并非内置下令,则调用相应的程序为其分配子进程并运行
  • 若输入参数非法,则返回错误信息
  • 处置惩罚完当前参数后继续处置惩罚下一参数,直到处置惩罚完毕


6.3 Hello的fork进程创建过程

在输入下令执行hello后,父进程如果不是内部指令,即会通过fork函数创建子进程。子进程与父进程近似,并得到一份与父进程用户级捏造空间相同且独立的副本——包括数据段、代码、共享库、堆和用户栈。父进程打开的文件,子进程也可读写。二者之间最大的不同或许在于PID的不同。fork函数只会被调用一次,但会返回两次,在父进程中,fork返回子进程的PID,在子进程中,fork返回0。



    • Hello的execve过程

调用函数fork创建新的子进程之后,子进程会调用execve函数,在当前进程的上下文中加载并运行一个新程序hello。execve 函数不返回,它将删除该进程的代码和地址空间内的内容并将其初始化,然后通过跳转到程序的第一条指令或入口点来运行该程序,将私有的地区映射进来。这个函数是在体系目的文件ctrl.o中定义的,对全部的c程序都一样。_start函数调用体系启动函数,_libc_start_main,该函数定义在libc.so里,初始化情况,调用用户层的main函数,处置惩罚main函数返回值,并且在需要的时候返回给内核。

6.5 Hello的进程执行

1. 用户模式和核心模式
处置惩罚器通常是用某个控制寄存器中的一个模式位来提供这种功能的,该寄存器形貌了进程当前享有的特权。当设置了模式位时,进程就运行在内核模式中(偶尔也叫超等用户模式)。一个运行在内核模式的进程可以执行指令会集的任何指令,并且可以访问体系中的任何内存位置。
没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令,比如停止处置惩罚器。改变模式位,或者发起一个IO操纵。也不允许用户模式中的进程直接引用地址空间中内核区的代码和数据。任何如许的实验都将导致致命的保护故障。反之,用户程序必须通过体系调用接口间接地访问内核代码和数据。

2. 上下文
       上下文就是内核重新启动一个被抢占的进程所需要恢复的原来的状态,由寄存器、程序计数器、用户栈、内核栈和内核数据布局等对象的值构成。操纵体系内核使用一种称为上下文切换的较高层形式的非常控制流来实现多任务。下图(图6-1)就是一个上下文切换的例子,进程A初始运行在用户模式中,直到它通过执行体系调用read陷入到内核。内核中的陷阱处置惩罚程序请求来自磁盘控制器的DMA传=传输,并且安排在磁盘控制器完成从磁盘到内存的数据传输后,磁盘停止处置惩罚器。
(图6-1)


3.进程时间片:一个进程执行它的控制流的一部分的每一时间段,多任务也称为时间分片

4.调度的过程:

在进程执行的某些时候,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,是由内核中称为调度器的代码处置惩罚的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。

6.6 hello的非常与信号处置惩罚

1.正常运行状态:分别展示了不敷三个参数和满意三个参数的情况(设定休眠为5s)(图6-2)
(图6-2)

2.乱按键盘的情况:会把回车之前的字符串看成下令,在hello结束之后实验运行(图6-3)

(图6-3)



3.Ctrl + C:(图6-4)
从键盘输入此下令会让操纵体系内核发送一个SIGINT信号给到前台进程组中的每个进程,结果是终止前台进程,通过ps下令发现这时hello进程已经被接纳。

(图6-4)




4.Ctrl + Z:(图6-5)
      与上方Ctrl+C类似,但是不同的是发送的是SIGSTP信号,结果是挂起进制不终止,在挂起之后还可以执行jobs等指令,输入fg可以取消挂起,继续执行。

(图6-5)


5.pstree查看进程树状图:(图6-6)
       在Shell中输入pstree下令,可以将全部进程以树状图显示(图6-6仅展示部分)

(图6-6)



6.Kill指令杀死指定进程:(图6-7)
(图6-7)




6.7本章小结

本章重点介绍了进程,以及Shell的根本概念和使用。主要以hello可执行文件的进程创建、终止、接纳等操纵为例,展示了linux下进程的相干实践操纵。
别的,本章还介绍了很多有关信号的知识,重点介绍了体系对各种信号的处置惩罚,例如Ctrl+C发出SIGINT信号终止进程、Ctrl+Z发出SIGSTP信号挂起进程等。每种信号都有不同的默认行为,体系也有相应的不同的处置惩罚机制。

(第61分)


第7章 hello的存储管理

7.1 hello的存储器地址空间

7.1.1 逻辑地址:
逻辑地址由选择符和偏移量两部分组成。要经过寻址方式的盘算或变换才得到内存储器中的物理地址。即hello中的偏移地址。

7.1.2 线性地址:
       线性地址由逻辑地址经过转换得到。先用段选择符到全局形貌符表中取得段基址,再加上段内偏移量,即得到线性地址。在IA64中,我们以为线性地址就是逻辑地址。

7.1.3 捏造地址:
       在IA32中,捏造地址需要经过到线性地址再到物理地址的变换。IA6中,我们以为捏造地址就是逻辑地址,也是线性地址。

7.1.4 物理地址
       物理地址是寻址物理内存的地址,是地址变换的最闭幕果地址。体系可以根据物理地址直接访存。如果启用了分页机制,那么hello的线性地址会使用页目录和页表中的项变换成hello的物理地址;如果没有启用分页机制,那么hello的线性地址就直接成为物理地址了。

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

一个逻辑地址由段标识符和段内偏移量两部分组成。其中段标识符是一个16位长的字段组成,也叫段选择符,其前13位是一个索引号。后面三位包罗一些硬件细节。如下图(图7-1)。
(图7-1)

    由上图可以知道,索引号就是段选择符的前13位。不同的段有不同的索引号,我们主要通过索引号寻找段。
   当体系给定一个完备的逻辑地址段选择符+段内偏移地址,时我们就可以开始进行逻辑地址到线性地址的变换(即段式管理):
   1.看段选择符的T1=0还是1,知道当前要转换是全局段形貌符表(GDT)中的段,还是局部段形貌符表(LDT)中的段,再根据相应寄存器,得到其地址和巨细。我们就有了一个数组了。
   2.拿出段选择符中前13位,可以在这个数组中,查找到对应的段形貌符,即基地址就知道了。
3.把基址加上偏移量,就是要转换的线性地址了


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

线性地址(VA)到物理地址(PA)的转化是CSAPP书中的重点,它是地址翻译过程中最重要的一部分。这一过程主要由页式管理负责,其总体过程如图7-2所示。
在linux体系下,每个段被分割捏造页(VP)巨细的块,用来作为进行数据传输的单位,每个捏造页巨细为4KB。MMU(内存管理单位)负责地址翻译,它使用存放在物理内存中的页表将捏造页到物理页的映射,即捏造地址到物理地址的映射。

当体系确定VPN和VPO的位数之后,就可以通过页表基址寄存器在页表中获得条目PTE,一条PTE中包罗有效位、权限信息、物理页号等信息。若有效位是0 + NULL则代表没有在捏造内存空间中分配该内存,如果是有效位0 + 非NULL,则代表在捏造内存空间中分配了但是没有被缓存到物理内存中,如果有效位是1则代表该内存已经缓存在了物理内存中,可以得到其物理页号PPN,与捏造页偏移量共同构成物理地址PA。

(图7-2)


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

首先介绍对一些有关地址翻译的器件进行开端介绍:

  • TLB:后备缓冲器,也被翻译为页表缓存、转址旁路缓存,为CPU的一种缓存,由存储器管理单位用于改进捏造地址到物理地址的转译速率。TLB具有固定数量的空间槽,用于存放将捏造地址映射至物理地址的标签页表条目。其搜刮关键字为捏造内存地址,其搜刮结果为物理地址。如果请求的捏造地址在TLB中存在,TLB将给出一个非常快速的匹配结果,之后就可以使用得到的物理地址访问存储器。如果请求的捏造地址不在 TLB 中,就会使用标签页表进行虚实地址转换,而标签页表的访问速率比TLB慢很多。有些体系允许标签页表被互换到次级存储器,那么虚实地址转换大概要花非常长的时间。
  • 页表条目:页表就是一个页表条目(Page Table Entry,PTE)的组成的数组。每个 PTE 至少由一个有效位(valid bit)和一个N位地址字段(PFN)组成。有效位表明该捏造页是否被缓存到内存中。地址字段指向页的起始位置。

接下来是VA到PA的详细过程的介绍,这里以Core i7为例(流程图见图7-3):

假设捏造地址空间 48 位,物理地址空间 52 位,页表巨细 4KB,4 级页表。TLB 4 路 16 组相联。则我们可以知道:由一个页表巨细 4KB,一个 PTE 条目8B,一共4个页表共使用36位二进制索引,故 VPN 共 36 位,则 VPO 12 位;因为 TLB 16 组,则TLBI 4 位,因为 VPN 36 位,所以 TLBT 32 位。

CPU 产生捏造地址 VA,VA 传送给 MMU,MMU 使用前 36 位 VPN 作为 TLBT(前 32 位)+TLBI(后 4 位)向 TLB 中匹配,如果掷中,则得到 PPN (40bit)与 VPO组合成 PA。 如果 TLB 中没有掷中,MMU 向页表中查询, VPN1确定在第一级页表中的偏移量,查询出 PTE,如果在物理内存 中且权限符合,确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到 PPN,与 VPO 组合成 PA,并且向 TLB 中添加条目。如果查询 PTE 的时候发现不在物理内存中,则引发缺页故障。如果发现权限不够,则引发段错误。

(图7-3)



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

我们仍旧以Core i7为例,三级Cache进行物理访存的流程图依然用上图(图7-3)为例:
经过上一节地址翻译,我们已经得到了物理地址PA,根据Cache的相干布局,体系会将PA分为CT(标记位),CS(组号),CO(偏移量)三部分。根据CS寻找到精确的组,比较每一个Cache行是否标记位有效以及CT是否相称。如果掷中就直接返回想要的数据,如果不掷中,就依次去L2,L3,主存判断是否掷中,当掷中时,将数据传给CPU同时更新各级cache的Cache行(如果cache已满则要采取换入换出战略)。
这里我们再对上一天然段中Cache行满时的更换战略做一些阐明:
常见的更换算法有:随机更换算法,先进先出更换算法(FIFO,First In First Out)、最不经常使用更换算法(LFU,Least FrequencyUsed)
1.随机更换算法
Cache 在发生更换时随机的选取一行换出。这种更换战略速率快、硬件实现简朴,但是有大概更换最近访问行而降低 Cache 的掷中率。
2.先进先出更换战略
将更换规则与访问地址的历史序次相干联,每次都把最先放入的一行数据块被更换出,但是该计划方法与 Cache 计划原理的局部性原理相悖,并不能反应数据块的使用频率。
3.最不经常使用更换算法
LFU 是更换最近一段时间内长期未被访问且访问频率最低的 Cache 行数据,这种战略是通过设置计数器来完成计数每个数据块的使用频率。每行设置一个从 0 开始的计数器,计数器在每次被访问时自增 1。当由于发生 Cache 缺失时,需要更换计数值最小行,同时清零这些行的计数器。这种更换算法只计数到两次特定时间内,不能严酷反映出近期访问情况,与局部性原理不符。

7.6 hello进程fork时的内存映射

       fork函数的相干知识已经在之前的第六章中详细介绍过,这里我们就着重介绍关于fork的内存映射。
当fork函数被当前进程调用时,内核为新进程创建各种数据布局,并分配给它一个唯一的PID,同时为这个新进程创建捏造内存。
它创建了当前进程的mm_struct、地区布局和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个地区布局都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的捏造内存刚好和调用fork时存在的捏造内存相同。当这两个进程中的任一个后来进行写操纵时,写时复制机制就会创建新页面。因此,也就为每个进程保持了私有空间地址的抽象概念。


7.7 hello进程execve时的内存映射

execve函数加载并运行hello需要以下几个步调:

  • 删除已存在的用户地区:删除当前进程hello捏造地址的用户部分中的已存在的地区布局。
  • 映射私有地区:为新程序的代码、数据、bss和栈地区创建新的私有的、写时复制的地区布局。其中,代码和数据地区被映射为hello文件中的.text和.data区。bss地区是请求二进制零的,映射到匿名文件,其巨细包罗在hello中。栈和堆地区也是请求二进制零的,初始长度为零。
  • 映射共享地区:若hello程序与共享对象或目的(如标准C库libc.so)链接,则将这些对象动态链接到hello程序,然后再映射到用户捏造地址空间中的共享地区内。
  • 设置程序计数器:末了,execve设置当前进程上下文中的程序计数器,使之指向代码地区的入口点。

关于加载器是怎样映射用户地址空间的,这里也有一张示意图作参考:(图7-4)
(图7-4)


7.8 缺页故障与缺页停止处置惩罚

       当发生缺页故障时,硬件和操纵体系内核会协同进行缺页的处置惩罚。大要流程如图7-5所示:

(图7-5)



(1)处置惩罚器生成一个捏造地址,并把它传送给 MMU
(2)MMU 生成 PTE 地址,并从高速缓存/主存请求得到它。
(3)高速缓存/主存向 MMU 返回 PTE
(4)PTE 中的有效位是零,所以 MMU 触发了一次非常,传递 CPU 中的控制到操纵体系内核中的缺页非常处置惩罚程序。
(5)缺页处置惩罚程序确定出物理内存中的捐躯页,如果这个页面已经被修改了,则把它换出到磁盘。
(6)缺页处置惩罚程序页面调入新的页面,并更新内存中的 PTE。
(7)缺页处置惩罚程序返回到原来的进程,再次执行导致缺页的指令。CPU 将引起缺页的捏造地址重新发送给 MMU。

       同时我们值得注意的是,缺页故障是一种非常,所以它也会造成非常停止处置惩罚。当MMU中查找页表时发现所需地址不在内存中,就会发生故障。其处置惩罚流程大抵如下图7-6所示。

(图7-6)


7.9动态存储分配管理(课程内容未涉及,本节跳过)

以下格式自行编排,编辑时删除
Printf会调用malloc,请简述动态内存管理的根本方法与战略。
7.10本章小结

       本章主要介绍了存储管理的各种相干知识。包括捏造地址(VA)、物理地址(PA)以及这二者的转换方式——地址翻译。别的另有fork、execve等函数的内存映射的内容。末了还介绍了一些Cache的寻址方法以及缺页时的故障处置惩罚等。

(第7 2分)


第8章 hello的IO管理

(课程未涉及,本章跳过)

8.1 Linux的IO设备管理方法

以下格式自行编排,编辑时删除
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数

以下格式自行编排,编辑时删除
8.3 printf的实现分析

以下格式自行编排,编辑时删除
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write体系函数,到陷阱-体系调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析

以下格式自行编排,编辑时删除
异步非常-键盘停止的处置惩罚:键盘停止处置惩罚子程序。接受按键扫描码转成ascii码,保存到体系的键盘缓冲区。
getchar等调用read体系函数,通过体系调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结

以下格式自行编排,编辑时删除
(第81分)

结论

hello.c虽然是一个简朴的小程序,但是它在盘算机体系中从预处置惩罚到末了进程接纳的一生也可谓是波澜壮阔。在它被接纳进程的末了一刻,它或许会回忆自己的一生:

  • 预处置惩罚:预处置惩罚器cpp会将hello.c中的各种头文件插入文本中,删除多余空白字符和注释,生成hello.i,等候后续处置惩罚。
  • 编译:hello.i文件在编译器ccl的作用下被翻译成汇编语言文本hello.s。
  • 汇编:将hello.s在汇编器as的作用下变为可重定位目的文件hello.o。
  • 链接:将hello.o与可重定位目的文件(例如printf.o文件)和动态链接库链接成为可执行目的程序。
  • 运行:在shell中输入: ./hello即运行可执行文件hello。
  • 进程创建:体系会使用fork函数为hello产生一个进程。
  • 执行指令:CPU按照二进制呆板码一步步执行hello中的指令。
  • 存储管理:MMU将VA翻译为PA,PA再进一步访存。别的另有fork和execve函数进行内存的映射。
  • 信号处置惩罚:在运行的过程中,体系会处置惩罚各种大概出现的信号(比如键盘输入的Ctrl+C或Ctrl+Z等),针对信号的默认行为采取不同的操纵。
  • 进程接纳:执行完毕末了return的hello程序会被shell中的父进程接纳,删除它全部的相干数据,从此结束了它的一生。

    作为一个将来的程序员,明白各种语言和编写目的代码只是根本的要求。更重要的是,我们需要明白程序在盘算机体系中真正的运行过程,知道它每一步的生成文件和背后的原理,相识程序底层的运行逻辑。如许会让我们可以面对体系,面对CPU写出更优化的代码,也可以办理更多的看似匪夷所思的bug。


(结论0分,缺失 -1分,根据内容酌情加分)


附件


文件

作用

hello.c

源文件

hello.i

预处置惩罚后生成的文件

hello.s

编译后生成的文件

hello.o

汇编后生成的可重定位目的程序

hello

最终生成的可执行目的文件

hello.asm

hello.o反汇编生成的文件

hello.elf

hello.o的elf格式文件

hello2.asm

hello反汇编生成的文件

hello2.elf

hello的elf格式文件


(附件0分,缺失 -1分)


参考文献

为完本钱次大作业你翻阅的册本与网站等

[1] 《深入明白盘算机体系》 Randal E.Bryant  David R.O’Hallaron 机械工业出书社
[2]  Shell知识大全_shell学习资料_大数据点滴的博客-CSDN博客
[3]  动态内存分配_努力啃C语言的小李的博客-CSDN博客
[4]  C语言Printf函数深入剖析_printf函数原型_猿来不是梦的博客-CSDN博客
[5]  ELF格式解读 Dynamic节_elf .dynamic_不会写代码的丝丽的博客-CSDN博客

(参考文献0分,缺失 -1分)




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

尚未崩坏

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表