程序人生-hello‘sP2P

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业      计算机科学与技术 

学     号     -       

班     级        -        

学       生        符世博       

指 导 教 师         -         

计算机科学与技术学院

2022年11月

摘  要

本文讲述了hello的一生,从源代码到预处理文件,从预处理文件到汇编文件,从汇编文件到可重定位目标文件,从可重定位目标文件到可执行文件以及加载到内存中运行到终止的全过程。

关键词:计算机系统;预处理;编译;汇编;链接;                           

目  录

第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 -

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

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

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

第1章 概述

1.1 Hello简介

P2P:from program to process,即从程序到进程。在Linux系统中。hello.c文件经过预处理生成hello.i文件、再经过编译生成hello.s、之后经过汇编生成hello.o文件、最后通过链接生成可执行程序hello文件。之后在shell中输入命令./hello,shell调用系统内核的进程管理为其创建子进程。

020:from zero to zero。Hello程序在执行前不占用内存空间。再shell为其创建子进程后,首先调用execve,载入虚拟内存和物理内存。之后操作系统为这个进程分时间片。当该进程时间片到达时,操作系统设置上下文,并跳转到程序开始处。之后进入主程序执行代码,调用各种系统函数实现输出信息等功能。最终程序结束,shell回收此进程,释放其占用的资源。

1.2 环境与工具

  1. 硬件环境

CPU: Intel® Core™ i7-8565U CPU @ 1.80GHz

RAM: 16.00GB

  1. 软件环境

Windows10 64位

Oracle VM VirtualBox 6.1.16 r140961 (Qt5.6.2)

Ubuntu 20.04.1

  1. 开发与调试工具

Visual Studio Code

cpp(预处理器)

gcc(编译器)

as(汇编器)

ld(链接器)

GNU readelf

GNU gdb

EDB等

1.3 中间结果

hello.i

预处理文件

hello.o

可重定位目标文件

hello.s

汇编文件

hello_o_dis.txt

hello.o反汇编结果

hello_elf.txt

hello中的elf信息结果

hello_dis.txt

hello反汇编结果

hello_o_elf.txt

hello.o中的elf信息

hello

可执行目标文件

1.4 本章小结

本章介绍了P2P、020的概念,之后给出了所使用的软硬件环境以及使用的工具,最后介绍了完成本论文所用到的所有中间文件。

第2章 预处理

2.1 预处理的概念与作用

预处理的概念:预处理是C语言的一个重要的功能,它由预处理程序负责完成,它可以进行代码文本的替换工作,同时还会删除程序中的注释和多余的空白字符,但不会去做文本检查。最后将修改的程序保存成,i文件。

预处理的作用:预处理的作用主要可以分成以下三部分:

1.将源文件中以include格式包含的文件复制到编译的源文件中。

2.用实际值替换用#define定义的字符串。

3.根据#if后面的条件决定需要编译的代码。

2.2在Ubuntu下预处理的命令

使用gcc -E hello.c -o hello.imin命令可以对hello.c文件进行预处理,并将结果输出到hello.i文件。

图 2-1预处理命令截图

2.3 Hello的预处理结果解析

打开生成的.i文件,发现原本23行的.c文件经过预处理后拓展成了3060行的.i文件。

文件的开始是源代码文件的一些信息,如下图:

图 2-2预处理结果截图

之后是预处理拓展的内容,部分内容如下图:

图 2-3预处理结果截图

图 2-4预处理结果截图

最后是hello.c中的源代码部分,除去了注释和“include”语句,如下图:

图 2-5预处理结果截图

那么.i文件中那么多的拓展文本是从哪里来的呢?这就要说到预处理的过程了。

由于预处理只是对源代码中以“#”开头的语句进行处理,因此在预处理阶段中程序定义的其他操作不会进行处理。由于在程序中有关于头文件的文件包含,因此在预处理时对这一段进行了解析。以“stdio.h”为例:由于它使用<>进行引用的,所以gcc会到Linux系统的环境变量下寻找stdio.h,在/usr/include目录下找到stdio.h并打开,发现其中也使用了“#define”和“#include”,如下图,所以会对这些东西进行递归的展开并替换,而最终的.i文件会删除这些东西。而对于文件使用的“#ifdef”、“#ifndef”等语句,gcc会对条件值进行判断来决定是否包含其中的内容。

图 2-6sdtdio.h文件截图

2.4 本章小结

本章介绍了hello.c的预处理过程,并对产生的hello.i文件进行了分析。

第3章 编译

3.1 编译的概念与作用

编译的概念:编译是指对预处理后的代码进行检查和分析,确保所有语句符合规则并将其翻译成汇编代码的过程。

编译的作用:

将预处理完成后的代码进行一系列的分析和优化后生成相应的汇编代码文件。

3.2 在Ubuntu下编译的命令

使用gcc -S hello.i -o hello.s命令可以对hello.i文件进行编译,并将结果输出值hello.s文件。

图 3-1编译命令截图

3.3 Hello的编译结果解析

3.3.1数据

3.3.1.1常量:

在这个程序中,常量数据有printf输出的格式串,它们被保存在了.rodata段

hello.c:

14行 printf("用法: Hello 学号 姓名 秒数!\n");

18行 printf("Hello %s %s\n",argv[1],argv[2]);

这两个输出对应的格式串被储存在了helloc.s的5-8行:

.LC0:

      .string    "\347\224\250\346\263\225: Hello \345\255\246\345\217\267 \345\247\223\345\220\215 \347\247\222\346\225\260\357\274\201"

.LC1:

      .string    "Hello %s %s\n"

同时还有一些以立即数的形式在汇编代码中出现,比如在.c文件中的13行的if判断语句中比较argc与4的关系,这里的这个4在汇编代码中就是以立即数的形式出现的。

hello.s:

24行 cmpl $4, -20(%rbp)

3.3.1.2变量:

在.c文件中有一个局部变量int i,在一个循环体中作为控制变量,在汇编代码中,它被存储在了栈中。

hello.c

11行 int i;

hello.s

53行 cmpl $8, -4(%rbp)

其他的变量也用类似的方式进行表示。

3.3.2算术操作

在for循环中每次对变量i进行++运算,在汇编代码中使用了addl指令进行这个操作。

hello.c

17行 for(i=0;i<9;i++)

hello.s

51行 addl  $1, -4(%rbp)

3.3.3数组/指针操作

在main函数的参数中有一个字符串数组char *argv[]。在汇编代码中,使用栈中的连续空间进行存储。

hello.c

10行 int main(int argc,char *argv[])

hello.s

23行 movq       %rsi, -32(%rbp)

这样第二个参数就被写入了栈空间中,并且如果传入了4个参数(即没有进入第一个if),观察后面调用的printf和sleep函数对应的汇编代码可以发现,第4个参数存储在了%rbp-8 指向的栈空间,第三个和第二个分别存在了%rbp-16和%rbp-24指向的栈空间中。

3.3.4控制转移

以main函数中的if和for语句为例,在if语句中判断了argc与4的关系,如果相等执行后面的代码块,否则不执行,这在汇编代码中使用了cmpl和je完成

hello.c

13行 if(argc!=4)

hello.s

24-25行 cmpl  $4, -20(%rbp)

               je .L2

在for语句中变量i从0开始,每次循环加1,直到i<8不满足为止。在汇编代码中使用cmpl指令,判断i是否小于8,是则继续执行,否则跳出循环。

hello.c

17行 for(i=0;i<9;i++)

hello.s

31行 movl $0, -4(%rbp)  赋初值

51行 addl  $1, -4(%rbp)  增1

53-54行 cmpl  $8, -4(%rbp) 判断循环是否继续

               jle      .L4

3.3.5函数操作

main函数有两个参数,在一开始由edi和rsi进行传送

hello.s:

22-23行 movl  %edi, -20(%rbp)

               movq %rsi, -32(%rbp)

返回值由eax寄存器传递,在参数正确时返回0,否则返回1。

hello.s:

28行 movl $1, %edi

56行 movl $0, %eax

在main函数中调用的函数有printf,exit,atoi,sleep,getchar函数

首先是printf函数,在参数传递时,第一个只需要传递一个输出字符串对应汇编代码如下

hello.s:

26行 leaq  .LC0(%rip), %rdi

而第二个还需要传入对应的参数argv[1]和argv[2],对应的汇编代码如下

hello.s:

34-43行 movq -32(%rbp), %rax

               addq  $16, %rax

               movq (%rax), %rdx

               movq -32(%rbp), %rax

               addq  $8, %rax

               movq (%rax), %rax

               movq %rax, %rsi

               leaq   .LC1(%rip), %rdi

               movl  $0, %eax

返回值在main函数中没有被使用

之后是exit函数,传入的参数时退出的状态值,对应的汇编代码如下

hello.s:

28行 movl $1, %edi

没有返回值

接着是atoi函数,传入的参数为字符串首地址,对应的汇编代码如下

hello.s:

44-48行 movq -32(%rbp), %rax

               addq  $24, %rax       argv[3]的地址

               movq (%rax), %rax     将argv[3]的字符串首地址传送到rax中

               movq %rax, %rdi

               call    atoi@PLT

返回值是对应的整数值,存放在eax寄存器中

之后是sleep函数,它使用atoi的返回值作为参数,返回值在main函数中不被使用,对应的汇编代码如下

hello.s

49-50行 movl   %eax, %edi

                call    sleep@PLT

最后是getchar函数,没有参数传递,返回值也不被使用,所以在汇编代码中只用了一条call指令。

3.4 本章小结

本章介绍了从.i文件编译到.s文件的过程,并对.s文件进行了分析

第4章 汇编

4.1 汇编的概念与作用

汇编的概念:汇编是指将汇编语言书写的程序翻译成等价的机器语言程序的过程。汇编器将.s文件翻译成机器语言指令,然后把这些指令打包成可重定位程序的格式,并将结果输出为可重定位目标文件hello.o。

汇编的作用

将hello.s 翻译成CPU语言指令,把这些指令打包成可重定位目标程序的格式,并且将结果保存在二进制目标文件hello.o当中。

4.2 在Ubuntu下汇编的命令

使用gcc -c hello.s -o hello.o命令可以对hello.s文件进行汇编,并将结果输出到hello.o文件

图 4-1汇编命令截图

4.3 可重定位目标elf格式

使用readelf -a hello.o > hello_o_elf.txt命令可以将hello.o中ELF格式相关信息重定向到hello_o_elt.txt文件。

首先是ELF头部分,内容如下:

ELF 头:

  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

  类别:                              ELF64

  数据:                              2 补码,小端序 (little endian)

  Version:                           1 (current)

  OS/ABI:                            UNIX - System V

  ABI 版本:                          0

  类型:                              REL (可重定位文件)

  系统架构:                          Advanced Micro Devices X86-64

  版本:                              0x1

  入口点地址:               0x0

  程序头起点:          0 (bytes into file)

  Start of section headers:          1240 (bytes into file)

  标志:             0x0

  Size of this header:               64 (bytes)

  Size of program headers:           0 (bytes)

  Number of program headers:         0

  Size of section headers:           64 (bytes)

  Number of section headers:         14

  Section header string table index: 13

第一行的magic用来表示ELF文件,7f 45 4c 46分别对应ASCII码的Del E L F,其余的用来标识为位数,大端/小端序,版本号等,后九个未定义。剩下的部分包括了ELF头大小,目标文件类型,机器类型,字节头部表的文件偏移和节头部表中的条目大小和数量等信息。

节头部表

节头部表给出了文件中14个节的名称、类型、地址、偏移量和大小等信息。如下:

[号] 名称              类型             地址              偏移量

       大小              全体大小          旗标   链接   信息   对齐

  [ 0]                   NULL             0000000000000000  00000000

       0000000000000000  0000000000000000           0     0     0

  [ 1] .text             PROGBITS         0000000000000000  00000040

       0000000000000092  0000000000000000  AX       0     0     1

  [ 2] .rela.text        RELA             0000000000000000  00000388

       00000000000000c0  0000000000000018   I      11     1     8

  [ 3] .data             PROGBITS         0000000000000000  000000d2

       0000000000000000  0000000000000000  WA       0     0     1

  [ 4] .bss              NOBITS           0000000000000000  000000d2

       0000000000000000  0000000000000000  WA       0     0     1

  [ 5] .rodata           PROGBITS         0000000000000000  000000d8

       0000000000000033  0000000000000000   A       0     0     8

  [ 6] .comment          PROGBITS         0000000000000000  0000010b

       000000000000002c  0000000000000001  MS       0     0     1

  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  00000137

       0000000000000000  0000000000000000           0     0     1

  [ 8] .note.gnu.propert NOTE             0000000000000000  00000138

       0000000000000020  0000000000000000   A       0     0     8

  [ 9] .eh_frame         PROGBITS         0000000000000000  00000158

       0000000000000038  0000000000000000   A       0     0     8

  [10] .rela.eh_frame    RELA             0000000000000000  00000448

       0000000000000018  0000000000000018   I      11     9     8

  [11] .symtab           SYMTAB           0000000000000000  00000190

       00000000000001b0  0000000000000018          12    10     8

  [12] .strtab           STRTAB           0000000000000000  00000340

       0000000000000048  0000000000000000           0     0     1

  [13] .shstrtab         STRTAB           0000000000000000  00000460

       0000000000000074  0000000000000000           0     0     1

Key to Flags:

  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),

  L (link order), O (extra OS processing required), G (group), T (TLS),

  C (compressed), x (unknown), o (OS specific), E (exclude),

  l (large), p (processor specific)

There are no section groups in this file.

本文件中没有程序头。

There is no dynamic section in this file.

由于这是可重定位目标文件,所以每个节都从0开始。并且还描述了每个节的读写权限。

重定位节

这一部分记录了每一段中引用的符号的相关信息,在链接时,通过重定位节对这些地址进行重定位。如下

重定位节 '.rela.text' at offset 0x388 contains 8 entries:

  偏移量          信息           类型           符号值        符号名称 + 加数

00000000001c  000500000002 R_X86_64_PC32     0000000000000000 .rodata - 4

000000000021  000c00000004 R_X86_64_PLT32    0000000000000000 puts - 4

00000000002b  000d00000004 R_X86_64_PLT32    0000000000000000 exit - 4

000000000054  000500000002 R_X86_64_PC32     0000000000000000 .rodata + 22

00000000005e  000e00000004 R_X86_64_PLT32    0000000000000000 printf - 4

000000000071  000f00000004 R_X86_64_PLT32    0000000000000000 atoi - 4

000000000078  001000000004 R_X86_64_PLT32    0000000000000000 sleep - 4

000000000087  001100000004 R_X86_64_PLT32    0000000000000000 getchar - 4

重定位节 '.rela.eh_frame' at offset 0x448 contains 1 entry:

  偏移量          信息           类型           符号值        符号名称 + 加数

000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0

符号表

这一部分用于存放定义和引用的函数和全局变量信息,如下

Symbol table '.symtab' contains 18 entries:

   Num:    Value          Size Type    Bind   Vis      Ndx Name

     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND

     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c

     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1

     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3

     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4

     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5

     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7

     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8

     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    9

     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    6

    10: 0000000000000000   146 FUNC    GLOBAL DEFAULT    1 main

    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_

    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts

    13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND exit

    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

    15: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND atoi

    16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND sleep

    17: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND getchar

4.4 Hello.o的结果解析

使用objdump -d -r hello.o  >  hello_dis.txt命令对hello.o进行反汇编,并将结果重定向到hello_dis.txt文件中。

反汇编结果如下:

hello.o:     文件格式 elf64-x86-64

Disassembly of section .text:

0000000000000000 <main>:

   0:    f3 0f 1e fa               endbr64

   4:    55                          push   %rbp

   5:    48 89 e5               mov    %rsp,%rbp

   8:    48 83 ec 20             sub    $0x20,%rsp

   c:    89 7d ec               mov    %edi,-0x14(%rbp)

   f:    48 89 75 e0             mov    %rsi,-0x20(%rbp)

  13:    83 7d ec 04             cmpl   $0x4,-0x14(%rbp)

  17:    74 16                je     2f <main+0x2f>

  19:    48 8d 3d 00 00 00 00       lea    0x0(%rip),%rdi        # 20 <main+0x20>

                    1c: R_X86_64_PC32 .rodata-0x4

  20:    e8 00 00 00 00           callq  25 <main+0x25>

                    21: R_X86_64_PLT32      puts-0x4

  25:    bf 01 00 00 00            mov    $0x1,%edi

  2a:    e8 00 00 00 00           callq  2f <main+0x2f>

                    2b: R_X86_64_PLT32      exit-0x4

  2f:    c7 45 fc 00 00 00 00 movl   $0x0,-0x4(%rbp)

  36:    eb 48                jmp    80 <main+0x80>

  38:    48 8b 45 e0             mov    -0x20(%rbp),%rax

  3c:    48 83 c0 10             add    $0x10,%rax

  40:    48 8b 10               mov    (%rax),%rdx

  43:    48 8b 45 e0             mov    -0x20(%rbp),%rax

  47:    48 83 c0 08             add    $0x8,%rax

  4b:    48 8b 00               mov    (%rax),%rax

  4e:    48 89 c6               mov    %rax,%rsi

  51:    48 8d 3d 00 00 00 00       lea    0x0(%rip),%rdi        # 58 <main+0x58>

                    54: R_X86_64_PC32 .rodata+0x22

  58:    b8 00 00 00 00           mov    $0x0,%eax

  5d:    e8 00 00 00 00           callq  62 <main+0x62>

                    5e: R_X86_64_PLT32      printf-0x4

  62:    48 8b 45 e0             mov    -0x20(%rbp),%rax

  66:    48 83 c0 18             add    $0x18,%rax

  6a:    48 8b 00               mov    (%rax),%rax

  6d:    48 89 c7               mov    %rax,%rdi

  70:    e8 00 00 00 00           callq  75 <main+0x75>

                    71: R_X86_64_PLT32      atoi-0x4

  75:    89 c7                mov    %eax,%edi

  77:    e8 00 00 00 00           callq  7c <main+0x7c>

                    78: R_X86_64_PLT32      sleep-0x4

  7c:    83 45 fc 01              addl   $0x1,-0x4(%rbp)

  80:    83 7d fc 08              cmpl   $0x8,-0x4(%rbp)

  84:    7e b2                jle    38 <main+0x38>

  86:    e8 00 00 00 00           callq  8b <main+0x8b>

                    87: R_X86_64_PLT32      getchar-0x4

  8b:    b8 00 00 00 00           mov    $0x0,%eax

  90:    c9                   leaveq

  91:    c3                   retq  

可以看出,在hello.o文件中,操作数改成了十六进制表示。并且,printf输出的字符串也被替换成了待重定位的地址。同时,在使用call指令时,.o文件使用的是待链接器重定位的相对偏移地址,而不是使用.s文件中使用的函数名。还有,对于跳转的位置,使用了相对于main函数起始位置偏移的地址来进行跳转。

4.5 本章小结

本章对汇编的过程进行了介绍,并分析了hello.o中 ELF 头、节头部表、重定位节以及符号表。比较了hello.o和hello.s文件的区别。

5章 链接

5.1 链接的概念与作用

链接的概念:链接是指将可重定位目标文件经过符号解析和重定位步骤合并成可执行目标文件的过程。

链接的作用:使得一个项目可以被分解成许多小模块,每个模块可单独进行修改、更新,最后通过链接形成一个项目时,减少不必要的操作。

5.2 在Ubuntu下链接的命令

使用ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o hello.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o -o hello命令对hello.o文件进行连接,生成可执行目标文件hello。

图 5-1链接命令截图

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

使用readelf -a hello > hello_elf.txt命令可将hello中ELF格式信息重定向至hello_elf.txt文件。

各段的起始地址、大小信息如下所示

[号] 名称              类型             地址              偏移量

       大小              全体大小          旗标   链接   信息   对齐

  [ 0]                   NULL             0000000000000000  00000000

       0000000000000000  0000000000000000           0     0     0

  [ 1] .interp           PROGBITS         00000000004002e0  000002e0

       000000000000001c  0000000000000000   A       0     0     1

  [ 2] .note.gnu.propert NOTE             0000000000400300  00000300

       0000000000000020  0000000000000000   A       0     0     8

  [ 3] .note.ABI-tag     NOTE             0000000000400320  00000320

       0000000000000020  0000000000000000   A       0     0     4

  [ 4] .hash             HASH             0000000000400340  00000340

       0000000000000038  0000000000000004   A       6     0     8

  [ 5] .gnu.hash         GNU_HASH         0000000000400378  00000378

       000000000000001c  0000000000000000   A       6     0     8

  [ 6] .dynsym           DYNSYM           0000000000400398  00000398

       00000000000000d8  0000000000000018   A       7     1     8

  [ 7] .dynstr           STRTAB           0000000000400470  00000470

       000000000000005c  0000000000000000   A       0     0     1

  [ 8] .gnu.version      VERSYM           00000000004004cc  000004cc

       0000000000000012  0000000000000002   A       6     0     2

  [ 9] .gnu.version_r    VERNEED          00000000004004e0  000004e0

       0000000000000020  0000000000000000   A       7     1     8

  [10] .rela.dyn         RELA             0000000000400500  00000500

       0000000000000030  0000000000000018   A       6     0     8

  [11] .rela.plt         RELA             0000000000400530  00000530

       0000000000000090  0000000000000018  AI       6    21     8

  [12] .init             PROGBITS         0000000000401000  00001000

       000000000000001b  0000000000000000  AX       0     0     4

  [13] .plt              PROGBITS         0000000000401020  00001020

       0000000000000070  0000000000000010  AX       0     0     16

  [14] .plt.sec          PROGBITS         0000000000401090  00001090

       0000000000000060  0000000000000010  AX       0     0     16

  [15] .text             PROGBITS         00000000004010f0  000010f0

       0000000000000145  0000000000000000  AX       0     0     16

  [16] .fini             PROGBITS         0000000000401238  00001238

       000000000000000d  0000000000000000  AX       0     0     4

  [17] .rodata           PROGBITS         0000000000402000  00002000

       000000000000003b  0000000000000000   A       0     0     8

  [18] .eh_frame         PROGBITS         0000000000402040  00002040

       00000000000000fc  0000000000000000   A       0     0     8

  [19] .dynamic          DYNAMIC          0000000000403e50  00002e50

       00000000000001a0  0000000000000010  WA       7     0     8

  [20] .got              PROGBITS         0000000000403ff0  00002ff0

       0000000000000010  0000000000000008  WA       0     0     8

  [21] .got.plt          PROGBITS         0000000000404000  00003000

       0000000000000048  0000000000000008  WA       0     0     8

  [22] .data             PROGBITS         0000000000404048  00003048

       0000000000000004  0000000000000000  WA       0     0     1

  [23] .comment          PROGBITS         0000000000000000  0000304c

       000000000000002b  0000000000000001  MS       0     0     1

  [24] .symtab           SYMTAB           0000000000000000  00003078

       00000000000004c8  0000000000000018          25    30     8

  [25] .strtab           STRTAB           0000000000000000  00003540

       0000000000000158  0000000000000000           0     0     1

  [26] .shstrtab         STRTAB           0000000000000000  00003698

       00000000000000e1  0000000000000000           0     0     1

5.4 hello的虚拟地址空间

使用edb加载hello,如下图

图 5-2使用edb加载hello

可以看到hello的虚拟地址起始于0x40100

通过与Symbols窗口,可以查看各段对应的信息与虚拟地址,如下图

图 5-3与symbols对照(部分)

图5-4与symbols对照(部分)

5.5 链接的重定位过程分析

使用objdump -d -r hello > hello_dis.txt命令,将hello的反汇编代码重定向至hello_dis.txt文件。

分析可知,在hello中包含了外部库代码,有了新的节,不再有重定位条目,并且跳转地址也变成了虚拟地址。

链接的过程:链接器会扫描分析所有可重定位目标文件,并完成符号解析和重定位。首先是符号解析,将每个符号引用于一个符号定义关联起来;之后是重定位,在重定位之前,链接器会把所有的文件进行合并,然后将运行时的虚拟地址重新赋给每个节,之后链接器通过编译器产生的重定位条目进行分析,把每个符号定义和一个内存位置关联起来。最后产生一个所需的可执行目标文件,

5.6 hello的执行流程

使用gdb进行调试。根据前面得到的ELF信息,可以知道函数入口在0x4010f0,所以在执行程序时,首先从_start开始执行,所以在这里打上断点。

图 5-5gdb调试

之后单步执行,发现首先调用的是__libc_start_main这个函数,它用来向main函数传递参数。

图 5-6函数调用

之后又调用了 __GI___cxa_atexit,__lll_cas_lock,__new_exitfn,之后又回到了__libc_start_main。

图 5-7函数调用

之后调用__libc_csu_init函数,然后调用init再返回,接着再返回__libc_start_main。

图 5-8函数调用

图 5-9函数调用

之后调用_setjmp、_sigsetjmp和_sigjmp_save,然后返回到__libc_start_main,最后来到了main函数中。

图 5-10函数调用

在main函数中,根据输入,调用不同的函数,比如我这里就没有输入,之后调用puts@plt,之后再调用__GI__IO_puts,再调用一系列的函数,返回到__GI__IO_puts

图 5-11函数调用

图 5-12函数调用

 

图 5-13函数调用

之后__GI__IO_puts返回到main函数,接着调用exit。

之后调用了__GI_exit再调用__run_exit_handlers,__GI___call_tls_dtors之后返回到__run_exit_handlers,再调用__GI___call_tls_dtors,再回到 __run_exit_handlers,然后调用 _IO_cleanup,再调用 _IO_flush_all_lockp,最后回到__run_exit_handlers,再调用__GI_exit,回到main函数。

5.7 Hello的动态链接分析

在加载hello时,动态链接器对共享目标文件中的相应的代码和数据进行重定位,加载共享库,生成完全的可执行目标文件。

   使用edb查看.got.plt段的内容,在未调用dl_init时,内容如下;

图 5-14未调用dl_init时.got.plt段的内容

调用之后的内容如下:

图 5-15调用dl_init后.got.plt段的内容

可以看出在调用前后对应的内容发生了变化。

延迟绑定通过GOT和PLT实现,其中GOT存放函数地址,PLT使用GOT中的地址跳转到目标函数。当程序调用共享库定义的函数时,编译器无法预测这个函数运行时的地址因为定义它的共享模块可以在运行时加载到任意地址。所以,在调用共享库函数时,会首先跳转到PLT执行指令,第一次跳转时,GOT条目为PLT下一条指令,首先将函数ID压栈,然后跳转到PLT[0],之后在PLT[0]再将重定位表地址压栈,然后转进动态链接器,在动态链接器中使用两个栈条目确定函数运行时地址重写GOT,再将控制传递给目标函数。在之后的运行过程中如果再次调用同一函数,则通过间接跳转将控制直接转移至目标函数。

5.8 本章小结

本章首先说明了hello.o重定位生成可执行目标文件hello的过程,之后说明了hello的虚拟地址空间与节头部表的对应关系,接着分析了hello的执行过程并对hello进行了动态链接分析。

6章 hello进程管理

6.1 进程的概念与作用

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

进程的作用:每次用户通过shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文中运行它们自己的代码或其他应用程序。

进程提供给应用程序的两个抽象:一个独立的逻辑控制流,好像程序独占地使用处理器;一个私有的地址空间,好像程序独占地使用内存系统。

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

Shell的作用:Shell是一个交互型应用级程序,代表用户运行其他程序。它是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行。

Shell的处理流程:shell首先检查命令是否是内部命令,若不是再检查是否是一个应用程序。然后shell在搜索路径里寻找这些应用程序(。如果键入的命令不是一个内部命令并且在路径里没有找到这个可执行文件,将会显示一条错误信息。如果能够成功找到命令,该内部命令或应用程序将被分解为系统调用并传给Linux内核。

6.3 Hello的fork进程创建过程

首先,在shell界面输入命令 ./hello 2021113140 符世博 1之后,shell会判断输入的是否为内置命令,若不是再检查是否是一个应用程序。在当前目录下找到了hello,之后通过fork创建子进程。子进程与父进程相似,会得到一份与父进程用户级虚拟空间相同且独立的副本——包括数据段、代码、共享库、堆和用户栈等,父进程打开的文件,子进程也可读写。二者之间最大的不同在于PID的不同。fork函数被调用一次会返回两次,在父进程中,fork函数返回子进程的PID,在子进程中,fork函数返回0。

6.4 Hello的execve过程

Execve函数加载并执行可执行程序hello,并且构造参数列表和环境变量。只有当加载出现错误时函数才会返回,否则不返回。

首先execve函数会删除已经存在的用户区域,之后会为hello的代码、数据、.bss和栈区域创建新的区域结构,接着它会映射共享区,最后会设置PC,让PC指向代码的入口。

6.5 Hello的进程执行

上下文:内核重新启动一个被抢占的进程所需要恢复的原来的状态,由寄存器、程序计数器、用户栈、内核栈和内核数据结构等对象的值构成。

上下文切换:在内核调度了一个新的进程运行时,它就抢占当前进程,并使用一种上下文切换的机制来控制转移到新的进程。具体过程为:保存当前进程的上下文,恢复某个先前被抢占的进程被保存的上下文,将控制传递给这个新恢复的进程。

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

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

用户模式和内核模式:为了保证系统安全,需要限制应用程序所能访问的地址空间范围。因而存在用户模式和内核模式的划分,内核模式拥有最高的访问权限,而用户模式的访问权限会受到一些限制。处理器使用一个寄存器作为模式位来描述当前进程的特权。进程只有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,一定程度上保证了系统的安全性。

Hello在执行时是运行在用户模式下的,在运行过程中,内核不断的进行上下文的切换,使得运行过程被切分成很多的时间片,与其他进程交替进行。如果在运行时收到信号,就会进入内核模式,运行信号处理程序,之后再由信号处理程序决定下一步操作。

6.6 hello的异常与信号处理

执行截图:

图 6-1运行时随便输入

图 6-2运行时输入ctrl+c

图 6-3运行时输入ctrl+z之后输入ps和jobs指令

图 6-4进程停止时输入pstree命令(部分)

图 6-5输入kill命令

异常的分类:中断:来自I/O设备的信号(Ctrl-C、Ctrl-Z),异步。陷阱:有意的异常,执行指令的结果(例如:exit),同步。故障:潜在可恢复的错误(如:缺页异常),同步。终止:遇到不可修复的错误终止程序的运行,同步在hello运行过程中,会出现前三种异常,中断(键盘输入Ctrl-C、Ctrl-Z),陷阱(调用fork,exit函数),故障(缺页故障)。

信号:在hello运行过程中,会产生以下信号:SIGINT(键盘输入Ctrl-C),SIGSTP(键盘输入Ctrl-Z),SIGCONT(输入Ctrl-Z后使用fg命令),SIGKILL(使用kill命令),下面分别进行测试:

SIGINT:在hello运行过程中输入Ctrl-C,会给它发送SIGINT信号,终止这个进程。

图 6-6输入Ctrl-C

       SIGSTP:当hello运行时,输入Ctrl-Z会向它发送SIGSTP信号,这个进程就会被挂起。

图 6-7输入Ctrl-Z

       SIGCONT:当进程被挂起后,使用fg命令,会向它发送SIGCONT信号,让它继续在前台运行。

图 6-8输入fg命令

SIGKILL:当进程在后台执行时,使用kill命令可以向它发送SIGKILL信号,终止这个进程,让进程在后台执行可以在执行命令后加上 &。同时使用ps或者jobs命令查看hello进程。这两个命令不同的地方是,ps会显示出所有的进程,而jobs只会显示当前shell正在维护的进程。

图 6-9后台运行并输入ps和jobs

图 6-10输入kill命令后输入ps命令

6.7本章小结

本章阐述了hello如何运行在系统上,并且对它的异常和信号机制进行了说明。

结论

Hello程序的历程:

  1. hello.c源代码文件通过预处理,得到了预处理文件hello.i;
  2. hello.i经过编译器的编译得到汇编代码文件hello.s;
  3. hello.s经过汇编器的汇编得到可重定向目标文件hello.o;
  4. hello.o经过链接器的链接过程成为可执行目标文件hello;
  5. 用户在shell中键入执行hello程序的命令后,shell解释用户的输入,找到hello可执行目标文件并为其执行fork创建新进程;
  6. fork得到的新进程通过调用execve完成在其上下文中对hello程序的加载,hello开始执行;
  7. hello作为一个进程运行,接受内核的进程调度;
  8. hello执行的过程中,会遇到不同的异常和信号,对于不同的异常和信号会做出不同的反应;
  9. hello程序运行结束后,父进程shell会进行回收,内核也会清除在内存中为其创建的各种数据结构和信息。

附件

hello.i

预处理文件

hello.o

可重定位目标文件

hello.s

汇编文件

hello_o_dis.txt

hello.o反汇编结果

hello_elf.txt

hello中的elf信息结果

hello_dis.txt

hello反汇编结果

hello_o_elf.txt

hello.o中的elf信息

hello

可执行目标文件

参考文献

  1. GCC online documentation. GCC online documentation- GNU Project
  2. 深入理解计算机系统(原书第三版).机械工业出版社, 2016.
  3. 编译.百度百科. 编译_百度百科
  4. ELF文件头结构_js0huang的博客-CSDN博客_elf.h

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值