什么是机器码?
机器码是对诸如操作数据、管理内存、读写在存储设备上的数据、通过网络通信等底层操作进行编码的字节序列。
什么是汇编码?
汇编码是机器码的文本表示,给出了程序中的各个指令。
什么是X86-64?
X86-64是一种机器语言,用于今天的台式电脑、笔记本电脑、大型数据中心、超级计算机等机器上的大部分处理器上。这种语言有很长的历史,开始于1978年的Intel公司的第一个16位的处理器,然后拓展到32位,最近的不部分都是64位。沿着这条线,为了更好的利用半导体技术、满足市场需要,添加了许多特征。虽然主要是由Intel公司驱动的,但是Intel的竞争对手AMD也作出了重要的贡献。导致现在的X86-64具有独特的功能设计,只有从历史的角度看才有意义。X86-64还具有向后兼容的能力,这种特性是不被现代编译器和操作系统使用的。
32位机器和64位机器
计算机工业最近已从32位机器迁移到了64位机器。32位机器只能利用大约4GB的随机访问内存。随着内存价格不断下降,计算需求和数据量不断增加,突破32位机器能访问的内存限制已变得经济上可行且技术上可期。现在的64位机器可使用多达256TB(字节),且很容易拓展到使用16EB(字节)。虽然很难想象有那么大内存的机器的样子,但是要记住:在32位机器很常见的20世纪70年代和80年代,4GB的内存似乎也是很大的内存。
计算机执行的是机器码。
基于编程语言的规则、目标机器的指令集、操作系统遵守的约定,编译器通过一系列的阶段来生成机器码。比如GCC的C编译器先生成汇编码,后调用汇编器和链接器来从汇编码中生成可执行机器码。
在这一章里,我们将仔细地研究机器码和汇编码。
当使用诸如C语言和Java等高级语言编程时,程序的机器级别详细实现是向程序员屏蔽的。而当使用汇编语言编程时,程序员必须指定用于执行一次计算所需的底层指令。
在多数情况下,在由高级语言提供的更高级的抽象上工作要更有效率和更可靠。由编译器提供的类型检查帮助检测许多编程错误,确保以一致的方式引用和操作数据。现代有优化功能的编译器生成的代码通常跟一个有经验的汇编程序员手写的代码效率一样。用高级语言编写的程序能被编译和在多台不同的机器上执行,而汇编码是高度机器相关的。
为什么要花费时间研究机器码?
- 即使编译器做了生成汇编码的大部分工作,但能阅读和理解机器码也是程序员的一项重要技能。
- 使用合适的命令行参数来调用编译器,编译器会生成一个汇编码形式的输出文件。通过阅读汇编码,我们能理解编译器的优化功能和分析潜在的低效率代码。
- 如我们将在第5章里体会到的那样,寻求最大化关键代码的性能的程序员通常会尝试对源代码的做不同的修改,每编译一次并检查汇编代码来了解程序的运行效率。
- 程序员学习汇编代码的需求随着时间的推移发生了变化,以前是要求能直接用汇编码编写程序,现在是能阅读和理解编译器生成的代码。
而且,由高级语言提供的抽象有时会隐藏程序的运行时信息。
- 当使用线程包来写并发程序时,理解不同线程如何共享程序数据和保持数据私有,怎样精确地访问共享数据是非常重要的。这些信息在机器码中是可见的。
- 程序遭受攻击的的许多方式都涉及程序存储运行时控制信息的细节。许多攻击都利用了系统程序中的弱点来覆盖信息,从而获取了系统的控制权。理解这些漏洞如何出现,以及如何防御攻击需要具备程序的机器码表示的知识。
在这一章里,我们将学习一种特定的汇编语言,了解C程序是如何被编译成这种形式的机器码的。
阅读由编译器生成的汇编码需要具备不同于手工编写汇编码不同的什么技能?
能理解经典编译器在将C结构转换成机器码时都做了哪些事?
相对于用C代码表示的计算,带优化功能的编译器可重排执行顺序、消除不必要的计算、用更快的操作替换较慢的操作、甚至能将递归计算编程迭代计算等。
理解源代码和汇编码之间的关系通常是一个挑战,有点像拼一个跟包装盒上的图片稍微不同的图。这是一个逆向工程,通过研究系统并反向推导系统是如何被创建的。在这里,前述的系统就是一个机器生成的汇编程序。这就简化了逆向工程的任务,因为生成的代码遵循相当规则的模式,且我们可以运行实验,让编译器为不同的程序生成汇编码。
在本章里,我们给出了许多示例,提供了许多练习来说明汇编语言和编译器的不同方面。在这里,掌握细节是理解更深入和更基础的概念的前提。
花时间来研究这些示例、做这些练习、测试你的答案跟参考答案是很重要的。
我们的展示是基于X86-64,将集中在被GCC和Linux使用的特征子集上。这就使得我们避免了X86-64的许多复杂性和奥秘版的特征。
我们的展示集中在编译C代码生成的机器码程序的类型上。因此,我们不会去介绍X86-64的许多支持早期微处理器编码的特征。
本章的主要内容
首先,介绍C语言、汇编码、机器码之间的关系。
然后,介绍X86-64的细节
- 从数据的表示和操作、控制的实现等开始。我们会看到在C语言中诸如
if
、while
、switch
语句等控制结构是如何实现的。 - 然后,我们介绍函数的实现,包括程序是如何维护一个运行时栈来支持函数间的数据和控制等信息的传递,以及局部变量的存储等。
接着,考虑诸如数组、结构体、联合体等数据结构在机器级别是如何实现的。有了这些机器级别编程的背景后,我们讨论内存访问越界、系统容易遭受缓冲区溢出攻击等问题。
最后,介绍使用GDB调试器来检查机器码程序的运行时行为,以及展示有关浮点数数据和操作的机器码程序。
网友评论