HELLO:P2P

//以下内容为个人搜集资料、查阅资料书而写,如有错误,欢迎指正评论

目  录

第1章 概述................................................................................... - 4 -

1.1 Hello简介............................................................................ - 4 -

1.2 环境与工具........................................................................... - 4 -

1.3 中间结果............................................................................... - 4 -

1.4 本章小结............................................................................... - 4 -

第2章 预处理............................................................................... - 5 -

2.1 预处理的概念与作用........................................................... - 5 -

2.2在Ubuntu下预处理的命令................................................ - 5 -

2.3 Hello的预处理结果解析.................................................... - 5 -

2.4 本章小结............................................................................... - 5 -

第3章 编译................................................................................... - 6 -

3.1 编译的概念与作用............................................................... - 6 -

3.2 在Ubuntu下编译的命令.................................................... - 6 -

3.3 Hello的编译结果解析........................................................ - 6 -

3.4 本章小结............................................................................... - 6 -

第4章 汇编................................................................................... - 7 -

4.1 汇编的概念与作用............................................................... - 7 -

4.2 在Ubuntu下汇编的命令.................................................... - 7 -

4.3 可重定位目标elf格式........................................................ - 7 -

4.4 Hello.o的结果解析............................................................. - 7 -

4.5 本章小结............................................................................... - 7 -

第5章 链接................................................................................... - 8 -

5.1 链接的概念与作用............................................................... - 8 -

5.2 在Ubuntu下链接的命令.................................................... - 8 -

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

5.4 hello的虚拟地址空间......................................................... - 8 -

5.5 链接的重定位过程分析....................................................... - 8 -

5.6 hello的执行流程................................................................. - 8 -

5.7 Hello的动态链接分析........................................................ - 8 -

5.8 本章小结............................................................................... - 9 -

第6章 hello进程管理.......................................................... - 10 -

6.1 进程的概念与作用............................................................. - 10 -

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

6.3 Hello的fork进程创建过程............................................ - 10 -

6.4 Hello的execve过程........................................................ - 10 -

6.5 Hello的进程执行.............................................................. - 10 -

6.6 hello的异常与信号处理................................................... - 10 -

6.7本章小结.............................................................................. - 10 -

第7章 hello的存储管理...................................................... - 11 -

7.1 hello的存储器地址空间................................................... - 11 -

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

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

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

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

7.6 hello进程fork时的内存映射......................................... - 11 -

7.7 hello进程execve时的内存映射..................................... - 11 -

7.8 缺页故障与缺页中断处理................................................. - 11 -

7.9动态存储分配管理.............................................................. - 11 -

7.10本章小结............................................................................ - 12 -

第8章 hello的IO管理....................................................... - 13 -

8.1 Linux的IO设备管理方法................................................. - 13 -

8.2 简述Unix IO接口及其函数.............................................. - 13 -

8.3 printf的实现分析.............................................................. - 13 -

8.4 getchar的实现分析.......................................................... - 13 -

8.5本章小结.............................................................................. - 13 -

结论............................................................................................... - 14 -

附件............................................................................................... - 15 -

参考文献....................................................................................... - 16 -

第1章 概述

1.1 Hello简介

   P2P:首先程序员是用高级语言编写hello.c文件。接着通过预处理得到hello.i文件,这个文件中宏定义被展开,注释被删除,条件预编译语句被处理(如#if,#ifndef)……然后通过编译器将hello.c文件编译为hello.s文件,这个文件中高级语言被翻译为汇编语言,相当于机器执行的命令。接下来,通过汇编,汇编语言被翻译为机器语言,相应的产生hello.o文件。最后,将程序中调用的外部库函数等链接进来,产生可执行文件hello。通过bash创建子进程,execve()从而成为process。

       020:bash程序通过fork函数创建一个新的子进程,获得一段新的虚拟内存,通过execve函数将可执行文件的信息转移到虚拟内存之中。通过页表结构,在运行中,应用缺页处理子程序将虚拟内存的信息加载到物理内存。程序接受虚拟内存被释放,页表复原。

1.2 环境与工具

软件环境:Ubuntu

硬件环境:intel I7 12700h

开发环境:VSCode

1.3 中间结果

1.Hello.c 源程序

2.hello.i 源程序预处理结构

3.hello.s 预处理文件转换为汇编语言

4.hello.o 可重定位目标文件

5.hello 可执行文件

6.obj_o.txt hello.o反汇编结果

7.obj_out.txt hello反汇编结果

1.4 本章小结

       Hello的P2P、020在机器中都经历了复杂的过程。复杂的编译过程,复杂的执行过程。下面将分解介绍。

第2章 预处理

2.1 预处理的概念与作用

预处理就是将程序中,处理#开始的预处理语句。

作用

2.1.1条件编译

      预处理会将程序中的条件编译语句处理掉,有选择性地保留或删除相应的       源程序。比如:

      #ifdef CSAPP

             #define NUM 1

      #else

             #define NUM 2

      #endif

      如果程序中已经定义了CSAPP那么NUM就会被定义为1,否则NUM被      定义为2.

2.1.2宏替换

      预处理会将源程序中的宏定义替换为相应的东西。

      比如#define x 4处理后,文本中的x会被替换为4。

      比如#include “XXX.h”,那么对应的h文件中的内容会在该位置展开,将       该语句替换掉。

2.1.3处理空白字符(空格)

2.1.4 将注释删除,替换为空格

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

上两图是hello.i文件的部分内容

可见预处理已经将<stdio.h>,<unistd.h>,<stdlib.h>的内容替换到了文件之中

2.4 本章小结

预处理仅是将源程序代码进行了简单的替换,简化,方便了后续编译器的处理。

第3章 编译

3.1 编译的概念与作用

编译就是将预处理完的XXX.i的文件的内容通过分析处理转化为汇编语言

3.2 在Ubuntu下编译的命令

Hello.s文件的部分内容

3.3 Hello的编译结果解析

3.3.1函数操作

      3.3.1.1参数传递

             函数通过寄存器的特定位置来传递参数。

             例如主函数参数argc,argv分别是通过edi,rsi传递的

      3.3.1.2函数调用

             通过call函数调用函数,提前将参数存储至寄存器

             例如主函数调用exit(1)时:

      3.3.1.3局部变量

             函数的局部变量存储在内存的栈

             例如函数中的局部变量i,存储在 -4(%rbp)中,循环控制变量初始化为零

      3.3.1.4函数返回

             函数返回通过    ret操作,返回值存储在rax中

3.3.2数据

      常量

      可见printf的参数以字符串的形式存储了起来

      在调用printf函数时,将该字符串转移到rdi作为参数传入即可

      其他数字常量,被直接引用

3.3.3赋值

      在循环开始时,对循环控制变量i赋予了初值0,采用了movl指令

3.3.4数字操作

      Hello.c源程序中仅涉及到了+操作,加操作可以通过add指令实现

      循环过程中,循环控制变量i每次循环加一

3.3.5关系操作

      3.3.5.1 !=

             在程序中,首先一句argc参数判断用户执行程序时传入的参数数量。       在此执行了argc与4是否相等的操作。

      3.3.5.2 <

             循环控制条件时,i变量小于8.编译成果并未完全按照源代码的意思。   采用了判断i是否小于等于7来控制循环

      其中jle指令的作用是,比较结果如果时小于等于那么跳转至 .L4段,或       者继续执行下一指令

3.3.6数组 指针操作

      程序中调用了用户传入的参数argv[1],argv[2],且argv[]数组存储的是字符       串的首地址。

      其中rax加0x16和0x8就是argv[1]和argv[2],但是这两个元素存储的是       字符串的地址,所以将rax的内容传给了寄存器,这样寄存器存储的就是字符       串的首地址

3.3.7控制转移

      循环结构出出现了控制转移。

      每次循环都要将i与7比较,如果小于等于7就跳转至循环内部,继续执       行,否则就继续执行下条指令

3.4 本章小结

编译过程就是将预处理过的程序文件hello.i中的内容转变为汇编语言

第4章 汇编

4.1 汇编的概念与作用

汇编就是将编译产生的汇编语言转换为机器码,产生的文件为二进制的可重定位的目标文件hello.o

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式


       从节头可以看见elf文件中的各节信息:.text, .rela.text, .data, .bss, .rodata, .comment, .symtab, .strtab, .shstrtab……

       重定位节中,偏移量为符号相对重定位节首的偏移量。类型指的是重定位的引用使用32位PC相对地址。其中最重要的信息是,符号名称后的加数,这个数值直接关系链接时符号运行时的位置(一般来说这个位置,临时放进去的0x0,会被替代为ADDR(r.symbol) - (refaddr - r.addend),其中r.addend就是这个加数)。

   

4.4 Hello.o的结果解析

       上图就是hello.o文件的反汇编结果。

       可以看到,调用函数的地方已经预设为0x0,等待链接时计算运算时的地址。

       汇编语言和机器语言的框架是相同的。汇编语言像是大致描述程序进程,但是机器语言就需要精确(有部分信息需要补全)的描述个步骤的准确信息,例如循环,汇编语言的”jle .L4”已经被替换为相对函数的位置了,更加精确了。

4.5 本章小结

       汇编就是将编译所得的汇编语言文件.s文件转变为可重定位的目标文件hello.o

5章 链接

5.1 链接的概念与作用

       链接是将各种代码和数据片段收集并组合成为一个单一文件的过程。链接就是将汇编生成的可重定位目标文件,与所需的库文件合并的过程,进而生成可执行文件(例如:程序中调用了printf函数,就需要将程序与printf.o文件链接起来)。

5.2 在Ubuntu下链接的命令

上图即为Ubuntu采用ld链接hello的方式。

其中所用的库文件可以通过查看gcc 编译信息来查看

其中的collect2就是调用的ld来实现链接功能

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

       5.3.0基本信息

              上图为节头的部分内容。我们从节头我们就可以看出各节的基本信息。比       如某节的起始点就可以通过上面的地址(偏移量)求得。大小就表示这个节所       占空间大小。

       5.3.1程序头

      

              这个部分十分重要,建立了elf文件中各节向物理内存的映射。比如两个       LOAD就代表着程序的代码段和数据段,后面列出了二者对应的虚拟内存和物       理内存

       5.3.2重定位段

              重定位分析同4.3

       5.3.3符号表

              上图为elf文件中的符号表的部分内容。

              我们知道符号包括函数和全局变量。Type信息就是表明这个符号是什么类       型。Value就是符号的值(函数就对应着地址)。

5.4 hello的虚拟地址空间

   

              从edb查看的虚拟地址可以看到,与反汇编获得的机器语言可以对应上,也与readelf所得到的.text的起始地址相对应
5.5 链接的重定位过程分析

上图为hello.o文件的反汇编的部分结果

上图为可执行文件hello的反汇编的部分结果。

可以看到,二者在机器语言的内容基本一致,不同的只有对于可以重定位的地址不同。比如调用函数printf时,hello.o文件填写的地址内容为0x0,在可执行文件hello中就是0x401090,可知连接器已经将可重定位的地址定位完成。

举函数的例子来说重定位的过程,机器码中,对于函数的定位实际上是相对位置,相对于返回地址的位置。已知上面信息我们就好得到重定位的计算过程了,就是用函数的实际运行地址减去返回地址。比如printf函数,位于0x401090,返回地址是0x4011f9.所以我们得到这个相对位置为负的0x169转换为有符号整数就是0xfffffe97,所得就是图片中的97 fe ff ff。

5.6 hello的执行流程

程序先执行init子程序,跳转到main。

如果传入参数要求不符合要求,那么就先后调用printf和exit,地址分别为0x401203和0x401090

如果进入循环那么就会多次先后调用printf,atoi, sleep,地址分别为0x4010a0, 0x4010c0, 0x4010e0

循环结束后,调用getchar,地址为0x4010b0

5.7 Hello的动态链接分析

   

上面三个模块与动态链接相关,可以看见这些模块的相关信息,我们来观察执行前后节的内容变化。

上图就是init为执行前的内容

可以看见在执行init后,对应模块的内容发生了变化

动态链接是在程序执行的过程中实现的。

5.8 本章小结

       链接就是将多个模块合并到一起,形成一个完整的软件程序,生成一个可执行文件。这种机制有利于多模块分开实现。

6章 hello进程管理

6.1 进程的概念与作用

进程就是程序运行的抽象表现。这种数据结构,清晰的刻画了程序运行时的相关信息,有利于对于程序运行的观察、管理。

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

Bash是一种应用程序,构建了用户于linux操作系统之间的交流桥梁。用户可以通过向bash输入的指令来对操作系统进行操作。

处理流程:

1.读取从键盘输入的命令

2.判断命令是否正确,且将命令行的参数改造为系统调用execve() 内部处理所要求的形式

3.终端进程调用fork() 来创建子进程,自身则用系统调用wait() 来等待子进程完成

4.当子进程运行时,它调用execve() 根据命令的名字指定的文件到目录中查找可行性文件,调入内存并执行这个命令

5.如果命令行末尾有后台命令符号& 终端进程不执行等待系统调用,而是立即发提示符,让用户输入下一条命令;如果命令末尾没有& 则终端进程要一直等待。当子进程完成处理后,向父进程报告,此时终端进程被唤醒,做完必要的判别工作后,再发提示符,让用户输入新命令。

6.3 Hello的fork进程创建过程

Shell接收到运行可执行文件hello时,会建立一个新的进程,再利用fork()函数来创建一个子程序。这个子程序除了pid与父进程不同,其他信息均与父进程相同。再把这个新建立的进程信息录入新建立的进程当中,shell还会让这个进程成为一个新的进程组的leader。

6.4 Hello的execve过程

从6.3可知在运行程序时,shell会建立一个新的进程。然而这个进程基本上与父进程相同。为了执行用户要求的程序,这个子进程会执行execve函数,将这个进程的所有信息(除了pid之外)更新为需要执行的程序的内容(比如代码段,数据段等)。在执行这个子进程时就会执行用户要求的程序了。

6.5 Hello的进程执行

       切换进程时,CPU都会执行上下文切换。就是将CPU内核中保存的停止的进程的寄存器信息、虚拟内存等调出,恢复停止之前的状态。

       CPU资源总归是有限的,为了让多个进程平均的利用CPU资源。每个进程执行时都会给予一个时间。进程时间片归零时,无论进程是否已经结束,都要被暂停,调入进程调度的程序。

       用户态和核心态转换,与进程转换相似,都是通过上下文信息的切换实现的。

6.6 hello的异常与信号处理

6.6.1不停按空格、回车

      可以看见并不会影响输出结果(如果忽视造成的格式变化),另外有意思     的是,因为没有清空缓存区,乱按的回车和空格还会被shell接受。

6.6.2 Ctrl-Z

      按Ctrl-z后程序被中断,直接退出。

6.6.3 Ctrl-C

      输入Ctrl-Z 后,作业被停止。

      通过jobs和ps可以看到被停止的hello进程

      在pstree所得的树形图中也可以看到被停止的hello进程

使用fg命令可以将hello进程调到前台继续运行

      用kill命令可以给被停止的hello进程信号,上图杀死了这个进程

6.7本章小结

进程的管理是个很复杂的事情。CPU看似在同时做一件事情,其实它只是在快速的运行不同的进程。进程的切换就运用到了上下文信息。

在shell中有很多内置命令方便用户对于进程进行管理。

7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址指段内的相对地址。

线性地址指段基址+逻辑地址。

物理地址要将线性地址通过地址转换机制转换为在内存上的地址。

其中逻辑地址、线性地址都属于虚拟地址。

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

逻辑地址可以有段选择符和段内偏移量组成。段选择符区分这段地址是在LDT、GDT、IDT中。通过索引找到相应的段信息。然后通过基址寄存器或变址寄存器获取段内偏移。相加就可以的到线性地址。

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

对于内存页,采用了多级页表的管理方式。

每一级页表每个表项对应一个下一级,再通过线性地址中存储的页表索引找到该级的页表项,直至找到最终的内存页基址。最终根据线性地址中存储的偏移量找到相应的物理地址。

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

现根据VA中的索引段在TLB中搜寻相应的索引,如果命中,直接获得PA的索引段。

如果没有命中,那么机器就会到四级页表中。索引段将会被解读为四分相同大小的四部分,分别代表在每一级页表的偏移段。如果未命中,那么机器先将对应的地址更新到内存中,再重新寻找。找到后TLB也会被修改。

经过上述部分得到的索引段与VA中的偏移段结合就是所得PA段。

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

在获得PA后,CPU会向存储传入读取信号。

接下来机器会从一级cache、二级cache等依次寻找直至找到后,向CPU返回相应的数据。

7.6 hello进程fork时的内存映射

调用fork后系统会给生成的子进程分配一块虚拟内存,将父进程所有的信息复制给子进程(除了PID)。也就是说父子进程共同使用一块物理内存。

但是如果子进程中发生了相应的数据变化,那么系统就会另开辟一段物理内存,并把相应更新部分的页表段也更新,指向新分配的物理内存。这块新分配的物理内存就存放修改的信息。

7.7 hello进程execve时的内存映射

Execve函数调用后,系统会删除进程所有用户部分中已存在的区域结构。

接下来系统会为代码段、数据段、共享库段分配新的区域,并将新程序的相应信息映射构建起来。另外堆栈等区域会申请一种匿名文件,内容全为二进制零(应该是为了防止堆栈错误产生不能挽回错误)。

这些内容其实还未被传入物理内存中,在真正执行程序触发缺页中断后才会被复制进物理内存。

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

如果触发缺页中断,系统会保留当前的上下文信息,调用内核中的缺页中断处理子程序(这里上下文切换其实并不算是进程切换,进程仍然是这个进程)。

子程序会先找到缺页的地址,然后判断内存中是否已经“满员”。如果不是那么子程序就直接把这段缺失的信息传进去。如果内存已经满了,那么系统会找到相应的内存段换下来(设计很复杂的算法),如果这段信息被修改过那么还会被写到外存。

7.9动态存储分配管理

       动态存储分配相关的函数主要有malloc、calloc、realloc、free。

       Malloc函数会向内存申请一个用户所需要的大小的内存,返回一个void *类型的指针。Free就是将获取的动态存储释放,还给系统。

       由于动态存储分配是直接对内存进行操作,不当的操作很容易导致程序运行缓慢、资源浪费等问题。

       所以要及时回收向系统要来的内存。另外释放内存后,一定要将相应的指针释放,设置为NULL,否则继续对这个指针进行操作,会让这片不知道用来干什么的内存发生以外的修改或调用,导致不可估计的错误。

7.10本章小结

对于程序的内存管理是个很复杂的课题。

系统为程序申请虚拟内存,建立映射。缺页中断时,向内存写入信息。

向内存中寻找信息也是个不简单的事情,逻辑地址、线性地址、物理地址,一步步转换才能真正找到相应的数据。不命中还需要一系列的操作。

8章 hello的IO管理

8.1 Linux的IO设备管理方法

       设备分为三种:字符设备、块设备、网络设备。Linux对设备的管理是将设备看作一种特殊的文件,然后通过linux的虚拟文件系统VFS来管理和控制各种设备。

8.2 简述Unix IO接口及其函数

Unix IO函数很简单,open(打开)、close(关闭)、read(读)、write(写)、lseek(定位)

8.2.1 open

8.2.2 close

8.2.3 read

(buf指的是存储器指针)

8.2.4 write

8.2.5 lseek

8.3 printf的实现分析

       首先,我们要知道,printf函数是一种特殊的函数,它并没有固定数量的参数。

       上图可见,在fmt后面有三个…表示不确定数量的参数(一般来说,不确定参数的函数至少要有一个参数)。这些不确定数量的参数就是格式串中需要使用变量代替的那些变量(printf(“%d”, x)比如说前面的x)。由于不确定函数的数量,后面的参数固然没有对应的名称,想要使用这些参数时,只能通过取地址(将fmt的地址,加上一定的值即可)。

       已知上面的的内容,我们就来浅析printf的实现。

       下面为printf函数的函数体

      

       int printf(const char *fmt, ...)

       {

              int i;

              char buf[256];

         va_list arg = (va_list)((char*)(&fmt) + 4);

          i = vsprintf(buf, fmt, arg);

         write(buf, i);

           return i;

}

Arg存储的就是第一个变量的地址。

Vsprintf实现的是将格式串完全转变为字符串,将那些需要替换的变量加入其中。返回的i是需要打印的字符串的长度。

Write函数就是将所获得的字符串在屏幕上显示出来。Write的实现就比较复杂,包括保护进程上下文信息、调用syscall等(实力不足,不予说明)。

8.4 getchar的实现分析

       键盘输入在IO中的优先级很高,每当键盘输入消息,CPU都会接受到一个中断信号。根据不同的中断向量,CPU会定位到不同的中断处理程序,保留寄存器信息,并根据输入信息不同采用不同的处理方式,还原现场即可。

       Getchar函数实际上是调用了read函数,从缓存区读取一个字符。而缓存区可不一定只接受一个字符。调用getchar后缓存区会一直存储进键盘输入的信息,直到遇到回车或满了。

      

8.5本章小结

系统IO是对于设备的管理。通过将设备看作一种文件,来实现接受设备信息、向设备传输信息等操作。

一系列函数的实现更是离不开设备的参与。IO管理是操作系统的重要板块。

结论

Hello从被编写,到执行,到执行结束。对于程序员来说,可能只是一小段无关紧要的时间,但是在机器内部经历了浩大的一项工程。

从一行行指令到可执行文件,hello就经历了不少过程。预处理、编译、汇编、链接一项不可缺少。这些流程逐渐将高级语言转化为机器语言,供机器执行。

为了执行hello,系统会为调用fork、execve函数为hello创建一个新的进程。让hello在虚拟内存中有一块自己的一席之地。另外系统还会为hello构建从磁盘到虚拟内存、从虚拟内存到物理内存的映射。方便在执行时,系统从虚拟内存中获取想要的信息。为了实现系统的各项信息,IO管理也是不可或缺,依靠它系统从键盘等设备中获取用户输入的信息。

参考文献

1.程序执行过程-CSDN博客

2. 程序详细编译过程(预处理、编译、汇编、链接) - 知乎 (zhihu.com)

3.详解:链接中的重定位 - 知乎 (zhihu.com)

4. PLT & GOT 表动态链接详解及 pwn 应用_.got.plt 的序号-CSDN博客

5. 基础:正确理解CPU上下文切换_context switches per second-CSDN博客

6. 动态内存管理【详解】 - 知乎 (zhihu.com)

7. [转]printf 函数实现的深入剖析 - Pianistx - 博客园 (cnblogs.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值