3.1. GCC 内联汇编

著名的 GNU C/C++ 编译器 (GCC) 是 GNU 项目的核心,它是一个优化的 32 位编译器,对 x86 架构支持良好,并且能够在 C 程序中插入汇编代码,可以指定寄存器分配,也可以将其留给 GCC 处理。GCC 可以在大多数可用平台上运行,包括 Linux、*BSD、VSTa、OS/2、*DOS、Win* 等。

3.1.1. 在哪里可以找到 GCC

GCC 主页是 http://gcc.gnu.org。

GCC 的 DOS 移植版本称为 DJGPP。

有两个 Win32 GCC 移植版本:cygwin 和 mingw

还有一个名为 EMX 的 OS/2 GCC 移植版本;它也可以在 DOS 下运行,并且包含大量的 unix 模拟库例程。请访问以下站点:ftp://ftp.leo.org/pub/comp/os/os2/leo/gnu/emx+gcc/。

3.1.2. 在哪里可以找到 GCC 内联汇编的文档

GCC 的文档包括 TeXinfo 格式的文档文件。您可以使用 TeX 编译它们并打印结果,或者将它们转换为.info,然后用 emacs 浏览,或者将它们转换为.html,或者几乎任何你喜欢的东西;使用正确的工具转换为任何你喜欢的格式,或者直接阅读。 这些.info文件通常可以在 GCC 的任何良好安装中找到。

要查找的正确部分是C Extensions::Extended Asm:

章节Invoking GCC::Submodel Options::i386 Options:也可能有所帮助。特别是,它给出了 i386 特定的寄存器约束名称abcdSDB分别对应于%eax, %ebx, %ecx, %edx, %esi, %edi%ebp(没有字母对应于%esp).

DJGPP 游戏资源(不仅适用于游戏黑客)有一个专门关于汇编的页面,但它已关闭。 然而,它的数据已经在 DJGPP 站点上恢复,其中包含大量其他有用的信息:http://www.delorie.com/djgpp/doc/brennan/。

GCC 依赖于 GAS 进行汇编,并遵循其语法(见下文);请注意,内联汇编需要引用百分号,它们将被传递给 GAS。 请参阅下面关于 GAS 的部分。

在以下位置找到大量有用的示例linux/include/asm-i386/Linux 内核源代码的子目录。

3.1.3. 调用 GCC 构建正确的内联汇编代码

因为来自内核头文件的汇编例程(以及您自己的头文件,如果您尝试使您的汇编程序设计像 Linux 内核中一样干净)嵌入在extern inline函数中,必须使用-O标志(或-O2, -O3等)调用 GCC,这些例程才能可用。 否则,您的代码可能会编译,但无法正确链接,因为它将寻找非内联的extern在程序链接的库中的函数! 另一种方法是链接包含例程后备版本的库。

可以使用以下命令禁用内联汇编-fno-asm,这将导致编译器在使用扩展内联汇编语法时崩溃,或者生成对名为asm()的外部函数的调用,链接器无法解析该函数。 要反击这样的标志,-fasm恢复对asm关键字的处理。

更一般地,x86 平台上 GCC 的良好编译标志是

gcc -O2 -fomit-frame-pointer -W -Wall

-O2在大多数情况下,这是一个很好的优化级别。 除此之外的优化需要更多时间,并产生更大的代码,但速度只快一点; 这种过度优化可能只对紧密循环有用(如果有的话),无论如何你都可能在汇编中这样做。 在某些情况下,您需要对少数文件进行非常强大的编译器优化,请考虑使用高达-O6.

-fomit-frame-pointer允许生成的代码跳过愚蠢的帧指针维护,这使得代码更小更快,并释放一个寄存器以进行进一步的优化。 它排除了轻松使用调试工具 (gdb),但当您使用这些工具时,您根本不在乎大小和速度了。

-W -Wall启用所有有用的警告,并帮助您捕获明显的愚蠢错误。

您可以添加一些 CPU 特定的-m486或此类标志,以便 GCC 生成更适合您精确 CPU 的代码。 请注意,现代 GCC 具有-mpentium和此类标志(PGCC 甚至更多),而 GCC 2.7.x 和更旧的版本没有。 在 Linux 内核中应该有一个好的 CPU 特定标志选择。 查看当前 GCC 安装的 TeXinfo 文档以获取更多信息。

-m386将有助于优化大小,因此也有助于在内存紧张和/或负载重的计算机上提高速度,因为大型程序会导致交换,这比更大的代码所期望的任何“优化”都更重要。 在这种设置中,停止使用 C 并改用一种有利于代码分解的语言(例如函数式语言和/或 FORTH)并使用基于字节码或字代码的实现可能很有用。

请注意,您可以更改每个文件的代码生成标志,因此性能关键文件将使用最大优化,而其他文件将针对大小进行优化。

为了进一步优化,选项-mregparm=2和/或相应的函数属性可能有所帮助,但在链接到外部代码时可能会出现很多问题,包括 libc。 有一些方法可以正确声明外部函数,以便生成正确的调用序列,或者您可能想要重新编译外部库以使用相同的基于寄存器的调用约定...

请注意,您可以通过编辑文件来使这些标志成为默认值/usr/lib/gcc-lib/i486-linux/2.7.2.3/specs或者它在您系统上的任何位置(最好不要添加-W -Wall在那里,虽然)。 可以通过 gcc -v 找到系统上 GCC 规范文件的确切位置。

3.1.4. 宏支持

GCC 允许(并要求)您在内联汇编代码中指定寄存器约束,以便优化器始终知道它;因此,内联汇编代码实际上是由模式组成的,而不是强制性的精确代码。

因此,您可以将您的汇编放入 CPP 宏和内联 C 函数中,以便任何人都可以像任何 C 函数/宏一样使用它。 内联函数与宏非常相似,但有时使用起来更简洁。 请注意,在所有这些情况下,代码都会被复制,因此只有局部标签(属于1:风格)应该在该汇编代码中定义。 但是,宏允许将非本地定义的标签名称作为参数传递(否则,您应该使用额外的元编程方法)。 另请注意,传播内联汇编代码会传播其中的潜在错误; 因此,请加倍注意此类内联汇编代码中的寄存器约束。

最后,C 语言本身可以被认为是汇编编程的一个很好的抽象,它可以减轻您大部分的汇编麻烦。