哈尔滨工业大学计算机系统

大作业

题 目程序人生-Hellos P2P

专 业 计算机科学与技术

学   号 2021112558

班 级2103101

学 生 陈思达

指 导 教 师刘宏伟

计算机科学与技术学院

2022年5月

摘 要

本论文主要介绍了从最开始编写的C语言源文件hello.c逐步生成hello可执行文件的过程,重点分析了计算机体系在生成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过程:

P2P既是From Program to Process的简写,hello.c这个C语言源文件先经过预处理器处理生成hello.i,再经过编译器处理生成汇编程序hello.s,然后经过汇编器as处理生成可重定位目标程序hello.o,最后通过链接器ld链接生成可执行文件hello。

O2O过程:

O2O既是From Zero-0 to Zero-0的简写,输入执行hello的命令后,shell通过execve和fork创建子进程并将hello载入,映射虚拟内存,进入程序入口后将程序载入物理内存,开始执行hello的代码。程序运行完成后,shell回收子进程,内核清除数据痕迹。一切归零。

1.2 环境与工具

硬件环境:AMD Ryzen 7 5800H with Radeon Graphics 3.20 GH

16.0 GB RAM

软件环境:VMware Ubuntu

开发与调试工具:Vim objump gdb edb gcc readelf等工具

1.3 中间结果

hello.c C语言源文件

hello.i 预处理文件,由预处理器预处理C语言源文件得到,是对C语言源文件的#include,#define等对编译器命令进行解释生成的。

hello.s 汇编语言文件,都编译器处理C语言源文件得到,是C语言代码对应的汇编语言形式。

hello.o 可重定位文件,都汇编器翻译汇编语言文件得到,是C语言代码对应的二进制机器语言形式。

hello 可执行文件,对hello.c源文件进行编译的最终结果。

1.4 本章小结

本章介绍了hello的P2P,O2O过程,介绍了本实验的硬件环境、软件环境、开发工具以及本实验中生成的中间结果文件的名字和作用。


第2章 预处理

2.1预处理的概念与作用

基本概念:

预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前。典型地,由预处理器对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。

作用:

预处理过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位–预处理记号用来支持语言特性。预处理的功能可以分为宏定义,文件包含,条件编译三方面,由预处理器解析宏定义命令、文件包含命令、条件编译命令。为进一步编译提供准备文件。

2.2在Ubuntu下预处理的命令

预处理指令为:gcc -E hello.c-o hello.i

2.3 Hello的预处理结果解析

在对hello.c文件进行预处理之后,使用VIM预览预处理得到的hello.i文件,发现,代码行数惊人的多,从之前hello.c的几十行扩展到3000行

预处理器主要对源代码做了以下两个工作,代码的替换与增添,代码的删除。

代码的替换与增添:预处理器将源代码中的#include,#define等命令,解释替换成相应的代码。如上图中可以发现源代码中的#include已经被替换成1026 ”/usr/include/stdlib.h”3 4等命令 。在对代码进行替换的同时,也增加了大量的函数声明,外部变量声明,typedef等定义,这都是为了之后进一步生成可执行程序做的准备工作。

代码的删除:主要是删除了注释等于生成程序无用的信息。

2.4 本章小结

本章主要介绍了预处理的概念,功能和机制。展示了对hello.c文件进行预处理的操作和预处理得到的结果。并对hello.c预处理的结果进行了分析。

第3章 编译

3.1 编译的概念与作用

概念:将编程语言翻译成汇编语言的过程。

作用:将预处理后的程序翻译成汇编语言程序,为不同高级语言的不同编译器提供了通用的输出语言。

3.2 在Ubuntu下编译的命令

编译指令为:gcc -Og -S hello.c

3.3 Hello的编译结果解析

在对hello.c文件进行编译之后,使用VIM预览预处理得到的hello.s文件。

数据:

hello.c中的数据只有字符串和局部变量

字符串:

在源代码文件中的字符串一共有两条:1.“用法: Hello 学号 姓名 秒数!\n”2.“Hello %s %s\n”

在.s文件中,中文字符被转换成了UTF-8码而英文字符保持不变

局部变量:

局部变量都被保存在寄存器或栈中。

赋值操作:

对变量的赋值使用MOV指令,如:

对hello.c中的i赋初值,次数i被保存在寄存器%rbx中

算数操作:

在hello.c中用到了一种算数操作:+操作

这里是对局部变量i进行加一操作。可以发现,在汇编语言中使用的是addl指令

关系操作:

hello.c中的两个逻辑操作:1.argc!=42.i<9

进行这两个逻辑操作时需要使用cmpl指令

  1. 判断argc是否等于4

此时argc变量被保存在寄存器%edi中

  1. 判断i是否小于9

此时i变量被保存在寄存器%ebx中

控制转移:

1.

如果argc不等于4就进入if语句块内部,如果等于就跳过if语句块

2.

这是循环的控制转移操作部分,如果i<=8就再一次跳转到循环开始位置

函数操作:

printf函数的调用:

第一个printf函数:

将该printf的字符串的其实位置作为参数传递给printf函数

第二个printf函数:

第一个参数:%rdi中存放的格式控制参数

第二个参数:%rsi中存放着这个printf函数的格式串的地址

第三个参数:因为%rbp中保存的是argv的地址,所以%rdx中存放着argv[1]

第四个参数:%rcx中存放着argv[2]

atoi函数的调用:

第一个参数被保存在%rdi中,是argv[3]

返回值保存在%rax中

sleep函数的调用:

第一个参数%rdi中保存的是 atoi函数的返回值

getchar函数的调用:

getchar函数从标注输入流得到参数

3.4 本章小结

介绍了编译的概念和作用,介绍了生成.s文件的操作,并结合hello.s文件,分析了C语言的各种数据与操作在汇编语言中的表现形式。

第4章 汇编

4.1 汇编的概念与作用

汇编的概念:汇编器将.s文件中的汇编语言翻译成二进制机器语言并生成可重定位文件。

汇编的作用:将汇编语言转换成二进制的机器语言。

4.2 在Ubuntu下汇编的命令

汇编指令为:gcc -Og -c hello.c

4.3 可重定位目标elf格式

ELF头

ELF头描述了文件的整体格式

节头部表

节头部表指示了各个节的大小和信息。

.rela.txt节

是一个.txt节中位置的列表,指示了当链接器要把这个目标文件和其他文件组合时,需要修改的位置。一般来说,任何调用外部函数或应用全局变量的指令都需要修改。可以发现在这节中的类型有两种R_X86_64_PC32和R_X86_64_PLT32,其中R_X86_64_PC32就是深入理解计算机系统书上的PC相对跳转,而R_X86_64_PLT32是一个新的函数入口表,书上并没有涉及。

.symtab节

符号表指示了符号的名字,地址,目标的大小,类型,对应的节。

在这个符号表中,我们可以发现,在hello.c中引用的函数,都被包含在了表中,它们分别对应这一个name,如printf()函数对应着__printf_chk,它们的类型都是全局符号。在这个符号表中,可以发现只有main这个符号的size是大于零的,这是因为,只有main是本地符号,是被声明并被定义在这个文件中的函数,而别的函数都只是仅仅被引用,实际的声明和定义并不咋这个文件中,所以并不占用大小。

4.4 Hello.o的结果解析

与hello.s文件与hello.o文件的差异:

  1. hello.o文件的反汇编明显多出了二进制的机器代码部分。汇编语言是面向机器的程序设计语言,它是为了解决机器语言难以理解和记忆的缺点,用易于理解和记忆的名称和符号表示机器指令中的操作码,这样用符号代替机器语言的二进制码,就把机器语言变成了汇编语言。而机器语言是一种指令集的体系,它是用二进制代码表示的语言,是计算机唯一可以直接识别和执行的语言,它具有计算机可以直接执行、简洁、运算速度快等优点,但它的直观性差,非常容易出错,程序的检查和调试都比较困难,此外对机器的依赖型也很强。
  2. hello.s文件中的call和jump分别对应着相对的函数名和标签名,而在hello.o文件的反汇编中call和jump都对应着一个相对位置,而在对应的二进制机器语言中则都是0x0这是因为hello.o文件还没有经过重定位,相对位置还不能确定,这些地址在经过链接器的处理后才能确定。

4.5 本章小结

在本章中介绍了汇编的概念和应用,介绍了生成.o文件的操作,使用readelf和objdump工具对生成的hello.o文件进行了分析,展示并分析了可重定位目标elf格式和hello.o的反汇编结果。

5链接

5.1 链接的概念与作用

链接的概念:链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载到存储器并执行。

链接的应用:将分离的文件链接合成为一个文件,使得编写程序时可以进行分离编译。

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.3 可执行目标文件hello的格式

ELF头:

入口起始地址:0x400570 大小64字节

节头部表:

起始地址:0x19e8 一共有29节

符号表:

程序中的全局变量和引用的函数

5.4链接的重定位过程分析

反汇编结果如下:

hello与hello.c对比:

1.发现hello代码的长度较hello.o大大增加了,这是因为在链接的过程中加入了printf等函数的代码。

2.在链接后hello的call,jmp等指令都拥有了一个确定的值,而hello.o中则全为0x0,这是因为链接器对hello.o进行了重定位。

重定位的原理:

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

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

5.5hello的执行流程

//call main

5.6Hello的动态链接分析

在节头部表中找到该节,标注为DYNAMIC

5.7本章小结

本章分析了链接的过程,通过edb查看hello程序的相关信息,对比hello与hello.o的反汇编代码,分析了链接的过程中重定位的过程和作用。

6hello进程管理

6.1 进程的概念与作用

进程的概念:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。是一个执行中程序的实例,是操作系统对一个正在运行的程序的抽象。

进程的作用:一个程序在系统上运行时,操作系统会提供一种程序在独占这个系统,包括处理器,主存,I/O设备的假象。处理器看上去在不间断地一条一条执行程序中的指令。

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

Shell-bash的作用:

Linux系统中,Shell是一个交互型应用级程序,代表用户运行其他程序,是一个命令行解释器,以用户态方式运行的终端进程,其基本功能是解释并运行用户的指令。

处理流程:shell会重复如下处理过程

(1)终端进程读取用户由键盘输入的命令行。

(2)分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量。

(3)检查第一个(首个、第0个)命令行参数是否是一个内置的shell命令。

(3)如果不是内部命令,调用fork( )创建新进程/子进程。

(4)在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。

(5)如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait…)等待作业终止后返回。

(6)如果用户要求后台运行(如果命令末尾有&号),则shell返回。

6.3 Hello的fork进程创建过程

用户在键盘上键入“./hello 学号 姓名 秒数”这一字符串,shall读取到从键盘输入的命令行,shell首先对这个命令行进行分割构造出argv向量,shall对argv[0]进行解释,发现这不是一个shell的内置命令,于是shell使用fork()函数创建一个子进程。

6.4 Hello的execve过程

在shell使用fork()函数创建出一个子进程之后,这个子进程使用execvc()函数将命令对应的可执行文件加载入这个进程,并同时将构造好的argv向量和envp向量传递给execve()函数来加载程序。在程序头部表的引导下,加载器将可执行文件的片复制到代码段和数据段,之后,加载器跳转到程序的入口:入口函数的地址。入口函数调用系统启动函数,之后初始化环境,调用用户层的main函数,处理main函数返回值,并且在需要的时候返回给内核。

6.5 Hello的进程执行

hello进程的上下文信息:

hello的进程上下文是hello进程正常运行所需要的信息,主要包含以下几种信息:hello进程存放在内存中的代码和数据,hello的栈,通用寄存器,程序计数器,环境变量和打开文件描述符的集合。

hello的进程时间片:

当hello进程在运行状态时,操作系统会为hello进程分配一个时间片,在这个时间片里,CPU执行hello进程的命令,当hello进程的时间片结束之后,操作系统会将控制从hello进程中收回,运行其他进程,然后再将控制交给hello进程,如此循环往复直到hello进程执行完毕。

hello的进程调度过程:

在执行过程中,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这个决策称为调度。调度的过程是由调度器完成的,当内核调度新的进程运行后,它就会抢占当前进程,并保存以前进程的上下文,然后恢复新进程被保存的上下文最后将控制传递给这个新恢复的进程,来完成上下文切换。

6.6 hello的异常与信号处理

hello执行过程中会出现的异常

1.中断:运行 hello 程序时,外部 I/O 设备可能出现异常,如键盘的输入等。

2.陷阱:运行hello程序时调用sleep()函数时会出现陷阱。

3.故障:运行hello程序时,可能会发生缺页故障。

hello执行过程中会出现的信号:

  1. SIGCHILD信号

当hello进程结束时,会向父进程发送一个SIGCHILD信号

  1. SIGINT信号

当键盘输入CTRL+C时,操作系统会向hello发送SIGINT信号。

  1. SIGTSTP信号

当键盘输入CTRL+Z时,操作系统会向hello发送SIGTSTP信号。

4.其他等等类型的信号

在hello运行时按ctrl+c:

hello进程直接终止

在hello运行时按ctrl+z:

发现hello进程被挂起并被移入后台

在hello运行时乱按键盘:

乱按键盘不影响hello进程的运行

在hello进程被挂起后重新移入前台执行:

6.7本章小结

介绍了进程的概念与作用,简述了Shell的作用与处理流程,结合hello程序介绍了进程的异常与信号处理。

结论

在用hello.c源程序生成并运行hello可执行文件的过程中,主要有以下几个流程:

1.预处理器解释hello.c中的#include,#define等命令,并删除无关的信息,生成hello.i文件。

2.编译器将经过预处理的C语言文件翻译成汇编语言文件,生成hello.s文件。

3.汇编器将汇编语言文件翻译成二进制的可重定位文件,生成hello.o文件。

4.链接器将hello.o文件与其他必要的可重定位文件链接到一起,为每个需要进行重定位的符号分配地址,生成hello可执行文件。

5.bash创建新进程并将hello可执行文件加载入这个进程中,从而hello可执行程序开始运行。

计算机系统无比的复杂繁琐,若想对每个微操作都了如指掌真可谓是天方夜谭,但是如果我们使用抽象的方法来了解这个复杂的系统,难度就会大大的减少。我们可以发现,计算机系统在设计和运行时也大量的采用了抽象的方法,如指令集架构和进程的概念,都是对复杂流程的抽象化解释。若是在理解计算机系统时掌握了抽象的方法,那我们的学习就会事半功倍。


附件

hello.c C语言源文件

hello.i 预处理文件,由预处理器预处理C语言源文件得到,是对C语言源文件的#include,#define等对编译器命令进行解释生成的。

hello.s 汇编语言文件,都编译器处理C语言源文件得到,是C语言代码对应的汇编语言形式。

hello.o 可重定位文件,都汇编器翻译汇编语言文件得到,是C语言代码对应的二进制机器语言形式。

hello 可执行文件,对hello.c源文件进行编译的最终结果。

参考文献

  1. [S]ISO/IEC(E) 9899:2011
  2. C语言程序设计
  1. Computer Systems:A Programmer’s Perspective Bryant,R.E..
  2. C语言编译全过程介绍 – 百度文库