锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

计算机系统大作业

时间:2023-05-09 09:35:17 电容接触器lc1d40k


计算机系统

大作业

题 目程序人生-Hello’s P2P

专 业 计算机大类

学 号 120L021501

班 级2003012

学 生 曹煜浩

指 导 教 师郑贵滨

计算机科学与技术学院

2021年5月

摘 要

本文将通过对hello.c在linux以下是如何运行的讨论,引导我们了解计算机系统中程序的运行过程,让我们了解计算机的奥秘。本文将详细介绍预处理、编译、汇编、链接、流程管理、存储管理、IO让我们深入了解计算机系统的几个部分。

关键词:编译;预处理;链接;流程管理;存储管理;IO;

(摘要0分,缺失-1分,根据内容精彩,称都酌情加分0-1分)

目 录

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念和作用

2.2在Ubuntu下一个预处理命令/u>

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

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的整个过程。

1.P2P:

from program to progress。我们将代码输入计算机,利用编译器将代码转化为程序,然后通过cpp、ccl、as、ld,将程序变为可执行程序。

2.020:

  1. shell为hello进程execve,映射虚拟内存,进入程序入口后程序开始载入物理内存。
  2. 进入 main 函数执行目标代码,CPU为运行的hello分配时间片执行逻辑 控制流。
  3. 当程序运行结束后,shell父进程负责回收hello进程,内核删除相关数据 结构。

1.2 环境与工具

硬件环境:Intel Core i7-6700HQ x64CPU,16G RAM,256G SSD.

软件环境:Ubuntu20.04.1 LTS

开发与调试工具:gedit,gcc,as,ld,gdb,readelf,HexEdit

1.3 中间结果

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

hello.i:cpp处理后的文件

hello.s:编译后的文件

hello.o:汇编后的可重定位目标执行

hello:可执行文件

hello.elf:hello.o的ELF格式

hello.objdump:hello.o反汇编代码

hello1.objdump:hello的反汇编代码

1.4 本章小结

本章主要简单介绍了 hello 的 p2p,020 过程,列出了本次实验信息:环境、中间结果

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位——(用C/C++的术语来说是)预处理记号(preprocessing token)用来支持语言特性(如C/C++的宏调用)。

预处理的过程:

  1. 将源文件中用#include 形式声明的文件复制到新的程序中。比如 hello.c第 6-8 行中的#include 等命令告诉预处理器读取系统头文件stdio.h unistd.h stdlib.h 的内容,并把它直接插入到程序文本中。
  2. 用实际值替换用#define 定义的字符串
  3. 根据#if 后面的条件决定需要编译的代码
  4. 特殊符号,预编译程序可以识别一些特殊的符号,预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

2.2在Ubuntu下预处理的命令

cpp hello.c > hello.i

2.3 Hello的预处理结果解析

利用gedit打开hello.i,发现程序扩充到3060行,main函数从3047行开始。

在这一步中,头文件unistd.h,stdio.h,stdlib.h全部展开,cpp 到Ubuntu中默认的环境变量下寻找头文件,并在相应的地址处打开这些文件。拿stdio.h举例,切换到计算机下的usr/local/stdio.h,我们发现一件事情,stdio.h中依然有#define,而.i文件中是没有#define文件的,取而带之的是#ifndef,#ifdef的语句,由此得到的结论是cpp 会对条件值进行判断来决定是否执行包含其中的逻辑。特殊符号,预编译程序可以识别一些特殊的符号,预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

2.4 本章小结

一个简简单单的hello.c文件,所需要的代码非常多,甚至达到了近4000行。在这一章中,我利用cpp将hello.c变为了hello.i。理解了预处理器如何处理代码。

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

编译是指把用高级程序设计语言书写的源程序,翻译成等价的机器语言格式目标程序的翻译程序。编译程序属于采用生成性实现途径实现的翻译程序。它以高级程序设计语言书写的源程序作为输入,而以汇编语言或机器语言表示的目标程序作为输出。编译出的目标程序通常还要经历运行阶段,以便在运行程序的支持下运行,加工初始数据,算出所需的计算结果。

编译程序的作用是将高级程序语言源程序翻译为目标程序。编译程序属于采用生成性实现途径实现的翻译程序。它以高级程序设计语言书写的源程序作为输入,而以汇编语言或机器语言表示的目标程序作为输出。

以下为编译的基本流程:

  1. 语法分析:编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位,方法分为两种:自上而下分析法和自下而上分析法。
  2. 中间代码:源程序的一种内部表示,或称中间语言。中间代码的作用是可使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现中间代码。
  3. 代码优化:指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。
  4. 目标代码:生成是编译的最后一个阶段。目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。此处指汇编语言代码,须经过汇编程序汇编后,成为可执行的机器语言代码。

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

        

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析

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

3.3.1

    

指令

含义

.file

声明源文件

.text

以下是代码段

.section .rodata

以下是 rodata 节

.globl

声明一个全局变量

.type

用来指定是函数类型或是对象类型

.size

声明大小

.long、.string

声明一个 long、string 类型

.align

声明对指令或者数据的存放地址进行对齐的方式

3.3.2

hello.s中用到的数据类型有整数,字符串,数组。

一、字符串:

"Hello 120L021501 \346\233\271\347\205\234\346\265\251 \347\247\222\346\225\260\357\274\201"

"Hello %s %s\n"

"GNU"

其中,第一个字符串是在第一个printf中打印的内容

printf("Hello 120L021501 曹煜浩 秒数!\n");

该字符串被编译成了UTF-8格式一个数字一个字节,一个汉字在 utf-8 编码中占三个字节,一个\代表一个字节。

第二个字符串是在第二个printf中打印的内容

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

后两个字符串都声明在了.rodata 只读数据节。

第三个字符串表明使用的是GNU。

  • 整数:

(1)int i, 

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

.L2:

movl $0, -4(%rbp)

jmp .L3

可以看出,这是将一个4字节的数据压入栈中

  1. int argc,作为第一个参数传入
  2. 立即数,类似于$32这种
  • 数组
  1. char *argv[]

这个是main函数执行时输入的命令行,argv 单个元素 char*大小为 8字节,argv 指针指向存放着字符指针的连续空间,起始地址为 argv。main 函数中访问数组元素argv[1],argv[2]时,按照起始地址 argv 大小8B计算数据地址取数据,在hello.s 中,使用两次(%rax)(两次 rax 分别为 argv[1]和 argv[2]的地址)取出其值。第一次是在语句movq (%rax), %rdx,第二次是在语句movq (%rax), %rax。

addq $16, %rax

movq (%rax), %rdx

movq -32(%rbp), %rax

addq $8, %rax

movq (%rax), %rax

movq %rax, %rsi

3.3.3、数据类型

数据的赋值:

这里涉及到的数据赋值是i=0,其中在汇编代码中的指令为movl $0, %eax,因为i的数据类型为int,所以使用的是%eax.

3.3.4、算术操作

涉及的算术操作:

指令

算术表达式

leaq a,b

b=&a

inc a

a+=1

dec a

a-=1

neg a

a=-a

add a,b

b=b+a

sub a,b

b=b-a

imulq a

r[%rdx]:r[%rax]=a*r[%rax](有符号)

mulq a

r[%rdx]:r[%rax]=a*r[%rax](无符号)

idivq a

r[%rdx]=r[%rdx]:r[%rax] mod a(有符号) r[%rax]=r[%rdx]:r[%rax] div a

divq a

r[%rdx]=r[%rdx]:r[%rax] mod a(无符号) r[%rax]=r[%rdx]:r[%rax] div a

程序中涉及到的算术操作:

  1. i++

     addl $1, -4(%rbp)

  1. 汇编中使用leaq .LC1(%rip),%rdi,使用了加载有效地址指令leaq计算LC1 的段地址%rip+.LC1并传递给%rdi。

leaq .LC1(%rip), %rdi

 3.3.5 关系操作

指令

意义

描述

CMP S1,S2

S2-S1

比较-设置条件码

TEST S1,S2

S1&S2

测试-设置条件码

SET** D

D=**

按照**将条件码设置

D J**

——

根据**与条件码进行跳转

该程序中涉及的关系运算:

  1. argc!=4。判断argc和4是否相等。具体在代码中是cmpl $4, -20(%rbp)
  2. i<8,具体在代码中体现是cmpl $7, -4(%rbp)。

3.3.5、控制转移

(1)if控制语句,当argc!=4的时候执行该语句。

if(argc!=4){

printf("Hello 120L021501 曹煜浩 秒数!\n");

exit(1);

}

汇编代码为

cmpl $4, -20(%rbp)

je .L2

对于if判断,可以通过跳转指令实现。利用cmpl比较argv和4,设置条件码,使用je判断ZF标志位,如果为0,说明argv-4=0,则不执行if中的代码直接跳转到.L2,否则顺序执行执行if中的代码。

(2)for循环,让变量循环8次

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

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

sleep(atoi(argv[3]));

}

如果i<8,则执行跳转功能,继续循环,不满足条件,跳出循环。

3.3.6、函数操作

C语言中,子程序的作用是由一个主函数和若干个函数构成。由主函数调用其他函数,其他函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次。在程序设计中,常将一些常用的功能模块编写成函数,放在函数库中供公共选用。要善于利用函数,以减少重复编写程序段的工作量。

函数包括如下内容:

  1. 函数表达式:函数作为表达式中的一项出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。
  2. 函数语句:函数调用的一般形式加上分号即构成函数语句。
  3. 函数实参:函数作为另一个函数调用的实际参数出现。这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的。

调用函数的动作如下:

  1. 传递控制:进行过程 Q 的时候,程序计数器必须设置为 Q 的代码的起始地址,然后在返回时,要把程序计数器设置为 P 中调用 Q 后面那条指令的地址。
  2. 传递数据:P 必须能够向 Q 提供一个或多个参数,Q 必须能够向 P 中返回一个值。
  3. 分配和释放内存:在开始时,Q 可能需要为局部变量分配空间,而在返回前,又必须释放这些空间。

64 位程序参数存储顺序:

1

3

4

6

%rdi 

%rsi

%rdx

%rcx

%r8

%r9 

栈空间

浮点数使用 xmm

该程序中涉及到的函数:

  1. main函数

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

  1. 传递控制,main 函数因为被调用 call 才能执行(被系统启动函数__libc_start_main 调用),call 指令将下一条指令的地址 dest 压栈,然后跳转到 main 函数。
  2. 传递数据,外部调用过程向 main 函数传递参数 argc 和 argv,分别使用%rdi 和%rsi 存储,函数正常出口为 return 0,将%eax 设置 0返回。
  3. 分配和释放内存,使用%rbp 记录栈帧的底,函数分配栈帧空间在%rbp 之上,程序结束时,调用 leave 指令,leave 相当于mov %rbp,%rsp,pop %rbp,恢复栈空间为调用之前的状态,然后 ret返回,ret 相当 pop IP,将下一条要执行指令的地址设置为 dest。
  4. printf函数

     相关代码,call puts@PLT

  1. 传递数据:

第一次 printf 将%rdi 设置为“Usage: Hello 学号 姓名!\n”字符串的首地址。第二次 printf 设置%rdi 为“Hello %s %s\n”的首地址,设置%rsi 为 argv[1],%rdx 为 argv[2]。

  1. 控制传递:

第一次 printf 因为只有一个字符串参数,所以 callputs@PLT;第二次 printf 使用 call printf@PLT。

  1. exit 函数:

call exit@PLT

  1. 传递数据:将%edi 设置为 1。
  2. 控制传递:call exit@PLT。
  3. sleep函数

call sleep@PLT

  1. 传递数据:将%edi 设置为 sleepsecs。
  2. 控制传递:call sleep@PLT
  3. getchar函数

   call getchar@PLT

控制传递:call gethcar@PLT

3.4 本章小结

本章主要阐述了编译器处理 C 语言的各个数据类型的过程,阐述了从 hello.c 程序到 hello.s 汇编代码之间的映射关系。编译器将.i 的拓展程序编译为.s 的汇编代码,以方便机器进行进一步的处理。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

汇编程序是指把汇编语言书写的程序翻译成与之等价的机器语言程序的翻译程序。汇编程序输入的是用汇编语言书写的源程序,输出的是用机器语言表示的目标程序。汇编语言是为特定计算机或计算机系列设计的一种面向机器的语言,由汇编执行指令和汇编伪指令组成。采用汇编语言编写程序虽不如高级程序设计语言简便、直观,但是汇编出的目标程序占用内存较少、运行效率较高,且能直接引用计算机的各种设备资源。它通常用于编写系统的核心部分程序,或编写需要耗费大量运行时间和实时性要求较高的程序段。

汇编语言是二进制指令的文本形式,与指令是一一对应的关系。

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

4.2 在Ubuntu下汇编的命令

as hello.s -o hello.o

4.3 可重定位目标elf格式

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

readelf -a hello.o > hello.elf

ELF组成如下:

  1. ELF头:从16字节的序列magic开始,Magic 描述了生成该文件的系统的字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包括 ELF 头的大小、目标文件的类型、机器类型、字节头部表的文件偏移,以及节头部表中条目的大小和数量等信息。
  2. 结头:节头部表,包含了文件中出现的各个节的语义,包括节的类型、位置和大小等信息。
  3. 重定位节.rela.text,一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。8 条重定位信息分别是对.L0(第一个 printf 中的字符串)、puts 函数、exit 函数、.L1(第二个 printf 中的字符串)、printf 函数、sleepsecs、sleep 函数、getchar 函数进行重定位声明。

.rela节的内容

offset

需要进行重定向的代码在.text或.data节中的偏移位置,8个字节。

Info

包括symbol和type两部分,其中symbol占前4个字节,type占后4个字节,symbol代表重定位到的目标在.symtab中的偏移量,type代表重定位的类型

Addend

计算重定位位置的辅助信息,共占8个字节

Type

重定位到的目标的类型

Name

重定向到的目标的名称

下面以.L1 的重定位为例阐述之后的重定位过程:链接器根据 info 信息向.symtab 节中查询链接目标的符号,由 info.symbol=0x05,可以发现重定位目标链接到.rodata 的.L1,设重定位条目为 r,

r 的构造为:r.offset=0x18, r.symbol=.rodata, r.type=R_X86_64_PC32, r.addend=-4

重定位一个使用 32 位 PC 相对地址的引用。计算重定位目标地址的算法如下(设需要重定位的.text 节中的位置为 src,设重定位的目的位置 dst):

refptr = s +r.offset (1)

refaddr = ADDR(s) + r.offset (2)

*refptr = (unsigned) (ADDR(r.symbol) + r.addend-refaddr)(3)

其中(1)指向 src 的指针(2)计算 src 的运行时地址,(3)中,

ADDR(r.symbol)计算 dst 的运行时地址,在本例中,ADDR(r.symbol)获得的是 dst 的运行时地址,因为需要设置的是绝对地址,即 dst 与下一条指令之间的地址之差,所以需要加上 r.addend=-4。之后将 src 处设置为运行时值*refptr,完成该处重定位。

  1. 符号表目标文件的符号表中包含用来定位、重定位程序中符号定义和引用的信息。符号表索引是对此数组的索引。索引 0 表示表中的第一表项,同时也作为定义符号的索引。
  2. 程序头部表,ELF文件头结构就像是一个总览图,描述了整个文件的布局情况。因此在ELF文件头结构允许的数值范围内,整个文件的大小是可以动态增减的。如果存在的话,告诉系统如何创建进程映像。

   有图像知,该程序不存在程序头

  1. 节头表,通过目标文件中的节头表,我们就可以轻松地定位文件中所有的节。节头表是若干结构Elf32_Shdr或者结构Elf64_Shdr组成的数组。节头表索引(section header table index)是用来定位节头表的表项的。ELF文件头中的e_shoff代表的是节头表在文件中的偏移值;e_shnum代表的是节头表的表项总数;e_shentsize代表的是节头表的表项大小。

由图像知,该程序没有节头表

  1. Dynamic section,如果目标文件参与动态链接,则其程序头表将包含一个类型为 PT_DYNAMIC 的元素。此段包含 .dynamic 节。特殊符号 _DYNAMIC 用于标记包含以下结构的数组的节

有图像知,还是没有

  

4.4 Hello.o的结果解析

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

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

hello.s与objdump得到的文件主要差异如下:

  1. 分支转移:反汇编代码跳转指令的操作数使用的不是段名称如.L1,因为段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后显然不存在,而是确定的地址。
  2. 函数调用:在.s 文件中,函数调用之后直接跟着函数
锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章