HIT计算机系统大作业——hello的一生

摘 要
本文通过一个结构简单的hello程序,说明了由文本到可执行的文件,再到一个进程的整个过程中,计算机系统都起到了怎样的作用。在这一过程中,我们会接触到程序预处理、编译、汇编、链接等过程,同时也介绍了有关进程、存储、I/O等相关的知识,深入系统底层的软硬件结合的部分,对计算机系统进行了较为系统和广泛的探讨。
第1章 概述
1.1 Hello简介
Hello.c是一个以字节序列方式储存在文件中的源程序(Program),经过预处理器(cpp)变为Hello.i,然后在编译器(ccl)的处理后变为Hello.s的汇编程序,然后通过汇编器(as),生成了Hello.o的可重定位目标程序(二进制),然后在链接器(ld)的链接后,生成可执行的Hello目标程序。在编译系统之后,调用shell命令行输入,fork产生子进程,Hello从program成为Process计算机完成了From Program to Process的过程:

图 2 预处理命令
2.3 Hello的预处理结果解析
图 4 预处理结果-2
接下来还有很多对于库中预置的函数的定义:
图 6 编译命令
3.3 Hello的编译结果解析
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.3.1开始的声明
在文本的开始,有如下的声明
图 7 汇编命令
4.3 可重定位目标elf格式
可以使用指令:readelf -a hello.o > hello_0.elf得到.elf的文件。
ELF头如下:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX – System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 1192 (bytes into file)
标志: 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
节头如下:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000008e 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000358
00000000000000c0 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000ce
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 000000ce
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 000000d0
0000000000000033 0000000000000000 A 0 0 8
[ 6] .comment PROGBITS 0000000000000000 00000103
000000000000002c 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 0000012f
0000000000000000 0000000000000000 0 0 1
[ 8] .note.gnu.propert NOTE 0000000000000000 00000130
0000000000000020 0000000000000000 A 0 0 8
[ 9] .eh_frame PROGBITS 0000000000000000 00000150
0000000000000038 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 00000418
0000000000000018 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 00000188
0000000000000198 0000000000000018 12 10 8
[12] .strtab STRTAB 0000000000000000 00000320
0000000000000032 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 00000430
0000000000000074 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
重定位的信息包括了类型和偏移量,在链接阶段会用到这些信息来进行地址的计算,需要进行重定位的信息包括了.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar等函数
重定位节如下:
重定位节 ‘.rela.text’ at offset 0x358 contains 8 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
00000000001a 00050000000a R_X86_64_32 0000000000000000 .rodata + 0
00000000001f 000b00000004 R_X86_64_PLT32 0000000000000000 puts – 4
000000000029 000c00000004 R_X86_64_PLT32 0000000000000000 exit – 4
000000000050 00050000000a R_X86_64_32 0000000000000000 .rodata + 26
00000000005a 000d00000004 R_X86_64_PLT32 0000000000000000 printf – 4
00000000006d 000e00000004 R_X86_64_PLT32 0000000000000000 atoi – 4
000000000074 000f00000004 R_X86_64_PLT32 0000000000000000 sleep – 4
000000000083 001000000004 R_X86_64_PLT32 0000000000000000 getchar – 4

重定位节 ‘.rela.eh_frame’ at offset 0x418 contains 1 entry:
偏移量 信息 类型 符号值 符号名称 + 加数
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
.symtab是一个符号表,其中存放着程序中定义和引用的函数和全局变量的信息:
Symbol table ‘.symtab’ contains 17 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 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 9
9: 0000000000000000 0 SECTION LOCAL DEFAULT 6
10: 0000000000000000 142 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
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND atoi
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND sleep
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND getchar
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的hello.s进行对照分析。
分析hello.o的反汇编,并与第3章的 hello.s进行对照分析:
操作数:hello.s中的操作数采用十进制来表示,而hello.o的反汇编代码中的操作数采用了十六进制。
分支转移:在hello.s的跳转语句中采用的是.L2和.LC1等段的名称,而反汇编代码中跳转指令之后是间接地址,是每条语句之间相对偏移的地址。
函数调用:hello.s中,call指令直接使用了函数名称,而反汇编代码中call指令使用的是相对于main函数的偏移地址。同时在.rela.text节中为其添加了重定位条目,待在链接之后确定物理地址。
4.5 本章小结
本章主要介绍了汇编后程序的文本文件被转化为可重定位目标程序的过程,然后通过ELF文件格式分析了我们得到的.o文件,然后通过对所得的.o文件进行反汇编,发现了经过汇编后程序的改动——用逻辑地址的相对偏移来约定跳转指令和call指令。
第5章 链接
5.1 链接的概念与作用
链接是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件可被复制到内存并执行。
链接使得分离编译成为可能,我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,并对它们进行独立地修改和编译。
5.2 在Ubuntu下链接的命令

图 9 edb
edb中程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000002a0 0x00000000000002a0 R 0x8
INTERP 0x00000000000002e0 0x00000000004002e0 0x00000000004002e0
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000005c0 0x00000000000005c0 R 0x1000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
0x0000000000000245 0x0000000000000245 R E 0x1000
LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000
0x000000000000013c 0x000000000000013c R 0x1000
LOAD 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001fc 0x00000000000001fc RW 0x1000
DYNAMIC 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001a0 0x00000000000001a0 RW 0x8
NOTE 0x0000000000000300 0x0000000000400300 0x0000000000400300
0x0000000000000020 0x0000000000000020 R 0x8
NOTE 0x0000000000000320 0x0000000000400320 0x0000000000400320
0x0000000000000020 0x0000000000000020 R 0x4
GNU_PROPERTY 0x0000000000000300 0x0000000000400300 0x0000000000400300
0x0000000000000020 0x0000000000000020 R 0x8
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001b0 0x00000000000001b0 R 0x1
其含义如下:
PHDR:程序头表
INTERP:程序执行前调用的解释器
LOAD:程序目标代码和常量信息
DYNAMIC:动态链接器所使用的信息
NOTE::辅助信息
GNU_EH_FRAME:异常信息保存
GNU_STACK:使用系统栈所需要的权限信息
GNU_RELRO:保存在重定位之后只读信息的位置
可以看到最开始是GNU_STACK,而LOAD从0x400000开始,接着是PHDR,INTERP和NOTE。最后是DYNAMIC和GNU_RELRO部分。
5.5 链接的重定位过程分析
HIT计算机系统大作业——hello的一生
图 10 执行dl_init前的地址
图 12 用户态和内核态的切换
用户态与核心态转换:
为了能让处理器安全运行,不至于损坏操作系统,必然需要先知应用程序可执行指令所能访问的地址空间范围。因此,就存在了用户态与核心态的划分,核心态拥有最高的访问权限,处理器以一个寄存器当做模式位来描述当前进程的特权。进程只有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,保证了系统的安全性。
6.6 hello的异常与信号处理
hello执行过程中可能会出现以下异常:
类别 原因 异步/同步 返回行为
中断 来自I/O设备的信号 异步 返回到下一条指令
陷阱 有意的异常 同步 返回到下一条指令
故障 潜在的可恢复的错误 同步 可能返回到当前指令
终止 不可恢复的错误 同步 不会返回
运行程序时,参数是学号+姓名+秒数,在命令行中键入./hello 120L021327 DAIHONGQIAN 1,可以得到如下结果:
图 18 执行ctrl+Z
接下来,我们键入ctrl+C的终止指令,进程收到 SIGINT 信号,我们可以结束彻底结束hello进程,这时我们再通过ps和jobs命令检查正在运行的命令,我们发现hello进程已经结束。
图 20 执行kill
6.7本章小结
本章主要介绍了有关hello进程管理的内容,通过shell我们可以管理计算机运行的进程。我们还介绍了hello程序的fork、execve过程,然后通过命令行研究了程序运行当中可能会遇见的异常,以及我们的系统是如何处理这些由异常发出的信号的。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址,是指由程序hello产生的与段相关的偏移地址部分,hello.o文件中的地址就是逻辑地址。
线性地址,是逻辑地址到物理地址变换之间的中间层。程序hello的代码会产生逻辑地址——也就是段中的偏移地址,它加上相应段的基地址就会生成线性地址。
虚拟地址,是由CPU生成的一个仰赖访问主存的地址,它会被送到内存之前先转换成适当的物理地址,地址翻译的任务由CPU芯片上的内存管理单元MMU来承担,MMU会用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。在linux系统中,只会分页而不会分段,因此对于我们的hello程序逻辑地址几乎就是虚拟地址。
物理地址,是主存上被组织成一个由M个连续的字节大小的单元组成的数组,每个字节都有一个唯一的物理地址,hello的物理地址来自于虚拟地址的地址转换,它也是程序运行的最终地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部分组成,段标识符,段内偏移量。段标识符是一个16位长的字段组成,称为段选择符,其中前13位是一个索引号。后面三位包含一些硬件细节。
索引号,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
Base,它描述了一个段的开始位置的线性地址。
全局的段描述符,放在全局段描述符表(GDT)中,而局部的段描述符,处于局部段描述符表(LDT)之中。GDT在内存中的地址和大小会存放在gdtr控制寄存器中,而LDT则在ldtr寄存器中。
一个完整的逻辑地址包括段选择符+段内偏移地址,
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理,是一种内存空间存储管理的技术,分为静态页式管理和动态页式管理。页式管理将各进程的虚拟空间划分成若干个长度相等的页(page),再把内存空间按页的大小划分成片,进而建立起内存地址与页式虚拟地址一一对应关系,存储在页表之中。为了解决离散地址变换问题,它会调用相应的硬件地址变换构件。页式管理采用请求调页或预调页技术实现了内外存的统一管理与调用。
优点:它有效地解决了碎片问题。由于页式管理不需要进程的程序段和数据在内存中连续存放,从而提供了内外存统一管理的虚拟内存实现方式,使用户可以利用的碎片化的空间大大增加,也非常有利于多个进程同时执行。
缺点:需要相应的硬件支持,增加了机器成本,并增加了系统开销。例如缺页中断的产生和选择淘汰页面等都要求有相应的硬件支持;缺页中断处理时,请求调页的算法如果不够恰当,有可能产生抖动现象。碎片式的管理,也使得每个进程内总有一部分空间得不到利用,当页面比较大的时候,这一部分的损失会非常显著。
7.4 TLB与四级页表支持下的VA到PA的变换
TLB:每次CPU产生一个虚拟地址,MMU就必须查阅相应的PTE,每次都必然造成缓存不命中等一系列时间开销,为了消除这样的开销,MMU中存在一个全相联高速缓存,称为TLB。
四级页表:如果只采用两级的页表结构,当需要使用的虚拟内存很小,但仍然需要建立其一个庞大的页表,这很容易造成内存的浪费,检索巨大的页表也是非常浪费性能的事。由此,在虚拟地址到物理地址的转换过程中,又开发出了多级页表的机制:上一级的页表映射到下一级页表,直到页表映射到虚拟内存。四级页表,也就指共有四级的多级页表。
7.5 三级Cache支持下的物理内存访问
物理地址被分为CT(标记)+CL(索引)+CO(偏移量),然后到1级cache里去找对应的标记位为有效的。如果命中就直接返回想要的数据,如果不命中,就依次去L2,L3,主存判断是否命中,当命中时,将数据传给CPU同时更新各级cache的cacheline。当cache已满时,需要按一定策略,将cache中近期不太可能再次访问到的数据替换为我们所需要的物理内存访问索引。
7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,同时为这个新进程创建虚拟内存。它创建了当前进程的mm_struct、区域结构和页表的副本,将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。因此,也就为每个进程保持了私有空间地址的抽象概念。
7.7 hello进程execve时的内存映射
1)在bash中的进程中执行了execve调用:execve(“hello”,NULL,NULL);
2)execve函数在当前进程中加载并运行包含在可执行文件hello中的程序,用hello替代了当前bash中的程序。
3)删除已存在的用户区域。
4)映射私有区域
5)映射共享区域
6)设置程序计数器(PC)
exceve做的最后一件事是设置当前进程的上下文中的程序计数器,是指指向代码区域的入口点。而下一次调度这个进程时,他将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。
7.8 缺页故障与缺页中断处理
整体的处理流程:
1.处理器生成一个虚拟地址,并将它传送给MMU
2.MMU生成PTE地址,并从高速缓存/主存请求得到它
3.高速缓存/主存向MMU返回PTE
4.PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
5.缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。
6.缺页处理程序页面调入新的页面,并更新内存中的PTE
7.缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。
7.9动态存储分配管理
基本方法与策略:通过维护虚拟内存(堆),一种是隐式空闲链表,一种是显式空闲链表。显式空闲链表法是malloc(size_t size)每次声明内存空间都保证至少分配size_t大小的内存,双字对齐,每次必须从空闲块中分配空间,在申请空间时将空闲的空间碎片合并,以尽量减少浪费。分配器一般按以下策略进行分配:首次适配、下一次适配合最佳适配。分配完后可以分割空闲块减少内部碎片。同时分配器在面对释放一个已分配块时,可以合并空闲块,其中便利用隐式空闲链表的边界标记来进行合并。
7.10本章小结
本章主要介绍了 hello 的存储器地址空间、 intel 的段式管理、 hello 的页式管理,在指定环境下介绍了 VA 到 PA 的变换、物理内存访问,还介绍 hello 进程 fork 时的内存映射、 execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化
文件(所有的I/O设备都被模型化为文件,甚至内核也被映射为文件)
设备管理
unix io接口
这种将设备映射为系统文件的方式,允许Linux内核引出一个简单的应用接口,称为Unix I/O。
文件操作包括:打开和关闭操作;读写操作;更改当前文件位置……
8.2 简述Unix IO接口及其函数
open函数
功能描述:打开或创建文件,可以指定文件的属性及用户的权限等各种参数。
函数原型:int open(const char *pathname,int flags,int perms)
参数:pathname:被打开的文件所在路径,flags:文件打开方式,
返回值:成功:返回文件描述符;失败:返回-1
close函数
功能描述:用于关闭一个被打开的的文件
所需头文件: #include <unistd.h>
函数原型:int close(int fd) 参数:fd文件描述符
函数返回值:0成功,-1出错
read函数
功能描述: 读取文件中数据
所需头文件: #include <unistd.h>
函数原型:ssize_t read(int fd, void *buf, size_t count);
参数:fd:目标文件描述符。buf:缓冲区。count:表示调用一次read操作,应该读取的字符数量。
返回值:返回所读取的字节数;-1(出错)。
write函数
功能描述:写入数据。
所需头文件:#include <unistd.h>
函数原型:ssize_t write(int fd, void *buf, size_t count);
返回值:写入文件的字节数(成功);-1(出错)
lseek()函数
功能描述: 用于在指定的文件描述符中将将文件指针定位到相应位置。
所需头文件:#include <unistd.h>,#include <sys/types.h>
函数原型:off_t lseek(int fd, off_t offset,int whence);
参数:fd;文件描述符。offset:偏移量,每一个读写操作所需要移动的距离,单位是字节,可正可负(向前移,向后移)
返回值:成功:返回当前位移;失败:返回-1
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
printf函数的函数体:
int printf(const char *fmt, …)
{
int i;
char buf[256];

}
vsprintf函数将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。write函数将buf中的i个元素写到终端。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量),最后就在显示器上实现了信息的打印。
8.4 getchar的实现分析
异步异常-键盘中断的处理:
getchar可以理解为对于键盘中断的异步异常的处理。当键盘中断处理子程序。系统会接受按键输入的信息,并将其转换为ascii码,保存到键盘输入的缓冲区。getchar等调用read系统函数,通过系统调用读取按键输入数据,当接受到回车n时才返回。getchar有一个int型的返回值。当程序调用getchar时,程序就等着用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车为止,回车字符n也会被读入缓冲区中。
当用户键入回车之后,getchar开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
8.5本章小结
本章介绍了 Linux 的 I/O 设备的基本概念和管理方法,以及Unix I/O 接口及其函数。最后分析了printf 函数和 getchar 函数的工作过程。
结论
最初接触到C 语言的我们,在计算机的屏幕前,通过键盘鼠标等I/O设备键入hello程序的代码,将它保存为了.c的文本文件,我们点击codeblocks中的编译按钮,再运行可执行的文件,hello便横空出世了。对于一个初学者而言,编写这样结构简单的程序似乎不是一件难事,然而计算机系统这一节课把hello的神秘面纱缓缓解开了,我们看到了hello的“漫漫长征路”:
1.hello.c经过预编译,拓展得到hello.i文本文件
2.hello.i经过编译,得到汇编代码hello.s汇编文件
3.hello.s经过汇编,得到二进制可重定位目标文件hello.o
4.hello.o经过链接,生成了可执行文件hello
5.bash进程调用fork函数,生成子进程;并由execve函数加载运行当前进程的上下文中加载并运行新程序hello
6.hello的变化过程中,会有各种地址,但最终我们真正期待的是PA物理地址。
7.hello再运行时会调用一些函数,比如printf函数,这些函数与linux I/O的设备模拟化密切相关
8.hello最终被shell父进程回收,内核会收回为其创建的所有信息
hello的一生并不是一条平坦的大路,它更像是个通过神秘穿梭机来回变换的时间旅客,每一步的变换都极端复杂,让人琢磨不透。然而通过计算机系统的学习,我们可以将这些“魔术”背后的奥秘一一揭晓,由0和1,人类的智慧一步步将它变为高楼大厦,一切复杂的系统背后都是非常质朴的想法,为了贴切地把这个想法落地生根,精妙而复杂的系统就被建立了起来。
我们现在所使用的计算机系统,经过了UI的改革,变得直观易懂,而繁琐的系统实现的细节被悄悄地包装在了我们看不见的深处里,这门课程之所以学习接近底层的那些细节,不厌其烦地探讨那些被藏起来的内容,能很好地帮助我们认清计算机系统的实质。也许科技发展到怎样的地步,我们提起骇客、提起极客这类名词时,脑海中浮现的都是那个手指飞快地敲击键盘,屏幕上是荧光绿色的命令行,一切可能,皆系于其中。

附件
处理后的文件 hello.i
译之后的汇编文件 hello.s
编之后的可重定位目标文件 hello.o
接之后的可执行目标文件 Hello
ello.o 的 ELF 格式 elf.txt
ello.o 的反汇编代码 Disas_hello.s
ello的ELF 格式 hello1.elf
hello 的反汇编代码 hello1_objdump.s
参考文献
[1] 条件编译,C语言条件编译详解. http://c.biancheng.net/view/289.html
[2] .bss、.data 和 .rodata section 之间的区别. https://blog.csdn.net/wohenfanjian/article/details/106007978
[3] 信息安全系统设计基础. 异常控制流.
[4] linux后台运行、挂起、恢复进程相关命令https://blog.csdn.net/koberonaldo24/article/details/103136125
[5] printf 函数实现的深入剖析. https://www.cnblogs.com/pianist/p/3315801.html

来源:DAILY1641

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年4月17日
下一篇 2022年4月17日

相关推荐