有人能解释一下这个直接汇编的x86 JMP操作码吗?

8

在学校里,我们一直使用Bootstrap程序来运行独立的程序,而不需要操作系统。我一直在研究这个程序,当保护模式启用时,会通过直接组合操作码和操作数作为程序内的数据执行far跳转。这是针对GNU汇编器的:


         /* this code immediately follows the setting of the PE flag in CR0 */

.byte   0x66, 0xEA
.long   TARGET_ADDRESS
.word   0x0010          /* descriptor #2, GDT, RPL=0 */

首先,为什么有人想这样做(而不是使用指令助记符)?
我一直在查看英特尔手册,但仍对代码感到有些困惑。具体来说,在第2A卷的第3-549页中,有一个操作码表。相关条目如下:
EA *cp* JMP ptr16:32 Inv. Valid Jump far, absolute, address given in operand
实际的操作码很明显,但第一个字节0x66让我感到困惑。参考英特尔手册中的表格,cp显然表示接下来会有6个字节的操作数。显然,在接下来的两行中,会有6个字节跟随。0x66编码为“操作数大小覆盖前缀”。这与表格中的cp有什么关系呢?我原以为cp会有一些十六进制值,但实际上是这个覆盖前缀。请问有人能帮我澄清一下吗?
以下是od的转储:
c022 **ea66 0000 0001 0010** ba52 03f2 c030
TARGET_ADDRESS定义为0x00010000。
我也有点困惑最后两个字节的意义。但那似乎是另一个问题了。现在已经很晚了,我已经盯着代码和英特尔手册几个小时了,希望我能表达清楚我的观点。
谢谢!

1
人们使用操作码(而不是指令)有两个原因。第一个原因是当汇编器“不够充分”并且不支持他们需要的指令时(当添加新指令时,旧汇编器尚未支持它们时,这种情况很常见)。第二个原因是当汇编器确实支持他们需要的指令,但程序员不知道如何说服汇编器生成它。基本上,要么是糟糕的工具(包括旧工具、混乱的语法和/或糟糕的文档),要么是糟糕的程序员。 - Brendan
注意:我上面的评论是“一般性的”,适用于所有汇编器。我不使用GAS,并且不知道它是否支持“16位代码中的32位远跳转”指令(或文档的好坏)。 - Brendan
3个回答

13

0x66表示JMP(0xEA)引用了六个字节。默认情况下,在实模式下引用64K(16位),在保护模式下引用32位(如果我记得正确的话)。如果它增加,它还包括段描述符,该段在GDT或LDT中的索引,这意味着,这段代码正在执行传统上称为“长跳跃”的操作:在x86架构中跨越段的跳跃。在这种情况下,该段指向GDT上的第二个条目。如果您在程序之前查看,可能会看到如何根据段起始地址和长度定义GDT(请参阅Intel手册以研究GDT和LDT表,32位条目描述每个段)。


啊,现在我明白了。早些时候,在定义GDT时,第一个条目是空的(就像手册上说的那样),但第二个是代码段。重新阅读手册的一些部分后,我明白了这是如何工作的。感谢您澄清这一点。 - Mr. Shickadance
再说,我仍然很好奇作者为什么选择这样做而不是使用助记符。 - Mr. Shickadance
这是操作数大小前缀,但它将jmp ptr16:16转换为jmp ptr16:32。这个答案声称没有前缀版本将是jmp rel16jmp rel32,但那是一个不同的操作码,不是EA而是E9EA始终是带有立即偏移和段的远跳转。 - Peter Cordes

2

我遇到了这个问题。有些汇编程序只会跳转到标签。在这种情况下,人们想要对一个特定的硬编码偏移量进行绝对跳转。我猜jmp TARGET_ADDRESS不会起作用,所以他们只能将其作为字节来解决这个问题。


0

0x66指定当前代码段大小的操作数大小覆盖。假设当前代码大小为16位,则新的指令指针将是32位,而不是16位。如果当前代码段大小为32位,则0x66将使目标指令指针为16位。当前代码大小属性取决于正在使用的CS选择器及其从GDT / LDT表加载的属性。在实模式下,代码段大小通常为16位,除了“非真实”模式的特殊情况。


非保护模式是带有缓存描述符的实模式,其限制仍然设置为64k,从而可以在保护模式和实模式之间切换。32位CS明确表示保护模式,但如果禁用分页,则仍直接使用物理地址。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接