计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 202111xxxx
班 级 21xxxxx
学 生 xxx
指 导 教 师 xxxx
计算机科学与技术学院
2022年11月
本论文逐步分析hello程序从原始程序文件.c开始,经过预处理、编译、汇编、链接阶段生成可执行目标文件,以及该可执行目标文件是如何在shell中运行的过程,对于一个程序的一生作出较为详细且通俗的论述。其他论述内容有预处理器、编译器、汇编器、链接器在各阶段所作的工作以及之间的协同配合;计算机系统中几个重要抽象概念的具体体现;软硬件是如何配合以实现进程的并发执行和不同层次异常的处理情况等。
关键词:预处理;编译;汇编;链接;进程管理;
目 录
第1章 概述
1.1 Hello简介
P2P(From program to process):Hello的P2P指的是从program到process。Program指的是我们认为编写程序的起点,即根据不同高级语言的语法规则,从键盘敲入代码形成的原始文件,在本论文中指hello.c文件,然而,hello.c文件将经过预处理、编译、汇编、链接的阶段后才可转化为可执行目标文件,这个可执行目标文件还不是最终的process,只有通过fork函数创建子进程,才可能在子进程(process)的上下文中加载并最终运行这一可执行目标文件。
图1 从源文件到目标文件的转化过程
020(From zero to zero): Hello的一生从0到0,具体指的是Hello程序运行在进程上下文中时,有自己的虚拟地址空间。当子进程通过execve系统同调用启动加载器时,加载器会创建一组新的代码、数据、堆和栈段,新的栈和堆段被初始化为0,而通过虚拟地址空间中的页映射到可执行文件大小的页大小的片,新的代码和数据段被初始化为可执行文件的内容。事实上,直到CPU引用一个被映射的虚拟页时才会进行复制,而当程序停止运行后,OS会将子进程回收,进程从此不再存在。因此,Hello的一生从0开始而又以0结束。
1.2 环境与工具
硬件环境:AMD RYZEN 7-5000-64
软件环境:VMware, Codeblocks,Visual Studio 2019
开发与调试工具:gcc; vim/gedit; edb; objdump
1.3 中间结果
hello.i 预处理的结果——修改了的源程序
hello.s 编译的结果——汇编程序
hello.o 汇编的结果——可重定位目标文件
hello 链接的结果——可执行目标文件
aso.txt hello.o的反汇编,用于与第3章的 hello.s进行对照分析。
ase.txt hello的反汇编,用于与hello.o进行对照分析
1.4 本章小结
为了详细分析程序的一生中经历的各阶段:预处理、编译、汇编、链接中具体发生了什么样的变化以及计算机系统如何依靠软硬件和操作系统的配合最终能够运行此程序,使用vim/objdump/edb等调试工具对hello进行处理生成多个中间文件以便分析,更好地了解程序的一生。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
(1)概念:C语言中的预处理指在正式编译之前对源程序进行一些修改,相当于编译前的准备阶段。
(2)作用:预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。C语言编译器的预处理可能会出现以下情况:
- 如果源程序以#include包括了头文件,cpp需要读取系统头文件的内容并将其直接插入程序文本中;
- 如果源程序存在#define,cpp用实际值替换define定义的字符串;
- 如果源程序存在#if,那么根据其后面的条件来决定需要编译的代码。
2.2在Ubuntu下预处理的命令
预处理命令:cpp hello.c > hello.i ; 预处理结果:生成hello.i文件
2.3 Hello的预处理结果解析
hello.i的文件内容如下(由于文件内容篇幅过大,在此只展示部分内容):
可以看到hello.c源文件中主函数部分完整地保留在hello.i文件中,但是在主函数部分的代码之前插入许多如外部全局变量、函数名等,这是cpp所做的上述第一种情况的工作,即将#include后包含的stdio.h, unistd.h, stdlib.h的内容插入到程序文本中。
2.4 本章小结
Hello.c经过预处理阶段后,比原始程序文本增加了头文件的诸多内容,为之后的编译阶段做好准备。(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
(1) 概念:编译的过程是指编译器(ccl)将文本文件hello.i翻译成hello.s文本文件,hello.s中包含一个汇编语言程序。
(2)作用:将原始文本文件翻译成汇编语言程序,其中每条语句都以一种文本的格式(汇编语言)描述了一条低级机器语言指令,这里的汇编语言为不同高级语言的不同编译器提供了通用的输出语言。编译后的结果更便于汇编器的翻译。
3.2 在Ubuntu下编译的命令
编译命令:gcc -S hello.c -o hello.s
3.3 Hello的编译结果解析
注:相同类型的操作仅举出1例进行说明
3.3.1 常量
hello.s里常量内容显示如下:
(1) 编译器将hello.c中的printf()输出的字符串常量“用法: Hello 学号 姓名 秒数!\n”放在.LC0中,并且使用3位数字编码汉字(UTF-8编码);
(2) 编译器把printf()输出格式字符串“Hello %s %s”放在.LC1中;
3.3.2局部变量
//将i和进行比较
编译器在栈帧中分配空间用于保存局部变量,在本例中int i 就被保存在距离栈帧指针%rbp偏移量为4的空间中;编译器通过对%rsp减去一定的值来为局部变量和其他参数留出空间。
3.3.3赋值操作
编译器使用mov、leaq指令进行赋值操作如下(相似操作只举出1例不多赘述):
//使用立即数为局部变量i赋初值
//将常量字符串赋给puts函数的第一个参数
//将%rax指向的内存中字符串赋给%rax
//将一个寄存器里的值赋值给另一个寄存器
3.3.4算术操作
编译器使用add进行算数加法:
//对i进行+1操作
//使用sub作减法操作
3.3.5关系操作
编译器通过cmp来进行比较操作:
//将argv和常量(立即数)4进行比较
3.3.6 指针操作
编译器通过把指针保存在寄存器中,然后通过一系列mov指令对指针进行具体操作:
以上就是通过多条mov指令来把指针数组argv[i]指向的字符串赋值给不同的寄存器作为函数调用时的参数。例如,将argv[2](保存在-16(%rbp))和argv[1](保存在-24(%rbp)) 指向的字符串分别作为printf()函数的第三个和第二个参数传给相对应的寄存器里。
3.3.7 控制转移
本文件中编译器通过条件跳转指令来实现控制的转移:
以上编译器通过跳转指令实现for循环;其中-4(%rbp)保存循环变量i的值,movl指令为i赋初值,addl对其进行+1操作,cmpl和jle指令保证了当i<=8时继续循环体(.L4),若i>8, 则执行下一条指令即调用getchar()。
3.3.7 函数操作
(1)参数传递:编译器通过指定寄存器进行参数传递:
已知源程序中printf("Hello %s %s\n",argv[1],argv[2]); 编译器将格式字符串传给了%rdi, 将argv[1]指向的字符串传给了%rsi,将argv[2]指向的字符串传给了%rdx
通过分析上述printf()各参数的传递情况可知:第一个参数用%rdi,第二个参数用%rsi,第三个参数用%rdx。
(2)函数调用:编译器通过call指令调用函数
(3)函数返回:编译器指定%rax保存返回值
3.4 本章小结
在编译阶段,编译器做了许多工作,包括但不限于对C语言各操作的汇编指令的转换,为局部变量分配栈帧,用跳转指令实现控制结构、为每个函数调用作传参准备等,为之后的汇编阶段提供诸多便利。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
(1)概念:汇编是指把汇编语言翻译成机器语言的过程。
(2)作用:汇编器将hello.s文件翻译成机器指令,并打包成可重定位目标文件,汇编器产生重定位条目便于链接器进行重定位。
4.2 在Ubuntu下汇编的命令
汇编指令:as hello.s -o hello.o
4.3 可重定位目标elf格式
(1) hello.o的ELF格式:使用readelf -a hello.o命令产生的hello.o的ELF格式主要包括有:ELF Header、Section Headers、Relocation section、和Symbol table。针对hello.o来说,没有section groups、program headers、dynamic section和version information。此外,unwind sections由于设备不支持不予显示。
(2) elf各节具体内容如下:
- ELF头:
图2 可重定位目标文件elf格式ELF头
其中包括了字节顺序、ELF头的大小、目标文件的类型、机器类型、节表的偏移以及节头部表中条目的大小和数量。
- 节头部表:
图3 可重定位目标文件elf格式节头部表
其中包含了各节的大小、偏移、类型以及各种标志。
- 重定位条目:
图4 可重定位目标文件elf格式重定位条目
其中包含了所有需要重定位的符号,如图所示,offset代表需要被修改的引用的节偏移量、type告知链接器如何修改新的引用,addend 代表在重定位某些类型的值时需要做偏移调整,Sym.标识被修改引用应当指向的符号。在hello.o中,我们可以看到,需要进行重定位的符号包括函数调用puts、exit、printf、atoi、sleep、getchar等,还包括位于.rodata节的两个字符串常量。
- 符号表:
图5 可重定位目标文件elf格式符号表
符号表中包括了value(距定义目标的节的起始位置的偏移),size是目标的大小,type通常是数据或函数,bind是表示符号是本地的或是全局的,ndx表示符号被分配到的节。
4.4 Hello.o的结果解析
使用objdump -d -r hello.o得到的结果如下:
图6 hello.o反汇编结果
可见机器语言是由二进制数构成的,以16进制如图左半部分所示;每条机器指令对应一条汇编语言,但是机器指令对应的汇编语言与hello.s中的汇编语言存在着以下几个方面的不同:
类型 | 截图 | 不同之处 |
反汇编 | 跳转指令后跟的是下一条要执行的指令的地址 | |
hello.s | 跳转指令后跟的是节的名字 | |
反汇编 | 对字符串常量寻址时%rip+0,并插入重定位条目;指令lea; | |
hello.s | 对字符串常量寻址用的是节.LC0;指令为leaq; | |
反汇编 | 函数调用call指令后是下一条要执行的指令地址,同时插入了重定位条目 | |
hello.s | 函数调用call指令后直接加函数名 | |
反汇编 | jmp指令后跟要跳转的地址值 | |
hello.s | jmp指令后直接跟要跳转的节的名字 |
4.5 本章小结
经过汇编,程序从文本文件转换成二进制文件,并增加了重定位条目、符号表等可用于后续链接过程。无论程序最终运行时的地址如何,经过汇编之后各符号、函数的相对位置以及如何链接,组装起来的方式已经确定。hello程序做好了为可执行所作的准备。(第4章1分)
第5章 链接
5.1 链接的概念与作用
(1) 链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存并执行。
(2) 作用:链接器使得分离编译成为可能,不必将一个大型的应用程序组织成一个巨大的源文件,而是可以把其分解为更小的模块,可以独立地修改和编译这些模块,改变诸多模块中的一个时,只是简单地重新编译并重新链接应用,而不必编译其他文件。
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令
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的格式
(1) hello的ELF格式包括ELF Header、Section Headers、Program Headers、Dynamic Section, Relocation Section、Symbal Table、Version symbols section。可执行文件中额外包括了段头部表。
(2) 用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
图7 可执行文件elf格式段头部表
其中offset为可执行目标文件中的偏移量,由此来定位到段的起始位置;VirtAddr为分配的虚拟内存地址;PhysAddr为分配的真实物理地址;FileSiz为要加载的数据节的大小;MemSiz表示申请的内存空间;Flags表示段的属性,比如Type是LOAD类型的第一行代码段的R E表示可读可执行,第二行数据段的RW表示可读可写。
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。在edb中查看data dump如下图所示。
图8 hello虚拟地址空间
分析:从上图可以看出,本进程虚拟地址空间地址从0x400000开始,起始地址处是ELF头,而由5.3节中的图分析可知,.interp段的起始地址是0x400200,该段的大小是0x1c,相对于开始地址的偏移量是0x00000200。在datadump中,我们可以发现.interp段的起始地址为0x400200,与虚拟空间起始地址0x400000相差0x200,这与5.3节的图是一致的,同时该节的大小是0x1c,那么在datadump中显示该节于下一行的04处结束。同理,对于,init节,其起始地址是0x4004c0,该段的大小是0x17,偏移量是0x000004c0,对应于datadump中该段的起始地址为0x4004c0,到0x4004d7的位置结束;从地址0x4004e0开始即为.plt节,这与5.3中.plt节的起始位置0x4004e0也是相对应的。
5.5 链接的重定位过程分析
(1) 使用objdump -d -r hello命令得到hello的反汇编文件如下:
分析hello与hello.o的不同,说明链接的过程。
图9 hello反汇编结果
通过与hello.o对比分析,其主要的不同点在于:
- hello.o反汇编后得到的文件中只包含main函数对应的机器指令及其对应的汇编语言;而hello反汇编后得到的文件中包含有.init .plt .text多个节的机器指令和及其对应的汇编语言;
- hello.o反汇编得到的结果中main函数的起始地址从0x0开始;而hello反汇编之后各个节的起始位置不是从0开始,而是运行时地址,不同节的地址各不相同。
- hello.o反汇编得到的结果中对于各函数和符号的引用没有定位到具体的位置,例如对于常量字符串的引用,采用相对寻址时偏移量是0,0x0(%rip);以及在调用函数时,call指令后跟的是顺序执行的下一条指令的地址(相对起始地址的偏移量),并非是被调用函数的实际地址;在hello反汇编后得到的结果中链接器把每个符号定义与一个内存位置关联起来,重定位所有节,为每个节分配了运行时地址,然后修改所有对于全局符号(字符串常量以及函数)的引用,使得这些引用指向内存位置。例如,对于printf字符串常量的引用,采用lea 0xfa(%rip) %rdi指令对printf应该输出的常量字符串"用法: Hello 学号 姓名 秒数!\n”进行引用,该字符串的地址位于相对于%rip偏移量为0xfa的位置;同理,对于函数的调用,每个函数的定义对应一个地址,hello反汇编后得到的所有call指令后跟的是要调用的函数的入口地址。以下表格展示了main函数中call指令的调用情况。
call指令 | |
函数入口 | |
call指令 | |
函数入口 | |
call指令 | |
函数入口 | |
call指令 | |
函数入口 | |
call指令 | |
函数入口 | |
call指令 | |
函数入口 |
- Hello.o反汇编得到的文件对控制转移条件跳转指令后面跟的是要跳转的位置相对于函数起始位置的偏移量;而hello反汇编中跳转指令后面跟的是要跳转的位置的运行时地址。具体差别如下图:
可见,二者要跳转的位置相对于起始地址偏移量是相同的,不同点在于一个是运行时地址,一个是相对地址。
- Hello.o反汇编中机器指令没有对引用地址的说明,而在hello反汇编文件中,对于存在重定位条目的机器指令二进制位做了修改。(具体修改方式见(2)重定位分析)。
由上述对比可知链接的过程包括:
- 符号解析:将每个符号引用和一个符号定义关联起来
- 重定位:链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。链接器使用汇编器产生的重定位条目的详细指令,不加甄别地执行这样的重定位。
(2) 重定位分析
通过和hello.o反汇编文件的对比,发现区别的根源在于链接器对于可重定位目标文件中所有的引用作了重定位。下面结合重定位条目,以
59: e8 00 00 00 00 callq 5e <main+0x5e>
5a: R_X86_64_PLT32 printf-0x4
为例进行重定位的具体说明。
(需要注意的是:较新的 GCC 版本使用 R_X86_64_PLT32 而不是R_X86_64_PC32 来标记 32 位 PC 相对寻址。如果函数是在本地定义的,链接器始终可以将 PLT32 重定位减少到PC32。)
- 确定各量的对应值。由重定位条目定义可知:0x5a为需要被修改的引用的节偏移,在本例中即为机器指令e8后面的字节;重定位类型是R_X86_64_PLT32,采用PC相对寻址;printf为需要引用的符号名;-0x4是偏移调整量。
- 计算相对引用的地址:由公式refaddr=ADDR(s)+r.offset ref=(ADDR(r.symbol) + r.addend - refaddr; 首先需计算出要修改的地址的运行时地址,由于main的起始地址为0x400582,所以要重定位的字节位于0x400582+0x5a=0x4005dc处,printf函数的地址为0x400500,因此计算出的相对引用的地址为0x400500+(-0x4)-0x4005dc=0xffffff20。因此将e8之后的字节修改为20 ff ff ff(小端序)。
5.6 hello的执行流程
(1) hello的执行流程:
- 使用edb执行hello,控制首先转移到dl_start(通过call ld-2.27.so! dl_start实现);
- 然后程序进入dl_init(通过call ld-2.27.so! dl_init);
- 程序进入<libc-2.27.so!__libc_start_main>main函数,执行main函数中的指令;
- 在main函数执行过程中先后调用了puts函数,如果用户输入命令行参数,则接续调用printf()、atoi()、sleep()等函数,循环结束后调用getchar函数;程序返回值为0,终止。
- 若没有命令行参数,调用exit函数,程序终止。
(2) 请列出其调用与跳转的各个子程序名或程序地址。
使用step over得到程序运行过程如下:
图10 程序运行过程
5.7 Hello的动态链接分析
通过运行时程序过程分析可知,hello程序动态链接了linux-x86-64.so这一共享库,而编译器在产生共享模块代码时产生的是位置无关代码(可以加载而无需重定位。由于本程序调用了由共享库定义的函数,然而编译器没有办法预测这个函数运行时的地址,因为定义函数的共享模块可以在运行时加载到任意的位置。为了解决这一问题,GNU编译系统采用延迟绑定技术,把函数地址的解析推迟到它实际被调用的地方,这一技术依赖的数据结构是GOT和PLT。
通过readelf查看got地址如下:
执行_dl_init之前got在内存中如图:
执行_dl_init之后got在内存中如图:
可以发现,在dl_init之前,地址0x600ff0起始后的8个字节为全0;而在dl_init之后,0x600ff0起始后的字节变为a0 ab 28 61 03 7f 00,也即地址0x7f036128aba0。同时,地址0x601000的后8个字节也从全0变成了70 51 88 61 03 7f 00 00,也即地址0x7f0361885170, 地址0x601010的前8个字节由全零变成f0 18 67 61 03 7f 00 00也即地址0x7f03616718f0。
5.8 本章小结
通过链接,可重定位目标文件中的所有引用的外部定义的变量或是函数都被精准定位,并且通过修改机器指令中的二进制数据使得程序在引用这些函数时能够链接到相对应的位置。在链接过程中,链接器和编译器、汇编器相配合,不仅为每个函数分配运行时地址,还可以通过生成位置无关代码的方式来实现共享库的链接,避免其在加载时进行成千上万个并不需要的重定位,这样做进一步提高了链接的效率。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
(1) 概念:进程的定义就是一个正在运行的程序的实例。系统中的每个程序都运行在某个进程的上下文中。而上下文是由程序正确运行所需要的状态组成的。
(2) 作用:进程提供给应用程序两个关键抽象:一个独立的逻辑控制流和一个私有的地址空间。逻辑控制流提供一个假象,好像程序独占地使用处理器;私有地址空间提供的假象是程序独占地使用内存系统。
6.2 简述壳Shell-bash的作用与处理流程
(1) 作用:shell是一个命令行解释器,可以解释用户从键盘输入的命令行。shell也可以理解成是一个交互型应用级程序,代表用户运行其他程序;shell执行一系列的读/求值步骤,然后终止。读步骤读取来自用户的一个命令行,求值步骤解析命令行,并代表用户运行程序。
(2) 处理流程:
- 终端进程读取用户从键盘输入的命令行;
- 分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量;
- 检查首个命令行参数是否为内置的shell命令;
- 如果不是则调用fork()创建子进程;
- 子进程调用2中的参数,execve执行特定程序;
- 若用户没有要求后台运行(即结尾没有&),shell使用waitpid或wait等待作业终止后返回。
- 如果用户要求后台运行(即结尾有&),shell就返回
6.3 Hello的fork进程创建过程
当我们在命令行输入./hello,时,shell会读取输入的这个命令行并分析命令行字符串,发现首个命令行参数不是内置的shell命令,于是调用fork()创建子进程。这个子进程是新的运行的子进程,它得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈,子进程还或的与父进程任何打开文件描述符相同的副本,这意味着子进程可以读写父进程中打开的任何文件。
6.4 Hello的execve过程
shell在分析命令行时,不仅会获取命令行参数,还会构造传给execve函数的argv数组,argv数组是指针数组,数组里的每个元素都是指向一个字符串的指针,argv[0]即为程序的名字。而main函数的参数还包括argc,这一参数为int 型,指明了命令行参数的总数,子进程会调用argv参数,并调用execve函数加载并运行可执行目标文件hello。execve函数的参数包括文件名、参数列表和环境变量列表。在其加载了文件名之后,会调用启动代码,由启动代码设置栈(包括参数列表和环境变量列表),并将控制传递给主函数。
图11 启动代码设置的栈
6.5 Hello的进程执行
进程的上下文保存着内核重新启动一个被抢占的进程所需的状态,包括通用目的寄存器,浮点寄存器,程序计数器,用户栈,状态寄存器,内核栈和各种内核的数据结构。在时间片中,一个进程执行它的控制流的一部分,由于进程与进程之间是并发执行的,所以在hello程序运行过程中,很可能被其他进程抢占,这是由内核决定的。内核调度另一个进程开始执行之前,将保存hello的上下文,恢复另一个进程被保存的上下文,然后把控制传递给新的进程;当然,hello程序也可以作为新的进程来抢占其他进程的执行。对于系统调用函数sleep的执行,当程序执行这一指令时,内核同样也会进行上下文切换,让hello休眠。hello程序必须通过系统调用sleep来进行休眠,这是因为用户模式中的进程不允许执行特权指令,也不允许直接引用地址空间中内核区的代码和数据,因此用户程序必须通过系统调用接口间接地访问内核代码和数据。而一个运行在内核模式下的进程可以执行指令集中的任何指令并访问系统中的任何内存位置。
6.6 hello的异常与信号处理
(1) hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
异常 | 信号 | 处理 |
来自键盘的中断 | SIGINT/SIGQUIT | 终止 |
系统调用 | 调用内核程序 | |
用户定义的信号,例如kill | SIGKILL | 终止 |
(2) 程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
操作 | 现象 | 分析 |
按回车 | 按下回车不影响程序的执行过程 | |
Ctrl-z | 键入ctrl-z进程停止,仍在后台运行; | |
Ctrl-c | 键盘键入ctrl-c,内核发送SIGINT给前台进程组的进程,终止hello进程 | |
ps | 显示hello进程并没有终止 | |
jobs | 当前作业中hello被停止 | |
pstree | 以树形结构显示程序与进程的关系 | |
fg | hello可在前台运行 | |
kill | 内核发送SIGKILL信号给hello进程终止进程 |
6.7本章小结
本章分析了程序是如何在进程中运行的,以shell为载体,从创建子进程开始到调用execve来加载程序文件到程序最终的执行。操作系统内核通过使用上下文切换的较高层形式的异常控制流来实现多任务,此外,针对程序运行过程中可能遇到的异常,计算机系统引入信号作为更高层的软件形式的异常来允许内核和进程中断其他进程。通过分析各阶段获可见,软件和硬件的充分配合,以及几个重要的抽象概念,使得大量的进程以并发的形式在计算机中运行。
(第6章1分)
结论
本论文中hello程序主要经过了预处理、编译、汇编、链接、进程管理的阶段;在预处理阶段,预处理器cpp主要做的工作就是将#include包含的头文件的内容插入到程序的文本中;在编译阶段,程序由高级语言翻译成汇编语言,汇编语言和机器指令一一对应的,在这一阶段,编译器为计算常量表达式等,为全局变量分配节,为局部变量分配栈中的空间,决定寄存器的使用情况,为程序中的控制转移产生相应的汇编指令以实现控制,对于函数的调用分配用于传参的寄存器和返回值;在汇编阶段,程序由文本文件转换成可重定位的二进制目标文件,汇编器为每一个需要重定位的符号生成重定位条目;在链接阶段,为不同段分配运行时地址,并且进行符号解析和重定位过程,以程序中引用到的外部函数或全局变量地址来修改机器指令;对于共享库,采用GOT和PLT两种数据结构来实现对共享库位置无关代码的定位。在进程管理中,操作系统和硬件相互配合协作,为多个进程能够并发地执行提供机制基础,同时,针对不同层面的异常涉及不同处理机制包括信号等。
从hello.c到最终在进程的上下文中运行,程序经历了复杂而又精细的处理过程。从中得以窥见计算机系统里所包含的伟大的抽象与庞大的知识体系结构。计算机系统的各个部分是紧密联系的,无论是不同硬件单元之间的物理连接,还是存储器体系结构之间传递,无论是操作系统涉及的各个管理机制,还是操作系统与硬件之间的配合,都不是脱离了其他而单独存在的,各个体系,各个层面都有着深刻且广泛的联系。而这一切的背后离不开几个伟大的概念:表格、虚拟内存、进程等等。或许未来的计算机系统可以在当前的基础上继续优化,在并行度的提高以及不同计算机之间如何尽可能消除移植方面遇到的问题等。
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.i 预处理的结果,用于分析预处理阶段发生的变化
hello.s 编译的结果,用于分析编译阶段发生的变化
hello.o 汇编的结果,反汇编后用于分析汇编器所作的处理
aso.txt hello.o的反汇编,用于与第3章的 hello.s进行对照分析。
ase.txt hello的反汇编,用于与hello.o进行对照分析
hello 可执行程序,用于运行时测试不同shell指令以及edb调试
参考文献
[1] Bryant,R.E. 深入理解计算机系统系统(第三版). [M], 2016
[2] Shell简介:Bash的功能与解释过程(一) Shell简介 - 知乎
[3] R_386_PC32和R_X86_64_PC32在link(GNU ld)重定位过程中有什么区别? - IT宝库