摘 要
本文详细的介绍了程序从编译到最终执行的全过程,从原理上简明阐释了计算机程序的工作原理,概括了计算机执行程序的主要步骤,记录了程序分析的整个过程。对Linux系统下的程序执行从编译,链接,分配内存到系统IO等不同环节做出了具体分析。
关键词:程序;系统;原理;
第1章 概述
1.1 Hello简介
首先hello.c通过I/O设备如键盘等经过总线存入磁盘中。然后通过gcc对于hello.c进行预处理,通过预处理器cpp变成hello.i。再通过汇编器得到hello.s(汇编程序)。Hello.i通过汇编器as变成hello.o(可重定位目标程序),通过与其他可重定位目标程序的链接最终成为hello(可执行程序)。
在shell程序中,首先fork一个子进程,再通过execve函数加载hello进入内存, 加载器删除子进程现有的虚拟内存段,然后使用mmap函数创建新的内存区域,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零。PC跳转指向hello程序的首地址开始执行程序,其中对内存的访问都是通过VA到PA的地址翻译猜能实现的,在地址翻译的过程中可能需要TLB加速,多级页表的技术才能最终实现.CPU在执行hello程序时,可能出发系统中断,将控制权暂时转移到其他程序中.在运行过程中,hello会调用函数向屏幕输出,Hello在结束以后,shell会回收 hello产生的垃圾,避免产生僵尸进程.
1.2 环境与工具
1.2.1 硬件环境
X64 CPU;3.3GHz;8G RAM;128GSSD + 1T DISK;
1.2.2 软件环境
Windows10 64位;Vmware 14;Ubuntu 18.04 LTS 64位;
1.2.3 开发工具
Visual Code;gdb/edb/gcc;
1.3 中间结果
Rawasm.txt hello.o的反汇编结果,用来比较hello反汇编的区别。
Elf.txt 方便记录elf中的内容产生的文件。
Helloasm.txt hello的反汇编,查看最终程序的反汇编
1.4 本章小结
Hello.c大概是每个程序员的开始,但是初级接触时,却不知道它背后有这么多复杂的知识,hello.c引导我们学习计算机技术,它虽然简单,但是却不断指引人们去探索更复杂的技术。Hello的一生可能匆匆结束,但它的运行成功标志着每个人对计算机学习的正式开始。今天就让我们一起探索hello程序背后的细节,原来曾经认为简单程序其实有许多复杂的知识来支撑。
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:在编译之前对源代码进行的处理。 C语言的预处理主要有三个方面的内容:
1.宏定义,将所有的#define删除,并且展开所有的宏定义;
2. 文件包含;处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件。
3.条件编译,处理所有条件编译指令,如#if,#ifdef等;
4.删除所有的注释//和 /**/;
5. 加行号和文件标识,如#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息;
6.保留所有的#pragma编译器指令,因为编译器须要使用它们。
预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/41edab317b0616f98e752ffccbabef17.png)
Hello.i的文件比较长,在程序的最下边是hello.c的源码,不过少了预处理的部分,源代码预处理已经将需要的头文件载入到程序之前。
2.4 本章小结
Hello.c在预处理后,完成了对源码的预处理工作,载入了需要的文件,去掉了注释,并按照约定的宏定义将源码中需要的部分替换,但是对于程序的执行,这还只是第一步。
第3章 编译
3.1 编译的概念与作用
编译是利用编译程序从源语言编写的源程序产生目标程序的过程,是用编译程序产生目标程序的动作。 编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。 编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。
编译的作用是产生可执行程序的第一步,将预处理文件编译为汇编原始文件,以便进行接下来的工作。
3.2 在Ubuntu下编译的命令
gcc -S hello.i
3.3 Hello的编译结果解析
3.3.1数据
有变量int sleepsecs,编译器将其编译成
.type sleepsecs, @object
.size sleepsecs, 4
3.3.2赋值
编译器将这条赋值语句int sleepsecs=2.5; ,直接赋值2给sleepsecs。
sleepsecs:
.long 2
.section .rodata
.align 8
局部变量int i在定义是没有初始化,只是给它开辟了栈空间。i表示为
-4(%rbp)
3.3.3表达式
i++编译成如下,每次用立即数加到-4(%rbp)的位置,即i变量储存的位置上。
addl $1, -4(%rbp)
3.3.4控制转移
在程序执行前,需要判断argv的大小,argv由%edi传入,被储存在
movl %edi, -20(%rbp)
即-20(%rbp)中,将立即数$3与之比较
cmpl $3, -20(%rbp)
相等则继续执行以后程序
je .L2
否则打印错误并结束程序
3.3.5 关系操作
i<10在编译器中被写成了
cmpl $9, -4(%rbp)
jle .L4
比较-4(%rbp)与立即数$9的大小,若小于等于则执行.L4内容即继续循环。
3.3.6指针数组操作
printf函数里面的一系列对指针和对数组的操作编译器编译为
movq -32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
3.3.7函数调用
编译器将调用printf函数解释为
movq -32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
movl