摘 要
本文介绍了 Hello 程序的生命周期。本文通过对Hello在Linux下的预处理、编译、汇编、链接等进程的分析,详细讲解了一个程序从诞生到执行再到消亡的典型过程。虽然程序执行的过程在程序员眼中只是屏幕上显示的一根字符串,但在短短的几毫秒内,程序经历了预处理、编译、汇编、链接、进程管理、IO管理、内存分配和回收等一系列复杂的过程。同时,这本书的知识也在这篇文章中进行了梳理,整本书的内容与Hello的生活相协调。回顾您在计算机系统中学到的知识,加深您的印象,并提高您对程序过程和计算机内部结构的理解。
第1章 概述
1.1 Hello简介
P2P:
P2P指的是程序由一个项目变成一个进程的过程.
- Program:Hello程序的诞生是程序员通过键盘输入得到hello.c
2.Process:C语言源程序hello.c在预处理器(cpp)处理下,得到hello.i,通过编译器(ccl),得到汇编程序hello.s,再通过汇编器(as),得到可重定位的目标程序hello.o,最后通过链接器(ld)得到可执行的目标程序hello。在shell中键入运行命令后,shell调用fork函数为其创建子进程。
020:
020为程序“从无到有再到无”的过程。程序经过系统OS,shell为hello进程execve,映射虚拟内存,进入程序入口后程序开始载入物理内存。进入 main 函数执行目标代码,CPU为执行文件hello分配时间周期,执行逻辑控制流,每条指令在流水线上取值、译码、执行、访存、写回、更新PC。当程序运行结束后, shell 父进程负责回收 hello 进程,内核删除相关数据结构。Hello程序从无到有再到无的这一过程就是020。
1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk
软件环境:Windows10 64位;VirtualBox/Vmware 15以上;Ubuntu 16.04;
开发工具:CodeBlocks 64位;vi/vim/gedit+gcc
1.3 中间结果
hello.c :hello程序的C语言代码
hello.i :hello.c经过预处理后产生的文本文件
hello.s :hello.i经过编译产生的汇编文件
hello.ld :链接后产生的编译文件
hello.o :可重定位的目标文件
hello :可执行文件
helloo.objdump :hello.o反汇编文件
hello.objdump :hello的反汇编文件
1.4 本章小结
本部分对hello从诞生到执行到消亡的P2P和020过程进行了简介,并介绍了整个过程中所使用的环境工具及生成的中间结果。
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:预处理是C语言的一个重要功能,由预处理器完成,预处理器将源文件.c预处理为.i文件,主要处理#开始的预编译指令
预处理的作用:合理利用预处理功能编写程序,便于读取、修改、调试,有利于模块化设计。
2.2在Ubuntu下预处理的命令
预处理指令:gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
由图可知,经过预处理后,hello.c的23行代码被扩展为3084行,其中main函数占第3071~3084行,main函数前为hello.c引用的stdio.h等头文件的内容。同时,注释内容被去除。
2.4 本章小结
本章主要介绍预处理的概念和功能,包括预处理的宏定义、文件包含、条件编译等,并对hello.c预处理获得的hello.i文件进行比较和分析。
第3章 编译
3.1 编译的概念与作用
概念:编译器(ccl)将预处理生成的文件hello.i编译成汇编文件文件hello.s。
作用:将高级语言编译为相应的机器语言,这里将C语言转化为intel x86汇编指令。
3.2 在Ubuntu下编译的命令
指令:gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
3.3.1数据
1.字符串
1)"\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"对应.c文件中"用法: Hello 学号 姓名 秒数!\n",其中中文已被编码为UTF-8 格式 一个汉字占3个字节
2) “Hello %s %s\n” 对应原c文件中"Hello %s %s\n"第二个printf中的格式化参数,其中后两个字符串已在.rodata中声明
2.整数
1)int i(局部变量)
根据movl $0, -4(%rbp) 可以看出编译器将i存到了-4(%rbp) 中,且占4个字节。与addl $1, -4(%rbp)和cmpl $7, -4(%rbp)实现循环结构for(i=0;i<8;i++)。
2)int argc
作为第一个参数被压栈pushq %rbp,传入main函数
3)立即数
程序中其他整型都是以立即数的形式出现
4)数组 char *argv[ ]
这是一个指针数组,由addq $, %rax可以看出,一个内容占8个字节,说明linux中一个地址的大小是8个字节。
3.3.2赋值
1) i=0
movl $0, -4(%rbp) 通过movl指令将0赋给了i
3.3.3算术操作
1)i++ addl $1, -4(%rbp)
3.3.4 关系判断
1)argc!=4
2)i<8
3.3.5控制转移
1)if(argc!=4)
2)for(i=0;i<8;i++)
3.3.6数组操作
argv[1],argv[2]
3.3.7函数调用
函数的调用需要有以下过程:传递控制、数据传递、分配空间
1)main函数
main 函数由系统启动的函数__libc_start_main调用,call 指令将 main 函数的地址分配给 %rip,后跟 main 函数。主函数 argc *argv[](第一个地址)的两个参数分别存储在 %rdi 和 %rsi 中。通常,该函数将返回 0 作为出口,并将 %eax 设置为 0。
2)printf函数
当第一次调用 printf 时,字符串是固定的,只有一个参数,因此调用put@PLT。第二个调用具有三个参数,调用printf@PLT
3.4 本章小结
本章总结并分析编译器如何处理C语言的各种数据类型和运算,如变量处理、赋值、算术运算、关系判断和函数调用等。
第4章 汇编
4.1 汇编的概念与作用
概念:驱动程序运行汇编程序 as,将汇编语言 (hello.s) 转换为机器语言 (hello.o) 的过程称为汇编,它是一个包含程序指令代码的二进制文件。
作用:将高级语言翻译成机器可以直接识别和执行的二进制机器代码。此二进制机器代码是此机器上程序的机器语言的表示形式。
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
ELF头:包含信息为文件结构的说明信息:16字节的标识信息,文件类型,机器类型,节头表偏移,节头表的表项大小,表项个数,生成该文件的系统字大小和字节顺序
节头部表:
节头表描述了不同节的位置和大小,目标文件中的1节对每个节都有一个固定大小的条目,相关信息包括节的名称、类型、地址、偏移量、对齐方式、标志标记等。
由输出可知,Hello程序共有14个节。
重定位节:
重定位用于在汇编程序生成目标模块时为最终位置未知的目标参照生成重定位条目。链接器在链接生成可执行文件时相应地修改此引用。
Rela.text和.rela.eh_frame中包含.text节中需要进行重定位的信息,在链接时需要修改这些信息的位置。
该程序重定位有R_X86_64_PC32,R_X86_64_PLT32两种基本类型,分别用于重定位使用32bitPC相对地址、绝对地址的引用
4.4 Hello.o的结果解析
运行 objdump -d -r hello.o 获取 hello.o 的反汇编代码。由于反汇编代码是由机器语言翻译形成的,因此在跳转时,地址通常表示为相对地址。同时,操作数通常以十六进制表示。
4.5 本章小结
本章介绍Hello程序汇编过程,并展示ELF格式,最后分析汇编代码和反汇编代码,比较两者之间的差异,了解两种不同的程序表示形式和组装和反汇编的原因,揭示从汇编语言到机器语言的过渡过程。
第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的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
Hello的ELF头:
与hello.o的ELF头相比,有以下几处不同:
1. 文件类型从可重定位更改为可执行
2. 程序的入口点、程序开始点、节头表偏移发生改变
3. 共有27个节头表,增加了10个。
节头部表
程序头:
段节:
目录
第1章 概述
1.1 Hello简介
1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk
1.3 中间结果
1.4 本章小结
第2章 预处理
2.1 预处理的概念与作用
2.2在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
2.4 本章小结
第3章 编译
3.1 编译的概念与作用
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.4 本章小结
第4章 汇编
4.1 汇编的概念与作用
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
4.4 Hello.o的结果解析
编辑
4.5 本章小结
第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的格式
5.5 链接的重定位过程分析
5.7 Hello的动态链接分析
5.8 本章小结
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是操作系统对正在运行的程序的抽象。
6.2 简述壳Shell-bash的作用与处理流程
shell 的作用:提供一个接口,用户可以通过该接口访问操作系统内核的服务。
6.3 Hello的fork进程创建过程
执行 hello 后,bash 解析此命令,发现 ./hello 不是 bash 内置命令,因此它会尝试在当前目录中查找并执行 hello 文件。此时,bash 调用 fork 函数来创建一个子进程,该子进程类似于父进程,并获取父进程的用户级虚拟空间的相同且独立副本的副本 - 包括数据段、代码、共享库、堆和用户堆栈。二者间的PID不相同,fork函数会返回两次,在父进程中,返回子进程的PID,在子进程中,返回0。
6.4 Hello的execve过程
execve函数的原型为:
6.5 Hello的进程执行
时间片:指进程执行部分控制流的每个时间段,多任务处理也称为时间分片。
6.6 hello的异常与信号处理
6.7本章小结
结论
Hello程序的一生:
附件
参考文献
文章来源地址https://www.toymoban.com/news/detail-423055.html
重定位节
5.4 hello的虚拟地址空间
使用 edb 加载 hello 以获取进程的虚拟地址空间的每个段的信息。它对应于反汇编代码中的虚拟地址。
Hello 头表在程序执行期间用于告诉链接器在运行时应加载的内容,并提供动态链接信息,提供有关虚拟和物理地址空间中每个段的大小、位置、标志、访问权限和对齐方式的信息。
5.5 链接的重定位过程分析
运行objdump -d -r hello,得到上图所示反汇编代码
链接包括以下4个过程:
重定位的步骤:
1.合并相同的部分:链接器首先将同一类型的所有部分合并到相同类型的新部分,例如,所有文件的 .data 部分合并到一个新的 .data 部分,合并完成后,新部分是可执行文件 hello 的 .data 部分。2.确定地址:然后,链接器将内存地址分配给新的聚合部分以及输入模块定义的部分和符号。确定地址后,全局变量、指令等都具有唯一的运行时地址
- 判断输入文件是否为库文件,如果不是则是目标文件f,目标文件放入集合E中。
- 链接器解析目标文件中的符号,如果它们出现,则将它们放在集合 U 中,如果它们看起来已定义但未使用,则将它们设置为集合 D 中
- 链接器读入crt*库1中的目标文件
- 接入动态链接库libc.so
5.6 hello的执行流程
使用edb执行hello,如左图所示,加载hello到_start,到call main,以及程序终止的过程如右图所示
5.7 Hello的动态链接分析
init前的Data Dump
init后的Data Dump
在 edb 中查找0x403ff0地址,并在_init之前和之后设置断点,如图所示。在dl_init前后,0x403ff0 处和0x404000 处的8bit数据分别由000000000000变为了c05fb1b3fe7e和90b1d2b3fe7e,GOT[1]指向重定位表,作用是确定调用函数的地址,GOT[2]指向动态链接器ld-linux.so运行时地址。
5.8 本章小结
本章检查并介绍了 Linux 系统下链接的进程。链接是将程序转换为可执行文件的最后一步。通过链接,代码片段和数据片段被整合。本章通过查看 hello 在 edb 或终端上的虚拟地址空间,比较 hello.o 和 hello 的反汇编代码等,来分析和总结重定位、执行和动态链接过程。
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是操作系统对正在运行的程序的抽象。
作用:流程为应用程序提供关键抽象;一个独立的逻辑控制流,就好像一个程序拥有处理器一样;和一个专用地址空间,就好像程序垄断了一个内存系统
6.2 简述壳Shell-bash的作用与处理流程
shell 的作用:提供一个接口,用户可以通过该接口访问操作系统内核的服务。
处理流程:
1. 从终端读入输入的命令, 将输入字符串切分获得所有的参数。如果是内置命令则立即执行,否则调用相应的程序为其分配子进程并运行。
6.3 Hello的fork进程创建过程
执行 hello 后,bash 解析此命令,发现 ./hello 不是 bash 内置命令,因此它会尝试在当前目录中查找并执行 hello 文件。此时,bash 调用 fork 函数来创建一个子进程,该子进程类似于父进程,并获取父进程的用户级虚拟空间的相同且独立副本的副本 - 包括数据段、代码、共享库、堆和用户堆栈。二者间的PID不相同,fork函数会返回两次,在父进程中,返回子进程的PID,在子进程中,返回0。
6.4 Hello的execve过程
execve函数的原型为:
int execve(const char *filename,const charargv[],const char envp[])
execve() 用于执行由参数 fixtureme 字符串表示的文件路径,第二个参数使用指针数组传递给执行文件,并且需要以空指针结尾。最后一个参数是传递给可执行文件的新环境变量数组,其中每个指针都指向一个环境变量字符串,每个字符串都是名称的名称-值对,例如 name=value。当 exeve 加载文件名时,它会调用启动代码(用于设置堆栈),并将控制权传递给新程序 main 的 main 函数 main。当找不到夹具等错误时,execve 将返回调用程序,这与返回两次的 fork 调用不同,execve 调用一次,永不返回。
execve开始执行hello:
删除已存在的用户区域。删除当前进程虚拟地址的“用户”部分中的现有区域结构。也就是说,删除在 shell 运行时之前已存在的区域结构。
映射专用区域。为 hello 的代码、数据、bss 和堆栈区域创建新的区域结构。所有这些新区域都是专用的、写入时复制的。代码和数据区域映射到 hello 文件中的 .text 和 .data 区域。请求 bss 区域二进制零,映射到一个匿名文件,其大小包含在 hello 中。堆栈和堆区域也针对初始长度为零的二进制零请求。
映射共享区域。Hello程序链接到共享对象,例如标准C库 1ibc.so,然后将这些对象动态链接到程序,然后映射到用户虚拟地址空间中的共享区域。
设置程序计数器 (PC)。最后,execve 将当前进程上下文中的程序计数器设置为指向代码区域的人口点
6.5 Hello的进程执行
时间片:指进程执行部分控制流的每个时间段,多任务处理也称为时间分片。
用户模式和内核模式:处理器通过使用控制寄存器的模式位实现限制应用程序可以执行的指令数和可以访问的地址空间范围的功能。此寄存器描述进程当前享有的权限,当设置了模式位时,进程在内核模式下运行,在内核模式下运行的进程可以执行指令集的任何指令,并且可以访问系统中的任何内存位置。
当模式位未设置时,进程在用户模式下运行,并且不允许用户模式下的进程执行特权指令,例如停止处理器,更改模式位,并且不允许进程直接引用地址空间中内核区域中的代码片段和数据,此时用户程序必须通过系统调用接口间接访问内核代码和数据。
控制流:从上电时间到断点位置计算的PC值序列,程序计数器称为控制流。
逻辑控制流:使用调试器单步执行程序时,您会看到一系列程序计数器 (PC) 值,这些值与程序的可执行对象文件中包含的指令唯一对应,或者包含在运行时动态链接到程序的共享对象中。此 PC 值序列称为逻辑控制流,或简称为逻辑流。也就是说,逻辑控制流是进程中的一系列 PC 值。
上下文:上下文是内核重新启动抢占进程所需的状态,它由对象的值组成,例如通用寄存器,浮点寄存器,程序计数器,用户堆栈,状态寄存器,内核堆栈和各种内核数据结构。
Hello的调度过程:
sleep 调度:当程序运行到 sleep 函数时,sleep 显式请求 hello 进程 sleep。等到睡眠时间 sleepsec(此处为 2 秒,而不是 2.5 秒)到达。从另一个进程切换到 hello 以继续执行。
正常调度:当 hello 运行一段时间后,虽然 hello 此时不请求暂停,但系统会切换到其他进程,在其他进程的执行结束后,hello 会再次调度并继续使用最后一个 PC 地址开始执行。调度的过程类似于休眠调度的过程。
6.6 hello的异常与信号处理
正常运行:
程序完成被正常回收。
2)输入ctrl+z
这时输入命令PS查看进程,
使用jobs指令查看,得到以下结果,故可知,此时,hello进程没有结束,而是被暂时挂起,PID为45856。
3)输入ctrl+c
输入jobs
文章来源:https://www.toymoban.com/news/detail-423055.html
输出为空,可以判断进程直接被终止,被回收.
4)输入kill
输入ps,
当输入kill后进程被杀死,此时再输入ps指令后发现当前无进程执行。
6.7本章小结
本章结合原理图介绍了流程的概念和功能,简要介绍了shell shell-bash的作用和处理,介绍了hello的分叉爆破创建过程和execve过程,并结合ps和job命令的输出,介绍了hello的流程执行以及异常和信号处理过程。
结论
Hello程序的一生:
- 预处理:gcc在hello .c中执行预处理命令,合并库,扩展宏
- 编译,将 hello.i 编译为程序集文件 hello.s
- 组装,将 hello.s 转换为可重定位的目标文件 hello.o
- 链接,将hello.o与可重定位的目标文件和动态链接库链接,成为可执行的目标程序hello
- 运行:在 shell 中输入 ./hello1170801219 杨金即可开始运行程序
- 创建子进程:shell 进程调用 fork 为其创建子进程,并分配 pid。
- 运行程序:子进程shell调用execve,execve调用启动加载程序,添加虚拟内存,进入程序入口,程序开始加载物理内存,然后进入main函数。
- 执行指令:CPU为其分配一个时间片,在一个时间片中,hello享受CPU资源,并依次执行自己的控制逻辑流
- 访问内存:MMU 通过页表将程序中使用的虚拟内存地址映射到物理地址。
- 动态请求内存:调用 malloc 以请求从堆到动态内存分配器的内存。
- 信号:在运行期间键入 ctr-ctr-z,分别调用 shell 的信号处理功能停止和挂起。
- 结束:shell 父进程回收子进程,内核删除为此进程创建的所有数据结构。
附件
hello.c: hello的C语言源程序
hello.i: ASCII码的中间文件(预处理器产生),用于分析预处理过程。
hello.s:用于分析已编译进程的 ASCII 汇编语言文件。
hello.o:可重定位的目标程序(汇编程序生成),用于分析装配过程。
hello:用于分析链接过程的可执行目标文件(由链接器生成)。
hello_o.txt:hello.o 的 objdump 反汇编文件,用于解析可重定位的目标文件 hello.o。
hello.txt: Hello 的 objdump 反汇编文件,用于解析可执行目标 hello.
参考文献
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.
到了这里,关于程序人生(CSAPP大作业)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!