Under the C, under the C Don’t you know it’s better Dealing with registers And assembly? -Sebastian, probably
在计算机早期编译器发明之前,许多程序员都使用汇编语言编写代码,这种语言直接指定计算机在执行过程中遵循的一组指令。汇编语言是程序员最接近机器级编码的语言,无需直接用 1 和 0 编写代码,是一种可读的机器代码形式。要编写高效的汇编代码,程序员必须深入了解底层机器架构的操作。
编译器的发明从根本上改变了程序员编写代码的方式。编译器将人类可读的编程语言(通常使用英语单词编写)转换为计算机可以理解的语言(即机器代码)。编译器使用编程语言的规则、操作系统的规范和机器的指令集将人类可读的代码转换为机器代码,并在过程中提供一些错误检测和类型检查。大多数现代编译器生成的汇编代码与过去的手写汇编代码一样高效。
学习汇编的好处
考虑到编译器的所有好处,学习汇编语言的好处可能并不明显。然而,学习和理解汇编语言代码有几个令人信服的理由。以下是几个例子。
1. 更高层次的抽象隐藏了有价值的程序细节
高级编程语言提供的抽象有利于降低编程的复杂性。同时,这种简化使程序员很容易做出设计决策,而无需完全了解他们的选择在机器层面上的影响。缺乏汇编知识通常会阻碍程序员理解程序如何运行的宝贵信息,并限制他们理解代码实际作用的能力。
例如,看一下下面的程序:
#include <stdio.h>
int adder() {
int a;
return a + 2;
}
int assign() {
int y = 40;
return y;
}
int main(void) {
int x;
assign();
x = adder();
printf("x is: %d\n", x);
return 0;
}
程序的输出是什么?乍一看,该assign
函数似乎没有效果,因为它的返回值未存储在 中的任何变量中main
。该adder
函数返回 的值a + 2
,尽管变量a
未初始化(尽管在某些机器上编译器会将其初始化a
为 0)。打印出来的x
结果应该是未定义的值。但是,在大多数 64 位机器上编译和运行它始终会得到 的答案42
:
$ gcc -o example example.c
$ ./example
x is: 42
乍一看,这个程序的输出似乎毫无意义,因为adder
和 assign
函数似乎断开了连接。了解堆栈框架以及函数在后台的执行方式将有助于您理解为什么答案是42
。我们将在接下来的章节中重新讨论这个例子。
2. 有些计算系统资源太有限,不适合编译器
最常见的“计算机”类型是我们无法轻易识别为计算机的那些。这些设备无处不在,从汽车和咖啡机到洗衣机和智能手表。传感器、微控制器和其他嵌入式处理器在我们的生活中扮演着越来越重要的角色,并且都需要软件才能运行。然而,这些设备中包含的处理器通常非常小,以至于它们无法执行由高级编程语言编写的编译代码。在许多情况下,这些设备需要独立的汇编程序,这些程序不依赖于常见编程语言所需的运行时库。
3. 漏洞分析
一部分安全专业人员整天都在尝试识别各种计算机系统中的漏洞。攻击程序的许多途径都涉及程序存储其运行时信息的方式。学习汇编语言可使安全专业人员了解漏洞是如何产生的以及如何利用漏洞。
其他安全专家则花时间对恶意软件和其他恶意软件中的恶意代码进行“逆向工程”。掌握汇编语言的应用知识对于这些软件工程师快速制定对策以保护系统免受攻击至关重要。最后,不了解自己编写的代码如何转换为汇编语言的开发人员可能会在不知情的情况下编写出易受攻击的代码。
4. 系统级软件中的关键代码序列
最后,计算机系统中有些组件无法通过编译器进行充分优化,需要手写汇编代码。在某些系统级别,在对性能至关重要的机器特定优化方面,有手写汇编代码。例如,所有计算机上的启动序列都是用汇编代码编写的。操作系统通常包含用于线程或进程上下文切换的手写汇编代码。对于这些简短且性能至关重要的序列,人类通常能够比编译器生成更优化的汇编代码。
您将在接下来的章节中学到什么
接下来的三章介绍了三种不同的汇编语言。 第 7 章和第 8 章介绍了 x86_64及其 早期版本 IA32。 第 9 章介绍了 ARMv8-A 汇编语言,这是大多数现代 ARM 设备(包括 Raspberry Pi 等单板计算机)上的 ISA。 第 10 章包含总结和学习汇编语言的一些关键要点。
这些不同类型的汇编语言都实现了不同的指令集架构 (ISA)。回想一下, ISA 定义了一组指令及其二进制编码、一组 CPU 寄存器以及执行指令对 CPU 和内存状态的影响。
在接下来的三章中,您将看到所有 ISA 的一般相似之处,包括 CPU 寄存器用作许多指令的操作数,并且每个 ISA 都提供类似类型的指令:
- 用于计算算术和逻辑运算的指令,例如加法或按位与
- 用于实现分支(例如 if-else、循环以及函数调用和返回)的控制流指令
- 用于在 CPU 寄存器和内存之间加载和存储值的数据移动指令
- 用于从堆栈中推送和弹出值的指令。这些指令用于实现执行调用堆栈,其中在函数调用时将新的堆栈内存框架(存储正在运行的函数的局部变量和参数)添加到堆栈顶部,并在函数返回时从堆栈顶部删除一个框架。
C 编译器将 C 源代码转换为特定的 ISA 指令集。编译器将 C 语句(包括循环、if
- else
、函数调用和变量访问)转换为由 ISA 定义并由旨在执行特定 ISA 指令的 CPU 实现的一组特定指令。例如,编译器将 C 转换为 x86 指令以在 Intel x86 处理器上执行,或将 C 转换为 ARM 指令以在 ARM 处理器上执行。
当您阅读本书汇编部分的章节时,您可能会注意到一些关键术语被重新定义,一些图表被重现。为了更好地帮助其他 CS 教育者,我们将每一章设计为在特定的学院和大学独立使用。虽然每章中的大部分材料都是独一无二的,但我们希望各章之间的共同点有助于加强读者心中不同汇编风格之间的相似性
准备好学习汇编了吗?让我们开始吧!点击下面的链接访问您感兴趣的章节: