2022spr HIT计统大作业——程序人生-Hello’s P2P

2022spr HIT计统大作业——程序人生-Hello’s P2P_第1张图片

摘要

本篇文章是2022春季计算机系统课程的大作业,介绍了hello程序的一生,即其从hello.c这个C文件开始经过预处理,编译,汇编,链接等操作最后展现在用户面前,最终进程被杀死,也就是死亡的过程。hello经历了这样的一生,这一个程序可能对于程序员来讲不复杂,但是这个过程中其底层的软硬件的配合过程是很复杂的。本文将这个过程细化为一个个章节,并且逐节讲述。

关键词:计算机系统;编译;链接;      

目录

摘要

第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.3.1 常量与赋值

3.3.2 局部变量与其赋值

3.3.3 数组/指针/结构操作

3.3.4 算术操作

3.3.5 关系操作

3.3.6 控制转移

3.3.7 函数操作

3.3.8 类型转换

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本章小结

结论

附件

参考文献


第1章 概述

1.1 Hello简介

P2P:P2P是from program to process的缩写,经过编辑器中的编辑操作得到hello程序,之后经过预处理、编译、汇编、链接生成可执行文件hello,以linux操作系统为例,在linux终端中使用./hello指令执行该文件,shell会解析这段命令,使用fork创建子进程,并且通过execve等函数进行运行这个进程,至此完成program to process的过程

020:shell fork出一些有关于hello的进程,并且映射出虚拟函数,产生属于自己的内存地址、时间周期等。在进程运行结束后释放内存,删除有关上下文。这时hello的进程就像在机器中没有存在过一般(变成了0),也就是实现了0到0的过程。

1.2 环境与工具

硬件:X64 CPU;2GHz;2G RAM;256GHD Disk 以上

软件:Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04

LTS 64位/优麒麟 64位 以上;

工具:Codeblocks 64位,vim,gcc,gedit,gdb,edb

1.3 中间结果

文件名

作用

hello.c

储存C语言源代码

hello.i

预处理生成的文件

hello.s

编译生成的文本文件

hello.o

汇编器生成的二进制文件

elf.txt

hello.o文件的可执行可链接文件的txt文件,包含hello.o的信息

hello

链接操作生成的可执行文件

hello1.elf

hello文件的可执行可链接文件,包含hello的信息

1.4 本章小结

本章主要介绍了hello的p2p,o2o过程,hello的简介,以及实验的环境,硬件、软件、工具。写出了为编写本论文,生成的中间结果文件的名字,文件的作用等。

第2章 预处理

2.1 预处理的概念与作用

预处理的概念:
预处理器根据以字符#开头的命令,修改原始的C程序,比如hello.c中的第一行的#include 命令告诉预处理器读取系统头文件stdio.h的内容,并把它插入到程序文本中,结果就得到了另一个以.i作为文件扩展名的C程序。
预处理的作用:
1. 将#include、宏定义、预编译指令如#if, #elif等展开或处理插入原文件
2. 删除所有注释
3. 添加行号信息文件名信息,便于调试;
4. 规定某段代码的条件与限制

2.2在Ubuntu下预处理的命令

gcc -E -o hello.i hello.c
预处理过程及结果:

 生成了一个.i文件,如下图:

2022spr HIT计统大作业——程序人生-Hello’s P2P_第2张图片

 2.3 Hello的预处理结果解析

打开.i文件:发现程序已经被拓展成3060行,原来的hello.c出现在3047行之后。
如下图,可以看出在预处理过程中,程序的头文件、宏定义、扩展库等,hello.i程序的依次开始是程序的头文件stdio.h,unistd.h,stdlib.h的依次展开。例如下图的stdio.h均被展开成很多行的代码(13行和14行明确给出),我们的原来程序(删除了头文件、扩展名等的)在.i文件最末尾处。
2022spr HIT计统大作业——程序人生-Hello’s P2P_第3张图片

2022spr HIT计统大作业——程序人生-Hello’s P2P_第4张图片

 2.4 本章小结

本章主要介绍了预处理的概念与作用,以及ubuntu下如何进行预处理操作,并且介绍了预处理的结果解析,了解了预处理的内涵。

第3章 编译

3.1 编译的概念与作用        

编译程序就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。 编译器将文本文件 hello.i 翻译成文本文件 hello.s。
作用:通过语法分析,检验是否符合语法标准,如果不符合就会出现错误,将hello.i翻译为hello.s,在编译的同时也会优化程序性能,可以很好地实现其原有机器语言功能的前提下提高编译器程序的性能和运行的效率。从高级语言转换为汇编语言,从而让语言更容易被机器理解。
 

3.2 在Ubuntu下编译的命令

终端输入以下命令:
gcc -S hello.i -o hello.s
编译过程:

产生.s文件:

 3.3 Hello的编译结果解析

3.3.1 常量与赋值

例如程序中的:

  1. if(argc!=4)  

这里面的常量4在汇编语言中这样显示:

  1. movl    %edi, -20(%rbp)  
  2.     movq    %rsi, -32(%rbp)  
  3.     cmpl    $4, -20(%rbp)  
  4.     je  .L2  

这里面$4就被存在了内存-20(%rbp)处,

同样的0,8,1,2,3也都是用这种方式存在了寄存器或者内存之中

3.3.2 局部变量与其赋值

局部变量会被存储在寄存器或者栈中,

例如这个初始化,后面i被设置为0

  1. int i;  

他的汇编语言是:

  1. .L2:  
  2.     movl    $0, -4(%rbp)  
  3.     jmp .L3  

说明这个局部变量被存储在内存之中。

3.3.3 数组/指针/结构操作

例如这个指针数组

  1. char *argv[]  

根据main的这段汇编代码:

  1. main:  
  2. .LFB6:  
  3.     .cfi_startproc  
  4.     endbr64  
  5.     pushq   %rbp  
  6.     .cfi_def_cfa_offset 16  
  7.     .cfi_offset 6, -16  
  8.     movq    %rsp, %rbp  
  9.     .cfi_def_cfa_register 6  
  10.     subq    $32, %rsp  
  11.     movl    %edi, -20(%rbp)  
  12.     movq    %rsi, -32(%rbp)  
  13.     cmpl    $4, -20(%rbp)  
  14.     je  .L2  
  15.     leaq    .LC0(%rip), %rdi  
  16.     call    puts@PLT  
  17.     movl    $1, %edi  
  18.     call    exit@PLT  
  19.     .L2:  
  20.     movl    $0, -4(%rbp)  
  21.     jmp .L3 

可以看出数组的首地址也就是argv[0]存在了-32(%rbp)的位置中,也是%rsi所存内容。

顺便也可以知道argc存在了-20(%rbp)处

同理也由下面汇编语言可以知道

  1. movq    -32(%rbp), %rax  
  2.     addq    $24, %rax  
  3.     movq    (%rax), %rax  
  4.     movq    %rax, %rdi  
  5.     call    atoi@PLT  

-8(%rbp)地址所存的内容为argv[3]

3.3.4 算术操作

关于i++的汇编语言表示:

使用add操作即可:

  1. addl    $1, -4(%rbp)  

3.3.5 关系操作

例如这个判断条件的代码:

  1. if(argc!=4)  

在汇编语言中这样表示:

  1. cmpl    $4, -20(%rbp)  
  2.     je  .L2  

就是使用cmp加上jmp条件跳转进行操作

同样的,循环中的最终条件判断也是类似的:

  1. cmpl    $7, -4(%rbp)  
  2.     jle .L4

3.3.6 控制转移

这里面的if和for都使用了控制转移的指令

套路就是cmp比较,加上jmp条件跳转(其实和关系操作的差不太多

例如这个判断条件的代码:

  1. if(argc!=4)  

在汇编语言中这样表示:

  1. cmpl    $4, -20(%rbp)  
  2.     je  .L2  

也就是判断一个数是否等于4,如果等于就跳转到.L2,否则不跳转。

同样的,循环中的最终条件判断也是类似的:

  1. cmpl    $7, -4(%rbp)  
  2.     jle .L4 

判断一个数是否小于等于7,如果是就跳转,否则不跳转。

3.3.7 函数操作

函数操作就是在调用之前向各个寄存器中传递参数,顺序为rdi, rsi, rdx, rcx, r8, r9,其余参数放在栈中,然后使用call进行函数的访问,之后将返回值存入rax,最后回到返回位置的操作。

这里面函数有main, exit, printf, atoi等,以main函数为例:

在.c中为如下代码:

  1. int main(int argc,char *argv[]){  
  2.     int i;  
  3.   
  4.     if(argc!=4){  
  5.         printf("用法: Hello 学号 姓名 秒数!\n");  
  6.         exit(1);  
  7.     }  
  8.     for(i=0;i<8;i++){  
  9.         printf("Hello %s %s\n",argv[1],argv[2]);  
  10.         sleep(atoi(argv[3]));  
  11.     }  
  12.     getchar();  
  13.     return 0;  
  14. }  

在汇编语言中部分代码如下所示:

  1. main:  
  2. .LFB6:  
  3.     .cfi_startproc  
  4.     endbr64  
  5.     pushq   %rbp  
  6.     .cfi_def_cfa_offset 16  
  7.     .cfi_offset 6, -16  
  8.     movq    %rsp, %rbp  
  9.     .cfi_def_cfa_register 6  
  10.     subq    $32, %rsp  
  11.     movl    %edi, -20(%rbp)  
  12.     movq    %rsi, -32(%rbp)  
  13.     cmpl    $4, -20(%rbp)  
  14.     je  .L2  
  15.     leaq    .LC0(%rip), %rdi  
  16.     call    puts@PLT  
  17.     movl    $1, %edi  
  18.     call    exit@PLT  
  19.     .L2:  
  20.     movl    $0, -4(%rbp)  
  21.     jmp .L3  

这里面可以看出输入的参数(11,12两行)放在了rdi和rsi中,

main函数是直接被系统函数启动的时候调用的

在下面的代码段的第五行可以看出将返回值0放在eax中,相当于return 0

  1. .L3:  
  2.     cmpl    $7, -4(%rbp)  
  3.     jle .L4  
  4.     call    getchar@PLT  
  5.     movl    $0, %eax  
  6.     leave  
  7.     .cfi_def_cfa 7, 8  
  8.     ret  
  9.     .cfi_endproc  

3.3.8 类型转换

这里面的atoi函数就是将参数 str 所指向的字符串转换为一个整数(类型为 int 型)是一种典型的显式转换。

在汇编语言中表示为调用atoi函数:

  1. movq    -32(%rbp), %rax  
  2.     addq    $24, %rax  
  3.     movq    (%rax), %rax  
  4.     movq    %rax, %rdi  
  5.     call    atoi@PLT  
  6.     movl    %eax, %edi  

如上面代码段的第四行,输入参数进入rdi,然后进行转换

3.4 本章小结

本章主要讲述了编译的概念和作用,在Ubuntu系统下如何进行编译操作,以及对于hello的编译结果的解析。更加深入地了解了编译程序是如何通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码的,也就是更加深入地了解了编译器将文本文件 hello.i 翻译成文本文件 hello.s的过程。

第4章 汇编

4.1 汇编的概念与作用

汇编器(assembler)将hello.s转换为hello.o,也就是将汇编代码转换成机器代码的过程就叫做汇编过程。

作用:汇编代码是人较为容易理解的代码,机器仍然难以理解。一个计算机只能理解{0, 1}*的语言。汇编的作用就是将我们得到的.s文件翻译成机器真正能够读懂的二进制代码,从而能够执行我们输入的程序。

4.2 在Ubuntu下汇编的命令

在linux终端输入:
gcc -c hello.s -o hello.o

 获得了.o的文件,也就是一种二进制文件,如图:

4.3 可重定位目标elf格式

调用指令readelf -a hello.o > ./elf.txt

产生elf.txt文件:

可执行与可链接格式 (Executable and Linkable Format,缩写 ELF),常被称为 ELF格式,在计算中,是一种用于可执行文件、目标代码、共享库和核心转储的标准文件格式。

(1)ELF头

生成的elf头如下段代码所示:

  1. ELF 头:  
  2.   Magic   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00   
  3.   类别:                              ELF64  
  4.   数据:                              2 补码,小端序 (little endian)  
  5.   Version:                           1 (current)  
  6.   OS/ABI:                            UNIX - System V  
  7.   ABI 版本:                          0  
  8.   类型:                              REL (可重定位文件)  
  9.   系统架构:                          Advanced Micro Devices X86-64  
  10.   版本:                              0x1  
  11.   入口点地址:               0x0  
  12.   程序头起点:          0 (bytes into file)  
  13.   Start of section headers:          1240 (bytes into file)  
  14.   标志:             0x0  
  15.   Size of this header:               64 (bytes)  
  16.   Size of program headers:           0 (bytes)  
  17.   Number of program headers:         0  
  18.   Size of section headers:           64 (bytes)  
  19.   Number of section headers:         14  
  20.   Section header string table index: 13  

elf头中包含整个程序的基本信息,包括了机器的类别(如大端机小端机等),数据的类别,操作系统的类别,系统的架构等信息。

第八行,类型为REL,表明文件的类别是可重定位文件。

(2)节头部表

节头部表如图所示:

  1. 节头:  
  2.   [名称              类型             地址              偏移量  
  3.        大小              全体大小          旗标   链接   信息   对齐  
  4.   [ 0]                   NULL             0000000000000000  00000000  
  5.        0000000000000000  0000000000000000           0     0     0  
  6.   [ 1] .text             PROGBITS         0000000000000000  00000040  
  7.        0000000000000092  0000000000000000  AX       0     0     1  
  8.   [ 2] .rela.text        RELA             0000000000000000  00000388  
  9.        00000000000000c0  0000000000000018   I      11     1     8  
  10.   [ 3] .data             PROGBITS         0000000000000000  000000d2  
  11.        0000000000000000  0000000000000000  WA       0     0     1  
  12.   [ 4] .bss              NOBITS           0000000000000000  000000d2  
  13.        0000000000000000  0000000000000000  WA       0     0     1  
  14.   [ 5] .rodata           PROGBITS         0000000000000000  000000d8  
  15.        0000000000000033  0000000000000000   A       0     0     8  
  16.   [ 6] .comment          PROGBITS         0000000000000000  0000010b  
  17.        000000000000002c  0000000000000001  MS       0     0     1  
  18.   [ 7] .note.GNU-stack   PROGBITS         0000000000000000  00000137  
  19.        0000000000000000  0000000000000000           0     0     1  
  20.   [ 8] .note.gnu.propert NOTE             0000000000000000  00000138  
  21.        0000000000000020  0000000000000000   A       0     0     8  
  22.   [ 9] .eh_frame         PROGBITS         0000000000000000  00000158  
  23.        0000000000000038  0000000000000000   A       0     0     8  
  24.   [10] .rela.eh_frame    RELA             0000000000000000  00000448  
  25.        0000000000000018  0000000000000018   I      11     9     8  
  26.   [11] .symtab           SYMTAB           0000000000000000  00000190  
  27.        00000000000001b0  0000000000000018          12    10     8  
  28.   [12] .strtab           STRTAB           0000000000000000  00000340  
  29.        0000000000000048  0000000000000000           0     0     1  
  30.   [13] .shstrtab         STRTAB           0000000000000000  00000460  
  31.        0000000000000074  0000000000000000           0     0     1  
  32. Key to Flags:  
  33.   W (write), A (alloc), X (execute), M (merge), S (strings), I (info),  
  34.   L (link order), O (extra OS processing required), G (group), T (TLS),  
  35.   C (compressed), x (unknown), o (OS specific), E (exclude),  
  36.   l (large), p (processor specific)  

该表描述了.o文件中出现的各个节的类型、位置、所占空间大小等信息。

(3)重定位节

如下图所示:

  1. 重定位节 '.rela.text' at offset 0x388 contains 8 entries:  
  2.   偏移量          信息           类型           符号值        符号名称 + 加数  
  3. 00000000001c  000500000002 R_X86_64_PC32     0000000000000000 .rodata - 4  
  4. 000000000021  000c00000004 R_X86_64_PLT32    0000000000000000 puts - 4  
  5. 00000000002b  000d00000004 R_X86_64_PLT32    0000000000000000 exit - 4  
  6. 000000000054  000500000002 R_X86_64_PC32     0000000000000000 .rodata + 22  
  7. 00000000005e  000e00000004 R_X86_64_PLT32    0000000000000000 printf - 4  
  8. 000000000071  000f00000004 R_X86_64_PLT32    0000000000000000 atoi - 4  
  9. 000000000078  001000000004 R_X86_64_PLT32    0000000000000000 sleep - 4  
  10. 000000000087  001100000004 R_X86_64_PLT32    0000000000000000 getchar - 4  
  11.   
  12. 重定位节 '.rela.eh_frame' at offset 0x448 contains 1 entry:  
  13.   偏移量          信息           类型           符号值        符号名称 + 加数  
  14. 000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0  
  15.   
  16. The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.  

偏移量:通常对应于一些需要重定向的程序代码所在.text或.data节中的偏移位置.

信息:重定位到的目标在符号表中的偏移量

类型:代表重定位的类型,与信息相互对应。

名称:重定向到的目标的名称

加数:用来作为计算重复或定位文件位置的一个辅助运算信息,共计约占8个字节。

注:本程序需要重定位的信息有:.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar这些符号。重定位节.rela.text ,一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。偏移量是需要被修改的引用的节偏移,符号标识被修改引用应该指向的符号。类型告知链接器如何修改新的引用,加数是一个有符号常数,一些类型的重定位要用它对被修改引用的值做偏移调整。

(4)Symbol table

  1. Symbol table '.symtab' contains 18 entries:  
  2.    Num:    Value          Size Type    Bind   Vis      Ndx Name  
  3.      0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND   
  4.      1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c  
  5.      2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1   
  6.      3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3   
  7.      4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4   
  8.      5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5   
  9.      6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7   
  10.      7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8   
  11.      8: 0000000000000000     0 SECTION LOCAL  DEFAULT    9   
  12.      9: 0000000000000000     0 SECTION LOCAL  DEFAULT    6   
  13.     10: 0000000000000000   146 FUNC    GLOBAL DEFAULT    1 main  
  14.     11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_  
  15.     12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts  
  16.     13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND exit  
  17.     14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf  
  18.     15: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND atoi  
  19.     16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND sleep  
  20.     17: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND getchar  

Symbol table用于存放程序中定义和引用的函数和全局变量的信息。

4.4 Hello.o的结果解析

输入objdump -d -r hello.o之后,如下图所示:

  1. hello.o     文件格式 elf64-x86-64  
  2.   
  3.   
  4. Disassembly of section .text:  
  5.   
  6. 0000000000000000 
    :  
  7.    0:   f3 0f 1e fa             endbr64   
  8.    4:   55                      push   %rbp  
  9.    5:   48 89 e5                mov    %rsp,%rbp  
  10.    8:   48 83 ec 20             sub    $0x20,%rsp  
  11.    c:   89 7d ec                mov    %edi,-0x14(%rbp)  
  12.    f:   48 89 75 e0             mov    %rsi,-0x20(%rbp)  
  13.   13:   83 7d ec 04             cmpl   $0x4,-0x14(%rbp)  
  14.   17:   74 16                   je     2f   
  15.   19:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 20   
  16.             1c: R_X86_64_PC32   .rodata-0x4  
  17.   20:   e8 00 00 00 00          callq  25   
  18.             21: R_X86_64_PLT32  puts-0x4  
  19.   25:   bf 01 00 00 00          mov    $0x1,%edi  
  20.   2a:   e8 00 00 00 00          callq  2f   
  21.             2b: R_X86_64_PLT32  exit-0x4  
  22.   2f:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)  
  23.   36:   eb 48                   jmp    80   
  24.   38:   48 8b 45 e0             mov    -0x20(%rbp),%rax  
  25.   3c:   48 83 c0 10             add    $0x10,%rax  
  26.   40:   48 8b 10                mov    (%rax),%rdx  
  27.   43:   48 8b 45 e0             mov    -0x20(%rbp),%rax  
  28.   47:   48 83 c0 08             add    $0x8,%rax  
  29.   4b:   48 8b 00                mov    (%rax),%rax  
  30.   4e:   48 89 c6                mov    %rax,%rsi  
  31.   51:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 58   
  32.             54: R_X86_64_PC32   .rodata+0x22  
  33.   58:   b8 00 00 00 00          mov    $0x0,%eax  
  34.   5d:   e8 00 00 00 00          callq  62   
  35.             5e: R_X86_64_PLT32  printf-0x4  
  36.   62:   48 8b 45 e0             mov    -0x20(%rbp),%rax  
  37.   66:   48 83 c0 18             add    $0x18,%rax  
  38.   6a:   48 8b 00                mov    (%rax),%rax  
  39.   6d:   48 89 c7                mov    %rax,%rdi  
  40.   70:   e8 00 00 00 00          callq  75   
  41.             71: R_X86_64_PLT32  atoi-0x4  
  42.   75:   89 c7                   mov    %eax,%edi  
  43.   77:   e8 00 00 00 00          callq  7c   
  44.             78: R_X86_64_PLT32  sleep-0x4  
  45.   7c:   83 45 fc 01             addl   $0x1,-0x4(%rbp)  
  46.   80:   83 7d fc 07             cmpl   $0x7,-0x4(%rbp)  
  47.   84:   7e b2                   jle    38   
  48.   86:   e8 00 00 00 00          callq  8b   
  49.             87: R_X86_64_PLT32  getchar-0x4  
  50.   8b:   b8 00 00 00 00          mov    $0x0,%eax  
  51.   90:   c9                      leaveq   
  52.   91:   c3                      retq     

1. 可以看出反汇编的结果包含了机器代码以及后面的汇编代码,而hello.s仅仅是汇编代码。

2. 在分支转移函数调用方面,反汇编结果相对于hello.s的.L1等跳转表式地址或者是直接是call + 函数名称来说,使用的是函数的相对偏移位置,因为只有在hello.o在链接之后才会知道每一个函数的绝对位置,所以在.rela.text中补充重定位的条目。

3. 在操作数方面,反汇编的结果使用的数字是16进制,而hello.s使用的是10十进制操作数。

4.5 本章小结

本章阐释了汇编的概念与作用,说明了ubuntu操作系统下使用汇编操作将hello.s转换成hello.o的方法,了解并解释了可重定位目标文件ELF,通过调用指令生成elf.txt,对文件中的elf头,节头部表,重定位节,symbol table等内容进行分析,获取系统、程序的信息。通过对于hello.o的反汇编程序与hello.s的关系,更加深入地理解编译和汇编等问题。

第5章 链接

5.1 链接的概念与作用

链接有几种:

静态链接:(背景:几个.c文件经过cpp预处理,cc1进行编译,as进行汇编称为.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
生成hello文件如下图所示

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

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

输入指令:readelf -a hello > hello1.elf

生成了hello1.elf文件

(1)ELF头如下图

  1. ELF 头:  
  2.   Magic   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00   
  3.   类别:                              ELF64  
  4.   数据:                              2 补码,小端序 (little endian)  
  5.   Version:                           1 (current)  
  6.   OS/ABI:                            UNIX - System V  
  7.   ABI 版本:                          0  
  8.   类型:                              EXEC (可执行文件)  
  9.   系统架构:                          Advanced Micro Devices X86-64  
  10.   版本:                              0x1  
  11.   入口点地址:               0x4010f0  
  12.   程序头起点:          64 (bytes into file)  
  13.   Start of section headers:          14208 (bytes into file)  
  14.   标志:             0x0  
  15.   Size of this header:               64 (bytes)  
  16.   Size of program headers:           56 (bytes)  
  17.   Number of program headers:         12  
  18.   Size of section headers:           64 (bytes)  
  19.   Number of section headers:         27  
  20.   Section header string table index: 26  

elf头中包含整个程序的基本信息,包括了机器的类别(如大端机小端机等),数据的类别,操作系统的类别,系统的架构等信息。

(2)节头

  1. 节头:  
  2.   [名称              类型             地址              偏移量  
  3.        大小              全体大小          旗标   链接   信息   对齐  
  4.   [ 0]                   NULL             0000000000000000  00000000  
  5.        0000000000000000  0000000000000000           0     0     0  
  6.   [ 1] .interp           PROGBITS         00000000004002e0  000002e0  
  7.        000000000000001c  0000000000000000   A       0     0     1  
  8.   [ 2] .note.gnu.propert NOTE             0000000000400300  00000300  
  9.        0000000000000020  0000000000000000   A       0     0     8  
  10.   [ 3] .note.ABI-tag     NOTE             0000000000400320  00000320  
  11.        0000000000000020  0000000000000000   A       0     0     4  
  12.   [ 4] .hash             HASH             0000000000400340  00000340  
  13.        0000000000000038  0000000000000004   A       6     0     8  
  14.   [ 5] .gnu.hash         GNU_HASH         0000000000400378  00000378  
  15.        000000000000001c  0000000000000000   A       6     0     8  
  16.   [ 6] .dynsym           DYNSYM           0000000000400398  00000398  
  17.        00000000000000d8  0000000000000018   A       7     1     8  
  18.   [ 7] .dynstr           STRTAB           0000000000400470  00000470  
  19.        000000000000005c  0000000000000000   A       0     0     1  
  20.   [ 8] .gnu.version      VERSYM           00000000004004cc  000004cc  
  21.        0000000000000012  0000000000000002   A       6     0     2  
  22.   [ 9] .gnu.version_r    VERNEED          00000000004004e0  000004e0  
  23.        0000000000000020  0000000000000000   A       7     1     8  
  24.   [10] .rela.dyn         RELA             0000000000400500  00000500  
  25.        0000000000000030  0000000000000018   A       6     0     8  
  26.   [11] .rela.plt         RELA             0000000000400530  00000530  
  27.        0000000000000090  0000000000000018  AI       6    21     8  
  28.   [12] .init             PROGBITS         0000000000401000  00001000  
  29.        000000000000001b  0000000000000000  AX       0     0     4  
  30.   [13] .plt              PROGBITS         0000000000401020  00001020  
  31.        0000000000000070  0000000000000010  AX       0     0     16  
  32.   [14] .plt.sec          PROGBITS         0000000000401090  00001090  
  33.        0000000000000060  0000000000000010  AX       0     0     16  
  34.   [15] .text             PROGBITS         00000000004010f0  000010f0  
  35.        0000000000000145  0000000000000000  AX       0     0     16  
  36.   [16] .fini             PROGBITS         0000000000401238  00001238  
  37.        000000000000000d  0000000000000000  AX       0     0     4  
  38.   [17] .rodata           PROGBITS         0000000000402000  00002000  
  39.        000000000000003b  0000000000000000   A       0     0     8  
  40.   [18] .eh_frame         PROGBITS         0000000000402040  00002040  
  41.        00000000000000fc  0000000000000000   A       0     0     8  
  42.   [19] .dynamic          DYNAMIC          0000000000403e50  00002e50  
  43.        00000000000001a0  0000000000000010  WA       7     0     8  
  44.   [20] .got              PROGBITS         0000000000403ff0  00002ff0  
  45.        0000000000000010  0000000000000008  WA       0     0     8  
  46.   [21] .got.plt          PROGBITS         0000000000404000  00003000  
  47.        0000000000000048  0000000000000008  WA       0     0     8  
  48.   [22] .data             PROGBITS         0000000000404048  00003048  
  49.        0000000000000004  0000000000000000  WA       0     0     1  
  50.   [23] .comment          PROGBITS         0000000000000000  0000304c  
  51.        000000000000002b  0000000000000001  MS       0     0     1  
  52.   [24] .symtab           SYMTAB           0000000000000000  00003078  
  53.        00000000000004c8  0000000000000018          25    30     8  
  54.   [25] .strtab           STRTAB           0000000000000000  00003540  
  55.        0000000000000158  0000000000000000           0     0     1  
  56.   [26] .shstrtab         STRTAB           0000000000000000  00003698  
  57.        00000000000000e1  0000000000000000           0     0     1  
  58. Key to Flags:  
  59.   W (write), A (alloc), X (execute), M (merge), S (strings), I (info),  
  60.   L (link order), O (extra OS processing required), G (group), T (TLS),  
  61.   C (compressed), x (unknown), o (OS specific), E (exclude),  
  62.   l (large), p (processor specific)  

该表描述了.o文件中出现的各个节的类型、位置、所占空间大小等信息。

5.4 hello的虚拟地址空间

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

2022spr HIT计统大作业——程序人生-Hello’s P2P_第5张图片

在edb中加载hello虚拟地址信息如上图所示
查看 ELF 格式文件中的 Program Headers,它告诉链接器运行时加载的内容,并提供动态链接的信息。每一个表项提供了各段在虚拟地址空间和物理地址空间的各方面的信息。
对比5.4.3中的elf文件结构:elf头+节头等,data dump同样可以查看,包含PHDR,INTERP,LOAD ,DYNAMIC,NOTE ,GNU_STACK,GNU_RELRO等部分。
 

5.5 链接的重定位过程分析

先使用objdump -d -r hello得到hello的反汇编,再使用objdump -d -r hello.o得到.o文件的反汇编文件。

(1)hello的反汇编,相对于hello.o的反汇编增加了很多节以及函数,例如.init和.plt和一些节中的函数等等

(2)hello的反汇编中的地址是有明确的地址的,而hello.o的有些部分则还是地址为一串0,原因是hello.o没有完成重定位的过程,hello已经完成了重定位,因此hello.o中为0串的地址再hello中已经被替换成了一个非零的地址。

如下图所示:

Hello.o反汇编部分程序:

  1. Disassembly of section .text:  
  2.   
  3. 0000000000000000 
    :  
  4.    0:   f3 0f 1e fa             endbr64   
  5.    4:   55                      push   %rbp  
  6.    5:   48 89 e5                mov    %rsp,%rbp  
  7.    8:   48 83 ec 20             sub    $0x20,%rsp  
  8.    c:   89 7d ec                mov    %edi,-0x14(%rbp)  
  9.    f:   48 89 75 e0             mov    %rsi,-0x20(%rbp)  
  10.   13:   83 7d ec 04             cmpl   $0x4,-0x14(%rbp)  
  11.   17:   74 16                   je     2f   
  12.   19:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 20   
  13.             1c: R_X86_64_PC32   .rodata-0x4  
  14.   20:   e8 00 00 00 00          callq  25   
  15.             21: R_X86_64_PLT32  puts-0x4  
  16.   25:   bf 01 00 00 00          mov    $0x1,%edi  
  17.   2a:   e8 00 00 00 00          callq  2f   
  18.             2b: R_X86_64_PLT32  exit-0x4  
  19.   2f:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)  
  20.   36:   eb 48                   jmp    80   
  21.   38:   48 8b 45 e0             mov    -0x20(%rbp),%rax  
  22.   3c:   48 83 c0 10             add    $0x10,%rax  
  23.   40:   48 8b 10                mov    (%rax),%rdx  
  24.   43:   48 8b 45 e0             mov    -0x20(%rbp),%rax  
  25.   47:   48 83 c0 08             add    $0x8,%rax  
  26.   4b:   48 8b 00                mov    (%rax),%rax  
  27.   4e:   48 89 c6                mov    %rax,%rsi  
  28.   51:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 58   
  29.             54: R_X86_64_PC32   .rodata+0x22  
  30.   58:   b8 00 00 00 00          mov    $0x0,%eax  
  31.   5d:   e8 00 00 00 00          callq  62   
  32.             5e: R_X86_64_PLT32  printf-0x4  
  33.   62:   48 8b 45 e0             mov    -0x20(%rbp),%rax  
  34.   66:   48 83 c0 18             add    $0x18,%rax  
  35.   6a:   48 8b 00                mov    (%rax),%rax  
  36.   6d:   48 89 c7                mov    %rax,%rdi  
  37.   70:   e8 00 00 00 00          callq  75   
  38.             71: R_X86_64_PLT32  atoi-0x4  
  39.   75:   89 c7                   mov    %eax,%edi  
  40.   77:   e8 00 00 00 00          callq  7c   
  41.             78: R_X86_64_PLT32  sleep-0x4  
  42.   7c:   83 45 fc 01             addl   $0x1,-0x4(%rbp)  
  43.   80:   83 7d fc 07             cmpl   $0x7,-0x4(%rbp)  
  44.   84:   7e b2                   jle    38   
  45.   86:   e8 00 00 00 00          callq  8b   
  46.             87: R_X86_64_PLT32  getchar-0x4  
  47.   8b:   b8 00 00 00 00          mov    $0x0,%eax  
  48.   90:   c9                      leaveq   
  49.   91:   c3                      retq   

Hello反汇编部分程序:

  1. Disassembly of section .init:  
  2.   
  3. 0000000000401000 <_init>:  
  4.   401000:   f3 0f 1e fa             endbr64   
  5.   401004:   48 83 ec 08             sub    $0x8,%rsp  
  6.   401008:   48 8b 05 e9 2f 00 00    mov    0x2fe9(%rip),%rax        # 403ff8 <__gmon_start__>  
  7.   40100f:   48 85 c0                test   %rax,%rax  
  8.   401012:   74 02                   je     401016 <_init+0x16>  
  9.   401014:   ff d0                   callq  *%rax  
  10.   401016:   48 83 c4 08             add    $0x8,%rsp  
  11.   40101a:   c3                      retq     
  12.   
  13. Disassembly of section .plt:  
  14.   
  15. 0000000000401020 <.plt>:  
  16.   401020:   ff 35 e2 2f 00 00       pushq  0x2fe2(%rip)        # 404008 <_GLOBAL_OFFSET_TABLE_+0x8>  
  17.   401026:   f2 ff 25 e3 2f 00 00    bnd jmpq *0x2fe3(%rip)        # 404010 <_GLOBAL_OFFSET_TABLE_+0x10>  
  18.   40102d:   0f 1f 00                nopl   (%rax)  
  19.   401030:   f3 0f 1e fa             endbr64   
  20.   401034:   68 00 00 00 00          pushq  $0x0  
  21.   401039:   f2 e9 e1 ff ff ff       bnd jmpq 401020 <.plt>  
  22.   40103f:   90                      nop  
  23.   401040:   f3 0f 1e fa             endbr64   
  24.   401044:   68 01 00 00 00          pushq  $0x1  
  25.   401049:   f2 e9 d1 ff ff ff       bnd jmpq 401020 <.plt>  
  26.   40104f:   90                      nop  
  27.   401050:   f3 0f 1e fa             endbr64   
  28.   401054:   68 02 00 00 00          pushq  $0x2  
  29.   401059:   f2 e9 c1 ff ff ff       bnd jmpq 401020 <.plt>  
  30.   40105f:   90                      nop  
  31.   401060:   f3 0f 1e fa             endbr64   
  32.   401064:   68 03 00 00 00          pushq  $0x3  
  33.   401069:   f2 e9 b1 ff ff ff       bnd jmpq 401020 <.plt>  
  34.   40106f:   90                      nop  
  35.   401070:   f3 0f 1e fa             endbr64   
  36.   401074:   68 04 00 00 00          pushq  $0x4  
  37.   401079:   f2 e9 a1 ff ff ff       bnd jmpq 401020 <.plt>  
  38.   40107f:   90                      nop  
  39.   401080:   f3 0f 1e fa             endbr64   
  40.   401084:   68 05 00 00 00          pushq  $0x5  
  41.   401089:   f2 e9 91 ff ff ff       bnd jmpq 401020 <.plt>  
  42.   40108f:   90                      nop  

重定位过程的分析:

链接器将不同程序相同类型的节放在一起,并且将hello.c的库函数链接进了hello之中,并且将地址转换为内存中的地址,有两种方式:绝对地址和PC相对寻址,重定位算法如下图所示:

2022spr HIT计统大作业——程序人生-Hello’s P2P_第6张图片

5.6 hello的执行流程

下面列出各个子程序名:
_dl_start
_dl_init
_start
__libc_start_main
__cxa_atexit
__libc_csu_init
_init
_setjmp
_sigsetjmp
__sigjmp_save
main
puts@plt
exit@plt
printf@plt
atoi@plt
sleep@plt
getchar@plt
_dl_runtime_resolve_xsave
_dl_fixup
_dl_lookup_symbol_x
exit

5.7 Hello的动态链接分析

编译器在动态链接的过程中需要添加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。
di_init运行之前,使用data dump查看对应的地址内容

发现是全零的地址,也就是没进行重定位
di_init运行之后,使用data dump查看对应的地址内容
 发现原来全0的一行变成了非0的地址,也就是进行了重定位处理。

 5.8 本章小结

本章复习了链接的概念以及作用,并且实现了ubuntu下的链接操作,通过转换成elf格式对比未链接的可执行文件,得出链接的操作特点,并且通过EDB的查看进行了hello流程的分析以及动态链接的分析。

第6章 hello进程管理

6.1 进程的概念与作用

概念:进程是一个执行中的程序的实例,每一个进程都有它自己的地址空间。用户通过向shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程。
作用:进程可以给我们一个假象:程序无间断处理一条指令,程序独占处理器和内存。
 

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

作用:Shell是一种应用程序,它为用户访问操作系统内核提供了一个交互界面。用户可以通过shell向操作系统发出请求,操作系统选择执行命令。

处理流程:先在shell中输入指令,然后shell程序使用praseline builtin函数进行字符串划分,获取命令。若得到的命令为内置命令,那么立即执行,如果不是内置命令那么就调用可执行文件fork一个子进程。

6.3 Hello的fork进程创建过程

在命令行输入./hello命令,然后shell程序使用praseline builtin函数进行字符串划分,获取命令。经过判断发现不是系统的内置命令,因此通过fork函数创建子进程,子进程得到一份与父进程用户级虚拟空间相同且独立的副本(包括数据段、代码、共享库、堆和用户栈)。

6.4 Hello的execve过程

Execve在当前进程中载入并运行程序,函数形式为int execve(char *filename, char *argv[], char *envp[])。覆盖当前进程的代码、数据、栈,保留:有相同的PID,继承已打开的文件描述符和信号上下文。调用一次并从不返回,除非有错误,例如:指定的文件不存在。

6.5 Hello的进程执行

用户模式和内核模式:

处理器使用一个寄存器提供两种模式的区分。用户模式的进程不允许执行特殊指令,不允许直接引用地址空间中内核区的代码和数据;内核模式进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。

上下文:

上下文是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。

调度:

当调用sleep之前,如果hello程序不被抢占则顺序执行,假如发生被抢占的情况,则进行上下文切换,上下文切换是由内核中调度器完成的,当内核调度新的进程运行后,它就会抢占当前进程,并进行如下操作:

(1)保存以前进程的上下文

(2)恢复新恢复进程被保存的上下文

(3)将控制传递给这个新恢复的进程,来完成上下文切换。

2022spr HIT计统大作业——程序人生-Hello’s P2P_第7张图片

如上图所示。

6.6 hello的异常与信号处理

异常有四种:
中断 来自I/O设备的信号 异步 总是返回到下一条指令
陷阱 有意的异常 同步 总是返回到下一条指令
故障 潜在可恢复的错误 同步 可能返回到当前指令
终止 不可恢复的错误 同步 不会返回
2022spr HIT计统大作业——程序人生-Hello’s P2P_第8张图片

2022spr HIT计统大作业——程序人生-Hello’s P2P_第9张图片

2022spr HIT计统大作业——程序人生-Hello’s P2P_第10张图片

2022spr HIT计统大作业——程序人生-Hello’s P2P_第11张图片

6.7本章小结

本章内容介绍了进程的概念和作用,shell-bash的作用和处理过程,fork,execve以及进程执行等过程,又通过实际操作了解了各种信号的作用和产生结果。

结论

用计算机系统的语言,逐条总结hello所经历的过程。
Hello.c程序经过预处理,将#include、宏定义、预编译指令如#if, #elif等展开或处理插入原文件,删除所有注释,添加行号信息文件名信息,便于调试,并规定某段代码的条件与限制,得到hello.i文件作为文件扩展名的C程序。然后使用编译器将文本文件hello.i编译成文本文件hello.s。之后通过汇编器将hello.s转换为hello.o,得到真正能够被机器读懂的二进制代码文件。接着使用连接器先进行符号解析,然后对于地址进行重定位,将多个文件合成为一个可执行文件。从而实现了多个文件合成为一个可执行文件的过程。在运行程序的时候,我们在shell中输入./hello,shell程序然后shell程序使用praseline builtin函数进行字符串划分,获取命令。若得到的命令为内置命令,那么立即执行,如果不是内置命令那么就调用可执行文件fork一个子进程。此时,./hello使shell程序fork了一个子进程,通过各种输入的异常信号控制程序的运行。通过hello的存储管理和I/O管理保证程序正常的运行。

你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
我的感悟:计算机系统对于一个程序的控制原来是如此复杂的,我们每输入一个简单的指令都需要调用计算机的软件和硬件进行配合工作。每一个看似很简单的操作背后都是多少年来数学家、计算机科学家抽象包装好的,才能够有这样程序员友好的编程环境、平台。但是程序员仍然需要了解计算机系统的内部构造,这样才能够编写出底层软硬件友好的程序。
创新理念:可以设计一个软件,用于显示出计算机系统内部为了编译运行一个程序而进行的工作,这样可以有利于新手学习计算机系统时的理解。

附件

文件名

作用

hello.c

储存C语言源代码

hello.i

预处理生成的文件

hello.s

编译生成的文本文件

hello.o

汇编器生成的二进制文件

elf.txt

hello.o文件的可执行可链接文件的txt文件,包含hello.o的信息

hello

链接操作生成的可执行文件

hello1.elf

hello文件的可执行可链接文件,包含hello的信息

参考文献

[1]  《深入理解计算机系统》Bryant and O’hallaron.
[2]     Global Descriptor Table - Wikipedia.
[3]    Translation lookaside buffer - Wikipedia

你可能感兴趣的:(计算机系统,p2p,计算机系统,哈工大,hello程序,编译)