.code16 和 .code32 x86 程序集的 Objdump

Objdump of .code16 and .code32 x86 assembly

我有这个汇编代码(在Linux):

.globl _start
_start:
  cli                         

  xorw    %ax,%ax             # Set %ax to zero
  movw    %ax,%ds             
  movw    %ax,%es             
  movw    %ax,%ss             

我先在最上面加上.code16生成一个16位的代码,然后用.code32替换生成一个32位的代码。 我用这两个命令编译它们:

gcc -m32 -nostdinc -c file.s
ld -m elf_i386 -o file.exe file.o

然后我用

检查
objdump -d file.exe

对于第一种情况 (.code16),我得到以下输出:

08048054 <_start>:
 8048054:   fa                      cli    
 8048055:   31 c0                   xor    %eax,%eax
 8048057:   8e d8                   mov    %eax,%ds
 8048059:   8e c0                   mov    %eax,%es
 804805b:   8e d0                   mov    %eax,%ss

对于第二种情况 (.code32) 我得到这个输出:

08048054 <_start>:
 8048054:   fa                      cli    
 8048055:   66 31 c0                xor    %ax,%ax
 8048058:   8e d8                   mov    %eax,%ds
 804805a:   8e c0                   mov    %eax,%es
 804805c:   8e d0                   mov    %eax,%ss

我理解 66 操作数前缀部分。令我困惑的是打印的汇编助记符。不应该为 .code32 案例打印 xor %eax, %eax 吗?或者,它应该为 .code16 案例打印 xor %ax, %ax 吗?有人可以澄清一下吗?

.code 16 告诉 assembler 假定代码在 16 位模式下将是 运行,例如为 32 位操作数大小使用 66 操作数大小前缀而不是默认的 16。但是,您将 assemble 和 link 转换为 elf32 二进制文件,这意味着文件元数据仍然表示 32 位代码。 (没有 x86-16 Linux ELF 文件这样的东西)。

Objdump disassembles根据文件元数据,因此为32位代码,unless you override-m i8086。您获得的大小与 32 位反汇编的二进制文件相匹配。

如果您 assemble 一条在 16 位模式下具有不同长度的指令,例如

,您实际上可能会看到破损
add  9,  %ax  # 129 doesn't fit in an imm8

如果assembled作为16位指令,它将没有前缀,并且是一个imm16源操作数。解码为 32 位指令后,它将有一个 imm32 源操作数,它在操作码后面占用更多的总字节数。对于任一模式,操作数大小前缀都会更改指令其余部分的长度(不包括前缀)。顺便说一句,对于这种特殊情况,(预)解码在 Intel CPU 上会变慢,其中前缀对于指令的其余部分是长度变化的。 (https://agner.org/optimize/)

无论如何,用错误的代码大小反汇编该指令将导致 disassembler 与指令边界不同步,因此它将最终测试它正在解释的模式。

如果您正在制作普通用户-space 代码(不是切换模式的内核,或者需要是 16 位的),.code32.code64 是无用的。他们只是让您将机器代码放入错误类型的 ELF 文件中。 (Assembling 32-bit binaries on a 64-bit system (GNU toolchain))


顺便说一句,移动到 %ss 隐含地防止中断,直到 下一条指令之后。 (这应该设置堆栈指针)。你可以这样避免cli/sti