有人还在使用汇编语言吗?
在我们计算机科学课程的核心计划中,我们提供两门汇编语言课程,作为我们序列中强调硬件的部分的要素。虽然学生确实学习使用这种神秘的语言进行编程,但重点是将汇编语言用作侦探工具来了解底层硬件。
这两门课程都涉及无处不在的 Intel 80x86 架构。但是,第一门课程将芯片视为 8086/88,并在 MS-DOS 环境中工作。在现有时间限制内切实可行的范围内,我们假装 MS-DOS 不存在,并尝试模拟嵌入式系统环境。本质事实是 MS-DOS 使我们负责系统资源,即在实地址模式下。第一门课程是我们后续硬件课程的先决条件。
第二门课程的重点是检查支持多任务、多用户操作系统的架构要素。对于本课程,我们选择了 Linux 作为环境。第二门课程是我们后续硬件课程以及操作系统序列的先决条件。Linux 通常在后者中使用。当然,在如此复杂的多任务、多用户系统中,我们不再直接控制硬件资源。看到操作系统如何保护自己是核心兴趣所在。
本文讨论第二门课程。目标受众是那些对支持 Linux 等操作系统的 80x86 (x >= 3) Intel 架构功能感兴趣的人。我们用于调查的两种技术如下
编写我们自己的汇编语言代码来探测架构。
检查其他人编写的汇编语言。
这两种方法将在本文后面的各自章节中讨论。本文并非试图调查 Intel 架构(这是一个庞大主题),而是描述可用于此目的的工具和资源。
几乎所有关于 Intel 80x86 架构的教科书都假设读者在 Microsoft 环境中工作,通常使用 Microsoft 汇编器 MASM。因为我们在 Linux 环境中工作,所以我们不使用传统的教科书;相反,我们使用 Intel486 处理器系列:程序员参考手册 (1995),Intel 订单号 240486-003 作为主要资源。这是一本大型手册,特别感兴趣的是分别处理应用程序和系统编程的第 I 部分和第 II 部分。其他有用的资源是在线 内核黑客指南(参见 http://www.ssc.com/linux/ldp.html)、Brennan 的内联汇编指南(参见 http://www.rt66.com/-brennan/djgpp/djgpp_asm.html)以及 Linux 本身提供的各种 man 页面和 info 文档。当然,使用这样一套资源而不是专注于教科书,通常是 真实世界 软件工程师的运作方式。
Linux 是一个自然的选择,原因显而易见
它是免费的。
它包含一套完整的开发和侦探工具。
源代码是可用的。
它是一个不断发展的多任务、多用户环境,利用了底层芯片架构的先进功能。
我向我们的学生推荐 Debian GNU/Linux,因为
它非常稳定。
它可以非破坏性地就地更新/升级。
各种库都位于标准位置。
它是非商业性的,因此学生可以在课程的后期更认真地参与维护和开发。
Debian 用户和开发者非常积极响应和乐于助人。
Red Hat 或 Craftworks 等其他发行版也很好地满足了这些要求中的大多数,但第 4 项除外,这对我们的学生很重要,但对其他人可能并不重要。
我们发现将我们的汇编语言以内联方式写入 C 源代码中既方便又高效。可以在源代码中的适当位置插入标签,为调试器提供断点。编写内联汇编语言的主要动机是检查架构特性。汇编语言语句是 AT&T 风格而不是 Intel 风格。前者似乎是 Unix 的习惯。
作为一个简单的例子,我们将展示一个简短的程序 example1.c(参见 列表 1),其目的是检查标志寄存器,该寄存器具有三种类型的标志位:状态位(例如,进位标志)、系统标志(例如,给出 I/O 特权级别的两位组合)和一个控制标志,方向标志。该程序执行以下操作
将标志寄存器的副本放入 eax 寄存器中以进行检查(断点 bp1)。
翻转该副本中的所有位(断点 bp2)。
尝试将该位翻转的副本写入标志寄存器,然后将结果标志寄存器的副本放入 eax 中以进行检查(断点 bp3)。
请注意 asm 宏如何支持内联汇编语言。
为了将其编译成可执行程序 example1.x,其中包含调试器后续使用所需的信息,我们在以下命令中使用 -g 开关
gcc -g example1.c -o example1.x
下一步是调用调试器。通过管道传输到 tee 命令来获取调试器活动的日志也很方便,因此命令行条目将是
gdb -silent example1.x | tee example1.log产生 gdb 提示符
(gdb)现在 gdb 准备好运行 example1.c,而 tee 将在 example1.log 中生成我们的活动记录。后者可以打印或使用编辑器检查。
本文的范围之外还包括 gdb 使用教程;此类文档在 man 页面和 info 格式中很容易获得。此外,为了在浏览器中使用,可以在 html 格式中找到 FSF 文档 使用 gdb 调试,作者是 Stallman 和 Pesch。当前的一个 URL 是:http://funnelweb.utcc.utk/~harp/gnu/tars。
首先查看 Loukides 和 Oram 在 Linux Journal 第 29 期(1996 年 9 月)中给出的 了解 gdb 中对 gdb 的简洁、可读的介绍可能会更有效率。
话虽如此,让我们至少展示一个典型的 example1.log(参见 列表 2),它显示了设置断点,然后在这些断点处停止以检查感兴趣的寄存器。以 (gdb) 提示符开头的行是用户输入的命令,而其他所有内容都是调试器自愿提供的信息。
日志文件说明如下
标志寄存器的原始值为 0x246。
“我们尝试翻转所有位并将翻转后的值写回标志寄存器的尝试仅部分成功,并且该尝试产生了异常(信号 SIGTRAP)。”
调查人员可能会经历类似于这样的质疑过程
标志寄存器的原始值就单个位而言意味着什么(例如,I/O 特权级别是什么)?
哪个指令生成了异常以及为什么?
哪些位可以翻转,哪些位不能翻转?为什么?
然后揭示了有趣的事实。例如,在显示的日志文件中,ID 标志(位 21)已成功翻转。根据 Intel 文档,这表明该处理器可以执行 CPU_ID 指令。另一方面,给出 I/O 特权级别(位 12 和 13)的位无法修改。显然,这是预期的——普通用户不应该能够更改任何可能有助于直接访问 I/O 硬件的东西。
通常,即使对于设备驱动程序,Linux 开发人员也不使用汇编语言。因此,检查内核中极少数 是 用汇编语言编写的部分尤其具有启发性。这些可以在 Linux 发行版中使用以下命令找到
find -name *.S
从根目录输入。
bootsect.S(Intel 风格指令)
setup.S(Intel 风格)
head.S(AT&T 风格)
这些都有大量注释,但在 Intel 文档和 Alessandro Rubini 的 Linux 内核源代码之旅 中可以找到更多指导,该指南可在内核黑客指南中找到。这些模块完成了系统初始化的第一部分,该过程由 C 例程完成。一旦它们被执行,汇编语言例程就完成了。另一个感兴趣的模块是 entry.S(AT&T 风格),其任务正在进行中。特别是,它包含用于处理系统调用和故障的低级例程。
Richard A. Sevenich 是华盛顿州切尼东华盛顿大学的计算机科学教授。他对 Linux 的最初热情部分源于这样一个事实,即它的开发是由用户需求而不是营销炒作驱动的。可以通过 rsevenich@ewu.edu 与他联系。