计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号
班 级
学 生
指 导 教 师 史先俊
计算机科学与技术学院
2022年5月
现在有一个可执行文件hello能够读取命令行参数,每间隔一段时间打印命令行输入的数据。本文主要对此程序包括从代码部分一直到程序生成,运行,结束回收的整个过程进行介绍。在本文中,笔者会从一个hello.c文本文件开始讲述,预处理,编译,汇编,链接生成一个可执行文件。然后是进程部分的进程调用,内存开销,子进程和父进程,进程回收和信号处理。并介绍文件的存储,以及CPU调用内存数据的原理,包括高速缓存,RAM,页表,快表的知识点。最后介绍系统级IO,简述文件的输入输出。
关键词:预处理,编译,汇编,链接,反汇编,edb,进程,内存,信号,异常,Cache,存储结构,缓存,高速缓冲存储器,页表,快表,磁盘,IO。
目 录
第1章 概述
1.1 Hello简介
1.P2P:
最开始,程序员们在文本编辑器上写着hello.c的代码,此时的hello.c仅仅只是一串文本文件罢了。在那之后,程序员调用一些编译软件,如gcc,cc1,cmake等,写好的hello.c经过预处理生成hello.s,再通过编译形成hello.i,最后通过汇编形成hello.o。之后链接器将hello.o与系统里面的库进行链接,最后生成可以运行的可执行文件。完成P2P的大致过程。
2.O2O:
shell调用fork生成一个子进程,同时子进程里通过execve函数调用我们的hello可执行文件。hello的文件被系统从磁盘读到内存,并通过mmap进行映射获取空间,在程序运行期间可以接收各种信号。当hello运行结束时,由父进程将其回收,删除其在内存中的数据,完成zero to zero的过程。
1.2 环境与工具
软件环境:Ubuntu 22.04 Windows 10(家庭中文版)
使用的工具:VMware-workstation gcc vim vscode gdb edb-debugger readelf
objdump
1.3 中间结果
1.hello.c:原始代码文本文件。
2.hello.i:预处理后的代码文本文件。
3.hello.s:编译后的汇编语言文件代码。
4.hello.o:汇编处理后的可重定位目标源文件。
5.hello:可执行文件hello
6.hello.elf对hello.s执行readelf生成的elf文件
7.hello.elf2:对可执行文件使用readelf生成的新的elf文件
8.hello.txt:对hello程序执行objdump后得到的反汇编文件
1.4 本章小结
本部分简单地介绍了程序hello是如何被程序员写出到整个被调用和最后被系统回收的过程,并介绍了本项目使用的环境和一些中间文件。
第2章 预处理
2.1 预处理的概念与作用
1、预处理的概念:
预处理指编译器将原本的.c文本文件改写成更加规范的文本文件,其主要操作包括注释的删除,宏定义的替换,引用库的调用等。
- 预处理的作用:
预处理能够使编译器更好的理解程序员所写的代码,同时也为程序员编写模块化代码提供了便利
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
可以看到main函数之前的库调用被扩展了,同时注释也被删除
2.4 本章小结
本章介绍了预处理的相关操作以及Linux下的指令调用
第3章 编译
3.1 编译的概念与作用
概念:编译即指编译器将C语言编码转换为汇编语言代码的一个过程,从文件形式来看就是将hello.i文件转换为hello.s文件的一个过程。
作用:将高级编程语言转换为机器能够识别的低级语言,一方面便于程序员使用各种编程语言进行程序编写,另一方面也使得机器能够更好的接收程序员的命令。
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
变量类型:
第三行中出现了.rodata,根据所学知识,可以知道指的是只读数据段,对应C语言类型中的const类型。其中.LC0和LC1对应printf中的字符串常量类型,
.string表示是字符串类型。.glabal以及.main表示为main函数中的局部变量。
循环操作:
.L2里面有一个jmp指令,根据c语言源代码我们可以推得这是循环开始的一个操作,在L3中会与循环条件比对并判断是否继续执行。不难发现这里的jmp(jle)后跳转的地址都是一个参数,而不是具体的地址,其根本原因是还需要后续编译器提供实际的地址。
数据操作:
cmp将两个数据进行比较,并根据比较结果改变条件码的数据。
如L3第5行 cmpl $8 -4(%rbp)即比较*(rbp-4)与立即数8的大小,jle表示若小于就跳转,这部分在c语言中就是i<=8继续循环的意思。
mov指令将一个寄存器的数据移动到另一个寄存器
add表示加法,如add A B表示A=A+B
函数调用:
我们可以看到代码中有call printf@PLT,call getchar@PLT。此时因为printf和getchar都还未重定位,所以这里用一个指代完成对这两个函数的调用。
返回值:
在汇编指令中,ret表示退出当前指令并返回返回值,一般返回值默认保存在rax寄存器中。
3.4 本章小结
本章主要讲了通过编译器将c语言文本代码转化为汇编语言代码的操作,以及简单介绍了一些汇编指令的作用以及汇编语言文件的一个格式问题。并通过汇编代码对hello.c的功能进行了一个简单的描述。
第4章 汇编
4.1 汇编的概念与作用
概念:
汇编就是编译器将低级的汇编语言进一步改写,转化为最低级的,机器可直接识别的机器语言,即二进制代码。在文件表示上就是将文本编辑器可以打开的hello.s文件转换为不可打开的hello.o文件。
作用:
机器语言是最低级的语言,其对计算机的操作无需通过软件进行,而是直接通过硬件电路进行,速度最快,效率最高。
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
ELF头:
ELF头以一个16字节的序列开始(magic那一行),这个序列描述了生成该文件的系统的字的大小和字节顺序。剩下的部分包含帮助链接器语法分析和解释目标文件的信息。包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。
节头表:
节头表中记录了每个节的各种信息,如大小,地址,偏移量等。
其中不同的节分别存储不同的信息,如:
.text:已编译程序的机器代码
.rodata:只读数据:如printf中的字符串,switch中的跳转表
.data:已初始化全局或静态C变量。
.bss:未初始化的全局和静态C变量。
.symtab:符号表,存放程序中定义和引用的函数和全局变量的信息。
重定位节:
符号表:
4.4 Hello.o的结果解析
使用objdump将.o文件重新转换为汇编代码,可以看到此时很多函数跳转的目标都是使用具体的地址表示的,而之前直接将c转到汇编时的地址跳转则是一个参数变量。原因是此处的.o文件被编译器赋予了一个自己的虚拟地址,再根据节头表中的偏移量,就直接将原来的一个地址引用转化为了具体的数值。
其次我们可以发现,两次汇编中某些指令使用的寄存器也发生了变化,并且新文件采用相对选址的方式,使整个指令呈现得更直观。
4.5 本章小结
本章主要介绍了编译器将汇编指令转化为二进制文件的一个过程,汇编器接受汇编代码,产生可重定位目标文件。它可以和其他可重定位目标文件合并而产生一个可以直接加载被运行的可执行目标文件。正因为它并不包含最终程序的完整信息,它的符号尚未被确定运行时位置,并用0占位。在第五部分中将说明如何将多个可重定位目标文件合并,并确定最终符号的最终运行位置。
第5章 链接
5.1 链接的概念与作用
概念:
链接是将各个单独的二进制代码文件加载到同一个文件,并使之可以加载到内存中执行的一个过程。链接可以在编译时被执行,也可以在程序运行中被执行。文件表示为若干个.o文件被合并成一个单独的可执行文件(Linux下默认为a.out文件)。
作用:
链接可以使代码完成分块化操作,大幅度简化了程序员对代码的编写,同时使程序的生成变得更加灵活,可移植性强。
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
ELF头:
节头表:
程序头:
5.4 hello的虚拟地址空间
使用edb加载hello:
Data Dump:
起始地址为0x401000在elf中可以看到对应:init
5.5 链接的重定位过程分析
我们可以看到通过反汇编后的汇编代码中多了很多函数的指令
如:puts,getchar,atoi,exit,sleep,printf等,根本原因是在编译的时候动态链接将系统中其他的标准库函数引入。
同时hello中无hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。对于hello.o的反汇编代码,函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。
5.6 hello的执行流程
执行进程:
[1] ld-2.27.so!_dl_start 0x7fce 8cc38ea0
[2] ld-2.27.so!_dl_init 0x7fce 8cc47630
[3] hello!_start 0x400500
[4] libc-2.27.so!libc_start_main 0x7fce 8c867ab0
[5] -libc-2.27.so!__cxa_atexit 0x7fce 8c889430
[6] -libc-2.27.so!__libc_csu_init 0x4005c0
[7] hello!_init 0x400488
[8] libc-2.27.so!_setjmp 0x7fce 8c884c10
[9] -libc-2.27.so!_sigsetjmp 0x7fce 8c884b70
[10] libc-2.27.so!__sigjmp_save 0x7fce 8c884bd0
[11] hello!main 0x400532
[12] hello!puts@plt 0x4004b0
[13] hello!exit@plt 0x4004e0
[14] *hello!printf@plt –
[15] *hello!sleep@plt –
[16] *hello!getchar@plt –
[17] ld-2.27.so!_dl_runtime_resolve_xsave 0x7fce 8cc4e680
[18] -ld-2.27.so!_dl_fixup 0x7fce 8cc46df0
[19]-ld-2.27.so!_dl_lookup_symbol_x 0x7fce 8cc420b0
[20] libc-2.27.so!exit 0x7fce 8c88912
5.7 Hello的动态链接分析
我们可以看到动态链接之前0x401000地址存的是INIT的虚拟地址,而动态链接后变成了实际地址。
5.8 本章小结
本章主要介绍了链接器在程序运行中的功能。连接器即编译、链接为一体,有效地减小了程序员的工作量。便于维护代码。同时,动态链接节省了大量内存,通过库函数能够更好的帮助程序员完成程序的模块化处理。
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:
进程是指计算机中已运行的程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。进程是程序真正运行的实例。
进程的作用:
现代计算机中给每个进程一个独立的PID,这使得程序员可以更好的调度某个正在运行程序的资源和数据。同时,每个程序独占一个进程可以更好的保护程序内部资源。CPU的一个核只能处理一个进程,这使得计算机的硬件资源能够得到更好的使用。进程为程序提供了两个抽象:逻辑控制流和私有地址空间。逻辑控制流使得每个进程都好像独立占用cpu,而私有地址使得每个程序好像独立占有系统内存。
6.2 简述壳Shell-bash的作用与处理流程
作用:
Shell,在计算机科学中俗称“壳”。在linux中,shell是一个命令解释器,能够读取用户从键盘发送的指令,并完成识别,将对应的系统信号发送给进程。是一种人机交互接口。
处理流程:
shell处理可以直接从Linux终端界面接收指令,也可以从shell脚本文件接收指令。
终端下:
shell首先读取命令行,将命令行的指令规范化,转换为shell内部函数能够识别的指令,然后将对应指令发送给对应函数,如execve函数接收对应文件名称,命令行,环境变量。或者shell内部直接解析信号类指令,转换为系统级信号,如quit,Ctrl+C,Ctrl+V等键盘指令。最后返回执行结果。
脚本下:
shell脚本就是将命令写入文本中,文本可以被执行。
脚本:本质是一个文件,文件里面存放的是 特定格式的指令,系统可以使用脚本解析器 翻译或解析 指令 并执行(它不需要编译)shell 既是应用程序,又是一种脚本语言(应用程序 解析 脚本语言)。
6.3 Hello的fork进程创建过程
首先,系统级父进程init调用fork()函数能够创建子进程,子进程能够共享父进程的文件资源,在init中由于init是整个系统进程,所以可以看成创建的子进程为单独一个进程,且占用整个内存。
Fork()会返回两个值,在父进程中返回子进程pid,在子进程中返回0。
6.4 Hello的execve过程
在C语言中,调用一个新的程序需要使用execve函数,execve读取整个程序的所有数据并在当前进程中执行。在调用hello程序时,可以看成bash程序调用了execve(./hello,**argv,**envp);envp是环境变量。调用程序时会设置一个用户栈,用户栈的大致结构如下,从栈底到栈顶:
栈底的是参数和环境字符串,往上是null结尾的指针数组,指向的是环境变量字符串,全局变量environ指向这些指针中的第一个envp[0]。环境变量指针数组后的是argv数组,指向的是参数字符串。栈的顶部是系统启动函数libc_start_main的栈帧,之后便是为main函数分配的栈帧了。
6.5 Hello的进程执行
上下文信息:
上下文就是内核重启一个被抢占的进程所需的状态。由通用寄存器,浮点寄存器,程序计数器,用户栈,状态寄存器,内核栈和各种内核数据结构组成。
其作用是能让逻辑控制流中被暂停的进程能够重新读取之前的状态,从而好像一直都在运行。
进程时间片:
进程时间片操作系统分配给每个进程在CPU上的处理时间。由于我们知道单核CPU某一时刻只能执行一个进程,所以想要多个进程同时进行就需要将每个进程划分为多个时间片,每次只执行某个时间片,且中间挂起的时间很短,这样就能近似地处理为所有进程同时运行的一个状态。
进程调度:
上面我们知道多个进程同时进行时是需要进程调度的,如某些程序挂起,某些进程开始执行。
用户态和内核态:
用户态顾名思义就是用户能够进行操作的系统状态,内核态指的就是由计算机系统内核进行操作的一个状态。hello程序最开始运行是在用户态下,此时用户可以通过键盘向程序发送命令,一般来说,只有在异常处理和信号处理的时候才会切换到内核态,如异常处理子程序和上下文切换。
6.6 hello的异常与信号处理
异常的种类:
中断,陷阱,故障,终止
发送的信号:
SIGINT(终止),SIGTSTP(停止直到下一个SIGCONT),SIGCONT(继续某个停止的进程),SIGCHILD(进程停止或终止)
如图,当我们运行hello之后,键盘输入Ctrl+z会向进程发送一个SIGTSTP信号,从而让当前子进程停止。系统调用重新开始bash进程。我们调用bash中的jobs指令可以查看当前进程,也可以在bash中调用ps查看,均可以看见有一个停止的进程
我们通过键盘输入fg指令,可以向被暂停的子进程发送一个SIGCONT指令使其重新开始运行。
最后键入Ctrl+C向进程发送SIGINT指令终止整个子进程,由其父进程完成回收。
6.7本章小结
本章主要简单介绍了hello进程在运行中的动态过程。首先是介绍了Linux端下系统使用shell-bash来解读键盘指令从而对系统进行操作的一个过程。其次介绍了主进程init如何通过fork和execve函数调用生成一个子进程的过程。并且介绍了进程在计算机中处理的一些内容。最后介绍了进程中的信号处理,介绍了异常控制流在进程中的作用。
第7章 hello的存储管理
7.1 hello的存储器地址空间
hello程序经过编译链接后最后以二进制数据文件的格式存储在计算机的硬盘里(也可能是SSB或者ROM),当系统运行hello时才需要从磁盘中读出,并且将对应数据映射到内存里。
映射到内存的程序的所有数据在内存中都占一块空间,为了方便系统查找调用,我们给每个空间都赋予了一个独立的地址。
地址表示类型:
逻辑地址:逻辑地址一般用于linux中汇编语言使用,其表达主要是通过段地址和偏移量完成。在代码指令中所有的地址表示都是段地址,每段基址都一样,在正真运行的时候会加上各自的偏移量生成新的地址,CPU读取新地址并在内存中请求空间。
物理地址:物理地址直接将内存分为对应的块,每块就是实际的地址空间,在内存中映射的新程序使用的都是物理地址,寄存器传的也是物理地址。
线性地址:逻辑地址经过段机制化得到的新地址,一般用于页表处理和虚拟地址存储中,方便寻址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式管理指将一串地址分成多端,每一段都对应一个单独的索引。首先逻辑地址会加上偏移量,得到一个新的线性地址,线性地址中每一段都对应不同的索引,如在RAM访问中,我们可以看到地址被分为三段,在寻址中分别对应不同的标志,从而能够找到地址对应的数据。
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理被用到虚拟内存中,可以大大减少额外内存的开销,加快数据访问速度。在页式管理中,地址对应同样被分为多段,不过相较于直接的段式管理,页式管理多了一个数据到页表的映射,数据的虚拟地址会与页表中的基址相加形成新的段地址,再发送到内存中。页式管理由MMU进行操作,页表是一个目录,存放着每一页对应的数据的虚拟地址和物理地址偏移量,每一页都有自己唯一的页表条目(PTE)。
7.4 TLB与四级页表支持下的VA到PA的变换
TLB:翻译后备缓冲器。页表中的线性地址可以分为虚拟页号(VPN)和虚拟页偏移(VPO)两部分。然后VPN在查找过程中又可以被拆分为两部分,一部分是TLB标记(TLBT),另一部分是TLB索引(TLBI)。这两部分可以类似于cache查找的方式在TLB缓存中找到对应的物理页号,最后的物理页号(PPN)加上VPO得到的新地址就是最后生成的物理地址。
7.5 三级Cache支持下的物理内存访问
Cache中物理地址可以被分为三部分,一部分是CT(标记),标记对应cache中的组数;CI(索引),索引用来查找某组中对应的行号;最后是CO(偏移量),在对应行号中根据偏移量就能找到对应数据在cache中存放的位置。当有多级cache时,首先从L1开始查找,如果不命中将会访问L2,进行一个块的替换,如果还不命中就L3,直到访问内存。
7.6 hello进程fork时的内存映射
当用fork函数创建一个子进程时,子进程会得到一个与父进程一样的页表副本,数据栈,以及虚拟地址。
7.7 hello进程execve时的内存映射
execve调用hello时,需要为hello在对应地址开辟新的空间。我们知道在计算机中就是一个数据覆盖的过程。首先需要覆盖对应的用户区域,刷新数据栈,使用mmap函数为hello映射一块新的空间。
然后,将hello中的各个段映射到新的结构中,具体操作如图:
7.8 缺页故障与缺页中断处理
缺页故障与缺页中断都将调用缺页异常处理子程序进行处理。
通常情况下,缺页异常都是由系统在内存找不到对应地址造成的。MMU会访问所需地址的页表,检测其有效位,同时在内存中选择一个牺牲页,进行一个牺牲替换。从而将所需地址内容传入内存,然而存在一些特殊情况:
段错误:
虚拟地址中对应的所有区域结构都不对应,说明发生了段错误。
非法访问:
所需地址的数据不能被访问。
7.9动态存储分配管理
内存分配可以分为显式分配和隐式分配。其中显示分配由用户自定义操作,如调用malloc,new等指令,在系统中开辟一块所需的空间。
而隐式分配会自动检测某个块是否被使用来决定空间的开辟和回收,由程序自动完成。
其中显示分配在使用结束后会产生很多碎片化空间和垃圾,这时需要系统回收或重新处理。第一种方法是使用显式或隐式链表来记录空闲的空间,从而为下一次新的空间开辟提供对应的记录。第二个是将内存重新存入file.sys中再重新写入内存,使得碎片化的空间能够变成连续的大空间。
7.10本章小结
本章简单讲解了在调用hello程序时内存和系统所发生的操作,以及在Intel Core I7处理器下系统分配内存资源,空间管理,空间开辟和回收,以及各自高效寻址,数据搜索方式。进一步阐述了hello程序在计算机中的实质。
(此部分教学内容未涵盖,以下内容均为自学理解)
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
在Linux系统下,一切的IO传输均以文件的形式,或者均被看作为文件。Linux内核引出一个简单、低级的应用窗口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
打开文件:一个应用程序通过要求内核打开相应的文件,来宣告他想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,他在后续对此文件的所有操作中标识这个文件。内核记录所有有关这个打开文件的所有信息。应用程序只需要记住这个描述符。
Linux shell创建的每个进程开始时都有三个打开的文件,标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2),头文件<unistd.h>定义了常量STDIN_FILEND、STDOUT_FILENO、STDERR_FILENO。
改变当前的文件位置:通过执行seek操作,显示地改变当前文件位置k。
读写文件,关闭文件。
8.2 简述Unix IO接口及其函数
open函数:
int open(char* filename,int flags,mode_t mode);
Flags:指明了进程打算如何访问这个文件。
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:可读写
mode参数指定了新文件的访问权限
打开成功返回描述符,失败则返回-1。
read函数:
Ssize_t read(int fd,void* buf,size_t n);
读取成功则返回读取的字节数,若EOF则为0,若出错为-1。
wirte函数:
Ssize_t write(int fd,const void *buf,size_t n);
用RIO包读写:
8.3 printf的实现分析
Va_list arg=(va_list)((char*)(&fmt)+4);
Va_list是一个字符型指针,其中((char*)(&fmt)+4)表示....中的第一个参数
其中vsprintf的函数内容如下:
我们可以看到vsprintf的功能主要是识别fmt中的字符,识别&以及对应的数据替换类型,并且将新的字符串放入buf中,实现一个翻译过程。
在那之后,printf函数调用系统函数write将长度为i的字符串从缓冲区buf中输出,实现整个打印过程。
8.4 getchar的实现分析
Getchar()原型:int getchar(void)
由于getchar的返回值是一个int型,对应的左值通常却是一个char型的数据,可以知道getchar返回的其实是一个字符对应的ASCII码。在读取失败的时候会返回-1。
getchar与大多数I/O函数一样,其输入和输出也采用缓冲区存储。getchar在读取用户缓冲区时,会依次读取,指针后移一位。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ASCII码,直到接受到回车键才返回。
8.5本章小结
本章主要简单介绍了Linux下系统I/O的一些相关概念和系统级函数调用。介绍了Linux下一些接口的使用和一些读写函数。同时针对printf函数内部原理进行了分析,还对getchar进行了一定的回顾。
结论
hello所经历的过程:
文本文件:程序员在文本文件中写好hello的代码,得到最初的hello.c文件
预处理:系统展开宏定义,删除注释,引入头文件函数,生成新的hello文本代码文件。
编译:编译器将C语言文本文件转化为汇编语言文件,即hello.s
汇编:编译器进一步将汇编语言文件转化成机器语言文件,生成重定位条目,生成可重定位文件hello.o
链接:链接器进行符号解析,重定位将hello.o与库文件链接一起生成一个可执行文件hello
运行:父进程调用fork函数,提供一个新的进程,在进程中调用execve函数运行hello程序。
运行中:在运行中,CPU和操作系统负责处理hello程序中的数据变化和内存调用。同时hello程序可以接收各种信号,各个存储单元互相工作。
运行结束:hello运行结束或接收结束信号,程序终止。由其父进程或init进程将其回收,并且删除hello进程在内存中的记录,清理空间,整理碎片化空间。hello继续在磁盘中等待下一次调用。
对计算机系统的感悟:
学了计算机系统这门课,我对计算机结构与程序有了更进一步的认识,对于以前难以理解的编程细节也能够理解其中的含义。并且在编写程序中更容易找到程序错误的问题所在,大大提高了我的编程能力。同时,计算机系统还让我对电脑的使用有了更深的理解。我逐渐熟悉自己电脑的能力,知道怎么最高效利用电脑,也知道自己电脑还有很多几乎未被使用到的功能。同时学了计算机系统,我对操作系统有了进一步的了解,了解了系统对文件的读写,内存的调用,以及不同操作系统下利用shell对系统发送指令的功能。我还理解了程序生成的实质,了解了IDE的实质功能,能够脱离IDE进行程序编写与生成。
总之学了计算机系统,我对计算机科学有了更好的认识。
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.c | 原始代码文本文件 | |
hello.i | 预处理后的代码文本文件 | |
hello.s | 编译后的汇编语言文件代码 | |
hello.o | 汇编处理后的可重定位目标源文件 | |
hello | 可执行文件hello | |
hello.elf | 对hello.s执行readelf生成的elf文件 | |
hello.elf2 | 对可执行文件使用readelf生成的新的elf文件 | |
hello.txt | 对hello程序执行objdump后得到的反汇编文件 |
参考文献
- 《深入理解计算机系统》 Computer Systems A progammer’s Perspective Third Edition 机械工业出版社
- ELF节头表分析_astrotycoon的博客-CSDN博客
/https://blog.csdn.net/astrotycoon/article/details/42042969
https://blog.csdn.net/yaxuan88521/article/details/122298118
https://www.cnblogs.com/pianist/p/3315801.html