从高级语言到汇编
hello world
从你写出hello world的C程序,到shell打出hello world发生了什么?
现在有个hello.c的c文件
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}现在让我们分析这个程序的编写好以后,编译和执行过程:
One
每个步骤对应的 GCC 执行命令如下:
预处理(Preprocessing):
gcc -E hello.c -o hello.i这个命令将源文件
hello.c进行预处理并将结果保存为hello.i,在这个阶段会处理#include指令,将标准库头文件的内容插入到程序中。编译(Compilation):
gcc -S hello.i -o hello.s这个命令将预处理后的文件
hello.i编译成汇编代码并将结果保存为hello.s,在这个阶段将C代码转换成机器可以理解的低级指令。汇编(Assembly):
gcc -c hello.s -o hello.o这个命令将汇编代码文件
hello.s汇编成目标文件hello.o,这个目标文件包含了机器指令。链接(Linking):
gcc hello.o -o hello这个命令将目标文件
hello.o与任何所需的库一起链接,并生成一个可执行文件hello,这就是可以运行的程序。
这些命令将 hello.c 编译成一个名为 hello 的可执行文件。
Two
此时hello 已经是一个只有1和0的二进制文件
./hello可执行文件执行(Execution): 该进程的执行过程可以从以下几个方面来看:
加载(Loading): 操作系统负责将可执行文件加载到内存中,准备执行。
解释(Interpretation): 处理器(CPU)开始执行加载到内存中的指令。这些指令来自于汇编代码,它们被解释成底层的机器指令。
输出(Output): 当程序执行到
printf函数时,它将"Hello, World!\n"字符串写入标准输出流(通常是终端)。这就是为什么你会在Shell中看到"Hello, World!"的原因。
现在你可能看到上面的一大堆名词很头疼,但这都没有关系,因为你会在ICS的课程中慢慢学到相关的知识。而这么一大段只是告诉你C语言从编写到编译到执行有一系列复杂的过程,而并非在IDE里面点击运行就好了,这也是为什么要学会使用gcc。
著名的拆炸弹 (Bomb Lab)
在上面一小节产生的中间文件hello.s就是我们需要重点学习的汇编语言
现在让我们打开这个文件

现在你可能还无法完全理解里面全部的指令
但我们专注于这么几行
leaq L_.str(%rip), %rdi
callq _printf
L_.str: ## @.str
.asciz "Hello, World!\n"
L_.str:: 这是一个标签,用于标识字符串 "Hello, World!\n" 的位置,它只是一个相对位置的符号名称L_.str(%rip)是一个汇编语言中的地址引用,用于表示字符串 "Hello, World!\n" 在内存中绝对的位置leaq L_.str(%rip), %rdi: 将字符串 "Hello, World!\n" 的地址加载到寄存器%rdi中,以供后续的printf函数使用。callq _printf: 调用printf函数来打印字符串,这个汇编代码中的printf调用中,%rdi寄存器包含了指向格式化字符串 "Hello, World!\n" 的地址。printf函数会根据这个格式化字符串来打印相应的内容,即 "Hello, World!\n"。
OK,如果这几行也没有看懂也没有关系,在ICS的学习中,我们将一个个学习这里指令和寄存器。简单来说,这些指令的目的就是获取
"Hello, World!\n"在内存中的地址并打印出来
现在请假想一下,没有C的源代码
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}现在只有汇编语言的这些指令,
你是否能倒退推理出C的源代码的逻辑呢?
在这个汇编文件中
.asciz "Hello, World!\n",字符串的内容是给出的,假如没有这一行字符串,你是否能从汇编语言中找到字符串的地址,并通过调试工具打印出来呢?
这正是Bomb Lab要做的,当然实际的Lab还要复杂地多。
Last updated