企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] ## 环境准备 ### 虚拟机 这里我使用的虚拟机是:[virtualbox](https://www.virtualbox.org/wiki/Downloads) ![](https://img.kancloud.cn/57/f7/57f79fbd0287f3540d67d23fba1e2848_1281x629.png) ### 镜像 镜像使用的[Ubuntu 20.04.2.0 LTS](http://releases.ubuntu.com/20.04/) ![](https://img.kancloud.cn/33/2a/332a6f0c3d68e438bf965347ae8322f4_1284x687.png) 关于virtualbox环境设置没什么说的,网络记得启用并且使用桥接模式即可 ![](https://img.kancloud.cn/c6/11/c611bdec9462dbcc4d29bf3a8cfa50c2_863x569.png) ### 安装gcc,vim,git 这个就很简单了三条命令 `sudo apt-get update` `sudo apt-get install gcc` `sudo apt-get install vim` `sudo apt-get install git` ## 开始编码 ### HelloWord 还是从C语言最开始的HelloWorld开始 ```c #include "stdio.h" int main(int argc, char const *argv[]) { printf("Hello World!\n"); return 0; } ``` ![](https://img.kancloud.cn/30/dc/30dcbfb84151099de6faf38527000ddb_414x162.png) ### 程序编码过程 使用gcc编译这段代码 `gcc HelloWorld.c -o HelloWorld` 然后执行发现执行成功 ![](https://img.kancloud.cn/03/f9/03f916cdf95c31609129f350c1e7b9ea_690x156.png) gcc -o 只是完成编译工作的驱动程序,它会根据编译流程分别调用**预处理程序**、**编译程序**、**汇编程序**、**链接程序**来完成具体工作。 接下来对比着图片按照每一个步骤编译一下这个程序来提升一下对这个过程的理解: ![](https://img.kancloud.cn/ac/85/ac8576000382550178084aeab2022886_3015x2410.png) #### 源文件生成预处理文件,加入头文件,替换宏。 `gcc -E HelloWorld.c -o HelloWorld.i ` #### 预处理文件生成编译文件 `gcc -S HelloWorld.i -o HelloWorld.s ` #### 编译文件生成汇编文件: `gcc -c HelloWorld.s -o HelloWorld.o ` #### 汇编文件生成可执行文件: `gcc HelloWorld.o -o HelloWorld ` #### 源文件生成可执行文件: `gcc HelloWorld.c -o HelloWorld ` #### Linux系统运行可执行文件: `./HelloWorld` ### 程序装载执行 在讲到这里的时候彭东老师提到了图灵机和冯诺依曼,这里我把中专栏的图片贴出来,我感觉这两个概念只要理解即可,所以不做深入学习,理解即可,他们只是计算机形成过程中的几个基础理论或者说必要条件。![](https://img.kancloud.cn/88/07/8807afe9e6991dff6e0caee41a26488a_1386x1026.png) ### 更形象地将 HelloWorld 程序装入原型计算机 #### 反汇编特定指令机器码 使用**objdump**从objfile中反汇编那些特定指令机器码的section `objdump -d HelloWorld` ![](https://img.kancloud.cn/41/15/41155454015deb996b0791c9e7a8dcad_701x287.png) 第一列为地址; 第二列为十六进制,表示真正装入机器中的代码数据; 第三列是对应的汇编代码; 第四列是相关代码的注释。这是 x86_64 体系的代码,由此可以看出 x86 CPU 是变长指令集。 #### 将代码装入上面图灵机+冯诺依曼体系结构 ![](https://img.kancloud.cn/27/f1/27f11e2facb7088a65434606e2f07656_3810x1815.png) #### 以上知识涉及到知识盲区查阅的资料 ##### 汇编中的栈帧理解 ###### 基本概念 引用百度百科的介绍:C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。栈帧也叫过程[活动记录](https://baike.baidu.com/item/%E6%B4%BB%E5%8A%A8%E8%AE%B0%E5%BD%95),是[编译器](https://baike.baidu.com/item/%E7%BC%96%E8%AF%91%E5%99%A8)用来实现过程/[函数调用](https://baike.baidu.com/item/%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8)的一种数据结构。可以理解为:栈帧就是存储在用户栈上的(当然内核栈同样适用)每一次函数调用涉及的相关信息的记录单元。 ###### 分析栈帧的记录活动 简单用c写个函数调用的demo ![](https://img.kancloud.cn/4a/56/4a565f2c1b4fbad02e9e5e77330a2334_382x251.png) 使用objdump命令反汇编看一下 ![](https://img.kancloud.cn/47/4a/474a822dded52293e79cf10152094132_859x490.png) > x86\_64通用寄存器 > %rax 通常用于存储函数调用的返回结果,同时也用于乘法和除法指令中。 > %rsp 是堆栈指针寄存器,通常会指向栈顶位置 > %rbp 是栈帧指针寄存器,用于标识当前栈帧的起始位置 > %rdi,%rsi,%rdx,%rcx,%r8,%r9 用来传递函数参数,依次对应第1参数,第2参数至第6参数 > %rbx,%r12,%r13,%14,%15 ,%r10,%r11 用作数据存储,属于通用性更为广泛的寄存器,编译器或汇编程序可以根据需要存储任何数据。 ![](https://img.kancloud.cn/24/ab/24ab569ffffbf3a5861a51ac29e4304a_1695x648.png) ## 课后思考题 为了实现 C 语言中函数的调用和返回功能,CPU 实现了函数调用和返回指令,即上图汇编代码中的“call”,“ret”指令,请你思考一下:call 和 ret 指令在逻辑上执行的操作是怎样的呢? 经过上面的代码分析,已经可以解决这个问题了,下面附一些精彩留言以供参考。 > 针对第一个问题,在gcc编译完成之后,函数对应的指令序列所在的位置就已经确定了,因此这是编译阶段需要考虑的问题 。 > 至于第二个问题,在执行完call指令的同时,需要将call指令下面一条指令的地址保存到栈内存中,同时更新%rsp寄存器指向的位置,然后就可以开始执行被调函数的指令序列,执行完毕后,由ret指令从rsp中获取栈顶的returnadress地址,然后跳转到call的下一条指令继续执行 >call和ret其实是一对相反指令,调用call时会将当前IP入栈,即push IP,然后执行跳转即jmp,而ret也是将栈中的IP推出写入IP寄存器,即pop IP。