计算机科学与技术学院
2019年12月
摘 要
摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。
关键词:关键词1;关键词2;……;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第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 -
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
Hello是指名为“hello”的可执行文件。首先hello的源代码以C语言文本的形式被程序员输入电脑,并存储在名为“hello.c”的C语言源文件中。其次,程序员通过预处理程序将源文件进行预处理,生成名为“hello.i”的预处理文件。接着,程序员通过使用编译器,将“hello.i”进行编译,将C语言fanyic机器语言,以文本形式存储在名为“hello.s”的汇编语言代码文件中。在这之后,程序员通过使用汇编程序,将“hello.s”从汇编语言翻译成机器语言,以可重定位目标文件的形式、二进制表示地存储在“hello.o”中。最后,程序员使用链接器,将“hello.o”和其他用到地可重定位目标文件(如预编译好的库文件)链接在一起,生成名为“hello”的可执行目标文件,这便是Hello的诞生。
在linux环境中,程序员开启Bash,执行“hello”,OS的进程管理程序就会在Bash中fork一个子进程,通过设置进程组id的方式使这个子进程与Bash进程分离,然后调用execve函数执行“hello”这个可执行目标文件,使用mmap函数进行内存映射以实现高校读写或者内存共享,分配给这个子进程时间片来决定它的运行实践,接着这个子进程就借助CPU对指令进行取值、译码、执行等操作,并借助流水线机制加快执行速度,在CPU、RAM、IO中进行数据读写和运算,最终实现“hello”的执行。
在数据读写的具体过程中,OS的存储管理程序和内存管理单元(MMU)将程序的虚拟内存地址(VA)转化为物理内存地址(PA),实现数据存取。为了提高数据访问的速度,计算机硬件按照局部性原理,设计了一套多级的存储结构。系统首先通过TLB查找数据,若存在TLB页表中,则直接从3级Cache中取出数据;若没有,则访问主存中的页表,若有则访问数据并更新TLB,若无则引发缺页异常,从磁盘中找到数据放入主存,同时更新TLB和页表。
在程序执行的过程中,IO管理程序调度着IO设备间的通信,而信号机制则处理着各种各样的异常,使程序最终能够正常执行。
在程序执行结束后,Bash进程会收到SIGCHILD异常,接着调用waitpid函数回收hello进程;若Bash提前终止,那么hello进程就变成了孤儿进程。这时,OS的__init__进程就会“收养”这个进程,成为它的养父进程,代为行使回收hello进程的职责。至此,hello进程实现了它的从program到progress的一生,结束了它的生命周期。
Hello的整个生命周期中,由计算机系统的编辑器以文本形式写就源代码,接着分别由Cpp、Compiler、AS、LD分别完成了预处理、编译、汇编和链接,生成可执行目标文件(简称可执行文件),在执行时由OS分配虚拟内存空间并映射到物理内存空间、由CPU进行执行,在RAM和IO间进行数据读写,最终结束执行后又被父进程或者养父进程回收,释放自己占用的资源。纵观它的整个生命周期,从无中来,到无中去,是为O2O。这一切,计算机系统(CS)是一切的参与者和见证者。
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
(第1章0.5分)
预处理是从源程序编程可执行程序的第一步。C预处理程序是cpp(C Preprocessor的缩写)。这一步是对编译的准备工作,主要实现对头文件的包含、宏定义的扩展、条件编译的选择等等。
cpp hello.c -o hello.i
或者
Gcc -E main.c -o hello.i
(以下格式自行编排,编辑时删除)
应截图,展示预处理过程!
(1)首先,源代码中的注释不见了,说明预处理程序删除了注释
(2)其次,宏定义也没有(测试时在源文件中另外添加了一行宏定义语句,而预处理之后不见了),说明预处理程序识别了宏定义并对源代码进行了相应替换
(3)#include指令被解析了,头文件被直接插入到了源文件中
本章主要探讨了预处理的作用。预处理是C语言源文件转换为可执行文件的第一步,是对编译环节的准备工作。这一阶段,程序员应使用C预处理程序,将源文件进行预处理,具体工作包括头文件的插入、注释的删除、宏定义的扩展和条件编译的选择等等。
(第2章0.5分)
编译是指将经过预处理的源文件转换为汇编语言文本文件的过程。
在这个环节,C编译器会对源程序进行词法分析和语法分析,根据分析的结果进行代码优化和存储分配,最终将其转换为汇编语言程序。一次编译由编译器通过对源代码进行多次扫描完成,一次完成其中的几项任务或者一项任务的某一部分。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
ccl hello.i -o hello.s
或者
gcc -S hello.i -o hello.s
另外可以进行参数设置,例如截图中的那样
首先是代码hello.s的代码全览:
.file "hello.c"
.text
.section .rodata.str1.8,"aMS",@progbits,1
.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"
.section .rodata.str1.1,"aMS",@progbits,1
.LC1:
.string "Hello %s %s\n"
.text
.globl main
.type main, @function
main:
.LFB52:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
pushq %rbx
.cfi_def_cfa_offset 24
.cfi_offset 3, -24
subq $8, %rsp
.cfi_def_cfa_offset 32
cmpl $4, %edi
jne .L6
movq %rsi, %rbp
movl $0, %ebx
jmp .L2
.L6:
movl $.LC0, %edi
call puts
movl $1, %edi
call exit
.L3:
movq 16(%rbp), %rcx
movq 8(%rbp), %rdx
movl $.LC1, %esi
movl $1, %edi
movl $0, %eax
call __printf_chk
movq 24(%rbp), %rdi
movl $10, %edx
movl $0, %esi
call strtol
movl %eax, %edi
call sleep
addl $1, %ebx
.L2:
cmpl $7, %ebx
jle .L3
movq stdin(%rip), %rdi
call _IO_getc
movl $0, %eax
addq $8, %rsp
.cfi_def_cfa_offset 24
popq %rbx
.cfi_def_cfa_offset 16
popq %rbp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE52:
.size main, .-main
.ident "GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0"
.section .note.GNU-stack,"",@progbits
3.3.1 整型常量定义
观察源文件“hello.c”,发现共有8个整型数值常量,分别是:
其中,(1)、(2)、(3)是以立即数的形式直接出现在汇编语言程序的代码段的。
针对(4),它并没有显式地出现在代码中,分析后发现,由于它的作用是限制循环的次数,当循环次数小于它时退出循环,所以编译器将这部分优化成让循环次数与“7”相比较,使用“jle”命令实现跳转。
类似地,(5)也没有显式的出现在代码中,它们是argv数组的索引,而argv是字符串数组,其元素是地址,大小是固定的8个字节,因此这三个索引被优化成地址的索引,分别是argv的地址加8个字节、16个字节和24个字节
3.3.2 局部整型变量定义
代码中有两个整型变量,一个是作为函数入口参数的,指明参数字符串组元素个数的argc,另一个是用于循环计数的整型变量i
Argc由于是函数参数,所以按照规范存储在寄存器edi中;i是变量,分析汇编代码后发现,编译器用寄存器ebx表示和存储i的值。
3.3.3 字符串常量定义
源代码中有两个字符串常量,分别是用于标准输出的字符串常量
"用法: Hello 学号 姓名 秒数!\n"
以及用于格式化输出的字符串常量
"Hello %s %s\n"
它们都定义在了汇编代码的只读数据段,如下面所示:
.section .rodata.str1.8,"aMS",@progbits,1
.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"
.section .rodata.str1.1,"aMS",@progbits,1
.LC1:
.string "Hello %s %s\n"
3.3.4 变量的声明和赋初值
Argc由于是父进程赋值,所以这里讨论i。从汇编代码中,程序开始时进行了将寄存器rbx入栈保存的操作:
pushq %rbx
这可以看作是对变量i的声明
而后,程序用movl命令对变量i进行了赋初值操作:
movl $0, %ebx
3.3.5 类型转换
代码中不存在隐式的类型转换操作,但存在一处显式的类型转换:
参数字符串组的第4个元素指明了sleep函数的参数,需要使用atoi函数将它从字符串转化为整型数值,这部分的汇编代码如下:
movq 24(%rbp), %rdi
movl $10, %edx
movl $0, %esi
call strtol
可以看到,首先,使用movq指令和寄存器间接寻址,将argv[3]的数据传给寄存器rdi,那么rdi中所储存的值就是所调用函数的第一个参数的值;接着,调用movl命令分别给寄存器esi和edx赋值为0和10,它们分别是所调用函数的第二个和第三个参数;最后,利用call命令调用方函数strtol。整个调用过程代表用strtol函数按照十进制将字符串转换为数字。也即是说,虽然我们调用的函数是atoi,但是底层是通过strtol函数实现的,函数atoi是函数strtol的一个特例。
3.3.6 算数操作
代码中只有一种算数操作,那便是用于循环计数的自增运算:
for(i=0;i<8;i++){
在汇编语言代码中,i的自增运算是同过addl命令给代表i的寄存器的值加上立即数$1完成的:
addl $1, %ebx
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.3.7 关系操作
代码中只有一种关系操作,那便是检查参数数量是否满足要求的“!=”:
if(argc!=4){
在汇编语言代码中,是通过cmpl命令实现的:
cmpl $4, %edi
3.3.8 数组操作:
代码中存在一个数组:参数字符串数组“argv”,由于argv是字符串的数组,因此它的每个元素大小相等,都是某个字符串的首地址。由于我们是按照64位编译的,因此每个地址的大小是8个字节。也就是说,元素argv[i]的地址就是argv的地址加上i个8的值。例如:
movq 16(%rbp), %rcx
movq 8(%rbp), %rdx
movl $.LC1, %esi
movl $1, %edi
movl $0, %eax
call __printf_chk
movq 24(%rbp), %rdi
上面的这段代码中,分别出现了16(%rbp)、8(%rbp)、24(%rbp),它们分别是argv[2]、argv[1]、argv[3]
3.3.9 控制转移
代码中共出现了两次控制转移,一次是if语句引起的控制转移,另一次是for语句引起的控制转移:
if(argc!=4){
和
for(i=0;i<8;i++){
cmpl $4, %edi
jne .L6
这两句话的意思是,将立即数4的大小与寄存器edi中存储的数的大小相比,如果不相等的话,就跳转到.L6代码段,否则继续按顺序执行接下来的代码
movl $0, %ebx
jmp .L2
现在跳转到.L2代码段
.L2:
cmpl $7, %ebx
jle .L3
若是小于或者等于7的话,跳转到.L3代码段:
.L3:
movq 16(%rbp), %rcx
movq 8(%rbp), %rdx
… …
addl $1, %ebx
.L2:
cmpl $7, %ebx
jle .L3
… …
可见,通过调用addl命令,将寄存器ebx存储的值增加1
至此,整个循环构造完成。我们同时可以看出来,原来是看i的值是否小于8相比并决定是否跳转,现在则是与7相比,观察它们是不是小于等于的关系,两者实际上是等价的。
3.3.10 函数操作
在C语言代码中,共有5个函数:main、printf、atoi、sleep、getchar、exit
而在汇编代码中两处printf函数被分别转化为了puts函数和__printf_chk函数,atoi函数被转化为了strtol函数,getchar函数被转化为了_IO_getc函数,其他的函数还是原来的名字。下面,分别从参数传递、函数调用和函数返回三方面进行分析:
在存在多个参数的情况下,寄存器rdi、rsi、rdx、rcx分别被用来传递第一个、第二个、第三个和第四个参数,例如:
movl $.LC0, %edi
call puts
先把.LC0的值传给edi作为puts函数的第一个参数,而.LC0是定义在只读数据区的字符串(string)
movq 16(%rbp), %rcx
movq 8(%rbp), %rdx
movl $.LC1, %esi
movl $1, %edi
movl $0, %eax
call __printf_chk
可见,第一个参数是立即数1,第二个参数是.LC1,它和.LC0一样,是定义在只读数据区的字符串常量。第三个参数是argv[1]的值,它是字符串数组的第二个元素,是某个字符串的首地址。同样地,第四个参数是argv[2]的值,字符串数组的第三个元素。
movq 24(%rbp), %rdi
movl $10, %edx
movl $0, %esi
call strtol
第一个参数是argv[3],它是字符串数组的第四个元素,是某一个字符串的首地址。第二个参数是立即数0,它是字符串结束的标志。第三个参数是10,他表示将字符串按照十进制数字解读
call strtol
movl %eax, %edi
call sleep
eax寄存器存储的是函数的返回值,刚掉用过strtol函数,因此将eax存储的值赋值给edi,再调用sleep函数,就是将strtol函数的返回值作为唯一的参数传递给sleep
movq stdin(%rip), %rdi
call _IO_getc
可以看到,rdi中存储的是标准输入流的描述符,即该函数唯一的参数便是标准输入流
movl $1, %edi
call exit
寄存器edi中存储的值是立即数1,也就是将整型数字1作为唯一的参数传给函数exit
从上面的分析中可以看出,在参数就位后,使用call命令就可以实现函数的调用
(3)函数返回
分析代码可以发现,函数返回是通过ret命令实现的,返回值存储寄存器eax中
本章我们探索了编译的概念、作用、方法和结果。
编译是指使用编译器将预处理文件经过分析,进行代码优化和存储分配,最终转化为汇编语言代码文件的过程。编译可以将C语言转化为与机器紧密相关的机器语言,从而使代码能够在对应机器上运行。Linux环境下常常使用gcc -S命令或者cpp命令进行编译工作。
在本章中,我们重点分析了编译的结果,讨论了编译器对各种操作在机器语言上的实现方式,包括数据定义与赋值、类型转换、算术操作、关系操作、数组操作、控制转移和函数操作,进一步体会了编译的实际工作。
(第3章2分)
汇编是指将编译生成的汇编语言代码转换为机器语言代码,生成可重定位目标文件。每个模块都生成一个可重定位目标文件,若最终的程序有多个模块组成,那么这一步将生成多个可重定位目标文件。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
使用
gcc -c main.s -o main.o
或者
as main.s -o main.o
编译结果如下:
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
ELF格式的全称是Executable and Linkable Format,即可执行可链接格式,是现代UNIX操作系统主要使用的目标文件格式。Hello.o属于可重定位目标文件,主要包含代码部分和数据部分,可以与其他可重定位目标文件链接,从而创建可执行目标文件、共享库文件或者其他可重定位目标文件。ELF可重定位目标文件有ELF头、节头表以及夹在ELF头和节头表之间的各个不同的节组成。
在命令行中使用readelf -a -W hello.o >hello.elf命令,可以列出hello.o各节的基本信息,并保存在hello.elf文件中,下面是hello.elf文件的全文:
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: 1216 (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
Section Headers:
[Nr] Name
Type Address Off Size ES Lk Inf Al
Flags
[ 0]
NULL 0000000000000000 000000 000000 00 0 0 0
[0000000000000000]:
[ 1] .text
PROGBITS 0000000000000000 000040 00007f 00 0 0 1
[0000000000000006]: ALLOC, EXEC
[ 2] .rela.text
RELA 0000000000000000 000358 0000d8 18 11 1 8
[0000000000000040]: INFO LINK
[ 3] .data
PROGBITS 0000000000000000 0000bf 000000 00 0 0 1
[0000000000000003]: WRITE, ALLOC
[ 4] .bss
NOBITS 0000000000000000 0000bf 000000 00 0 0 1
[0000000000000003]: WRITE, ALLOC
[ 5] .rodata.str1.8
PROGBITS 0000000000000000 0000c0 000026 01 0 0 8
[0000000000000032]: ALLOC, MERGE, STRINGS
[ 6] .rodata.str1.1
PROGBITS 0000000000000000 0000e6 00000d 01 0 0 1
[0000000000000032]: ALLOC, MERGE, STRINGS
[ 7] .comment
PROGBITS 0000000000000000 0000f3 00002c 01 0 0 1
[0000000000000030]: MERGE, STRINGS
[ 8] .note.GNU-stack
PROGBITS 0000000000000000 00011f 000000 00 0 0 1
[0000000000000000]:
[ 9] .eh_frame
PROGBITS 0000000000000000 000120 000040 00 0 0 8
[0000000000000002]: ALLOC
[10] .rela.eh_frame
RELA 0000000000000000 000430 000018 18 11 9 8
[0000000000000040]: INFO LINK
[11] .symtab
SYMTAB 0000000000000000 000160 0001b0 18 12 10 8
[0000000000000000]:
[12] .strtab
STRTAB 0000000000000000 000310 000041 00 0 0 1
[0000000000000000]:
[13] .shstrtab
STRTAB 0000000000000000 000448 000077 00 0 0 1
[0000000000000000]:
There are no section groups in this file.
There are no program headers in this file.
There is no dynamic section in this file.
Relocation section '.rela.text' at offset 0x358 contains 9 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000000016 000000050000000a R_X86_64_32 0000000000000000 .rodata.str1.8 + 0
000000000000001b 0000000b00000002 R_X86_64_PC32 0000000000000000 puts - 4
0000000000000025 0000000c00000002 R_X86_64_PC32 0000000000000000 exit - 4
0000000000000032 000000060000000a R_X86_64_32 0000000000000000 .rodata.str1.1 + 0
0000000000000041 0000000d00000002 R_X86_64_PC32 0000000000000000 __printf_chk - 4
0000000000000054 0000000e00000002 R_X86_64_PC32 0000000000000000 strtol - 4
000000000000005b 0000000f00000002 R_X86_64_PC32 0000000000000000 sleep - 4
000000000000006a 0000001000000002 R_X86_64_PC32 0000000000000000 stdin - 4
000000000000006f 0000001100000002 R_X86_64_PC32 0000000000000000 _IO_getc - 4
Relocation section '.rela.eh_frame' at offset 0x430 contains 1 entry:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000000020 0000000200000002 R_X86_64_PC32 0000000000000000 .text + 0
The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.
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 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 9
9: 0000000000000000 0 SECTION LOCAL DEFAULT 7
10: 0000000000000000 127 FUNC GLOBAL DEFAULT 1 main
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND exit
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __printf_chk
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND strtol
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND sleep
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND stdin
17: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _IO_getc
No version information found in this file.
下面挑选几个重要部分进行解释
(1) ELF Header
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: 1216 (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 Header即ELF头,其中指明了一些基本信息。如class项指明了这个文件是ELF64格式的,Data项指明数据以二进制补码形式表示、小端存储,machine项指明了这个程序适配于x86-64机器,type项指明这个文件是可重定位文件,Start of section headers指明了节头表的开始位置离文件位置的偏移量是1216个字节,size of this header和size of section header分别指明了这个ELF头和节头表中一个节头项的大小均为64字节,number of section headers指明了节头表共有14个节头,section header string table index指明了字符串表在节头中的索引是13。
(2) Section Headers
Section Headers:
[Nr] Name
Type Address Off Size ES Lk Inf Al
Flags
[ 0]
NULL 0000000000000000 000000 000000 00 0 0 0
[0000000000000000]:
[ 1] .text
PROGBITS 0000000000000000 000040 00007f 00 0 0 1
[0000000000000006]: ALLOC, EXEC
[ 2] .rela.text
RELA 0000000000000000 000358 0000d8 18 11 1 8
[0000000000000040]: INFO LINK
[ 3] .data
PROGBITS 0000000000000000 0000bf 000000 00 0 0 1
[0000000000000003]: WRITE, ALLOC
[ 4] .bss
NOBITS 0000000000000000 0000bf 000000 00 0 0 1
[0000000000000003]: WRITE, ALLOC
[ 5] .rodata.str1.8
PROGBITS 0000000000000000 0000c0 000026 01 0 0 8
[0000000000000032]: ALLOC, MERGE, STRINGS
[ 6] .rodata.str1.1
PROGBITS 0000000000000000 0000e6 00000d 01 0 0 1
[0000000000000032]: ALLOC, MERGE, STRINGS
[ 7] .comment
PROGBITS 0000000000000000 0000f3 00002c 01 0 0 1
[0000000000000030]: MERGE, STRINGS
[ 8] .note.GNU-stack
PROGBITS 0000000000000000 00011f 000000 00 0 0 1
[0000000000000000]:
[ 9] .eh_frame
PROGBITS 0000000000000000 000120 000040 00 0 0 8
[0000000000000002]: ALLOC
[10] .rela.eh_frame
RELA 0000000000000000 000430 000018 18 11 9 8
[0000000000000040]: INFO LINK
[11] .symtab
SYMTAB 0000000000000000 000160 0001b0 18 12 10 8
[0000000000000000]:
[12] .strtab
STRTAB 0000000000000000 000310 000041 00 0 0 1
[0000000000000000]:
[13] .shstrtab
STRTAB 0000000000000000 000448 000077 00 0 0 1
[0000000000000000]:
Section Headers即为节头表,指明了各个节头的基本信息。如type指明了此节的类型,address指明了此节的地址,size指明了此节的大小
(3).rela.text
这是一个节,包含了关于text节的重定位信息
Relocation section '.rela.text' at offset 0x358 contains 9 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000000016 000000050000000a R_X86_64_32 0000000000000000 .rodata.str1.8 + 0
000000000000001b 0000000b00000002 R_X86_64_PC32 0000000000000000 puts - 4
0000000000000025 0000000c00000002 R_X86_64_PC32 0000000000000000 exit - 4
0000000000000032 000000060000000a R_X86_64_32 0000000000000000 .rodata.str1.1 + 0
0000000000000041 0000000d00000002 R_X86_64_PC32 0000000000000000 __printf_chk - 4
0000000000000054 0000000e00000002 R_X86_64_PC32 0000000000000000 strtol - 4
000000000000005b 0000000f00000002 R_X86_64_PC32 0000000000000000 sleep - 4
000000000000006a 0000001000000002 R_X86_64_PC32 0000000000000000 stdin - 4
000000000000006f 0000001100000002 R_X86_64_PC32 0000000000000000 _IO_getc - 4
其中,Offset指明了需要重定位的位置相对于所在节起始位置的字节偏移量,若虫定位的是变量的位置,那么所在节是.data节;若重定位的是函数的位置,则所在节是.text节。Info项指出了当前需要重定位的符号所引用的符号在符号表中的索引值以及相应的重定位类型。其中info的高32位是符号的索引,低32位是重定位的类型。Type项其实就是info项低32位表示的内容,即重定位的类型。其中,R_X86_64_32指明引用处采用绝对地址的寻址方式,即有效地址就是重定位后的64位地址;R_X86_64_PC32指明引用处采用相对寻址的方式,即有效地址为PC的内容加上重定位后的64位地址,PC的内容是下条指令的地址。可以看到,函数符号的寻址全都采用相对寻址,剩下的两个字符串常量都是绝对寻址。
(4).symtab
此节是符号表
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 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 9
9: 0000000000000000 0 SECTION LOCAL DEFAULT 7
10: 0000000000000000 127 FUNC GLOBAL DEFAULT 1 main
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND exit
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __printf_chk
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND strtol
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND sleep
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND stdin
17: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _IO_getc
如上图,符号表包含了在程序模块中被定义和引用的所有符号的相关信息。其中,num是符号表索引,value是符号位置相对于所在节位置的偏移量,size是符号所表示对象的大小,type是符号的类型,bind是符号的绑定属性,ndx是符号所在节在节头表中的索引,name就是符号的名称。
Type这一项中,NOTYPE代表未定义类型,FILE代表文件,SECTION代表节,FUNC代表函数。Bind这一项中,LOCAL和GLOBAL分别代表本地符号(局部符号)和全局符号,另外,还有WEAK代表弱符号。
NDX用于指出符号所在节在节头表中的索引,其中有些符号属于三种特殊伪节之一,伪节在节头表中没有相应的表项,无法表示其索引值,因而用以下特殊的索引值表示:ABS表示该符号不会由于重定位而发生值的改变,即不该被重定位;UND表示未定义符号,即在本模块引用而在其他模块定义的符号;COM表示还未被分配位置的未初始化的变量。
(以下格式自行编排,编辑时删除)
使用命令objdump -d -r hello.o>hello.o.s可以将hello.o中的代码段反汇编成汇编语言代码,并通过Linux管道存储在hello.o.s文件中。下面是hello.o.s的全部内容:
hello.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 :
0: 55 push %rbp
1: 53 push %rbx
2: 48 83 ec 08 sub $0x8,%rsp
6: 83 ff 04 cmp $0x4,%edi
9: 75 0a jne 15
b: 48 89 f5 mov %rsi,%rbp
e: bb 00 00 00 00 mov $0x0,%ebx
13: eb 4d jmp 62
15: bf 00 00 00 00 mov $0x0,%edi
16: R_X86_64_32 .rodata.str1.8
1a: e8 00 00 00 00 callq 1f
1b: R_X86_64_PC32 puts-0x4
1f: bf 01 00 00 00 mov $0x1,%edi
24: e8 00 00 00 00 callq 29
25: R_X86_64_PC32 exit-0x4
29: 48 8b 4d 10 mov 0x10(%rbp),%rcx
2d: 48 8b 55 08 mov 0x8(%rbp),%rdx
31: be 00 00 00 00 mov $0x0,%esi
32: R_X86_64_32 .rodata.str1.1
36: bf 01 00 00 00 mov $0x1,%edi
3b: b8 00 00 00 00 mov $0x0,%eax
40: e8 00 00 00 00 callq 45
41: R_X86_64_PC32 __printf_chk-0x4
45: 48 8b 7d 18 mov 0x18(%rbp),%rdi
49: ba 0a 00 00 00 mov $0xa,%edx
4e: be 00 00 00 00 mov $0x0,%esi
53: e8 00 00 00 00 callq 58
54: R_X86_64_PC32 strtol-0x4
58: 89 c7 mov %eax,%edi
5a: e8 00 00 00 00 callq 5f
5b: R_X86_64_PC32 sleep-0x4
5f: 83 c3 01 add $0x1,%ebx
62: 83 fb 07 cmp $0x7,%ebx
65: 7e c2 jle 29
67: 48 8b 3d 00 00 00 00 mov 0x0(%rip),%rdi # 6e
6a: R_X86_64_PC32 stdin-0x4
6e: e8 00 00 00 00 callq 73
6f: R_X86_64_PC32 _IO_getc-0x4
73: b8 00 00 00 00 mov $0x0,%eax
78: 48 83 c4 08 add $0x8,%rsp
7c: 5b pop %rbx
7d: 5d pop %rbp
7e: c3 retq
机器语言与汇编语言类似,也是由一条条指令构成的,每条指令由一串二进制字符串构成,通常以16进制表示在程序员面前。指令的长度不是固定的,由操作码和操作数构成。汇编语言和机器语言存在着一一对应的关系,也即,汇编语言就是机器语言按照一定规则的文本级表示。
针对编译生成的汇编代码和汇编生成的机器语言,以及反汇编生成的汇编代码,存在以下不同:
首先,立即数的书写不同。在汇编代码中,立即数可以用10进制书写,但在机器语言中,统统是16进制;汇编代码中是从左往右按照自然顺序书写,机器代码中取决于指定的存储方式,本例中是小端存储,则低位字节在左,高位字节在右。
其次,对字符串常量的引用不同。在编译生成的汇编代码中,引用字符串常量是直接传送首地址,而在机器代码中,则是将本该填写字符串常量的位置全部置零,并在这些0的起始地址添加一条重定位条目,指明需要重定位的位置、重定位的寻址方式以及该符号所在的节和名称。
再次,分支结构转移的实现细节不同。在编译产生的汇编代码中,各个分支都有自己的标识,如.L3等,但在反汇编产生的汇编代码中,则是直接用相对于main函数首地址的偏移量代表各分支的首地址。
最后,函数调用的实现上存在差异。在编译生成的汇编代码中,call指令后面紧跟的是函数的标识符;而在机器代码中,标识符的位置全部置零,并在其后增加一个重定位条目,指明需要重定位的位置、重定位的寻址方式以及被重定位的符号。这里需要指出,在hello.o反汇编生成的代码中,需要重定位的一种是字符串常量,另一种是函数;前者采用的寻址方式都是绝对寻址,后者采用的寻址方式都是相对寻址。采用相对寻址时,需要使用PC的值加上偏移地址,这里的偏移地址就是重定位后原来被置零的地方应该填写的值,即重定位值。而这里使用的PC应是本条call指令所在的地址,然而PC会自动增加,指向下一条指令,因此我们需要先把PC的值减4 。这也就是为什么,重定位条目中函数的标识符后都会再加一个-0x4 。
本章我们探讨了汇编的概念和作用。我们首先指明,汇编是将编译生成的汇编语言代码转换为机器语言代码、生成可重定位目标文件的过程。然后,我们通过gcc工具尝试了这个过程。接着,我们介绍并分析了hello的ELF格式。为了加深对ELF文件的了解,我们重点讲解了ELF头、节头表、符号表和其他一些和重定位有关的表。最后,我们使用反汇编工具objdump从机器代码反汇编生成了新的汇编代码并将它和机器代码与我们通过编译生成的汇编代码作了细致的比较,分析了其中的差异。通过以上的步骤,我们对汇编语言、机器语言、ELF格式,以及汇编和反汇编工具的使用有了更加深入的理解。
(第4章1分)
一个大的程序往往分成多个源文件来编写,因而需要对各个不同源文件分别进行编译或者汇编,而这样会生成多个不同的目标代码文件。此外,程序还可能调用了一些标准库函数。链接,就是将所有用到了目标代码文件,包括用到的标准库函数目标文件,关联到一起的过程。
注意:这儿的链接是指从 hello.o 到hello生成过程。
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
(以下格式自行编排,编辑时删除)
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
(以下格式自行编排,编辑时删除)
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
(以下格式自行编排,编辑时删除)
(第5章1分)
进程就是程序的一次运行过程。更确切地说,进程是一个具有一定独立功能地程序关于某个数据集合地一次运行活动,具有着动态地含义。每进程是操作系统对处理器中程序运行过程的一个抽象,具有自己的生命周期。
进程的引入实现了每个程序独占计算资源和存储资源的假象,简化了编程、编译、链接、共享和加载等整个过程。
在我们在命令行中输入./hello并按下回车后,bash会首先解析命令行,判断出它的工作方式是前台还是后台,以及它是否是内置命令。显然hello属于外部的可执行文件,因此,bash就会屏蔽所有的信号,然后利用fork函数创建一个子进程,在调用后通过判断函数的返回值来判断子进程是否创建成功,以及哪一个是父进程的返回,哪一个是子进程的返回,子进程的pid号是多少。创建成功后,解除信号阻塞。同时,bash还会将子进程放入新进程组,以免用户输入的中断指令影响到整个bash进程。此时,子进程获得与父进程完全相同的虚拟存储空间的一个备份。
在创建子进程并解除阻塞后,通过判断pid的值得到子进程,利用分支结构让子进程执行execve函数,调用可执行目标文件hello。在函数execve中,通过启动加载器执行加载任务,将可执行目标文件hello中的.text、.data、.bss节等内同加载到当前进程的虚拟空间(通过修改当前进程上下文中关于存储映像的一些数据结构实现,并未将hello的代码从磁盘读入主存)。当加载器执行完加载任务后,便转到hello程序的第一条指令开始执行。
(以下格式自行编排,编辑时删除)
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
Hello的执行过程中会出现故障、陷阱、中断、终止这四类异常。
故障由错误情况引起,能够被故障处理程序修正。若处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它,负责处理程序返回到内核的abort例程,abort例程会终止引起故障的应用程序。
陷阱是有意的异常,是执行一条指令的结果,如syscall,陷阱处理程序将控制返回到下一条指令。
中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。中断发生时,控制转移给中断处理程序,处理程序返回时,它就将控制返回到下一条指令,程序继续执行。
终止是不可恢复的致命错误造成的结果,通常是一些硬件错误。终止处理程序要么重新执行当前指令,要么将控制返回给一个abort例程,abort例程会终止这个应用程序。
Hello执行过程中可能会产生很多信号,包括SIGINT SIGSTOP SIGCHILD等,它们会由相应的信号处理程序来处理。
下面展示Ctrl-Z后的现象:
从上图可以发现,乱按对程序没有影响,执行ctrl-z后程序停止,这时程序接收到了SIGSTOP信号,停止运行并且进入后台,ps命令和jobs命令都可以看到它。
执行pstree后在terminal的子进程中看到了它。
上面是利用fg命令将其调入前台,则程序继续执行。再次挂起后使用kill命令向其发送终止信号,发现第一次调用ps命令能够看到被杀死的进程,第二次就没有了,jobs命令执行后也没有看到该进程。
接下来我们看一看crtl-c的效果:
发现该进程已经完完全全地消失了。
本章我们主要讨论了关于进程的内容,包括进程的创建和管理,以及异常的分类和信号的处理等。
(第6章1分)
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
Hello在运行时,OS会给进程分配一个虚拟的内存空间,而逻辑地址就是通常所说的虚拟地址。逻辑地址由段选择符和段内偏移量组成。MMU首先根据段选择符确定描述符表,并从描述符表中找到对应的段描述符。从中去除基地址,与逻辑地址中的段内偏移量相加,就得到了线性地址
这步转换采用多级页表的形式,下面以2级页表为例。首先根据页目录表首地址找到页目录表,根据页目录索引找到对应的页表,再根据线性地址中间的页表索引找到页表中的页表项。最后,将页表项中的基地址和线性地址的页内偏移量相组合,就得到了物理地址。
MMU对TLB查表时,首先由组索引确定TLB的组,再比较标记为。若相等且有效位为1,则命中,直接通过TLB进行地址转换。否则TLB缺失,将虚拟页号分成四级,分别在四级页表中进行嵌套查询,将最终得到的页表项的基地址和线性地址内的页内偏移量相组合,就得到了物理地址。
(以下格式自行编排,编辑时删除)
(以下格式自行编排,编辑时删除)
(以下格式自行编排,编辑时删除)
(以下格式自行编排,编辑时删除)
(以下格式自行编排,编辑时删除)
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
(以下格式自行编排,编辑时删除)
(第7章 2分)
(以下格式自行编排,编辑时删除)
设备的模型化:文件
设备管理:unix io接口
(以下格式自行编排,编辑时删除)
(以下格式自行编排,编辑时删除)
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
(以下格式自行编排,编辑时删除)
(第8章1分)
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
(结论0分,缺失 -1分,根据内容酌情加分)
列出所有的中间产物的文件名,并予以说明起作用。
(附件0分,缺失 -1分)
为完成本次大作业你翻阅的书籍与网站等
[1] 袁春风. 计算机系统基础[M]. 北京:机械工业出版社.
[2] .深入理解计算机系统[M]
(参考文献0分,缺失 -1分)