【问题标题】:What is the purpose of GNU assembler directive .code16?GNU 汇编指令 .code16 的目的是什么?
【发布时间】:2020-02-02 11:25:16
【问题描述】:

我不明白.code16 或其他.code* 指令的实际用法。我从this question on StackOverflow 的答案中了解到的,

当有人在他们的汇编代码中定义 .code16 并执行以下操作时:

$ gcc -c -m32 -o main.o main.s

它会忽略.code16,并且输出程序集将在 32 位平台上运行。如果有人没有指定 -m 标志,它似乎将配置为 gcc 的标志作为默认值,具体取决于主机。因此,总而言之,.code* 指令总是被-m 标志忽略并取代。

如果我的理解有误,有人可以纠正我吗?在什么情况下我会使用.code16,因为我总是可以定义使用-m16.code*无论如何都会被忽略,具体取决于目标模式。

.code16(或其他)是否仅在数据无法放入 16 位寄存器时抛出错误,否则它们将保持休眠状态?

【问题讨论】:

  • 不,它不会忽略.code16。它将默认操作数和地址大小设置为 16 位。它生成旨在以 16 位模式运行的代码。它恰好被填充到 32 位 ELF 二进制文件中。
  • @Jester:谢谢。当您说“旨在以 16 位模式运行的代码”时,这到底是什么意思?我无法通过 objdump 确认它,因为它根据传递的 -M 标志解析 ELF。因此,它会为相同的 ELF 生成不同的结果,例如如果 -M 在解析时定义为 32 位而不是 16 位,%ax 将更改为 %eax
  • 另外-m16 仅对gcc 生成的代码有效,如果您有自己的手写程序集则无效。
  • 表示默认操作数和地址大小为 16 位。机器码中有前缀0x660x67切换到其他模式。在 16 位代码中,您不需要 16 位的前缀,但您需要 32 位的前缀。在 32 位模式下,情况正好相反。汇编器需要知道您将在哪种模式下运行,因此它可以生成适当的前缀。 16 位模式下的mov $42, %axb8 2a 00,32 位模式下同样为66 b8 2a 00
  • 是的,-M 标志到objdump 基本上模拟了 cpu 将如何看到指令。所以你可以用它来测试如果你试图在错误的模式下运行代码会发生什么。

标签: assembly x86 linker gnu gnu-assembler


【解决方案1】:

您通常使用.code16.code32.code64 的唯一原因是在内核或引导加载程序中当您希望在同一文件中包含不同模式的机器代码时。例如以实模式 (.code16) 启动的引导加载程序可以启用保护模式和远跳转 (ljmp) 到 32 位代码段。您可能希望在该代码块之前使用.code32

如果这不是你正在做的,请不要使用它们。

在其他情况下使用它们只会让您自取其辱,并将 16 位机器代码放入 32 位或 64 位 ELF 可执行文件中,这样您就会遇到运行时故障,而不是在构建时发现错误。 (例如,因为 push %eax 在 64 位模式下无效)。不要将.code32 放在 32 位程序的顶部;使用说明与 gcc -m32 组装的评论。

这些指令告诉汇编器在解码这些指令时 CPU 将处于何种模式。所以它知道默认操作数大小和地址大小是多少,以及使用 32 位或 16 位寄存器的指令是否需要前缀。

例如,mov %eax, (%ecx) 在 32 位模式下汇编为 89 01

但在.code16 之后,它组装到67 66 89 01

如果您随后将其反汇编为 32 位机器代码,则为 67 66 89 01 mov %ax, (%bx,%di)(因为 ModRM 对于 16 与 32 和 64 位模式下的内存操作数不同)。


您通常不会手动使用.code16。您可以使用 gcc -m16 foo.c 让 GCC 在文件顶部插入 .code16gcc,因此您可以在 16 位模式下运行它,即使它仍将使用 32 位操作数大小和地址大小(需要386 兼容的 CPU)。


如果您想在普通的 64 位程序中包含 32 位或 16 位机器代码作为数据,例如因此您的程序可以将其写入文件或使用它修改正在运行的进程,您也可以使用.code32.code16

【讨论】:

  • 但是当我添加了.code16 并且后续指令是push $10,%eax 时,即使 %eax 是一个 32 位的寄存器名称,汇编程序也不会抛出任何错误。在这种情况下,汇编程序在做什么?
  • 不,它将使用您指定的任何内容。您可以使用完整的 32 位 eax。汇编器将插入前缀以使其如此。
  • @InsaneCoder:不,汇编器使用正确的前缀,因此指令按写入方式解码。 (假设它以您告诉汇编器的相同模式执行)。这就是所有这一切的重点:所以您可以编写 add $12345, %eax 并让它运行为 add $1235, %eax 而不是 %ax 并立即剩余 2 个额外字节(因为在这种情况下指令长度取决于操作数大小)。
  • @InsaneCoder “没有足够的寄存器来保存这些数据”——没有。代码在不支持 32 位模式的处理器上失败(或执行不同的操作)。没有“没有足够的寄存器”这样的事情。
  • @InsaneCoder:是的,阅读 x86 手册,或者我的答案中的示例,它显示了 mov %eax, (%ecx) 如何在 16 位模式下组装。说真的,就像如果你问“2+2 真的是 4?”,那么我们解释它是,然后你问“2*3 真的是 6 吗?”拜托,你读到的哪一部分难以理解,让你想问我?您已经找到了一些完全解释它的文档:“更改指令预期的地址大小。32 位地址可以切换到 16 位,反之亦然”
猜你喜欢
  • 2011-11-09
  • 2023-01-17
  • 2010-10-18
  • 2014-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-19
  • 2019-04-10
相关资源
最近更新 更多