0.7 版
版本 0.7版权 © 2013 Leo Noordergraaf
版权 © 1999-2006 Konstantin Boldyshev
版权 © 1996-1999 Francois-Rene Rideau
$Date: 2013-03-03 16:47:09 +0100 (Sun, 03 Mar 2013) $
![]() | 如果您熟悉 HOWTO,或者只是讨厌阅读所有这些与汇编无关的废话,则可以跳过本章。 |
根据 GNU 自由文档许可证 1.1 版的条款,允许复制、分发和/或修改本文档;没有不变章节,没有封面文字,也没有封底文字。许可证的副本包含在附录中。
本文档的最新官方版本可从 Linux 汇编和 LDP 站点获得。如果您正在阅读几个月前的副本,请考虑检查上述 URL 以获取新版本。
本文档旨在解答那些使用自由软件,特别是在 Linux 操作系统下,编程或想要编程 32 位 x86 汇编的人的问题。在许多地方,都给出了通用资源定位符 (URL) 以指向某些软件或文档库。本文档还指向有关非自由、非 x86 或非 32 位汇编器的其他文档,尽管这不是其主要目标。另请注意,还有关于您喜欢的平台(无论它是什么)上编程的常见问题解答和文档,您应该查阅这些内容以了解特定于平台的问题,而不是直接与汇编编程相关的问题。
由于汇编编程的主要兴趣在于构建操作系统、解释器、编译器和游戏的内核,在这些领域,C 编译器无法提供所需的表达能力(性能问题越来越少),因此我们专注于此类软件的开发。
如果您不知道什么是自由软件,请仔细阅读 GNU 通用公共许可证 (GPL 或 copyleft),它在许多自由软件中使用,并且是大多数许可证的范本。它通常以名为COPYING(或COPYING.LIB)的文件形式出现。自由软件基金会 (FSF) 的文献也可能对您有所帮助。特别是,自由软件的有趣之处在于它附带源代码,您可以查阅和更正,有时甚至可以从中借鉴。请仔细阅读您的特定许可证并遵守它。
好吧,我不想干涉您正在做的事情,但这里有一些来自来之不易的经验的建议。
汇编可以表达非常底层的细节
您可以访问机器相关的寄存器和 I/O
您可以控制关键代码段的确切代码行为,否则这些代码段可能会涉及多个软件线程或硬件设备之间的死锁
您可以打破常用编译器的约定,这可能会允许进行一些优化(例如,暂时打破关于内存分配、线程、调用约定等的规则)
您可以构建使用不兼容约定的代码片段之间的接口(例如,由不同的编译器生成,或由低级接口分隔)
您可以访问处理器的不寻常编程模式(例如,16 位模式,用于连接启动代码、固件或 Intel PC 上的旧代码)
您可以为紧密循环生成相当快的代码,以应对糟糕的非优化编译器(但是,有免费的优化编译器可用!)
您可以生成针对特定硬件设置完美调整的手工优化代码,但不适用于其他人的硬件设置
您可以为新语言的优化编译器编写一些代码(这是极少数人会做的事情,即使是他们也不常做)
即,您可以完全控制您的代码
汇编是一种非常低级的语言(比手工编码二进制指令模式还要高一级)。这意味着
最初编写起来既冗长又乏味
非常容易出错
您的错误可能很难追踪
您的代码可能相当难以理解和修改,即难以维护
结果是不可移植到其他架构,无论是现有的还是即将出现的
您的代码将仅针对同一架构的特定实现进行优化:例如,在 Intel 兼容平台中,每个 CPU 设计及其变体(处理单元、缓存、RAM、总线、磁盘的相对延迟、吞吐量和容量,FPU、MMX、3DNOW、SIMD 扩展等的存在)都意味着潜在的完全不同的优化技术。CPU 设计已经包括:Intel 386、486、奔腾、PPro、PII、PIII、PIV;Cyrix 5x86、6x86、M2;AMD K5、K6 (K6-2、K6-III)、K7 (Athlon、Duron)。新的设计不断涌现,所以不要指望此列表和您的代码是最新的。
您花费更多时间在一些细节上,而无法专注于已知能带来最大加速的小型和大型算法设计(例如,您可能会花费一些时间在汇编中构建非常快速的列表/数组操作原语;只有哈希表才能更快地加速您的程序;或者,在另一种情况下,是二叉树;或者一些分布在 CPU 集群上的高级结构)
算法设计中的一个小变化可能会完全使您现有的所有汇编代码失效。因此,要么您已准备好(并且能够)重写所有代码,要么您被束缚于特定的算法设计
在与标准基准测试相差不远的代码上,商业优化编译器胜过手工编码的汇编(嗯,这在 x86 架构上不如在 RISC 架构上那么真实,对于广泛可用/免费的编译器来说可能不太真实;无论如何,对于典型的 C 代码,GCC 相当不错);
而且在任何情况下,正如版主 John Levine 在 comp.compilers 上所说,
"编译器 使得 使用 复杂 数据 结构 容易得多,
并且 编译器 不会 在 半途 感到 厌烦
并且 生成 可靠地 相当 不错的 代码。"
它们还将在整个(庞大的)程序中正确地传播代码转换,并在过程和模块边界之间优化代码。
总而言之,您可能会发现,尽管有时需要使用汇编,甚至在少数情况下可能有用,但您仍然希望
尽量减少汇编代码的使用
将此代码封装在明确定义的接口中
让您的汇编代码从以高于汇编的更高级语言表达的模式自动生成(例如,GCC 内联汇编宏)
拥有自动工具将这些程序翻译成汇编代码
如果可能,使此代码得到优化
以上所有内容,即编写(一个扩展到)优化编译器后端。
即使在需要汇编时(例如,OS 开发),您也会发现不需要太多汇编,并且上述原则仍然适用。
请参阅 Linux 内核源代码:尽可能少地使用汇编,从而产生快速、可靠、可移植、可维护的操作系统。即使像 DOOM 这样成功的游戏也几乎完全是用 C 语言编写的,只有一小部分是用汇编语言编写的,以提高速度。
正如 Charles Fiterman 在 comp.compilers 上关于人类与计算机生成的汇编代码所说
人类 应该 总是 获胜, 原因 如下。
首先, 人类 用 高级 语言 编写 整个 东西。
其次, 他 对其 进行 性能分析, 以 找到 花费 时间 的 热点 区域。
第三, 他 让 编译器 为 这些 小 代码 段 生成 汇编代码。
第四, 他 手工 调整 它们, 寻找 机器 生成 代码 的 微小 改进。
生成的 代码。
人类 获胜 是因为 他 可以 使用 机器。
ObjectiveCAML、SML、CommonLISP、Scheme、ADA、Pascal、C、C++ 等语言都具有免费的优化编译器,它们将优化您程序的大部分,并且通常比手工编码的汇编代码(即使是对于紧密循环)做得更好,同时允许您专注于更高级别的细节,并且不妨碍您以上述方式获得额外几个百分点的性能,一旦您达到了稳定的设计。当然,大多数这些语言也有商业优化编译器!
一些语言的编译器生成 C 代码,C 代码可以由 C 编译器进一步优化:LISP、Scheme、Perl 和许多其他语言。速度相当不错。
至于加速代码,您应该仅对性能分析工具始终如一地确定为性能瓶颈的程序部分执行此操作。
因此,如果您确定某个代码部分太慢,则应
首先尝试使用更好的算法;
然后尝试编译它而不是解释它;
然后尝试启用和调整编译器的优化;
然后给编译器关于如何优化的提示(LISP 中的类型信息;GCC 的寄存器使用;大多数编译器中的许多选项等)。
然后可能退回到汇编编程
最后,在您最终编写汇编代码之前,您应该检查生成的代码,以检查问题是否真的是代码生成不良,因为情况可能并非如此:编译器生成的代码可能比您编写的代码更好,尤其是在现代多流水线架构上!程序中速度慢的部分可能本质上就是如此。现代架构上使用快速处理器的最大问题是由于内存访问、缓存未命中、TLB 未命中和页面错误造成的延迟;寄存器优化变得毫无用处,您将更有利地重新思考数据结构和线程,以实现更好的内存访问局部性。也许完全不同的问题处理方法可能会有所帮助。
有很多理由检查编译器生成的汇编代码。以下是您将如何处理此类代码
检查是否可以通过手工编码的汇编(或通过调整编译器开关)明显地增强生成的代码
如果是这种情况,请从生成的代码开始修改,而不是从头开始
更一般地说,使用生成的代码作为存根进行修改,这至少可以正确地了解您的汇编例程与外部世界交互的方式
跟踪编译器中的错误(希望这种情况更少见)
生成汇编代码的标准方法是以-S标志调用您的编译器。这适用于大多数 Unix 编译器,包括 GNU C 编译器 (GCC),但 YMMV。至于 GCC,它将使用-fverbose-asm命令行选项生成更易于理解的汇编代码。当然,如果您想获得良好的汇编代码,请不要忘记您常用的优化选项和提示!
您可能已经注意到,在一般情况下,您不需要在 Linux 编程中使用汇编语言。与 DOS 不同,您不必用汇编语言编写 Linux 驱动程序(嗯,实际上如果您真的想这样做也可以)。并且使用现代优化编译器,如果您关心针对不同 CPU 的速度优化,则用 C 语言编写要简单得多。但是,如果您正在阅读本文,您可能有一些理由使用汇编而不是 C/C++。
您可能需要使用汇编,或者您可能想要使用汇编。简而言之,进入汇编领域的实际(需要)主要原因是代码小和libc 独立性。不切实际的(想要),最常见的原因只是成为一个老派的疯狂黑客,他有二十年的使用汇编语言完成一切的习惯。
但是,如果您要将 Linux 移植到某些嵌入式硬件,则整个系统的大小可能会非常短:您需要将内核、libc 以及 (file|find|text|sh|etc.) utils 的所有内容都放入数百 KB 中,并且每 KB 都非常昂贵。因此,一种可能的方法是用汇编语言重写系统的一些(或全部)部分,这将真正为您节省大量空间。例如,用汇编语言编写的简单 httpd 可以小于 600 字节;您可以将由内核、httpd 和 ftpd 组成的服务器装入 400 KB 或更小的空间中……考虑一下。
著名的 GNU C/C++ 编译器 (GCC) 是 GNU 项目的核心,它是一个优化型 32 位编译器,对 x86 架构提供很好的支持,并且包含在 C 程序中插入汇编代码的能力,这样寄存器分配可以由指定或留给 GCC。GCC 可以在大多数可用平台上运行,特别是 Linux、*BSD、VSTa、OS/2、*DOS、Win* 等。
GCC 主页是 http://gcc.gnu.org。
存在两个 Win32 GCC 端口:cygwin 和 mingw
还有一个名为 EMX 的 GCC OS/2 端口;它也可以在 DOS 下工作,并包含许多 unix 模拟库例程。请访问以下站点了解更多信息:ftp://ftp.leo.org/pub/comp/os/os2/leo/gnu/emx+gcc/。
GCC 的文档包括 TeXinfo 格式的文档文件。您可以使用 TeX 编译它们并打印结果,或者将它们转换为.info格式,然后使用 emacs 浏览它们,或者将它们转换为.html格式,或者几乎任何您喜欢的格式;使用正确的工具转换为您喜欢的任何格式,或者直接阅读。这些.info文件通常可以在任何良好的 GCC 安装中找到。
要查找的正确部分是C 扩展::扩展汇编:
章节调用 GCC::子模型选项::i386 选项:也可能会有所帮助。特别是,它给出了寄存器的 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 内核源代码的子目录中找到大量有用的示例。
由于内核头文件中的汇编例程(以及您自己的头文件,如果您尝试使您的汇编程序设计像 Linux 内核中那样干净)嵌入在extern inline函数中,GCC 必须使用-O标志(或-O2, -O3等)来调用,这些例程才能可用。否则,您的代码可能可以编译,但无法正确链接,因为它将查找未内联的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 specs 文件的确切位置。
GCC 允许(并要求)您在内联汇编代码中指定寄存器约束,以便优化器始终了解它;因此,内联汇编代码实际上是由模式组成的,而不是强制性的精确代码。
因此,您可以将汇编放入 CPP 宏和内联 C 函数中,以便任何人都可以在任何 C 函数/宏中使用它。内联函数非常类似于宏,但有时使用起来更简洁。请注意,在所有这些情况下,代码都会被复制,因此只有局部标签(样式为1:样式)才应在该汇编代码中定义。但是,宏允许将非局部定义的标签名称作为参数传递(否则,您应该使用额外的元编程方法)。另请注意,传播内联汇编代码会传播其中潜在的错误;因此,请双倍注意此类内联汇编代码中的寄存器约束。
最后,C 语言本身可以被认为是汇编程序设计的一个很好的抽象,它可以减轻您组装的大部分麻烦。
GAS 是 GNU 汇编器,GCC 依赖于它。
在您找到 GCC 的同一个位置,在 binutils 包中找到它。最新版本的 binutils 可从 http://sources.redhat.com/binutils/ 获取。
因为 GAS 是为支持 32 位 unix 编译器而发明的,所以它使用标准的 AT&T 语法,该语法与标准 m68k 汇编器的语法非常相似,并且在 UNIX 世界中是标准的。这种语法既不比 Intel 语法差,也不比它好。它只是不同。当您习惯它时,您会发现它比 Intel 语法更有规律,尽管有点枯燥。
以下是关于 GAS 语法的主要注意事项
寄存器名称以%%%eax, 为前缀,因此寄存器是%dl等等,而不是仅仅是, eaxdl
等等。这使得可以将外部 C 符号直接包含在汇编源代码中,而不会有任何混淆的风险,也不需要丑陋的下划线前缀。操作数的顺序是源操作数在前,目标操作数在后,这与 Intel 约定目标操作数在前,源操作数在后相反。因此,在 Intel 语法中是mov eax,edx(将寄存器edx等等,而不是仅仅是的内容移动到寄存器)在 GAS 语法中将是.
mov %edx,%eax操作数大小作为指令名称的后缀指定。后缀是b表示 (8 位) 字节,w表示 (16 位) 字,以及l表示 (32 位) 长字。例如,上述指令的正确语法应该是movl %edx,%eax
。但是,gas 不需要严格的 AT&T 语法,因此当可以从寄存器操作数中猜测大小,否则默认为 32 位(带有警告)时,后缀是可选的。$立即操作数用$前缀标记,如%eax).
addl $5,%eax(将立即长字值 5 添加到寄存器缺少操作数前缀表示它是内存内容;因此movl $foo,%eaxedx%eax将变量foo的地址放入 %eax,但movl $foo,%eaxedx%eax.
movl foo,%eax将变量索引或间接寻址是通过将索引寄存器或间接内存单元地址括在括号中来完成的,如%ebp).
testb $0x80,17(%ebp)
(测试从单元格指向的偏移量 17 处的字节值的高位.info 注意:有一些程序可以帮助您在 AT&T 和 Intel 汇编程序语法之间转换源代码;其中一些能够执行双向转换。GAS 在 TeXinfo 格式中具有全面的文档,至少与源代码发行版一起提供。使用 Emacs 或其他工具浏览提取的
页面。GAS 源代码包中曾经有一个名为 gas.doc 或 as.doc 的文件,但它已合并到 TeXinfo 文档中。当然,如有疑问,最终文档是源代码本身!您特别感兴趣的部分是机器依赖性::i386-依赖性:同样,Linux(OS 内核)的源代码提供了很好的示例;请参阅linux/arch/i386/, 以下文件, kernel/*.S.
boot/compressed/*.S
math-emu/*.S
最后,只需将 C 程序编译为汇编代码就可以向您展示您想要的指令类型的语法。请参阅上面的章节。3.2.3. Intel 语法好消息是从 binutils 2.10 版本开始,GAS 也支持 Intel 语法。可以使用
GAS 也有 GASP (GAS 预处理器),它为 GAS 添加了所有常用的宏汇编技巧。GASP 与 GAS 一起包含在 GNU binutils 存档中。它像 和 一样作为过滤器工作。我对细节一无所知,但它带有自己的 texinfo 文档,您可能想浏览 (info gasp)、打印、理解。带有 GASP 的 GAS 看起来像一个普通的宏汇编器。
注意:有一些程序可以帮助您在 AT&T 和 Intel 汇编程序语法之间转换源代码;其中一些能够执行双向转换。
它们可能处于不同的开发阶段,并且可能是非经典的/高级的/或其他任何类型。
![]() | AS86 是一个 80x86 汇编器(16 位和 32 位),具有集成的宏支持。它主要使用 Intel 语法,尽管在寻址模式方面略有不同。在一段时间之前,它曾在多个项目中使用,包括 Linux 内核,但最终这些项目中的大多数已转移到 GAS 或 NASM。据我所知,只有 ELKS 继续使用它。 |
YASM 是 NASM 汇编器的完全重写版本,采用“新” BSD 许可证。它从一开始就被设计为支持多种语法(例如,NASM、TASM、GAS 等),以及多种输出对象格式,包括 COFF、Win32 和 Mach-O。整体设计的另一个主要模块是优化器模块。
FASM(flat assembler,平面汇编器)是一个快速、高效的 80x86 汇编器,运行在“平面实模式”下。与许多其他 80x86 汇编器不同,FASM 仅要求源代码包含它真正需要的信息。它用自身编写,体积非常小且速度很快。它可以在 DOS/Windows/Linux 上运行,并可以生成平面二进制文件、DOS EXE、Win32 PE、COFF 和 Linux ELF 输出。请访问 http://flatassembler.net。
osimpa 是一个用于 Intel 80386 处理器及其后续产品的汇编器,完全用 GNU Bash 命令解释器 shell 编写。osimpa 的前身是 shasm。osimpa 经过了大量清理,可以创建有用的 Linux ELF 可执行文件,并具有各种类似 HLL 的扩展和程序员便利命令。
(当然)它比其他汇编器慢。它有自己的语法(并使用自己的名称来表示 x86 操作码)。包含相当好的文档。请查看:ftp://linux01.gwdg.de/pub/cLIeNUX/interim/(访问受密码控制)。你可能不会经常使用它,但至少它值得你作为一个有趣的想法而关注。
Aasm 是一个先进的汇编器,旨在支持多种目标架构。它被设计为易于扩展,并且应该被认为是为每个新的目标 CPU 和二进制文件格式进行单体汇编器开发的良好替代方案。
Aasm 应该通过提供一组高级功能(包括符号作用域、表达式引擎、大整数支持、宏功能、大量且准确的警告消息)来使汇编编程对开发人员来说更容易。其动态模块化架构使 Aasm 能够通过利用动态库来扩展其功能集。
输入模块支持 Intel 语法(如 nasm、tasm、masm 等)。x86 汇编器模块支持所有直到 P6 的操作码,包括 MMX、SSE 和 3DNow! 扩展。F-CPU 和 SPARC 汇编器模块正在开发中。多个输出模块可用于 ELF、COFF、IntelHex 和原始二进制格式。
http://savannah.nongnu.org/projects/aasm/
表驱动汇编器 (TDASM) 是一个免费的、可移植的交叉汇编器,适用于任何类型的汇编语言。应该可以使用它作为任何目标微处理器的编译器,使用一个定义编译过程的表。
它可以从 http://www.penguin.cz/~niki/tdasm/ 获取,但似乎不再积极维护。
HLA 是一种高级汇编语言。它使用类似于高级语言的语法(类似于 Pascal、C/C++ 和其他 HLL)来进行变量声明、过程声明和过程调用。它对标准机器指令使用修改后的汇编语言语法。它还提供了几个高级语言风格的控制结构(if、while、repeat..until 等),可以帮助你编写更具可读性的代码。
HLA 是免费的,并附带源代码,提供 Linux 和 Win32 版本。在 Win32 上,你需要 MASM 和 32 位的 MS-link 版本;在 Linux 上,你需要 GAS,因为 HLA 生成指定的汇编代码并使用该汇编器进行最终汇编和链接。
TALC 是另一个基于 MASM/Win32 的免费编译器(但是它支持 ELF 输出,是吗?)。
TAL 代表类型汇编语言 (Typed Assembly Language)。它使用类型注释、内存管理原语和一组健全的类型规则扩展了传统的无类型汇编语言,以保证 TAL 程序的内存安全、控制流安全和类型安全。此外,类型构造具有足够的表达力来编码大多数源语言编程特性,包括记录和结构、数组、高阶和多态函数、异常、抽象数据类型、子类型和模块。同样重要的是,TAL 足够灵活,可以允许许多底层编译器优化。因此,TAL 是类型导向编译器的理想目标平台,这些编译器希望生成可验证的安全代码,用于安全移动代码应用程序或可扩展操作系统内核。
Free Pascal 有一个内部 32 位汇编器(基于 NASM 表),并且有一个可切换的输出,允许
二进制 (ELF 和 coff,当交叉编译 .o 时) 输出
NASM
MASM
TASM
AS (aout,coff, elf32)
MASM 和 TASM 输出的调试效果不如其他两个,但有时可能很方便。
汇编器的外观和感觉基于 Turbo Pascal 的内部 BASM,IDE 支持类似的语法高亮,并且 FPC 可以完全与 gcc 集成(在 C 级别,而不是 C++)。
使用虚拟 RTL,甚至可以生成纯汇编程序。
Win32Forth 是一个免费的 32 位 ANS FORTH 系统,可以在 Win32s、Win95、Win/NT 下成功运行。它包含一个免费的 32 位汇编器(前缀或后缀语法),集成到反射式 FORTH 语言中。宏处理通过反射式语言 FORTH 的全部功能完成;但是,唯一支持的输入和输出上下文是 Win32For 本身(没有转储.obj文件,但你当然可以自己添加该功能)。请在 ftp://ftp.forth.org/pub/Forth/Compilers/native/windows/Win32For/ 找到它。
Terse 是一个编程工具,为 x86 系列提供了最紧凑的汇编器语法!然而,它是邪恶的专有软件。据说曾经有一个免费克隆的项目,但在毫无价值地声称语法将归原始作者所有之后被放弃了。因此,如果你正在寻找与汇编黑客相关的绝妙编程项目,我邀请你开发一个 terse 语法前端到 NASM,如果你喜欢那种语法的话。
作为一个有趣的历史评论,在 comp.compilers 上,
1999/07/11 19:36:51, 版主 写道
“没有 理由 汇编器 必须 有 糟糕的 语法。 大约
30 年前 我 使用过 Niklaus Wirth 的 PL360, 它 基本上 是一个 S/360
汇编器, 带有 Algol 语法 和 一点 语法糖, 比如 while
循环, 它 变成了 明显的 分支。 它 真的是 一个
汇编器, 例如, 你 必须 写出 你的 表达式, 并 显式地
将 值 赋值给 寄存器, 但 它 很棒。 Wirth 用它来
编写 Algol W, 一个 小巧快速的 Algol 子集, 它是 一个 前身
Pascal。 正如 经常 发生的情况, Algol W 是一个 重要的
改进, 超越了 许多 它的 继任者。 -John”
汇编编程很枯燥,但对于程序中的关键部分而言并非如此。
你应该为正确的任务使用合适的工具,所以当汇编不合适时不要选择它;在大多数情况下,C、OCaml、perl、Scheme 可能是更好的选择。
但是,在某些情况下,这些工具无法对机器进行足够精细的控制,而汇编是有用或必要的。在这些情况下,你会欣赏宏处理和元编程系统,该系统允许将重复模式分解为每个无限可重用的定义,从而实现更安全的编程、模式修改的自动传播等。即使仅进行与 C 链接的小例程,纯汇编器通常也不够用。
无论你的汇编器提供何种宏支持,或者你使用何种语言(甚至是 C!),如果该语言对你来说不够富有表现力,你可以使用 Makefile 规则将文件通过外部过滤器传递,如下所示
%.s: %.S other_dependencies $(FILTER) $(FILTER_OPTIONS) < $< > $@ |
CPP 确实不是很富有表现力,但对于简单的东西来说足够了,它是标准的,并且被 GCC 透明地调用。
作为其局限性的一个例子,你无法声明对象,以便在声明块末尾自动调用析构函数;你没有分支或作用域等。
CPP 随任何 C 编译器一起提供。但是,考虑到它有多么平庸,如果碰巧你可以不用 C 也能做到,那就远离它。
M4 为你提供了宏处理的全部功能,以及图灵等价语言、递归、正则表达式等。你可以用它做任何 CPP 做不到的事情。
请参阅 macro4th (this4th) 作为使用 m4 进行高级宏编程的示例。
但是,其功能失调的引用和取消引用语义迫使你使用显式的延续传递尾递归宏风格,如果你想进行高级宏编程(这让人想起 TeX -- 顺便说一句,有人尝试将 TeX 用作排版以外的任何东西的宏处理器吗?)。这并不比 CPP 更糟,CPP 无论如何都不允许引用和递归。
要获取的正确 M4 版本是GNU m4它具有最多的功能,并且所有版本中错误或限制最少。m4 被设计为除了最简单的用途之外都很慢,这对于大多数汇编编程来说可能仍然可以接受(你不是在编写百万行汇编程序,是吗?)。
除了使用扩展宏的外部过滤器之外,一种做事方式是编写程序来编写其他程序的部分或全部。
例如,你可以使用输出源代码的程序
来生成正弦/余弦/任何查找表,
来提取二进制文件的源形式表示,
来将你的位图编译成快速显示例程,
从同一源文件提取文档、初始化/终结代码、描述表以及常规代码,
拥有自定义汇编代码,从执行任意处理的 perl/shell/scheme 脚本生成,
将在一个点定义的数据传播到多个交叉引用表和代码块中。
等等。
考虑一下!
像 GCC、SML/NJ、Objective CAML、MIT-Scheme、CMUCL 等编译器,都有自己的通用汇编器后端,如果你打算从相应的语言或你修改的语言中半自动生成代码,你可以选择使用它们:与其编写出色的汇编代码,不如修改编译器,使其转储出色的汇编代码!
如果你正在开发混合 C-asm 项目,这是首选方法。查看 GCC 文档和 Linux 内核中的示例.s通过 gas 的文件(而不是通过 as86 的文件)。
32 位参数以相反的语法顺序压入堆栈(因此以正确的顺序访问/弹出),在 32 位近返回地址之上。%ebp, %esi, %edi, %ebx是被调用者保存的,其他寄存器是调用者保存的;%eax用于保存结果,或%edx:%eax用于 64 位结果。
FP 栈:我不确定,但我认为结果在st(0)中,整个堆栈由调用者保存。
请注意,GCC 有选项可以通过保留寄存器、将参数放在寄存器中、不假设 FPU 等来修改调用约定。查看 i386.info页面。
请注意,你必须为将遵循标准 GCC 调用约定的函数声明cdecl目录中的二进制版本。也应该可以作为regparm(0)属性。请参阅C 扩展::扩展汇编:GCC 信息页面中的部分。另请参阅 Linux 如何定义其asmlinkage宏。
一些 C 编译器在每个符号前添加下划线,而另一些则不添加。
特别是,Linux a.out GCC 会添加这样的前缀,而 Linux ELF GCC 则不会。
如果你需要同时处理这两种行为,请查看现有软件包的做法。例如,获取旧的 Linux 源代码树,Elk、qthreads 或 OCaml。
你还可以通过插入如下语句来覆盖隐式的 C->asm 重命名
void foo asm("bar") (void); |
请注意,来自 binutils 包的 objcopy 实用程序应该允许您将 a.out 对象转换为 ELF 对象,并且在某些情况下,也许反之亦然。更一般地说,它将进行大量的文件格式转换。
通常你会听到使用 C 库 (libc) 是唯一的方法,而直接系统调用是不好的。 这在某种程度上是真的。一般来说,你必须知道 libc 并非神圣不可侵犯,而且在大多数情况下,它只是做一些检查,然后调用内核,然后设置 errno。 你也可以在你的程序中轻松地做到这一点(如果需要),并且你的程序会小几十倍,这也会提高性能,仅仅因为你没有使用共享库(静态二进制文件更快)。 在汇编编程中使用或不使用 libc 更多的是品味/信仰问题,而不是实际问题。 记住,Linux 的目标是符合 POSIX 标准,libc 也是如此。 这意味着几乎所有 libc “系统调用”的语法都与真正的内核系统调用的语法完全匹配(反之亦然)。 此外,GNU libc(glibc) 的版本变得越来越慢,并且消耗越来越多的内存;因此,使用直接系统调用的情况变得非常普遍。 然而,抛弃 libc 的主要缺点是,你可能需要自己实现几个 libc 特定的函数(不仅仅是系统调用包装器)(printf()和类似的函数),你准备好了,不是吗? :-)
以下是直接系统调用优缺点的总结。
优点
尽可能小的尺寸;从系统中挤出最后一个字节
尽可能高的速度;从你最喜欢的基准测试中挤出周期
完全控制:你可以使你的程序/库适应你的特定语言或内存需求或其他任何需求
不受 libc 垃圾代码的污染
不受 C 调用约定的污染(如果你正在开发自己的语言或环境)
静态二进制文件使你独立于 libc 升级或崩溃,或来自悬空的#!解释器路径(并且速度更快)
仅仅为了乐趣(汇编编程难道不会让你感到兴奋吗?)
缺点
如果你的计算机上的任何其他程序使用了 libc,那么复制 libc 代码实际上会浪费内存,而不是节省内存。
在许多静态二进制文件中冗余实现的服务是一种内存浪费。 但是你可以将你的 libc 替代品制作成共享库。
通过某种字节码、字码或结构解释器来节省空间比用汇编编写所有内容要好得多。(解释器本身可以用 C 或汇编编写。)保持多个二进制文件小的最佳方法不是拥有多个二进制文件,而是拥有一个解释器进程来处理带有#!前缀的文件。 这就是 OCaml 在字码模式下使用时的工作方式(与优化的本机代码模式相反),并且它与使用 libc 兼容。 这也是 Tom Christiansen 的 Perl PowerTools 对 unix 实用程序的重新实现的工作方式。 最后,最后一种保持文件小的且不依赖于带有硬编码路径的外部文件(无论是库还是解释器)的方法是只拥有一个二进制文件,并拥有指向它的多个命名硬链接或软链接:同一个二进制文件将以最佳空间提供你需要的一切,而没有子例程或无用的二进制头文件的冗余;它将根据它的argv[0]来调度其特定行为; 如果它没有以可识别的名称调用,它可能会默认为 shell,因此也可用作解释器!
你无法从 libc 提供的许多功能中受益,除了仅仅是 linux 系统调用:也就是说,手册页第 3 节中描述的功能,而不是第 2 节,例如 malloc、线程、locale、密码、高级网络管理等。
因此,你可能不得不重新实现 libc 的大部分,从printf()到malloc()和gethostbyname。 这与 libc 的工作是冗余的,有时可能会非常无聊。 请注意,有些人已经重新实现了“轻量级”的 libc 部分替代品——去看看它们!(Redhat 的 minilibc,Rick Hohensee 的 libsys,Felix von Leitner 的 dietlibc,asmutils 项目正在开发纯汇编 libc)
静态库阻止你从 libc 升级以及 libc 附加组件(例如 zlibc 包)中受益,zlibc 包可以对 gzip 压缩文件进行即时透明解压缩。
与系统调用的成本相比,libc 添加的少量指令可能是非常小的速度开销。 如果速度是一个问题,那么你的主要问题在于你对系统调用的使用,而不是它们的包装器的实现。
当在 Linux 的微内核版本(如 L4Linux)中运行时,使用标准汇编 API 进行系统调用比使用 libc API 慢得多,L4Linux 有自己更快的调用约定,并且在使用标准调用约定(L4Linux 附带使用其系统调用 API 重新编译的 libc;当然,你也可以使用他们的 API 重新编译你的代码)。
有关一般速度优化问题,请参阅之前的讨论。
如果系统调用对你来说太慢了,你可能想要破解内核源代码(用 C 语言编写),而不是停留在用户空间。
如果你已经权衡了上述优缺点,并且仍然想使用直接系统调用,那么这里有一些建议。
你可以通过包含asm/unistd.h,并使用提供的宏,以可移植的方式在 C 语言中轻松定义你的系统调用函数(而不是使用不可移植的汇编)。
既然你试图替换它,那就去获取 libc 的源代码,并理解它们。(如果你认为你可以做得更好,那么请向作者发送反馈!)
作为纯汇编代码的示例,它可以完成你想要的一切,请检查 .
基本上,你发出一个int 0x80,带有__NR_syscallname 编号(来自asm/unistd.h)在等等,而不是仅仅是中,参数(最多六个)在ebx, ecx, (将寄存器, esi, edi, ebp中,分别。
结果在等等,而不是仅仅是中返回,负结果表示错误,其相反值是 libc 将放入errno中的值。 用户堆栈不会被触及,因此在进行系统调用时,你不需要有一个有效的堆栈。
《Linux 内核内幕》,尤其是《i386 架构上系统调用是如何实现的?》章节将为你提供更全面的概述。
至于进程启动时传递的调用参数,一般原则是堆栈最初包含参数的数量argc,然后是指针列表,构成*argv,然后是以 null 结尾的 null 结尾序列variable=value字符串,用于environ环境。 有关更多详细信息,请检查 ,阅读你的 libc 中的 C 启动代码源文件(crt0.S目录中的二进制版本。也应该可以作为crt1.S),或 Linux 内核中的那些源文件(exec.c和binfmt_*.c在linux/fs/).
如果你想在 Linux 下执行直接端口 I/O,要么它是一些非常简单的东西,不需要 OS 仲裁,你应该查看IO-Port-Programmingmini-HOWTO;要么它需要内核设备驱动程序,你应该尝试了解更多关于内核黑客技术、设备驱动程序开发、内核模块等的信息,LDP 提供了其他优秀的 HOWTO 和文档。
特别是,如果你的目标是图形编程,那么请加入 GGI 或 XFree86 项目之一。
有些人甚至做得更好,用解释型特定领域语言 GAL 编写小型且稳健的 XFree86 驱动程序,并通过部分求值实现了手写 C 驱动程序的效率(驱动程序不仅不是用汇编编写的,甚至不是用 C 编写的!)。 问题在于他们用于实现效率的部分求值器不是自由软件。 有人愿意替换它吗?
无论如何,在所有这些情况下,使用带有来自linux/asm/*.h的宏的 GCC 内联汇编比编写完整的汇编源文件要好。
这种事情在理论上是可能的(证明:看看 DOSEMU 如何选择性地授予程序硬件端口访问权限),并且我听说有传言说有人在某个地方确实做到了(在 PCI 驱动程序中?一些 VESA 访问的东西?ISA PnP?不知道)。 如果你有关于此的更精确的信息,我们将非常欢迎。 无论如何,查找更多信息的良好地方是 Linux 内核源代码、DOSEMU 源代码以及 Linux 下各种低级程序的源代码。(如果 GGI 支持 VESA,也许可以看看 GGI)。
基本上,你必须使用 16 位保护模式或 vm86 模式。
前者设置起来更简单,但仅适用于行为良好的代码,这些代码不会进行任何类型的段算术或绝对段寻址(特别是寻址段 0),除非偶然情况下,所有使用的段都可以预先在 LDT 中设置。
后者允许与原始 16 位环境具有更高的“兼容性”,但需要更复杂的处理。
在这两种情况下,在你跳转到 16 位代码之前,你必须
从/dev/mem到你的进程地址空间,mmap 16 位代码中使用的任何绝对地址(例如 ROM、视频缓冲区、DMA 目标和内存映射 I/O),
设置 LDT 和/或 vm86 模式监视器。
从内核获取正确的 I/O 权限(参见上一节)
再次强调,仔细阅读为 DOSEMU 项目贡献的代码的源代码,特别是用于在 Linux/i386 下运行 ELKS 和/或简单.COM程序的这些小型模拟器。
大多数 DOS 扩展器都带有一些与 DOS 服务的接口。 阅读他们的文档了解这一点,但通常,他们只是模拟int 0x21等等,所以你“好像”你在实模式下(我怀疑他们拥有的不仅仅是存根,并且扩展功能以与 32 位操作数一起工作;他们很可能只是将中断反映到实模式或 vm86 处理程序中)。
有关 DPMI 的文档(以及更多内容)可以在 http://en.wikipedia.org/wiki/DOS_Protected_Mode_Interface 上找到)。
DJGPP 也附带了它自己的(有限的)glibc 衍生产品/子集/替代品。
可以从 Linux 交叉编译到 DOS,请参阅你的本地 FTP 镜像站点的devel/msdos/目录,metalab.unc.edu; 另请参阅犹他大学 Flux 项目的 MOSS DOS 扩展器。
其他文档和常见问题解答更以 DOS 为中心; 我们不推荐 DOS 开发。
Windows 和 Co. 本文档不是关于 Windows 编程的,你可以在任何地方找到大量关于它的文档... 你应该知道的是有 cygwin32.dll 库,用于 GNU 程序在 Win32 平台上运行; 因此,你可以使用 GCC、GAS、所有 GNU 工具和许多其他 Unix 应用程序。
控制是吸引许多 OS 开发人员使用汇编的原因,通常也是导致或源于汇编黑客技术的原因。 请注意,任何允许自我开发的系统都可以被认为是“OS”,尽管它可以“在底层系统之上”运行(很像 Linux 在 Mach 之上或 OpenGenera 在 Unix 之上)。
因此,为了更容易调试,你可能希望首先将你的“OS”开发为在 Linux 之上运行的进程(尽管速度较慢),然后使用 Flux OS 工具包(它允许在你的自己的 OS 中使用 Linux 和 BSD 驱动程序)使其独立运行。 当你的 OS 稳定时,如果你真的喜欢那样,就该编写你自己的硬件驱动程序了。
本 HOWTO 不会涵盖诸如引导加载程序代码、进入 32 位模式、处理中断、关于 Intel 保护模式或 V86/R86 弱智性、定义你的对象格式和调用约定等主题。
找到关于所有这些的可靠信息的主要地方是现有 OS 和引导加载程序的源代码。 以下网页上有许多指针:http://www.tunes.org/Review/OSes.html
最后,如果你仍然想尝试这个疯狂的想法并用汇编编写一些东西(如果你已经读到这一节——你真的是汇编粉丝),那么这是你需要开始的。
正如你之前读到的,你可以用不同的方式为 Linux 编写代码; 我将展示如何使用直接内核调用,因为这是调用内核服务的最快方法; 我们的代码没有链接到任何库,不使用 ELF 解释器,它直接与内核通信。
我将展示用两种汇编器,nasm 和 gas 编写的相同的示例程序,从而展示 Intel 和 AT&T 语法。
你可能还想阅读《UNIX 汇编编程入门教程》,它包含其他类 UNIX 操作系统的示例代码。
Linux 是 32 位的,运行在保护模式下,具有平面内存模型,并使用 ELF 格式的二进制文件。
一个程序可以被划分为节 (sections).text用于你的代码(只读),.data用于你的数据(读写),.bss用于未初始化的数据(读写);实际上可能还有一些其他的标准节,以及一些用户自定义的节,但很少需要使用它们,而且它们不在此处讨论范围内。一个程序必须至少有一个.text节。
现在我们将编写我们的第一个程序。这里是示例代码
section .text ;section declaration ;we must export the entry point to the ELF linker or global _start ;loader. They conventionally recognize _start as their ;entry point. Use ld -e foo to override the default. _start: ;write our string to stdout mov edx,len ;third argument: message length mov ecx,msg ;second argument: pointer to message to write mov ebx,1 ;first argument: file handle (stdout) mov eax,4 ;system call number (sys_write) int 0x80 ;call kernel ;and exit mov ebx,0 ;first syscall argument: exit code mov eax,1 ;system call number (sys_exit) int 0x80 ;call kernel section .data ;section declaration msg db "Hello, world!",0xa ;our dear string len equ $ - msg ;length of our dear string |
.text # section declaration # we must export the entry point to the ELF linker or .global _start # loader. They conventionally recognize _start as their # entry point. Use ld -e foo to override the default. _start: # write our string to stdout movl $len,%edx # third argument: message length movl $msg,%ecx # second argument: pointer to message to write movl $1,%ebx # first argument: file handle (stdout) movl $4,%eax # system call number (sys_write) int $0x80 # call kernel # and exit movl $0,%ebx # first argument: exit code movl $1,%eax # system call number (sys_exit) int $0x80 # call kernel .data # section declaration msg: .ascii "Hello, world!\n" # our dear string len = . - msg # length of our dear string |
为了演示除了 x86 之外还存在其他体系结构,这里有一个 Spencer Parkin 编写的 MIPS 示例程序。
# hello.S by Spencer T. Parkin # This is my first MIPS-RISC assembly program! # To compile this program type: # > gcc -o hello hello.S -non_shared # This program compiles without errors or warnings # on a PlayStation2 MIPS R5900 (EE Core). # EE stands for Emotion Engine...lame! # The -non_shared option tells gcc that we`re # not interrested in compiling relocatable code. # If we were, we would need to follow the PIC- # ABI calling conventions and other protocols. #include <asm/regdef.h> // ...for human readable register names #include <asm/unistd.h> // ...for system serivices .rdata # begin read-only data segment .align 2 # because of the way memory is built hello: .asciz "Hello, world!\n" # a null terminated string .align 4 # because of the way memory is built length: .word . - hello # length = IC - (hello-addr) .text # begin code segment .globl main # for gcc/ld linking .ent main # for gdb debugging info. main: # We must specify -non_shared to gcc or we`ll need these 3 lines that fallow. # .set noreorder # disable instruction reordering # .cpload t9 # PIC ABI crap (function prologue) # .set reorder # re-enable instruction reordering move a0,$0 # load stdout fd la a1,hello # load string address lw a2,length # load string length li v0,__NR_write # specify system write service syscall # call the kernel (write string) li v0,0 # load return code j ra # return to caller .end main # for dgb debugging info. # That`s all folks! |
你学习 Linux/UNIX 汇编编程的主要资源是
http://asm.sourceforge.net/resources.html
请访问它,并获取大量关于不同 UNIX 操作系统和 CPU 的汇编项目、工具、教程、文档、指南等资源链接。因为它发展迅速,我将不再在此处重复。
Programming from the ground up
x86 汇编 FAQ (使用 Google)
CoreWars,一种有趣的学习通用汇编语言的方式
Usenet: comp.lang.asm.x86; alt.lang.asm
如果你对 Linux/UNIX 汇编编程感兴趣(或有疑问,或只是好奇),我特别邀请你加入 Linux 汇编编程邮件列表。
这是一个关于 Linux、*BSD、BeOS 或任何其他类 UNIX/POSIX 操作系统下汇编编程的公开讨论;它也不仅限于 x86 汇编(欢迎 Alpha、Sparc、PPC 和其他架构的爱好者!)。
邮件列表地址是<linux-assembly@vger.kernel.org>.
要订阅,请发送消息至<majordomo@vger.kernel.org>并在消息正文中包含以下行
subscribe linux-assembly |
详细信息和列表存档可在 http://asm.sourceforge.net/list.html 获取。
这里是关于 Linux 汇编编程的常见问题(及解答)。 一些问题(和答案)取自 linux-assembly 邮件列表。
Paul Furber 的回答
Ok you have a number of options to graphics in Linux. Which one you use depends on what you want to do. There isn't one Web site with all the information but here are some tips: SVGALib: This is a C library for console SVGA access. Pros: very easy to learn, good coding examples, not all that different from equivalent gfx libraries for DOS, all the effects you know from DOS can be converted with little difficulty. Cons: programs need superuser rights to run since they write directly to the hardware, doesn't work with all chipsets, can't run under X-Windows. Search for svgalib-1.4.x on http://ftp.is.co.za Framebuffer: do it yourself graphics at SVGA res Pros: fast, linear mapped video access, ASM can be used if you want :) Cons: has to be compiled into the kernel, chipset-specific issues, must switch out of X to run, relies on good knowledge of linux system calls and kernel, tough to debug Examples: asmutils (http://www.linuxassembly.org) and the leaves example and my own site for some framebuffer code and tips in asm (http://ma.verick.co.za/linux4k/) Xlib: the application and development libraries for XFree86. Pros: Complete control over your X application Cons: Difficult to learn, horrible to work with and requires quite a bit of knowledge as to how X works at the low level. Not recommended but if you're really masochistic go for it. All the include and lib files are probably installed already so you have what you need. Low-level APIs: include PTC, SDL, GGI and Clanlib Pros: very flexible, run under X or the console, generally abstract away the video hardware a little so you can draw to a linear surface, lots of good coding examples, can link to other APIs like OpenGL and sound libs, Windows DirectX versions for free Cons: Not as fast as doing it yourself, often in development so versions can (and do) change frequently. Examples: PTC and GGI have excellent demos, SDL is used in sdlQuake, Myth II, Civ CTP and Clanlib has been used for games as well. High-level APIs: OpenGL - any others? Pros: clean api, tons of functionality and examples, industry standard so you can learn from SGI demos for example Cons: hardware acceleration is normally a must, some quirks between versions and platforms Examples: loads - check out www.mesa3d.org under the links section. To get going try looking at the svgalib examples and also install SDL and get it working. After that, the sky's the limit. |
有一个早期版本的汇编语言调试器 (Assembly Language Debugger),它被设计用于处理汇编代码,并且具有足够的移植性,可以在 Linux 和 *BSD 上运行。它已经可以使用,应该是正确的选择,去看看吧!
你也可以尝试 gdb ;)。 虽然它是一个源代码级调试器,但它可以用于调试纯汇编代码,并且通过一些技巧,你可以让 gdb 完成你需要的工作(不幸的是,nasm '-g' 开关不会为 gdb 生成正确的调试信息;我认为这是一个 nasm 的 bug)。 这是 Dmitry Bakhvalov 的回答
Personally, I use gdb for debugging asmutils. Try this: 1) Use the following stuff to compile: $ nasm -f elf -g smth.asm $ ld -o smth smth.o 2) Fire up gdb: $ gdb smth 3) In gdb: (gdb) disassemble _start Place a breakpoint at _start+1 (If placed at _start the breakpoint wouldnt work, dunno why) (gdb) b *0x8048075 To step thru the code I use the following macro: (gdb)define n >ni >printf "eax=%x ebx=%x ...etc...",$eax,$ebx,...etc... >disassemble $pc $pc+15 >end Then start the program with r command and debug with n. Hope this helps. |
??? 的补充说明
I have such a macro in my .gdbinit for quite some time now, and it for sure makes life easier. A small difference : I use "x /8i $pc", which guarantee a fixed number of disassembled instructions. Then, with a well chosen size for my xterm, gdb output looks like it is refreshed, and not scrolling. |
如果你想在你的代码中设置断点,你可以直接使用int 3指令作为断点(而不是在 gdb 中手动输入地址)。
如果你正在使用 gas,你应该查阅 gas 和 gdb 相关的教程。
当然,strace 可以提供很大帮助(FreeBSD 上是 ktrace 和 kdump),它用于跟踪系统调用和信号。 阅读其手册页 (man strace) 和 strace - -help 输出以获取详细信息。
简短的回答是——不行。 这是保护模式,请改用操作系统服务。 再次强调,你不能使用int 0x10, int 0x13, 等等。 幸运的是,几乎所有事情都可以通过系统调用或库函数来实现。 在最坏的情况下,你可以通过直接端口访问,或者制作内核补丁来实现所需的功能,或者使用 LRMI 库来访问 BIOS 功能。
是的,确实可以。 虽然一般来说这不是一个好主意(它几乎不会加速任何东西),但可能需要这种技巧。 编写模块本身的过程并不难——一个模块必须有一些预定义的全局函数,它可能还需要调用内核中的一些外部函数。 查看内核源代码(可以构建为模块)以获取详细信息。
同时,这里有一个最小的、简单的内核模块的例子(module.asm)(源代码基于 APJ #8 中 mammon_ 的示例)
section .text global init_module global cleanup_module global kernel_version extern printk init_module: push dword str1 call printk pop eax xor eax,eax ret cleanup_module: push dword str2 call printk pop eax ret str1 db "init_module done",0xa,0 str2 db "cleanup_module done",0xa,0 kernel_version db "2.2.18",0 |
此示例唯一做的事情是报告其操作。 修改kernel_version以匹配你的内核版本,并使用以下命令构建模块
$ nasm -f elf -o module.m module.asm |
$ ld -r -o module.o module.m |
现在你可以使用 insmod/rmmod/lsmod 来玩它(需要 root 权限); 很有趣,不是吗?
H-Peter Recktenwald 的简洁回答
ebx := 0 (in fact, any value below .bss seems to do) sys_brk eax := current top (of .bss section) ebx := [ current top < ebx < (esp - 16K) ] sys_brk eax := new top of .bss |
Tiago Gasiba 的详细回答
section .bss var1 resb 1 section .text ; ;allocate memory ; %define LIMIT 0x4000000 ; about 100Megs mov ebx,0 ; get bottom of data segment call sys_brk cmp eax,-1 ; ok? je erro1 add eax,LIMIT ; allocate +LIMIT memory mov ebx,eax call sys_brk cmp eax,-1 ; ok? je erro1 cmp eax,var1+1 ; has the data segment grown? je erro1 ; ;use allocated memory ; ; now eax contains bottom of ; data segment mov ebx,eax ; save bottom mov eax,var1 ; eax=beginning of data segment repeat: mov word [eax],1 ; fill up with 1's inc eax cmp ebx,eax ; current pos = bottom? jne repeat ; ;free memory ; mov ebx,var1 ; deallocate memory call sys_brk ; by forcing its beginning=var1 cmp eax,-1 ; ok? je erro2 |
Patrick Mochel 的回答
When you call sys_open, you get back a file descriptor, which is simply an index into a table of all the open file descriptors that your process has. stdin, stdout, and stderr are always 0, 1, and 2, respectively, because that is the order in which they are always open for your process from there. Also, notice that the first file descriptor that you open yourself (w/o first closing any of those magic three descriptors) is always 3, and they increment from there. Understanding the index scheme will explain what select does. When you call select, you are saying that you are waiting certain file descriptors to read from, certain ones to write from, and certain ones to watch from exceptions from. Your process can have up to 1024 file descriptors open, so an fd_set is just a bit mask describing which file descriptors are valid for each operation. Make sense? Since each fd that you have open is just an index, and it only needs to be on or off for each fd_set, you need only 1024 bits for an fd_set structure. 1024 / 32 = 32 longs needed to represent the structure. Now, for the loose example. Suppose you want to read from a file descriptor (w/o timeout). - Allocate the equivalent to an fd_set. .data my_fds: times 32 dd 0 - open the file descriptor that you want to read from. - set that bit in the fd_set structure. First, you need to figure out which of the 32 dwords the bit is in. Then, use bts to set the bit in that dword. bts will do a modulo 32 when setting the bit. That's why you need to first figure out which dword to start with. mov edx, 0 mov ebx, 32 div ebx lea ebx, my_fds bts ebx[eax * 4], edx - repeat the last step for any file descriptors you want to read from. - repeat the entire exercise for either of the other two fd_sets if you want action from them. That leaves two other parts of the equation - the n paramter and the timeout parameter. I'll leave the timeout parameter as an exercise for the reader (yes, I'm lazy), but I'll briefly talk about the n parameter. It is the value of the largest file descriptor you are selecting from (from any of the fd_sets), plus one. Why plus one? Well, because it's easy to determine a mask from that value. Suppose that there is data available on x file descriptors, but the highest one you care about is (n - 1). Since an fd_set is just a bitmask, the kernel needs some efficient way for determining whether to return or not from select. So, it masks off the bits that you care about, checks if anything is available from the bits that are still set, and returns if there is (pause as I rummage through kernel source). Well, it's not as easy as I fantasized it would be. To see how the kernel determines that mask, look in fs/select.c in the kernel source tree. Anyway, you need to know that number, and the easiest way to do it is to save the value of the last file descriptor open somewhere so you don't lose it. Ok, that's what I know. A warning about the code above (as always) is that it is not tested. I think it should work, but if it doesn't let me know. But, if it starts a global nuclear meltdown, don't call me. ;-) |
现在就到这里,各位.
每个版本都包含一些修复和小修正,这些修正无需每次都重复提及。
修订历史 | ||
---|---|---|
修订版 0.7 | 2013 年 3 月 3 日 | 修订者:lnoor |
新维护者,重新格式化为 DocBook XML,已检查,更新或替换了失效链接。 | ||
修订版 0.6g | 2006 年 2 月 11 日 | 修订者:konst |
添加了 AASM,更新了 FASM,在快速入门部分添加了 MIPS 示例,添加了土耳其语和俄语翻译的 URL,其他 URL 更新 | ||
修订版 0.6f | 2002 年 8 月 17 日 | 修订者:konst |
添加了 FASM,添加了韩语翻译的 URL,添加了 SVR4 i386 ABI 规范的 URL,HLA/Linux 更新,hello.S 示例中的小修复,其他 URL 更新 | ||
修订版 0.6e | 2002 年 1 月 12 日 | 修订者:konst |
添加了描述 GAS Intel 语法的 URL;添加了 OSIMPA(前 SHASM);添加了 YASM;FAQ 更新。 | ||
修订版 0.6d | 2001 年 3 月 18 日 | 修订者:konst |
添加了 Free Pascal;新的 NASM URL | ||
修订版 0.6c | 2001 年 2 月 15 日 | 修订者:konst |
添加了 SHASM;FAQ 中的新答案,新的 NASM URL,新的邮件列表地址 | ||
修订版 0.6b | 2001 年 1 月 21 日 | 修订者:konst |
FAQ 中的新问题,更正了一些 URL | ||
修订版 0.6a | 2000 年 12 月 10 日 | 修订者:konst |
重做了关于 AS86 的部分(感谢 Holluby Istvan 指出过时的信息)。修复了几个可能从 sgml 渲染到 html 时不正确的 URL。 | ||
修订版 0.6 | 2000 年 11 月 11 日 | 修订者:konst |
HOWTO 使用 DocBook DTD 完全重写。布局完全重新排列;更改太多,无法在此处一一列出。 | ||
修订版 0.5n | 2000 年 11 月 7 日 | 修订者:konst |
向 FAQ 添加了关于内核模块的问题,修复了 NASM URL,GAS 也具有 Intel 语法 | ||
修订版 0.5m | 2000 年 10 月 22 日 | 修订者:konst |
Linux 2.4 系统调用可以有 6 个参数,向 FAQ 添加了 ALD 注释,修复了邮件列表订阅地址 | ||
修订版 0.5l | 2000 年 8 月 23 日 | 修订者:konst |
添加了 TDASM,NASM 更新 | ||
修订版 0.5k | 2000 年 7 月 11 日 | 修订者:konst |
FAQ 的一些补充 | ||
修订版 0.5j | 2000 年 6 月 14 日 | 修订者:konst |
完全重新排列了引言和资源部分。FAQ 添加到资源,其他清理和添加。 | ||
修订版 0.5i | 2000 年 5 月 4 日 | 修订者:konst |
添加了 HLA、TALC;资源、快速入门部分重新排列。一些新的指针。 | ||
修订版 0.5h | 2000 年 4 月 9 日 | 修订者:konst |
最终设法在文档上声明了 LDP 许可证,添加了新资源,其他修复 | ||
修订版 0.5g | 2000 年 3 月 26 日 | 修订者:konst |
关于不同 CPU 的新资源 | ||
修订版 0.5f | 2000 年 3 月 2 日 | 修订者:konst |
新资源,其他更正 | ||
修订版 0.5e | 2000 年 2 月 10 日 | 修订者:konst |
URL 更新,GAS 示例中的更改 | ||
修订版 0.5d | 2000 年 2 月 1 日 | 修订者:konst |
资源(前 "指针")部分完全重做,各种 URL 更新。 | ||
修订版 0.5c | 1999 年 12 月 5 日 | 修订者:konst |
新指针,更新和一些重新排列。重写 sgml 源代码。 | ||
修订版 0.5b | 1999 年 9 月 19 日 | 修订者:konst |
关于 libc 或非 libc 的讨论继续。新的 Web 指针和整体更新。 | ||
修订版 0.5a | 1999 年 8 月 1 日 | 修订者:konst |
快速入门部分重新排列,添加了 GAS 示例。几个新的 Web 指针。 | ||
修订版 0.5 | 1999 年 8 月 1 日 | 修订者:konstfare |
GAS 具有 16 位模式。新维护者(终于):Konstantin Boldyshev。关于 libc 或非 libc 的讨论。添加了带有汇编代码示例的快速入门部分。 | ||
修订版 0.4q | 1999 年 6 月 22 日 | 修订者:fare |
汇编中的进程参数传递 (argc, argv, environ)。这是 Fare 在新维护者接管之前的又一个“最后版本”。没有人知道谁可能是新的维护者。 | ||
修订版 0.4p | 1999 年 6 月 6 日 | 修订者:fare |
清理和更新 | ||
修订版 0.4o | 1998 年 12 月 1 日 | 修订者:fare |
修订版 0.4m | 1998 年 3 月 23 日 | 修订者:fare |
关于 gcc 调用的更正 | ||
修订版 0.4l | 1997 年 11 月 16 日 | 修订者:fare |
LSL 第 6 版发布 | ||
修订版 0.4k | 1997 年 10 月 19 日 | 修订者:fare |
修订版 0.4j | 1997 年 9 月 7 日 | 修订者:fare |
修订版 0.4i | 1997 年 7 月 17 日 | 修订者:fare |
关于从 Linux 访问 16 位模式的信息 | ||
修订版 0.4h | 1997 年 6 月 19 日 | 修订者:fare |
更多关于“如何不使用汇编”;NASM、GAS 更新。 | ||
修订版 0.4g | 1997 年 3 月 30 日 | 修订者:fare |
修订版 0.4f | 1997 年 3 月 20 日 | 修订者:fare |
修订版 0.4e | 1997 年 3 月 13 日 | 修订者:fare |
DrLinux 发布 | ||
修订版 0.4d | 1997 年 2 月 28 日 | 修订者:fare |
新 Assembly-HOWTO 维护者的预告 | ||
修订版 0.4c | 1997 年 2 月 9 日 | 修订者:fare |
添加了 “你需要汇编吗?” 部分。 | ||
修订版 0.4b | 1997 年 2 月 3 日 | 修订者:fare |
NASM 移动了:现在在 AS86 之前 | ||
修订版 0.4a | 1997 年 1 月 20 日 | 修订者:fare |
添加了 CREDITS 部分 | ||
修订版 0.4 | 1997 年 1 月 20 日 | 修订者:fare |
HOWTO 的首次发布 | ||
修订版 0.4pre1 | 1997 年 1 月 13 日 | 修订者:fare |
文本 mini-HOWTO 转换为完整的 linuxdoc-sgml HOWTO,以了解 SGML 工具是什么样的 | ||
修订版 0.3l | 1997 年 1 月 11 日 | 修订者:fare |
修订版 0.3k | 1996 年 12 月 19 日 | 修订者:fare |
什么?我忘记指向 terse 了吗??? | ||
修订版 0.3j | 1996 年 11 月 24 日 | 修订者:fare |
指向法语翻译版本 | ||
修订版 0.3i | 1996 年 11 月 16 日 | 修订者:fare |
NASM 变得非常出色 | ||
修订版 0.3h | 1996 年 11 月 6 日 | 修订者:fare |
更多关于交叉编译 - - 参见 sunsite: devel/msdos/ | ||
修订版 0.3g | 1996 年 11 月 2 日 | 修订者:fare |
创建了历史记录。在交叉编译部分添加了指针。添加了关于 Linux 下 I/O 编程(特别是视频)的部分。 | ||
修订版 0.3f | 1996 年 10 月 17 日 | 修订者:fare |
修订版 0.3c | 1996 年 6 月 15 日 | 修订者:fare |
修订版 0.2 | 1996 年 5 月 4 日 | 修订者:fare |
修订版 0.1 | 1996 年 4 月 23 日 | 修订者:fare |
Francois-Rene "Fare" Rideau 创建并发布了第一个 mini-HOWTO,因为“我已经厌倦了在 comp.lang.asm.x86 上回答同样的问题” |
我要感谢所有贡献了想法、答案、评论和精神支持的人们,以及以下人士(按出现顺序排列)
Linus Torvalds,感谢 Linux
Simon Tatham 和 Julian Hall,感谢 NASM
Greg Hankins 和现在的 Tim Bynum,感谢维护 HOWTO
Raymond Moon,感谢他的 FAQ
Eric Dumas,感谢他将 mini-HOWTO 翻译成法语(对于以英语写作的法国原作者来说,这件可悲的事情)
Paul Anderson 和 Rahim Azizarab,感谢他们帮助我,如果不是接管 HOWTO 的话
Marc Lehman,感谢他对 GCC 调用的见解
Abhijit Menon-Sen,感谢他帮助我弄清楚参数传递约定
本文档的此版本已获得 Leo Noordergraaf 的认可。
根据许可协议,修改(包括翻译)必须删除此附录。
$Id: Assembly-HOWTO.xml 11 2013-03-03 15:47:09Z lnoor $
GNU 自由文档 许可证
版本 1.1, 2000 年 3 月
版权所有 (C) 2000 自由软件基金会 公司.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
任何人均被允许复制和分发本许可证文档的完整副本,但不得对其进行更改。
of this license document, but changing it is not allowed.
本许可证的目的是使手册、教科书或其他书面文档在自由的意义上“自由”:确保每个人都拥有有效自由来复制和再分发它,无论是否对其进行修改,无论是商业性还是非商业性。其次,本许可证为作者和出版商保留了一种方式来获得对其作品的认可,同时不被视为对他人所做的修改负责。
本许可证是一种“著作权共享”(copyleft),这意味着该文档的衍生作品本身必须在相同的意义上是自由的。它补充了GNU通用公共许可证,后者是为自由软件设计的著作权共享许可证。
我们设计本许可证是为了将其用于自由软件的手册,因为自由软件需要自由的文档:一个自由程序应该附带提供与软件相同自由度的手册。但是,本许可证不限于软件手册;它可以用于任何文本作品,无论主题 matter 如何,也无论其是否以印刷书籍的形式出版。我们主要建议将本许可证用于以指导或参考为目的的作品。
本许可证适用于任何手册或其他作品,其中包含版权持有者放置的声明,表明可以根据本许可证的条款分发该手册或其他作品。“文档”(Document),在下文中,指任何此类手册或作品。任何公众成员均为被许可人,并被称为“您”。
文档的“修改版本”(Modified Version)是指包含文档或其一部分的任何作品,无论是逐字复制、还是经过修改和/或翻译成另一种语言。
“次要章节”(Secondary Section)是指文档的已命名的附录或前言部分,其专门处理文档的出版者或作者与文档的总体主题(或相关事项)的关系,并且不包含任何可能直接落入该总体主题范围内的内容。(例如,如果文档部分是数学教科书,则次要章节可能不解释任何数学。)这种关系可能是与主题或相关事项的历史联系,或者是关于它们的法律、商业、哲学、伦理或政治立场。
“不变章节”(Invariant Sections)是指某些次要章节,其标题在声明文档根据本许可证发布的通知中被指定为不变章节的标题。
“封面文字”(Cover Texts)是指某些简短的文本段落,它们在声明文档根据本许可证发布的通知中被列为“封面文字”或“封底文字”。
文档的“透明”(Transparent)副本是指机器可读的副本,以规范对公众可用的格式表示,其内容可以使用通用文本编辑器(对于像素组成的图像,可以使用通用绘画程序;对于绘图,可以使用一些广泛可用的绘图编辑器)直接且直接地查看和编辑,并且适合输入到文本格式化程序或自动翻译成各种适合输入到文本格式化程序的格式。以其他透明文件格式制作的副本,其标记旨在阻止或劝阻读者进行后续修改,则不是透明的。不“透明”的副本称为“不透明”(Opaque)。
透明副本的合适格式示例包括不带标记的纯ASCII、Texinfo输入格式、LaTeX输入格式、使用公开可用的DTD的SGML或XML,以及符合标准的、为人工修改而设计的简单HTML。不透明格式包括PostScript、PDF、只能由专有文字处理器读取和编辑的专有格式、DTD和/或处理工具通常不可用的SGML或XML,以及某些文字处理器为输出目的而生成的机器生成的HTML。
“标题页”(Title Page)对于印刷书籍而言,指的是标题页本身,以及清晰地容纳本许可证要求在标题页中出现的材料所需的后续页面。对于没有标题页格式的作品,“标题页”指的是作品标题最突出的位置附近的文本,位于正文开始之前。
您可以在任何媒介中复制和分发文档,无论是商业性还是非商业性,前提是在所有副本中复制本许可证、版权声明以及声明本许可证适用于文档的许可证声明,并且您不得在本许可证的条款之外添加任何其他条件。您不得使用技术措施来阻碍或控制您制作或分发的副本的阅读或进一步复制。但是,您可以接受报酬以换取副本。如果您分发的副本数量足够大,您还必须遵守第3节中的条件。
您也可以在上述相同条件下出借副本,并且您可以公开展示副本。
如果您出版的文档印刷副本数量超过100份,并且文档的许可证声明要求提供封面文字,则您必须将副本封装在封皮中,封皮上必须清晰且清晰可辨地印有所有这些封面文字:正面封皮上的封面文字和背面封皮上的封底文字。两个封皮还必须清晰且清晰可辨地标明您是这些副本的出版商。正面封皮必须以同等突出和可见的方式呈现完整标题的所有文字。您可以在封皮上添加其他材料。只要封面上的更改仅限于封面,并且保留文档的标题并满足这些条件,则在其他方面可以将其视为逐字复制。
如果任何一个封面的所需文本过于庞大而无法清晰地容纳,您应该将列出的第一个文本(尽可能多地合理容纳)放在实际封面上,并将其余部分延续到相邻页面上。
如果您出版或分发超过100份不透明文档副本,您必须在每个不透明副本中包含一个机器可读的透明副本,或者在每个不透明副本中或随附声明一个公众可访问的计算机网络位置,其中包含文档的完整透明副本,不含任何附加材料,普通网络用户可以使用公共标准网络协议免费匿名下载。如果您使用后一种选择,您必须在开始大量分发不透明副本时采取合理谨慎的措施,以确保该透明副本在声明的位置保持可访问状态,直到您向公众分发该版本的不透明副本(直接或通过您的代理商或零售商)的最后一次分发后至少一年。
建议(但非强制要求)您在重新分发大量副本之前与文档的作者联系,以便他们有机会向您提供文档的更新版本。
您可以根据上述第2节和第3节的条件复制和分发文档的修改版本,前提是您完全在本许可证下发布修改版本,并且修改版本充当文档的角色,从而将修改版本的发行和修改许可给拥有其副本的任何人。此外,您必须在修改版本中执行以下操作:
在标题页(以及封面,如果有的话)中使用与文档标题以及先前版本标题不同的标题(如果存在先前版本,则应在文档的“历史”章节中列出)。如果先前版本的原始出版商允许,您可以使用与先前版本相同的标题。
在标题页上,将对修改版本中的修改负责的一个或多个人员或实体列为作者,以及文档的至少五位主要作者(如果文档的主要作者少于五位,则列出所有主要作者)。
在标题页上声明修改版本的出版商名称,作为出版商。
保留文档的所有版权声明。
在其他版权声明旁边,为您所做的修改添加适当的版权声明。
在版权声明之后立即包含许可证声明,以按照以下附录中所示的形式,授予公众根据本许可证条款使用修改版本的许可。
在该许可证声明中,保留文档许可证声明中给出的不变章节和所需封面文字的完整列表。
包含本许可证的未修改副本。
保留标题为“历史”(History)的章节及其标题,并在其中添加一个项目,至少说明标题页上给出的修改版本的标题、年份、新作者和出版商。如果文档中没有标题为“历史”的章节,则创建一个,说明标题页上给出的文档的标题、年份、作者和出版商,然后添加一个项目,如前一句所述描述修改版本。
保留文档中给出的用于公众访问文档透明副本的网络位置(如果有),以及文档中给出的基于文档的先前版本的网络位置。这些可以放在“历史”章节中。您可以省略早于文档本身至少四年发布的作品的网络位置,或者如果其引用的版本的原始出版商允许。
在任何标题为“致谢”(Acknowledgements)或“献词”(Dedications)的章节中,保留该章节的标题,并在该章节中保留其中给出的每个贡献者致谢和/或献词的所有实质内容和语气。
保留文档的所有不变章节,其文本和标题均不得更改。章节编号或等效内容不被视为章节标题的一部分。
删除任何标题为“背书”(Endorsements)的章节。修改版本中不得包含此类章节。
不要将任何现有章节重新命名为“背书”,或使其标题与任何不变章节的标题冲突。
如果修改版本包含符合次要章节条件的新前言章节或附录,并且不包含从文档复制的材料,您可以选择将其中一些或全部章节指定为不变章节。为此,请将它们的标题添加到修改版本的许可证声明中的不变章节列表中。这些标题必须与其他任何章节标题不同。
您可以添加一个标题为“背书”的章节,前提是它只包含各方对您的修改版本的背书——例如,同行评审声明或文本已获得某个组织的批准,成为标准的权威定义。
您可以在修改版本中封面文字列表的末尾添加一段最多五个单词的文字作为封面文字,以及一段最多 25 个单词的文字作为封底文字。每个实体只能添加一段封面文字和一段封底文字(或通过其安排)。如果文档已经包含同一封面的封面文字,该封面文字 ранее 由您添加或通过您代表的同一实体的安排添加,则您不得添加另一个;但是,经先前添加旧封面文字的出版商明确许可,您可以替换旧的封面文字。
文档的作者和出版商在本许可证中未授权使用其姓名来宣传或声明或暗示对任何修改版本的认可。
您可以将文档与根据本许可证发布的其他文档合并,合并条件如上文第4节中针对修改版本所定义的条款,前提是您在合并中包含所有原始文档的所有不变章节,且不得修改,并在您的合并作品的许可证声明中将它们全部列为不变章节。
合并后的作品只需包含本许可证的一个副本,并且多个相同的不变章节可以用单个副本替换。如果存在多个名称相同但内容不同的不变章节,则通过在其末尾添加(括号内为该章节的原始作者或出版商的姓名,如果已知,否则为唯一编号)使每个此类章节的标题唯一。对合并作品的许可证声明中的不变章节列表中的章节标题进行相同的调整。
在合并中,您必须合并各个原始文档中任何标题为“历史”的章节,形成一个标题为“历史”的章节;同样合并任何标题为“致谢”的章节,以及任何标题为“献词”的章节。您必须删除所有标题为“背书”的章节。
您可以创建一个汇集,其中包含文档和根据本许可证发布的其他文档,并将各个文档中的本许可证单独副本替换为汇集中包含的单个副本,前提是您在所有其他方面都遵循本许可证关于每个文档逐字复制的规则。
您可以从此类汇集中提取单个文档,并根据本许可证单独分发它,前提是您在提取的文档中插入本许可证的副本,并在关于该文档逐字复制的所有其他方面都遵循本许可证。
在存储或分发介质的卷中或卷上,将文档或其衍生作品与其他单独和独立的文档或作品汇编在一起,整体上不应视为文档的修改版本,前提是对该汇编不主张汇编版权。这种汇编称为“聚合”(aggregate),并且本许可证不适用于因此与文档汇编在一起的其他独立的自包含作品,因为它们是如此汇编在一起的,如果它们本身不是文档的衍生作品。
如果第 3 节的封面文字要求适用于本文档的这些副本,那么如果本文档小于整个集合作品的四分之一,则本文档的封面文字可以放在仅围绕集合作品内本文档的封面上。否则,它们必须出现在围绕整个集合作品的封面上。
翻译被视为一种修改,因此您可以根据第 4 节的条款分发本文档的翻译版本。用翻译替换不变章节需要其版权持有者的特别许可,但您可以包含一些或所有不变章节的翻译版本,以及这些不变章节的原始版本。您可以包含本许可证的翻译版本,前提是您也包含本许可证的原始英文版本。如果本许可证的翻译版本与原始英文版本之间存在分歧,则以原始英文版本为准。
除非本许可证明确规定,否则您不得复制、修改、再许可或分发本文档。任何其他复制、修改、再许可或分发本文档的尝试均无效,并将自动终止您在本许可证下的权利。但是,根据本许可证从您处收到副本或权利的当事方,只要这些当事方保持完全合规,其许可证将不会被终止。
自由软件基金会可能会不时发布 GNU 自由文档许可证的新修订版本。这些新版本在精神上将与当前版本相似,但在细节上可能有所不同,以解决新的问题或疑虑。请参阅 https://gnu.ac.cn/copyleft/。
许可证的每个版本都有一个区分版本的编号。如果文档指定本许可证的特定编号版本“或任何更高版本”适用于它,您可以选择遵循该指定版本或自由软件基金会发布的任何更高版本(非草案)的条款和条件。如果文档未指定本许可证的版本号,您可以选择自由软件基金会发布的任何版本(非草案)。
要在您编写的文档中使用本许可证,请在文档中包含本许可证的副本,并将以下版权和许可证声明放在标题页之后
版权所有 (c) 年份 您的姓名。
特此授权复制、分发和/或修改本文档
根据 GNU 自由文档许可证 1.1 版的条款
或自由软件基金会发布的任何更高版本;
不变章节为 列表标题,其中
封面文字为 列表,封底文字为 列表。
许可证的副本包含在标题为“GNU
自由文档许可证”的部分中。
如果您没有不变章节,请写“没有不变章节”,而不是说哪些是不变的。如果您没有封面文字,请写“没有封面文字”,而不是“封面文字为 列表”;封底文字也一样。
如果您的文档包含重要的程序代码示例,我们建议您并行地在您选择的自由软件许可证(例如 GNU 通用公共许可证)下发布这些示例,以允许在自由软件中使用它们。