哈工大-计算机系统-2022 | 大作业

哈工大-计算机系统-2022 | 大作业_第1张图片

计算机系统

大作业

题目 程序人生-Hello’s P2P
专业 计算学部
学号 120L021403
班级 2003011
学生 冯新航
指导教师 郑贵滨

计算机科学与技术学院

2022年5月

摘 要

计算机系统是人类创造出的工业奇迹之一,其构造之精妙,功能之强大,令人赞叹。要全面描述计算机系统的方方面面实际上是非常困难的,因此本文从简单的 hello 程序入手,讲述计算机系统的一些基本概念、功能与原理。我们将讲述 hello 是如何从源码编译为可执行文件,并经由 shell 执行的过程,剖析一些概念的细节和实现原理。

关键词:Linux C语言 计算机系统 编译 内存管理 进程

文章目录

  • **计算机系统**

    • 摘 要
    • 第 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.2 函数
        • 3.3.3 赋值
        • 3.3.4 算术操作
        • 3.3.5 关系与条件转移
        • 3.3.6 类型转换
      • 3.4 本章小结
    • 第 4 章 汇编
      • 4.1 汇编的概念与作用
      • 4.2 在Ubuntu下汇编的命令
      • 4.3 可重定位目标 `elf` 格式
      • 4.4 Hello.o的结果解析
      • 4.5 本章小结
    • 第 5 章 链接
      • 5.1 链接的概念与作用
      • 5.2 在Ubuntu下链接的命令
      • 5.3 可执行目标文件hello的格式
      • 5.4 hello的虚拟地址空间
      • 5.5 链接的重定位过程分析
      • 5.6 hello的执行流程
      • 5.7 Hello的动态链接分析
      • 5.8 本章小结
    • 第 6 章 hello进程管理
      • 6.1 进程的概念与作用
      • 6.2 简述壳Shell-bash的作用与处理流程
      • 6.3 Hello的fork进程创建过程
      • 6.4 Hello的execve过程
      • 6.5 Hello的进程执行
      • 6.6 hello的异常与信号处理
      • 6.7 本章小结
    • 第 7 章 hello的存储管理
      • 7.1 hello的存储器地址空间
      • 7.2 Intel逻辑地址到线性地址的变换-段式管理
      • 7.3 Hello的线性地址到物理地址的变换-页式管理
      • 7.4 TLB与四级页表支持下的VA到PA的变换
      • 7.5 三级Cache支持下的物理内存访问
      • 7.6 hello进程fork时的内存映射
      • 7.7 hello进程execve时的内存映射
      • 7.8 缺页故障与缺页中断处理
      • 7.9 动态存储分配管理
      • 7.10 本章小结
    • 第8章 hello的IO管理
      • 8.1 Linux的IO设备管理方法
      • 8.2 简述Unix IO接口及其函数
      • 8.3 printf的实现分析
      • 8.4 getchar的实现分析
      • 8.5 本章小结
    • 结论
    • 附件
    • 参考文献

第 1 章 概述

1.1 Hello简介

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

hello 程序在 Linux 下将经历以下步骤:

  • 由文本编辑器编写成 hello.c 源文件
  • 由编译预处理器将其预处理成 hello.i 文件
  • 由汇编器将其翻译成汇编语言文件 hello.s
  • 由编译器将其编译成可重定向目标文件 hello.o
  • 由链接器 ldhello.o 与系统的其他目标文件链接起来,形成最终的 hello 可执行文件
  • shell 中输入相应命令后,shell 为其 fork 一个子进程,经由 execve 函数,使得子进程代表用户执行 hello 程序
  • 此过程中,该函数将 hello 可执行文件映射到虚拟内存,而后载入物理内存,进入 main 函数执行
  • 调度器调度该进程执行控制流,直到 main 函数返回
  • shell 回收该进程,内核删除相关数据结构

1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。

硬件环境

  • CPU: Intel Core i7-10875H @ 16x 2.304GHz
  • GPU: NVIDIA GeForce RTX 2060
  • RAM: 40GB DDR4 3200

软件环境

  • 操作系统:Windows 11 Home China、Ubuntu 20.04 (on the Windows Subsystem for Linux)

开发工具

  • Visual Studio Code

1.3 中间结果

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

  • hello.c 源代码文本
  • hello.i 编译预处理文件
  • hello.s 汇编文本文件
  • hello.o 可重定位目标文件
  • hello 可执行文件

1.4 本章小结

本章讲述了 hello 程序如何从源代码文本转换为可执行文件的过程,同时列出了实验环境等相关信息。

第 2 章 预处理

2.1 预处理的概念与作用

  • 概念:

    编译预处理器是针对源代码中的编译预处理指令进行的一类操作,如引入头文件、条件编译等

  • 作用:

    处理 #include 指令,将 #include 后的文件原封不动地拷贝到源码对应位置

    处理条件编译指令 #ifdef 等,保留满足条件的源码代码

    替换由 #define 定义的常量等

2.2 在Ubuntu下预处理的命令

应截图,展示预处理过程!

命令:

gcc -E hello.c -o hello.i

截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tIpRjdSD-1653009846945)(https://s1.ax1x.com/2022/05/20/OqqpZt.png)]

2.3 Hello的预处理结果解析

编译预处理器按照C语言标准规则修改和替换源码中某些位置的代码,我们可以在得到的 hello.i 底部找到原来的部分,而在顶端是由 #include 引入的头文件,它被原封不动地拷贝到对应位置。

2.4 本章小结

本章讲述了编译预处理器和编译预处理指令的相关概念及其作用,本质上是修改源文件的文本内容,为编译器将其编译成汇编语言做准备。

第 3 章 编译

3.1 编译的概念与作用

编译器将编译预处理文件 hello.i 编译成汇编文件 hello.s ,这个文件仍然是一个文本文件,内容是 hello 源文件的等价汇编结果。

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

3.2 在Ubuntu下编译的命令

编译命令:

gcc -S -g hello.c -o hello.s

哈工大-计算机系统-2022 | 大作业_第2张图片

应截图,展示编译过程!

3.3 Hello的编译结果解析

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

3.3.1 汇编文件头

	.file	"hello.c"
	.text
.Ltext0:
	.section	.rodata
	.align 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"
	.text
	.globl	main
	.type	main, @function

各部分内容说明:

  • .file 声明源文件
  • .text 代码段
  • .section.rodata 数据段
  • .align 地址对齐方式
  • .string 声明字符串
  • globl 声明全局符号
  • .type 声明符号类型

3.3.2 数据

  • 字符串

    main 函数中存在对 printf 的调用,格式控制字符串的地址作为 printf 的参数被传入,如对于:

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

    的汇编为:

    leaq	.LC0(%rip), %rdi
    call	puts@PLT
    
  • 局部变量

    main 函数中有局部变量 i 作为循环变量,由语句:

    cmpl	$7, -4(%rbp)
    

    可知该变量存储在栈上,位置为 %rpb-4

  • argcargv 参数

    argcargv 都是 main 的参数,在 main 函数开头有

    movl	%edi, -20(%rbp)
    movq	%rsi, -32(%rbp)
    

    可知这些变量都存储在栈上。

  • 立即数

    立即数是直接硬编码在汇编代码中的。

3.3.2 函数

  • 函数调用、参数传递与返回

    sleep(atoi(argv[3])) 调用为例,调用 sleep 函数需传入调用 atoi 的返回值,在调用 atoi 时,先访问 argv[3] 将其值保存到 %rdi

    movq	%rax, %rdi
    call	atoi@PLT
    

    atoi 中以 ret 返回,返回值保存在 %rax 中,再将返回值作为 sleep 函数参数传入:

    movl	%eax, %edi
    call	sleep@PLT
    

3.3.3 赋值

赋值语句对应 x86 汇编中的 mov 指令,后缀表示操作的字节数,如:

movq	(%rax), %rax

%rax 作为内存地址,取地址的值(四字)传入 %rax

其他后缀,有:

  • movb 单字
  • movw 双字
  • movl 四字
  • movq 八字

3.3.4 算术操作

四则运算在 x86 有指令直接实现,如:

addl	$1, -4(%rbp)

%rbp-4 的内存引用值加一,即 i++

其他指令的使用方法如图所示:

哈工大-计算机系统-2022 | 大作业_第3张图片

3.3.5 关系与条件转移

hello.c 的第一个判断语句为例:

if (argc != 4)
{
    printf("用法: Hello 学号 姓名 秒数!\n");
    exit(1);
}

汇编如下:

cmpl	$4, -20(%rbp)
je	.L2
.loc 1 16 3
leaq	.LC0(%rip), %rdi
call	puts@PLT
.loc 1 17 3
movl	$1, %edi
call	exit@PLT

因为 %rbp-20 就是 argc 的地址,将其与 4 比较,cmpl 会修改条件码寄存器,je 指令根据条件码寄存器的值决定是否转跳,此处可知,只有相等时转跳,不相等时进入 if 代码块内。

i < 8 ,同理:

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

3.3.6 类型转换

机器级的类型转换,如基本类型 int float double short 等是通过二进制表示的截断与扩展实现的。

字符串与数字间的类型转换则利用 atoi 这样的函数实现。

3.4 本章小结

本章对 hello.chello.i ,即源码到汇编的对应关系做出解释,讲述了 x86 的基本汇编命令的作用。

第 4 章 汇编

4.1 汇编的概念与作用

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

汇编器将汇编语言文本翻译成机器语言,产生二进制文件,并将这些文件打包成可重定向目标文件,将各类信息存储在文件头中。

4.2 在Ubuntu下汇编的命令

命令:

gcc -c -g hello.s -o hello.o

哈工大-计算机系统-2022 | 大作业_第4张图片

4.3 可重定位目标 elf 格式

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

编译命令:

  • gcc -c hello.s -o hello.o

ELF

命令:

readelf -h hello.o

结果:

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          1240 (bytes into file)
  Flags:                             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

ELF 头描述了该可重定向目标文件的一些信息,可以帮助链接器链接文件,其中包括了 ELF 头的大小、目标文件类型、各节的大小和偏移等信息。

在本例中,可以看见这个可重定向目标文件有 14 个节。

节分布

命令:

readelf -S hello.o

结果:

There are 14 section headers, starting at offset 0x4d8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 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
       000000000000002a  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  00000135
       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)

此处可以看到各节的类型、大小、位置等信息,还可以看出各节的可执行或可读情况。

符号表

命令:

readelf -s hello.o

结果

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

符号表包括了程序中各类符号的信息,指明其是函数还是全局变量等,和可重定向信息。

重定位节

命令:

readelf -r hello.o

结果:

Relocation section '.rela.text' at offset 0x388 contains 8 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
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

Relocation section '.rela.eh_frame' at offset 0x448 contains 1 entry:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0

rela.txt 保存了 .text 节中需要被修改的信息,因为链接时所有函数,全局变量等的地址都需要修正。在这个程序中,需要重定位的信息有:printf puts exit getchar sleep 等。

4.4 Hello.o的结果解析

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

反汇编命令:

objdump -d -r hello.o

结果:

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 
: 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 19: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 20 1c: R_X86_64_PC32 .rodata-0x4 20: e8 00 00 00 00 callq 25 21: R_X86_64_PLT32 puts-0x4 25: bf 01 00 00 00 mov $0x1,%edi 2a: e8 00 00 00 00 callq 2f 2b: R_X86_64_PLT32 exit-0x4 2f: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 36: eb 48 jmp 80 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 54: R_X86_64_PC32 .rodata+0x22 58: b8 00 00 00 00 mov $0x0,%eax 5d: e8 00 00 00 00 callq 62 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 71: R_X86_64_PLT32 atoi-0x4 75: 89 c7 mov %eax,%edi 77: e8 00 00 00 00 callq 7c 78: R_X86_64_PLT32 sleep-0x4 7c: 83 45 fc 01 addl $0x1,-0x4(%rbp) 80: 83 7d fc 07 cmpl $0x7,-0x4(%rbp) 84: 7e b2 jle 38 86: e8 00 00 00 00 callq 8b 87: R_X86_64_PLT32 getchar-0x4 8b: b8 00 00 00 00 mov $0x0,%eax 90: c9 leaveq 91: c3 retq

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

反汇编的结果与 hello.s 存在差异:

  • 分支与转跳:

    分支转跳的地址被重定位,像是 L2 被重定位为机器语言的地址:

    # before
    cmpl	$4, -20(%rbp)
    je	.L2
    
    # after
    cmpl   $0x4,-0x14(%rbp)
    je     2f 
    
  • 函数调用

    当编译时使用了 -g 选项,函数调用将保留函数名,若没有该选项,实际应为函数地址。

  • 立即数的变化

    可重定位目标文件中立即数均以十六进制表示:

    #before
    movl	%edi, -20(%rbp)
    movq	%rsi, -32(%rbp)
    
    #after
    mov    %edi,-0x14(%rbp)
    mov    %rsi,-0x20(%rbp)
    

4.5 本章小结

本章讲述了由 hello.s 生成可重定位目标文件 hello.oELF头、节、符号表和可重定位节,解释了 hello.shello.o 反汇编结果的差距。

第 5 章 链接

5.1 链接的概念与作用

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

链接是链接器将各类可重定位目标文件合并成可执行文件的过程,可执行文件可以加载到内存进行执行。

5.2 在Ubuntu下链接的命令

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

链接命令:

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

哈工大-计算机系统-2022 | 大作业_第5张图片

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

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

  • ELF

    ❯ readelf -h hello
    ELF Header:
      Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
      Class:                             ELF64
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              EXEC (Executable file)
      Machine:                           Advanced Micro Devices X86-64
      Version:                           0x1
      Entry point address:               0x4010f0
      Start of program headers:          64 (bytes into file)
      Start of section headers:          14208 (bytes into file)
      Flags:                             0x0
      Size of this header:               64 (bytes)
      Size of program headers:           56 (bytes)
      Number of program headers:         12
      Size of section headers:           64 (bytes)
      Number of section headers:         27
      Section header string table index: 26
    
  • 节分布

    > readelf -S hello
    There are 27 section headers, starting at offset 0x3780:
    
    Section Headers:
      [Nr] Name              Type             Address           Offset
           Size              EntSize          Flags  Link  Info  Align
      [ 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
           0000000000000029  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
    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)
    
  • 符号表

    > readelf -s hello
    Symbol table '.dynsym' contains 9 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
         2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)
         3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
         4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getchar@GLIBC_2.2.5 (2)
         5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
         6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND atoi@GLIBC_2.2.5 (2)
         7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@GLIBC_2.2.5 (2)
         8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sleep@GLIBC_2.2.5 (2)
    
    Symbol table '.symtab' contains 51 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 00000000004002e0     0 SECTION LOCAL  DEFAULT    1 
         2: 0000000000400300     0 SECTION LOCAL  DEFAULT    2 
         3: 0000000000400320     0 SECTION LOCAL  DEFAULT    3 
         4: 0000000000400340     0 SECTION LOCAL  DEFAULT    4 
         5: 0000000000400378     0 SECTION LOCAL  DEFAULT    5 
         6: 0000000000400398     0 SECTION LOCAL  DEFAULT    6 
         7: 0000000000400470     0 SECTION LOCAL  DEFAULT    7 
         8: 00000000004004cc     0 SECTION LOCAL  DEFAULT    8 
         9: 00000000004004e0     0 SECTION LOCAL  DEFAULT    9 
        10: 0000000000400500     0 SECTION LOCAL  DEFAULT   10 
        11: 0000000000400530     0 SECTION LOCAL  DEFAULT   11 
        12: 0000000000401000     0 SECTION LOCAL  DEFAULT   12 
        13: 0000000000401020     0 SECTION LOCAL  DEFAULT   13 
        14: 0000000000401090     0 SECTION LOCAL  DEFAULT   14 
        15: 00000000004010f0     0 SECTION LOCAL  DEFAULT   15 
        16: 0000000000401238     0 SECTION LOCAL  DEFAULT   16 
        17: 0000000000402000     0 SECTION LOCAL  DEFAULT   17 
        18: 0000000000402040     0 SECTION LOCAL  DEFAULT   18 
        19: 0000000000403e50     0 SECTION LOCAL  DEFAULT   19 
        20: 0000000000403ff0     0 SECTION LOCAL  DEFAULT   20 
        21: 0000000000404000     0 SECTION LOCAL  DEFAULT   21 
        22: 0000000000404048     0 SECTION LOCAL  DEFAULT   22 
        23: 0000000000000000     0 SECTION LOCAL  DEFAULT   23 
        24: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c
        25: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS 
        26: 0000000000403e50     0 NOTYPE  LOCAL  DEFAULT   19 __init_array_end
        27: 0000000000403e50     0 OBJECT  LOCAL  DEFAULT   19 _DYNAMIC
        28: 0000000000403e50     0 NOTYPE  LOCAL  DEFAULT   19 __init_array_start
        29: 0000000000404000     0 OBJECT  LOCAL  DEFAULT   21 _GLOBAL_OFFSET_TABLE_
        30: 0000000000401230     5 FUNC    GLOBAL DEFAULT   15 __libc_csu_fini
        31: 0000000000404048     0 NOTYPE  WEAK   DEFAULT   22 data_start
        32: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5
        33: 000000000040404c     0 NOTYPE  GLOBAL DEFAULT   22 _edata
        34: 0000000000401238     0 FUNC    GLOBAL HIDDEN    16 _fini
        35: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@@GLIBC_2.2.5
        36: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
        37: 0000000000404048     0 NOTYPE  GLOBAL DEFAULT   22 __data_start
        38: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getchar@@GLIBC_2.2.5
        39: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
        40: 0000000000402000     4 OBJECT  GLOBAL DEFAULT   17 _IO_stdin_used
        41: 00000000004011c0   101 FUNC    GLOBAL DEFAULT   15 __libc_csu_init
        42: 0000000000404050     0 NOTYPE  GLOBAL DEFAULT   22 _end
        43: 0000000000401120     5 FUNC    GLOBAL HIDDEN    15 _dl_relocate_static_pie
        44: 00000000004010f0    47 FUNC    GLOBAL DEFAULT   15 _start
        45: 000000000040404c     0 NOTYPE  GLOBAL DEFAULT   22 __bss_start
        46: 0000000000401125   146 FUNC    GLOBAL DEFAULT   15 main
        47: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND atoi@@GLIBC_2.2.5
        48: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@@GLIBC_2.2.5
        49: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sleep@@GLIBC_2.2.5
        50: 0000000000401000     0 FUNC    GLOBAL HIDDEN    12 _init
    
    
  • 重定位节:

    > readelf -r hello 
    Relocation section '.rela.dyn' at offset 0x500 contains 2 entries:
      Offset          Info           Type           Sym. Value    Sym. Name + Addend
    000000403ff0  000300000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
    000000403ff8  000500000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
    
    Relocation section '.rela.plt' at offset 0x530 contains 6 entries:
      Offset          Info           Type           Sym. Value    Sym. Name + Addend
    000000404018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
    000000404020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
    000000404028  000400000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0
    000000404030  000600000007 R_X86_64_JUMP_SLO 0000000000000000 atoi@GLIBC_2.2.5 + 0
    000000404038  000700000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
    000000404040  000800000007 R_X86_64_JUMP_SLO 0000000000000000 sleep@GLIBC_2.2.5 + 0
    
    

5.4 hello的虚拟地址空间

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

EDB显示 hello 的虚拟内存地址范围是 0x401000-0x402000

哈工大-计算机系统-2022 | 大作业_第6张图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-biFAIZ9X-1653009846949)(D:\Users\VonBrank\OneDrive - stu.hit.edu.cn\Project\Daily\20220317 - HIT-CSAPP\Final Assignment\大作业\report.assets\image-20220518091052579.png)]

举例来说,由前面的信息可知 .text 节的地址为 0x4010f0

哈工大-计算机系统-2022 | 大作业_第7张图片

5.5 链接的重定位过程分析

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

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

helloobjdump 结果为:

hello:     file format elf64-x86-64


Disassembly of section .init:

0000000000401000 <_init>:
  401000:       f3 0f 1e fa             endbr64 
  401004:       48 83 ec 08             sub    $0x8,%rsp
  401008:       48 8b 05 e9 2f 00 00    mov    0x2fe9(%rip),%rax        # 403ff8 <__gmon_start__>
  40100f:       48 85 c0                test   %rax,%rax
  401012:       74 02                   je     401016 <_init+0x16>
  401014:       ff d0                   callq  *%rax
  401016:       48 83 c4 08             add    $0x8,%rsp
  40101a:       c3                      retq   

Disassembly of section .plt:

0000000000401020 <.plt>:
  401020:       ff 35 e2 2f 00 00       pushq  0x2fe2(%rip)        # 404008 <_GLOBAL_OFFSET_TABLE_+0x8>
  401026:       f2 ff 25 e3 2f 00 00    bnd jmpq *0x2fe3(%rip)        # 404010 <_GLOBAL_OFFSET_TABLE_+0x10>
  40102d:       0f 1f 00                nopl   (%rax)
  401030:       f3 0f 1e fa             endbr64 
  401034:       68 00 00 00 00          pushq  $0x0
  401039:       f2 e9 e1 ff ff ff       bnd jmpq 401020 <.plt>
  40103f:       90                      nop
  401040:       f3 0f 1e fa             endbr64 
  401044:       68 01 00 00 00          pushq  $0x1
  401049:       f2 e9 d1 ff ff ff       bnd jmpq 401020 <.plt>
  40104f:       90                      nop
  401050:       f3 0f 1e fa             endbr64 
  401054:       68 02 00 00 00          pushq  $0x2
  401059:       f2 e9 c1 ff ff ff       bnd jmpq 401020 <.plt>
  40105f:       90                      nop
  401060:       f3 0f 1e fa             endbr64 
  401064:       68 03 00 00 00          pushq  $0x3
  401069:       f2 e9 b1 ff ff ff       bnd jmpq 401020 <.plt>
  40106f:       90                      nop
  401070:       f3 0f 1e fa             endbr64 
  401074:       68 04 00 00 00          pushq  $0x4
  401079:       f2 e9 a1 ff ff ff       bnd jmpq 401020 <.plt>
  40107f:       90                      nop
  401080:       f3 0f 1e fa             endbr64 
  401084:       68 05 00 00 00          pushq  $0x5
  401089:       f2 e9 91 ff ff ff       bnd jmpq 401020 <.plt>
  40108f:       90                      nop

Disassembly of section .plt.sec:

0000000000401090 :
  401090:       f3 0f 1e fa             endbr64 
  401094:       f2 ff 25 7d 2f 00 00    bnd jmpq *0x2f7d(%rip)        # 404018 
  40109b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000004010a0 :
  4010a0:       f3 0f 1e fa             endbr64 
  4010a4:       f2 ff 25 75 2f 00 00    bnd jmpq *0x2f75(%rip)        # 404020 
  4010ab:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000004010b0 :
  4010b0:       f3 0f 1e fa             endbr64 
  4010b4:       f2 ff 25 6d 2f 00 00    bnd jmpq *0x2f6d(%rip)        # 404028 
  4010bb:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000004010c0 :
  4010c0:       f3 0f 1e fa             endbr64 
  4010c4:       f2 ff 25 65 2f 00 00    bnd jmpq *0x2f65(%rip)        # 404030 
  4010cb:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000004010d0 :
  4010d0:       f3 0f 1e fa             endbr64 
  4010d4:       f2 ff 25 5d 2f 00 00    bnd jmpq *0x2f5d(%rip)        # 404038 
  4010db:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000004010e0 :
  4010e0:       f3 0f 1e fa             endbr64 
  4010e4:       f2 ff 25 55 2f 00 00    bnd jmpq *0x2f55(%rip)        # 404040 
  4010eb:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

Disassembly of section .text:

00000000004010f0 <_start>:
  4010f0:       f3 0f 1e fa             endbr64 
  4010f4:       31 ed                   xor    %ebp,%ebp
  4010f6:       49 89 d1                mov    %rdx,%r9
  4010f9:       5e                      pop    %rsi
  4010fa:       48 89 e2                mov    %rsp,%rdx
  4010fd:       48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
  401101:       50                      push   %rax
  401102:       54                      push   %rsp
  401103:       49 c7 c0 30 12 40 00    mov    $0x401230,%r8
  40110a:       48 c7 c1 c0 11 40 00    mov    $0x4011c0,%rcx
  401111:       48 c7 c7 25 11 40 00    mov    $0x401125,%rdi
  401118:       ff 15 d2 2e 00 00       callq  *0x2ed2(%rip)        # 403ff0 <__libc_start_main@GLIBC_2.2.5>
  40111e:       f4                      hlt    
  40111f:       90                      nop

0000000000401120 <_dl_relocate_static_pie>:
  401120:       f3 0f 1e fa             endbr64 
  401124:       c3                      retq   

0000000000401125 
: 401125: f3 0f 1e fa endbr64 401129: 55 push %rbp 40112a: 48 89 e5 mov %rsp,%rbp 40112d: 48 83 ec 20 sub $0x20,%rsp 401131: 89 7d ec mov %edi,-0x14(%rbp) 401134: 48 89 75 e0 mov %rsi,-0x20(%rbp) 401138: 83 7d ec 04 cmpl $0x4,-0x14(%rbp) 40113c: 74 16 je 401154 40113e: 48 8d 3d c3 0e 00 00 lea 0xec3(%rip),%rdi # 402008 <_IO_stdin_used+0x8> 401145: e8 46 ff ff ff callq 401090 40114a: bf 01 00 00 00 mov $0x1,%edi 40114f: e8 7c ff ff ff callq 4010d0 401154: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 40115b: eb 48 jmp 4011a5 40115d: 48 8b 45 e0 mov -0x20(%rbp),%rax 401161: 48 83 c0 10 add $0x10,%rax 401165: 48 8b 10 mov (%rax),%rdx 401168: 48 8b 45 e0 mov -0x20(%rbp),%rax 40116c: 48 83 c0 08 add $0x8,%rax 401170: 48 8b 00 mov (%rax),%rax 401173: 48 89 c6 mov %rax,%rsi 401176: 48 8d 3d b1 0e 00 00 lea 0xeb1(%rip),%rdi # 40202e <_IO_stdin_used+0x2e> 40117d: b8 00 00 00 00 mov $0x0,%eax 401182: e8 19 ff ff ff callq 4010a0 401187: 48 8b 45 e0 mov -0x20(%rbp),%rax 40118b: 48 83 c0 18 add $0x18,%rax 40118f: 48 8b 00 mov (%rax),%rax 401192: 48 89 c7 mov %rax,%rdi 401195: e8 26 ff ff ff callq 4010c0 40119a: 89 c7 mov %eax,%edi 40119c: e8 3f ff ff ff callq 4010e0 4011a1: 83 45 fc 01 addl $0x1,-0x4(%rbp) 4011a5: 83 7d fc 07 cmpl $0x7,-0x4(%rbp) 4011a9: 7e b2 jle 40115d 4011ab: e8 00 ff ff ff callq 4010b0 4011b0: b8 00 00 00 00 mov $0x0,%eax 4011b5: c9 leaveq 4011b6: c3 retq 4011b7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1) 4011be: 00 00 00000000004011c0 <__libc_csu_init>: 4011c0: f3 0f 1e fa endbr64 4011c4: 41 57 push %r15 4011c6: 4c 8d 3d 83 2c 00 00 lea 0x2c83(%rip),%r15 # 403e50 <_DYNAMIC> 4011cd: 41 56 push %r14 4011cf: 49 89 d6 mov %rdx,%r14 4011d2: 41 55 push %r13 4011d4: 49 89 f5 mov %rsi,%r13 4011d7: 41 54 push %r12 4011d9: 41 89 fc mov %edi,%r12d 4011dc: 55 push %rbp 4011dd: 48 8d 2d 6c 2c 00 00 lea 0x2c6c(%rip),%rbp # 403e50 <_DYNAMIC> 4011e4: 53 push %rbx 4011e5: 4c 29 fd sub %r15,%rbp 4011e8: 48 83 ec 08 sub $0x8,%rsp 4011ec: e8 0f fe ff ff callq 401000 <_init> 4011f1: 48 c1 fd 03 sar $0x3,%rbp 4011f5: 74 1f je 401216 <__libc_csu_init+0x56> 4011f7: 31 db xor %ebx,%ebx 4011f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) 401200: 4c 89 f2 mov %r14,%rdx 401203: 4c 89 ee mov %r13,%rsi 401206: 44 89 e7 mov %r12d,%edi 401209: 41 ff 14 df callq *(%r15,%rbx,8) 40120d: 48 83 c3 01 add $0x1,%rbx 401211: 48 39 dd cmp %rbx,%rbp 401214: 75 ea jne 401200 <__libc_csu_init+0x40> 401216: 48 83 c4 08 add $0x8,%rsp 40121a: 5b pop %rbx 40121b: 5d pop %rbp 40121c: 41 5c pop %r12 40121e: 41 5d pop %r13 401220: 41 5e pop %r14 401222: 41 5f pop %r15 401224: c3 retq 401225: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1) 40122c: 00 00 00 00 0000000000401230 <__libc_csu_fini>: 401230: f3 0f 1e fa endbr64 401234: c3 retq Disassembly of section .fini: 0000000000401238 <_fini>: 401238: f3 0f 1e fa endbr64 40123c: 48 83 ec 08 sub $0x8,%rsp 401240: 48 83 c4 08 add $0x8,%rsp 401244: c3 retq

hello.oobjdump 的结果为:

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 
: 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 19: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 20 1c: R_X86_64_PC32 .rodata-0x4 20: e8 00 00 00 00 callq 25 21: R_X86_64_PLT32 puts-0x4 25: bf 01 00 00 00 mov $0x1,%edi 2a: e8 00 00 00 00 callq 2f 2b: R_X86_64_PLT32 exit-0x4 2f: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 36: eb 48 jmp 80 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 54: R_X86_64_PC32 .rodata+0x22 58: b8 00 00 00 00 mov $0x0,%eax 5d: e8 00 00 00 00 callq 62 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 71: R_X86_64_PLT32 atoi-0x4 75: 89 c7 mov %eax,%edi 77: e8 00 00 00 00 callq 7c 78: R_X86_64_PLT32 sleep-0x4 7c: 83 45 fc 01 addl $0x1,-0x4(%rbp) 80: 83 7d fc 07 cmpl $0x7,-0x4(%rbp) 84: 7e b2 jle 38 86: e8 00 00 00 00 callq 8b 87: R_X86_64_PLT32 getchar-0x4 8b: b8 00 00 00 00 mov $0x0,%eax 90: c9 leaveq 91: c3 retq

可以发现重定位后,hello.o 中所有的可重定位符号都替换成了确切的地址,同时包含许多其他的汇编代码。

5.6 hello的执行流程

使用edb执行hello,说明从加载hello_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

hello 中各函数执行顺序如下:

  • _start
  • _libc_start_main
  • _main
  • _printf
  • _exit
  • _sleep
  • _getchar
  • exit

5.7 Hello的动态链接分析

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

节分布表可知, GOT 地址为 0x403ff0,在 dl_init 执行前,其内容为:

哈工大-计算机系统-2022 | 大作业_第8张图片

dl_init 执行完成后,其内容发生变化:

哈工大-计算机系统-2022 | 大作业_第9张图片

表明 printf 作为库函数,当第一次调用符号时会动态解析其绝对地址并写到 GOT 中,下次调用的时候就不用再次解析了。

5.8 本章小结

本章讲述了链接的相关内容,介绍了可重定位目标文件链接为可执行文件的过程,解释了 ELF 文件格式的意义,同时说明了 hello 运行时虚拟地址空间的布局及其重定位、执行和动态链接过程。

第 6 章 hello进程管理

6.1 进程的概念与作用

  • 概念:

    进程是操作系统中执行程序的一个实例,它是操作系统执行程序的基本单元。

  • 作用:

    操作系统中每个程序都运行在进程的一个上下文中,这个上下文维护了程序的执行状态,如栈、寄存器、环境变量、打开的文件描述符等。

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

shell 是一个用于人与操作系统交互的程序,它提供用户界面,接受命令,并替用户执行程序。

其处理流程大致如下:

  • 读取用户输入的命令
  • 检查是不是内置命令
  • 如果是,则立即执行
  • 如果不是,fork 一个进程,并调用 execve 函数代表用户执行相应程序
  • 若是前台程序,则等待其结束后继续
  • 若是后台则直接继续
  • 使用 waitpid 回收子进程
  • 循环往复

6.3 Hello的fork进程创建过程

当在终端输入执行 hello 的命令时,shell检测到它不是内部命令,就 fork 一个进程,这相当于在内存的另一个区域开辟一个和原来的 shell 相同的副本,然后继续执行。这个子进程的 PID 与父进程不同,是与父进程并发独立执行。

6.4 Hello的execve过程

fork 完成后,shell 调用 execve 函数执行 hello 程序,并传入相关参数。

execve 函数将删除当前虚拟地址中的用户区域,为新程序建立区域结构,将代码段和数据段分别映射至 hello.text.data 节。程序计数器将指向代码段的入口,下次调度该进程时从此开始执行。

哈工大-计算机系统-2022 | 大作业_第10张图片

6.5 Hello的进程执行

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

操作系统为 hello 进程提供一个独立的逻辑控制流和私有地址空间,创造了一种进程独占处理器和内存的假象。

  • 上下文信息:

    进程的上下文信息包括栈、寄存器、PC,系统 IO 等状态信息,每次被抢占时操作系统会保存这些信息,每次进程被调度时操作系统将以这些信息为基准继续执行控制流。

  • 进程时间片:

    一个进程执行其控制流的每一个之时间段称为时间片。

  • 进程调度:

    操作系统在多个进程间并发执行,一个进程执行一段时间后,操作系统可以让其休眠,然后切换到另一个进程。

  • 用户态与内核态:

    shell 使得用户程序有机会修改内核,因此需要手段保护内核。用户态与内核态的划分可以限制当前的进程行为、限制指令的作用范围和数据的可访问、修改范围。

6.6 hello的异常与信号处理

hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

常见异常:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6lxroF34-1653009846951)(https://s1.ax1x.com/2022/05/18/O7PfmQ.png)]

常见信号:

哈工大-计算机系统-2022 | 大作业_第11张图片

命令与键盘触发异常:

  • 正常执行:

    哈工大-计算机系统-2022 | 大作业_第12张图片

  • 挂起进程

    程序执行过程中, 按下 Ctrl+Z 挂起进程,然后用 ps 命令查看

    哈工大-计算机系统-2022 | 大作业_第13张图片

    jobs 可以看见挂起的进程,输入 fg %1 再调回前台执行:

    哈工大-计算机系统-2022 | 大作业_第14张图片

  • 终止进程

    运行时按下 Ctrl+C 可以终止进程,由于进程终止,所以 ps 命令看不见进程

    哈工大-计算机系统-2022 | 大作业_第15张图片

  • 乱按键盘

    运行时乱按键盘会将按下的内容输出到终端

    哈工大-计算机系统-2022 | 大作业_第16张图片

6.7 本章小结

本章介绍了进程的概念,以及 shell 处理流程,同时展示了 forkexecve 函数的原理及其在 hello 程序运行中的作用

第 7 章 hello的存储管理

7.1 hello的存储器地址空间

结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。

  • 逻辑地址:

    源码编译后出现在汇编代码的地址,用来标识一条指令或操作数的地址

  • 线性地址:

    如果地址空间中的整数是连续的, 那么我们说它是一个线性地址空间。 为了简化讨论,我们总是假设使用的是线性地址空间。

  • 虚拟地址:

    在一个带虚拟内存的系统中,CPU从一个有 N=2^n 个地址的地址空间中生成虚拟地址, 这个地址空间称为虚拟地址空间

  • 物理地址:

    一个系统还有一个物理地址空间,对应于系统中物理内存的 M 个字节

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

一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是个索引号,后面3位包含一些硬件细节 。

CPU将一个逻辑地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址,CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。

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

从线性地址到物理地址的变换由分页机制完成,分页即对虚拟地址的分页,使用页表维护。操作系统的数据传输单元为页,每个大小为 4KB ,其变换流程如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tk6SnRnN-1653009846959)(https://s1.ax1x.com/2022/05/19/O7LNGQ.png)]

对于 n 位虚拟地址,将其分为 VPOVPN 两个部分。MMU 会根据 VPN 选择 PTE ,虚拟页缓存是否命中与通常的缓存命中处理步骤完全相同,即若为缓存,则引发缺页异常,调用缺页处理程序处理,从而获得 PPNPPOVPO 是相同的,PPNPPO 拼接形成最终的物理地址。

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

Intel Core i7 处理器的地址翻译使用四级页表,如图所示:

哈工大-计算机系统-2022 | 大作业_第17张图片

CPU 产生 VA后,将其传给 MMU ,先使用 VPN 查询物理页地址是否缓存在 TLB 中,如果在,则得到物理页地址 PPN ;否则不命中,将VPN 分为四段,VPN1 指向一级页表对应页的地址,得到下一级页表的地址,VPN2 在下一级页表中查询,以此类推,VPN4 在最后一级页表中查询到 PPN ,将 PPNVPO 拼接形成最终的物理地址。

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

获得物理地址后,用此地址分成标记:组索引:字节偏移 三个部分,在 L1 中依次进行如下步骤:

  • 组选择:利用组索引找到缓存中对应的组
  • 行匹配:利用标记匹配选择的组中标记相同且有效的内存单元
  • 字抽取:利用偏移量在匹配到的行中选择对应字节
  • 不命中:若行匹配中找不到有效的对应的行,则发生不命中,则依次从请求 L2 L3 直至主存中对应地址的数据,每一层不匹配都将请求下一层的数据。请求完成后,若组内有空闲块,则直接替换;否则采用一定的策略驱逐某些块,进行替换。

7.6 hello进程fork时的内存映射

shell 调用 fork 创建新的进程时,内核为新进程创建数据结构或 PID 。为了创建虚拟内存,创建新的 mm_struct ,新的区域结构和页表,并保持这些结构与父进程一直,同时使用写时复制策略。

7.7 hello进程execve时的内存映射

fork 创建一个子进程后,子程序调用 execve 函数加载 hello 程序,依次执行以下步骤:

  • 删除当前进程虚拟地址的用户区域

  • 建立新的区域结构,布局如图所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KdTiIGyC-1653009846960)(https://s1.ax1x.com/2022/05/19/OHPegK.png)]

  • 若用到共享对象,则将共享对象映射到共享库的内存映射区域

  • 设置程序计数器,使得它指向代码段的入口,下次调度此进程时从此开始执行

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

当进程执行时遇到一个内存引用,但是这个地址指向的物理页面不在物理内存中,将引发一个缺页,引发缺页处理程序。通过 PTE 能查询该页面在磁盘中的位置,将页面调入物理内存,更新 PTE ,将控制权返回引发故障的指令,内存引用的指令再次访存,能正常执行。

7.9 动态存储分配管理

Printf会调用malloc,请简述动态内存管理的基本方法与策略。

动态内存管理需要动态内存分配器完成,它维护着进程的虚拟内存的堆区,它 .bss 节之后,向上生长。对每个进程,维护一个变量 brk ,指向堆顶。分配器将堆视作一组不同大小的块,分为已分配未分配两种。

有两种类型的分配器用于维护堆:

  • 显式分配器:

    显式地释放任何已分配的块,如 C 语言中任何 malloc 的块,都应该 free

  • 隐式分配器:

    分配其检测哪些块是不可达的,从而释放这些块,即垃圾回收器。

为了维护和分配内存,需要使用一些方法来维护堆中哪些块已分配,哪些空闲,有以下做法:

  • 隐式空闲链表,在空闲块的头部标记这个块的大小,以及它是否分配;更优化的做法是在空闲块的脚部维护相同的信息:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hkjnNPXQ-1653009846966)(https://s1.ax1x.com/2022/05/19/OHkva4.png)]

  • 显式空闲链表:

    将堆的空闲状态显式维护在真正的链表数据结构中,使用这种方法使得分配块的时间从块总数的线性实现降低到空闲链表长度相关的线性时间。

    哈工大-计算机系统-2022 | 大作业_第18张图片

7.10 本章小结

本章讲述了 hello 程序执行时的内存相关问题。简述了 hello 进程的 forkexecve 过程中的内存相关细节,以及从逻辑地址到物理地址的翻译过程、内存缺页处理等问题。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:Linux 世界中的所有设备都被视作文件,设备的输入输出被视作文件读写。

设备管理:Unix I/O 是 Linux 的一种接口,它实现了所有输入输出执行的统一。

8.2 简述Unix IO接口及其函数

Unix I/O 接口

  • 打开文件:

    一个程序需要通过内核来打开一个文件,内核返回文件描述符,之后程序可以通过此描述符来访问文件。

  • Linux Sehll 创建进程都打开了三个文件:

    stdinstdoutstderr 。文件描述符分别为 012

  • 改变文件位置:

    对打开的文件,内涵维护其文件位置 k ,默认为 0 。应用可以通过执行 seek 来改变其文件位置。

  • 读写文件:

    一个读操作是从文件复制 n 个字符 n>0 到内存。若当前文件位置是 k ,读取完成后为 k+n 。对大小为 m 的文件,k >= m 时将触发 EOF。写操作与之类似。

  • 关闭文件:

    程序完成对文件的访问后,通知内核关闭文件。进程终止时,内核都会关闭它们打开的文件并释放这些资源。

Unix I/O 函数

  • 打开文件 int open(char *filename, int flags, mode_t mode)
  • 关闭文件 int close(int fd)
  • 读文件 ssize_t read(int fd,void *buf,size_t n)
  • 写文件 ssize_t write(int fd, const void *buf, size_t n)

8.3 printf的实现分析

vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80syscall等.

字符显示驱动子程序:从ASCII到字模库到显示 vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取 vram ,并通过信号线向液晶显示器传输每一个点(RGB分量)。

具体来说,来看看 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; 
} 

... 表示一种可变参数,((char*)(&fmt) + 4) 表示可变参数的第一个参数的地址,通过 arg 可以访问所有参数。

vsprintf 的实现如下:

int vsprintf(char *buf, const char *fmt, va_list args)
{
    char* p;
    char tmp[256];
    va_list p_next_arg = args;

    for (p = buf; *fmt; fmt++)
    {
        if (*fmt != '%')
        {
            *p++ = *fmt;
            continue;
        }

        fmt++;

        switch (*fmt)
        {
            case 'x':
                itoa(tmp, *((int*)p_next_arg));
                strcpy(p, tmp);
                p_next_arg += 4;
                p += strlen(tmp);
                break;
            case 's':
                break;
            default:
                break;
        }
    }
   
    return (p - buf);
} 

可知 vsprintf 可以返回格式化好的字符串。

最后调用 write(buf, i) ,这是一个系统调用,将触发一次异常,系统将调用异常处理程序为我们打印字符。

8.4 getchar的实现分析

getchar 的源码如下:

int getchar(void)

{
    static char buf[BUFSIZ];

    static char* bb=buf;

    static int n=0;

    if(n==0)

    {
        n=read(0,buf,BUFSIZ);

        bb=buf;

    }

    return (--n>=0)?(unsigned char)*bb++:EOF;

}

可看到 getchar 是通过 read 来实现文件读的,每次读入不超过 BUFSIZ 字节的内容,失败时返回 EOF 。有如下特性:

  • 异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成 ascii 码,保存到系统的键盘缓冲区。

  • getchar等调用 read 系统函数,通过系统调用读取按键 ascii 码,直到接受到回车键才返回。

8.5 本章小结

本章讲述了 Linux I/O 的概念与原理,介绍了几种 I/O 函数的使用,特别是 printfgetchar 函数的实现。

结论

用计算机系统的语言,逐条总结hello所经历的过程。

终端运行 hello 程序,它将经历以下过程:

  • 编辑 hello.c 完成源码
  • 编译预处理程序将 hello.c 转化为 hello.i
  • 编译器将 hello.i 翻译成汇编文件 hello.s
  • 编译器将 hello.s 编译成可重定位目标文件 hello.o
  • 链接器将 hello.o 与动态链接库链接为可执行目标文件
  • 在终端输入 hello 及其参数,运行程序
  • shellhello 调用 fork 创建子进程。
  • shell 调用 execve 函数加载 hello ,此时重新构建虚拟地址区域,映射虚拟内存,将程序计数器指向程序入口,待下次调度时载入物理内存。
  • CPU 为进程分配时间片,每次调度该进程时,为其提供一种独占CPU、内存的假象,执行其逻辑控制流。
  • hello 执行时需要访问内存,此过程依赖 MMU 将虚拟地址转换为物理地址。
  • printf 等函数将使用内存分配器 malloc 为其向堆中申请内存。
  • 运行过程中,若在键盘上按下 Ctrl+C Ctrl+Z ,将向 hello 发送信号,并转到相应的异常处理程序。
  • hello 进程终止时,shell 回收子进程,内核将删除进程在内存中的数据结构。

你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法:

现代计算机系统设计之精妙,令人赞叹。它通过从底层至顶层的一系列概念的层层抽象,不仅使之易于实现,还为底层硬件工作者提供易于维护的平台、为上层开发者提供通用的抽象。这套体系形式之通用,功能之强大,有力推动了人类文明的发展。

附件

列出所有的中间产物的文件名,并予以说明起作用。

hello.c 源码

hello.i 编译预处理文件

hello.s 汇编文件

hello.o 可重定位目标文件

hello 可执行目标文件

参考文献

  • 深入理解计算机系统(原书第3版). (2016) (9787111544937th ed.). 机械工业出版社.
  • C++ Primer Plus 第6版 中文版. (2020) (9787115521644th ed.). 人民邮电出版社.

你可能感兴趣的:(linux,运维,服务器,系统架构,c语言)