哈工大2022年春季学期计算机系统大作业——程序人生

Posted 蔚来可期^=*

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈工大2022年春季学期计算机系统大作业——程序人生相关的知识,希望对你有一定的参考价值。

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业  人工智能未来技术                      

学     号  7203610716                      

班   级  20WJ102                      

学       生  孙铭蔚                

指 导 教 师  刘宏伟                   

计算机科学与技术学院

2021年5月

摘  要

本文以一个简单的程序hello.c为例,分析了程序执行的过程,包括预处理、编译、汇编、链接、进程管理、存储管理等过程,对《深入理解计算机系统》(第三版)中的相关知识进行简要概括。

关键词:预处理、编译 、汇编、链接、进程、存储                          

目  录

第1章 概述 - 4 -

1.1 Hello简介 - 4 -

1.2 环境与工具 - 4 -

1.3 中间结果 - 4 -

1.4 本章小结 - 4 -

第2章 预处理 - 5 -

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

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

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

2.4 本章小结 - 5 -

第3章 编译 - 6 -

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

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

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

3.4 本章小结 - 6 -

第4章 汇编 - 7 -

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

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

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

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

4.5 本章小结 - 7 -

第5章 链接 - 8 -

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

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

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

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

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

5.6 hello的执行流程 - 8 -

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

5.8 本章小结 - 9 -

第6章 hello进程管理 - 10 -

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

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

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

6.4 Hello的execve过程 - 10 -

6.5 Hello的进程执行 - 10 -

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

6.7本章小结 - 10 -

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

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

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

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

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

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

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

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

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

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

7.10本章小结 - 12 -

第8章 hello的IO管理 - 13 -

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

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

8.3 printf的实现分析 - 13 -

8.4 getchar的实现分析 - 13 -

8.5本章小结 - 13 -

结论 - 14 -

附件 - 15 -

参考文献 - 16 -


第1章 概述

1.1 Hello简介

hello程序起源于一个能被人类读懂的高级C语言程序。当我们刚学习一门编程语言后,作为程序员的共识,我们总是尝试着在IDE中打印出”hello!”,然而,当我们没有学习计算机底层时,会对这产生很浓厚的好奇心。事实上,我们编写的每一条C语言程序都必须先被转化为一系列的机器语言。下面,我们一起看看编写的hello程序是如何被打印出来的。

Hello的P2P过程:在我们的电脑中,源文件到目标文件的转化分为四个步骤:预处理、编译、汇编和链接。其中分别需要编译器驱动程序调用语言预处理器、编译器、汇编器和链接器。当我们在IDE中编写完hello程序,就得到了源文件。hello.c文件经过预处理器(cpp)得到修改了的源程序,即一个ASCII码的中间文件(hello.i)。之后,通过编译器(cc1)将这个中间文件翻译成汇编程序(hello.s)。然后汇编器(as)将汇编程序翻译成可重定位目标的程序(二进制)叫做hello.o。最后由链接器(ld)创建一个可执行目标二进制程序(hello),这个文件可以被加载到内存中。此后,在shell中输入./hello即可命令系统调用fork函数以及execve等使程序变成进程进而运行。这里的p2p是指程序向进程转化的过程。

Hello的020过程:这个过程对应的内存相关操作,首先加载把hello代码以及数据载入内存,shell为子进程映射虚拟内存,在软硬件结合的情况下,运行直至代码运行结束。在程序运行结束或,内核回收hello进程,删除内存中的相关代码以及数据,一切“归零”。

1.2 环境与工具

硬件环境:硬件1概览:

  型号名称: MacBook Air

  型号标识符: MacBookAir10,1

  芯片: Apple M1

  核总数: 8(4性能和4能效)

  内存: 8 GB

  系统固件版本: 7429.81.3

  操作系统加载程序版本: 7429.81.3

  序列号(系统): C02H479LQ6L5

  硬件UUID: B9297F28-81B7-5DB0-8760-89063F63E0E5

  预置UDID: 00008103-001205960108801E

  激活锁状态: 已启用

硬件2概览:

64 位操作系统, 基于 x64 的处理器

AMD Ryzen 7 4800H with Radeon Graphics  2.90 GHz

LAPTOP-CSSCDDGT

软件环境:Windows 10 64位;Virtualbox; Ubuntu 20.04

开发工具:gcc; gdb; objdump

1.3 中间结果

hello.i: ASCII码的中间文件(预处理器产生),用于分析预处理过程。

hello.s: ASCII汇编语言文件(预处理器产生),用于分析编译的过程。

hello.o:可重定位目标程序(汇编器产生),用于分析汇编的过程。

hello:可执行目标文件(链接器产生),用于分析链接的过程。

hello.txt:hello.o的反汇编文件,用于分析可重定位目标文件hello.o。

hellold.txt:hello的反汇编文件,用于分析可执行目标文件hello。

helloelf.txt:hello.o的ELF格式,用于分析可重定位目标文件hello.o。

helloldelf.txt:hello的ELF格式,用于分析可执行目标文件hello。

1.4 本章小结

本章介绍了Hello的P2P、020的整个过程并介绍了实验的环境、工具以及实验的中间结果。

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

预编译又称为预处理,作用是代码文本替换。预处理指令是以“#”开头的代码为编译做准备工作。C语言在编译之前会进行一步处理——预处理,例如#include<stdio.h>命令告诉处理器读取系统头文件stdio.h的内容,并把它直接插入到程序文本中。进而得到另外一个C语言程序,同城以.i作为拓展名。预处理包括宏定义、文件包含、条件编译等。大多数预处理器指令属于下面3种类型:

  1. 宏定义:#define 指令定义一个宏,#undef指令删除一个宏定义。
  2. 文件包含:#include指令导致一个指定文件的内容被包含到程序中。
  3. 条件编译:#if,#ifdef,#ifndef,#elif,#else和#dendif指令可以根据编译器可以测试的条件来将一段文本包含到程序中或排除在程序之外。

作用如下:

  1. 将源文件中以”include”格式包含的文件复制到编译的源文件中。
  2. 用实际值替换用“#define”定义的字符串。
  3. 根据“#if”后面的条件决定需要编译的代码。

2.2在Ubuntu下预处理的命令

首先,在乌班图终端输入命令gcc -E hello.c -o hello.i

预处理过程如下:

图2-2-1预处理命令

图2-2-2预处理结果

2.3 Hello的预处理结果解析

经过我们查看预处理生成的hello.i文件,可以发现,主函数中定义的变量没有发生变化,注意到以#include开头的代码发生了很大改变,被替换成头文件中的内容,同时,源文件中的注释也被删除了。说明预处理对我们的源程序进行了文本性质的处理,进而生成hello.i文件。

2.4 本章小结

本小结介绍了预处理的概念给出了相关实例,分析了程序运行过程,给出了详细说明,例如预处理指令、宏定义命令、文件包含以及条件编译等。1、

大致总结为:预处理阶段删除所有的#define,展开所有宏定义;处理条件预编译指令#if、#ifdef、#else;处理#include指令,引入递归头文件;删除注释,添加行号和文件名标识用于编译时产生调试信息;保留#pragma 编译器指令。

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

编译就是把代码转化为汇编指令的过程,汇编指令只是CPU相关的。也就是由hello.i转化为hello.s的过程。编译会对于处理文件进行词法分析(运用一种有限状态机的算法将源代码的字符序列分割成一系列的记号,将记号分成关键字、标识符、字面量、运算符)、语法分析(对语法分析中产生内容进行语法分析,从而产生语法树,以表达式为节点的数)、语义分析(只分析静态语义,如类型之间的赋值转换,而动态语义是运行时的出现的问题,如0做除数)、源代码优化、代码生成、目标代码优化(产生优化后的汇编代码)。

作用如下:

便于计算机“理解”程序,因为高级语言是便于人理解的语言,而机器只能(或者说更容易)理解汇编语言,通常汇编程序比C语言效率更高,而且它们采用兼容的指令体系,今儿提高兼容性。

3.2 在Ubuntu下编译的命令

编译命令为gcc -S hello.i -o hello.s

编译过程如下:

图3-2-1编译命令

hello.s文件内容如下:

.file "hello.c"

.text

.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

main:

.LFB6:

.cfi_startproc

endbr64

pushq %rbp

.cfi_def_cfa_offset 16

.cfi_offset 6, -16

movq %rsp, %rbp

.cfi_def_cfa_register 6

subq $32, %rsp

movl %edi, -20(%rbp)

movq %rsi, -32(%rbp)

cmpl $4, -20(%rbp)

je .L2

leaq .LC0(%rip), %rdi

call puts@PLT

movl $1, %edi

call exit@PLT

.L2:

movl $0, -4(%rbp)

jmp .L3

.L4:

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

call printf@PLT

movq -32(%rbp), %rax

addq $24, %rax

movq (%rax), %rax

movq %rax, %rdi

call atoi@PLT

movl %eax, %edi

call sleep@PLT

addl $1, -4(%rbp)

.L3:

cmpl $7, -4(%rbp)

jle .L4

call getchar@PLT

movl $0, %eax

leave

.cfi_def_cfa 7, 8

ret

.cfi_endproc

.LFE6:

.size main, .-main

.ident "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0"

.section .note.GNU-stack,"",@progbits

.section .note.gnu.property,"a"

.align 8

.long  1f - 0f

.long  4f - 1f

.long  5

0:

.string  "GNU"

1:

.align 8

.long  0xc0000002

.long  3f - 2f

2:

.long  0x3

3:

.align 8

3.3 Hello的编译结果解析

3.1.1数据

1)常量

hello.c文件中的常量为printf函数的参数(字符串),“用法: Hello 学号 姓名 秒数!\\n”和“Hello %s %s\\n”。

图 3-3-1hello.c源程序

.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"

对比代码可知,两个printf中的字符串是常量,这两个字符串常量被.LC0和.LC1标识。

2)局部变量

这个源程序中只有一个局部变量,即循环变量int i = 0

在hello.s中,这个局部变量存储在栈上相关代码为:

.L2:

movl $0, -4(%rbp)

jmp .L3

3)函数参数

函数参数有argc和argv,它们被保存在栈上,通过偏移地址进行访问,相关代码如下:

movl %edi, -20(%rbp)

movq %rsi, -32(%rbp)

3.3.2 操作

1)赋值操作

源程序在给局部变量i赋值时,由于i的数据类型是int占4个字节,只用movl指令,将栈指针移动4,下面操作左侧为立即数0.

movl $0, -4(%rbp)

jmp .L3

在赋值操作中,mov后面的字母代表数据类型不同大小。例如,b,w,l,q分别代表1,2,4,8个字节。

 2)算数操作

在for循环中的++操作,每次云环都会给局部变量i自增1,相应的汇编代码为:

addl $1, -4(%rbp)

此外,还有分配栈帧时的加减操作,例如:

subq $32, %rsp

还有改变数组的偏移操作,例如:

addq $16, %rax

3)关系操作和控制转移操作

if循环中的关系操作和控制转移,例如:

cmpl $4, -20(%rbp)

je .L2

对应与源程序的代码为:

    if(argc!=4)

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

        exit(1);

   

这段汇编代码会检查argc是否为4,进而设置条件码,如果相等则会跳转到.L2位置。

for循环中的关系操作和控制转移,例如:

cmpl $7, -4(%rbp)

jle .L4

这两句汇编代码对应于源程序:

    for(i=0;i<8;i++)

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

        sleep(atoi(argv[3]));

每次循环结束判断循环条件,进而决定是否跳出循环。

4)数组、指针、结构操作

数组操作:

在主函数中有指针数组char* argv[]

在argv数组中,argv[0],argv[1],argv[2],argv[3]分别对应指向输入程序的路径和名称、学号、姓名、休眠秒数。

movq -32(%rbp), %rax

addq $16, %rax

movq (%rax), %rdx

movq -32(%rbp), %rax

addq $8, %rax

movq (%rax), %rax

movq %rax, %rsi

以及:

movq -32(%rbp), %rax

addq $8, %rax

movq (%rax), %rax

movq %rax, %rsi

leaq .LC1(%rip), %rdi

从上述汇编代码可以看出,访问函数参数argv[0],argv[1],argv[2],argv[3]时候的寻址方法是基址-变址的方法,使用寄存器指向数组的首地址,再通过便宜的地址寻找所需变量.

movq %rsi, -32(%rbp)

从以上代码可知,rsi存储着argc的首地址。

5)函数操作

通过以下代码以及源程序可知,主函数调用了exit,puts,getchar,print,atoi,sleep函数。

leaq .LC1(%rip), %rdi

movl $0, %eax

call printf@PLT

movq -32(%rbp), %rax

addq $24, %rax

movq (%rax), %rax

movq %rax, %rdi

call atoi@PLT

movl %eax, %edi

call sleep@PLT

addl $1, -4(%rbp)

现在逐个函数进行分析:

main函数:传入参数argc和argv[],分别用寄存器%rdi和%rsi存储,并且返回使用movl $0, %eax,由所学知识可知,rax寄存器存储的是返回值,代表返回0.

printf函数:利用leaq .LC0(%rip)将第一个参数地址传递给%rip,其他参数传递方法类似。使用call puts@PLT调用printf函数。

exit函数:使用movl $1, %edi命令传给参数寄存器,当argc!=4时调用该函数,相关汇编指令为call exit@PLT。

sleep函数:相关汇编指令为 call atoi@PLT以及movl %eax, %edi,将atoi函数的返回结果作为参数传入sleep函数,也即是将%eax中的值复制给%edi。该函数通过汇编指令call sleep@PLT调用。

atoi函数:相关汇编指令为addq $24, %rax、movq (%rax), %rax以及movq %rax, %rdi将argv[3]的字符串地址传入%rdi.通过汇编指令call      atoi@PLT调用。

getchar函数通过汇编指令call  getchar@PLT调用。并返回读取字符的ASCII.

3.4 本章小结

本章首先介绍了编译的过程,接下来给出在乌班图下编译的命令。结合hello程序实例分析说明了编译结果,依次介绍了常量、局部变量、函数参数、赋值操作、函数操作、关系操作和控制转移操作、数组、指针、结构操作函数操作,并逐一分析了用到的函数以及对应的汇编指令。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

汇编的概念:概念:汇编器(as)将 hello.s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中。

作用:将汇编代码转变为机器指令,生成目标文件。

4.2 在Ubuntu下汇编的命令

命令:gcc hello.s -c -o hello.o

图4-2-1 汇编命令

4.3 可重定位目标elf格式

4.3.1 ELF

用命令readelf -h hello.o查看

查看后结果如下:

图4-3-1 hello.o中的ELF内容

从上图可以得出以下结论:

hello.o是64位文件;数据的存储形式为补码、小端序存储;文件的类型为REL(可重定位文件),入口点地址为0x0,程序头起点为0,节头表的起始位置为1256;文件共有13节。

4.3.2 头节表

使用命令readelf -S hello.o查看

查看结果如下图所示:

图4-3-2hello.o头节表内容

头节表中包含了很多信息,例如节的类型为之和大小等。具体信息可见上图。

4.3.3 符号表

用命令readelf -s hello.o查看,查看结果如下图所示:

图4-3-3hello.o中符号表内容

可以看到,符号表中存储了很多变量名和函数名,具体内容见上图。

4.3.4 重定位节

用命令readelf -r hello.o查看,查看结果如下图所示:

图4-3-4hello.o中重定位节的内容

从上图可以看到,重定位节中包含了偏移量、信息、类型、符号值和符号名称等信息。本程序需要重定位的信息有:.rodata,puts,exit,printf,atoi,sleep,getchar这些符号同样需要与相应的地址进行重定位。

4.4 Hello.o的结果解析

使用命令objdump -d -r hello.o > a.s将hello.o存入a.s中,便于查看

结果如下所示:

hello.o:     file format 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 07           cmpl   $0x7,-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的反汇编,并与第3章的 hello.s进行对照分析如下:

经过比较,可以知道,hello.o的反汇编和hello.s大体上比较相似。

有区别的地方如下:

  1. 操作数表示:hello.o反汇编代码中的操作数是以16进制表示,而hello.s中的操作数是以10进制表示。
  2. 分支转移:hello.o反汇编文件的分支转移是通过给出地址进行跳转实现的;ernhello.s文件中的分支转移是通过使用段名称实现的。
  3. 函数调用:hello.s文件中call指令后面的是函数名,而hello.o反汇编文件中call指令后面的是指令的地址。
  4. 全局变量访问:hello.s中全局变量访问是段的名称+寄存器,反汇编文件中全局变量访问是0+寄存器,因为全局变量数据的地址在运行时已经确定,访问也需要重定位。

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

4.5 本章小结

本章介绍了汇编的过程,并且分析了机器语言的构成,与汇编语言的映射关系。

(第4章1分)


第5章 链接

5.1 链接的概念与作用

链接的概念:链接是将一个或多个由编译器或汇编器生成的目标文件外加库链接为一个可执行文件。例如,hel1o程序调用了 printf 函数,它是每个C编译器都提供的标淮C库中的一个函数。printf 两数存在于一个名为 printf.o的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到我们的hel1o.o程序中。链接器(ld)就负责处理这种合并。结果就得到hel10文件,它是一个可执行目标文件(或者简称为可执行文件),可以被加载到内存中,由系统执行。

链接的作用:1)符号解析 2)重定位

5.2 在Ubuntu下链接的命令

链接的命令:ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o

图5-2-1 链接命令

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

5.3.1 ELF

在ubuntu终端输入readelf -a hello > all.txt,可以将内容输出到all.txt中,方便查看信息。ELF头信息如下图所示。

图5-3-1hello ELF头内容

从上图可以得出以下结论:

hello的数据的存储形式为补码、小端序存储;文件的类型为ELF64,入口点地址为0x1100,程序头起点为64,节头表的起始位置为14208,表明重定位已经完成;文件共有27节,多于hello.o文件数目。

5.3.2 节头表

图5-3-2hello中头节表部分内容

从上图可以得出以下结论:

hello中头节表包含了很多信息,例如节的类型为之和大小等,hello中节头表的条目数多于hello.o中节头表的条目数。特别地,每一节都有实际的地址而非hello.o中地址全是0。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。

5.3.3 符号表

查看符号表结果如下图所示

图5-3-3 hello符号表内容

由上图可知,hello 符号表内容包括很多符号引用和定义的信息,例如目标文件的符号表包含定位和重定位程序的符号定义和符号引用所需的信息。每个符号表是一个条目的数组,每个条目包括value:距定义目标的节的起始位置的偏移;size:目标的大小;type:指明数据还是函数;bind:表示符号是本地的还是全局的等等。

5.3.4动态区域

查看结果如下图所示

图5-3-4动态区域内容

5.4 hello的虚拟地址空间

在乌班图中运行命令edb –-run hello运行hello。

运行结果如下图所示:

图5-4-1edb运行结果图

经过初步分析,程序被载入至地址0x401000~0x402000中。在该地址范围内,每个节的地址都与对应的 Address 相同。根据edb查看的结果,在地址空间0x401000~0x401fff中存放着与地址空间0x401000~0x402000相同的程序,在0x401fff之后存放的是.dynamic到.shstrtab节的内容。

5.5 链接的重定位过程分析

使用命令objdump -d -r hello > hello.txt将hello文件返汇编代码存储到hello.txt便于查看。

 

图5-5-1获取反汇编代码命令

对hello进行反汇编部分结果如下:

图5-5-1 hello的反汇编部分结果

hello和hello.o相比,有很多地方发生变化。

首先多了很多经过重定位之后的函数,如多出了.plt,puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt,atoi@plt等,hello.o在.text段之后只有一个main函数。相关事例如下:

 

图5-5-2相关示例

其次,跳转指令参数发生变化。在链接过程中,链接器解析了重定位条目,并计算相对距离,修改了对应位置的字节代码为PLT 中相应函数与下条指令的相对地址,从而得到完整的反汇编代码。相关例子如下所示:

图5-5-3相关示例

最后,hello.o的地址是从0开始的,是相对地址,而hello的地址是从0x401000(_init的地址)开始的,是已经进行重定位之后的虚拟地址;在hello的main函数中,条件跳转指令和call指令后均为绝对地址,而hello.o中是相对于main函数的相对地址。相关例子如下所示:

图5-5-4相关示例

5.6 hello的执行流程

以下格式自行编排,编辑时删除

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

_dl_start、_dl_init

_start

_libc_start_main

_main

_printf

_exit

_sleep_getchar

_dl_runtime_resolve_xsave

_dl_fixup

_dl_lookup_symbol_x

Exit

ld-2.27.so!_dl_start

ld-2.27.so!_dl_init

hello!_start

lib-2.27.so!__libc_start_main

hello!puts@plt

5.7 Hello的动态链接分析

   (以下格式自行编排,编辑时删除

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

编译器在运行时会添加重定位记录,等待动态链接器处理,目的是避免运行时修改调用模块的代码段。动态链接器使用过程链接表+全局偏移量表实现函数的动态链接,在全局偏移量表中存放函数目标地址,过程链接表使用全局偏移量表中地址跳转到目标函数,在加载时,动态链接器会重定位全局偏移量表中的每个条目,使得它包含目标的正确的绝对地址。

.got与.plt节保存着全局偏移量表,其内容从地址0x404000开始。通过edb查看,在dl_init调用前,其内容如下:

               exit@plt的GOT条目       调用前的.got.plt

图5-7-1调用前的情况

在调用后,其内容变为:

                                                                           调用后的.got.plt

图5-7-2调用后的情况

比较可以得知,0x404008~0x404017之间的内容,对应着全局偏移量表的内容发生了变化。其一保存的是指向已经加载的共享库的链表地址,其二是动态链接器在ld-linux.so模块中的入口。这样,接下来执行程序的过程中,就可以使用过程链接表和全局偏移量表进行动态链接。

5.8 本章小结

本章介绍了链接相关概念,介绍了乌班图中链接器链接的命令,通过可执行目标文件hello,分析了hello虚拟地址空间,借助edb软件分析了重定位过程以及执行流程。


第6章 hello进程管理

6.1 进程的概念与作用

进程被认为是计算机科学中最深刻、最成功的概念之一。在现代系统上运行一个程序时,我们会得到一个假象,就好像我们的程序是系统中当前运行的唯一的程序一样。我们的程序好像是独占地使用处理器和内存。处理器就好像是无间断地一条接一条地执行我们程序中的指令。

以上是关于哈工大2022年春季学期计算机系统大作业——程序人生的主要内容,如果未能解决你的问题,请参考以下文章

哈工大2022计算机系统大作业---程序人生

哈工大2022计算机系统大作业“程序人生”

哈工大计算机系统大作业——程序人生

2022春哈工大ICS大作业——程序人生-hello‘sP2P

信号与系统2022春季学期:作业内容与参考答案

山东大学 软件学院 2021-2022 春季学期 《大数据安全》期末考题