在 x86-64 中编码 JMP FAR 和 CALL FAR

Rya*_*own 3 assembly x86-64 machine-code instruction-encoding

我熟悉 r/m8、r/m16、imm16 等,但如何编码 m16:16、m16:32 和 m16:64?这些在 JMP 和 CALL 指令中......

m16:16 是地址位置吗?或者它就像一个直接地址?任何帮助将不胜感激!

Pet*_*des 6

“编码”通常意味着机器代码字节。但我认为你问的是汇编语法,因为英特尔的手册对机器代码很清楚。(请参阅 的条目,或英特尔第 2 卷指令集参考手册的jmp其余部分,了解有关条目的格式和含义的更多信息。)


jmp m16:64是内存间接远跳转,具有新的 RIP 和 CS 值(按此顺序,因为 x86 是小端)。

就像内存间接近跳转一样,您只需提供一个寻址模式,CPU 就会从那里加载内存操作数。但它是一个 10 字节内存操作数,而不是用于近跳转的 8 个字节。

您可以使用任何寻址模式。我用的[rdi]是简单的。所有这些都与call far/lcall相同。

NASM 语法:

jmp far [rdi]        ; for YASM, you need a manual REX prefix somehow
Run Code Online (Sandbox Code Playgroud)

AT&T 语法:

rex64 ljmp *(%rdi)        # force REX prefix which buggy GAS omits
ljmpq *(%rdi)             # clang accepts this, GAS doesn't.
Run Code Online (Sandbox Code Playgroud)

.intel_syntax noprefix`objdump -drwC -Mintel 的GAS反汇编:

  400080:       48 ff 2f        rex.W jmp FWORD PTR [rdi]
Run Code Online (Sandbox Code Playgroud)

或者从llvm-objdump -dAT&T 语法:

  400080:  48 ff 2f             ljmpq   *(%rdi)
Run Code Online (Sandbox Code Playgroud)

GNU Binutils 错误,它需要48REX.W 前缀才能将操作数大小设置为 64 位。(我认为是内存源操作数。)

FWORD(48 位远字 = m16:32)实际上可能是没有 REX 前缀的正确反汇编,这就是为什么它不是我们想要的,以及为什么如果指向的内存实际上是在没有 REX.W 的情况下它会崩溃一个m16:64。我们需要48 ff 2f一个 TWORD (m16:64) 内存操作数。

GAS 不会组装ljmpq *(%rdi),但 clang 会。


例如,设置 CS=si 和 RIP=rdi

; NASM syntax
mov   [rsp], rdi
mov   [rsp+8], si     ; new CS value goes last because x86 is little-endian
jmp far  [rsp]       ; loads 10 byte from memory
Run Code Online (Sandbox Code Playgroud)

push rsi/ push rdi/ jmp far [rsp],或您想要使用的任何其他内存位置。


NASM 知道远跳转需要 REX.W 前缀,这与 YASM 和 GNU Binutils 不同。它用

; assembled by NASM (not YASM), disassembled with objdump -drwC -Mintel
400080:       48 ff 2f                rex.W jmp FWORD PTR [rdi]
Run Code Online (Sandbox Code Playgroud)

printf '\xff\x2f' | ndisasm -b64 -向我们展示了 NASM 的反汇编输出:

; ndisasm -b64 output thinks it's a dword (m16:16)?
00000000  FF2F              jmp dword far [rdi]
Run Code Online (Sandbox Code Playgroud)

Intel 的手动条目列出jmp m16:64需要 REX.W 前缀,但 GAS / binutils 错误地认为这是不必要的。另请参阅https://lkml.org/lkml/2012/12/23/164lret上有关vs. Linux 内核代码使用的讨论rex64 ljmp *initial_code(%rip),以及有关 AMD CPU 是否支持FF /5REX.W 前缀的猜测。因为 AMD 文档没有明确提及它。


实验测试:远跳/调用需要REX前缀

在 GNU/Linux 上的静态 pie 可执行文件中对此进行了测试(因此它将在低 32 位之外加载),在 Intel i7-6700k Skylake 上:

default rel
foo:
    mov  eax, 231
    syscall              ; exit_group(edi)

global _start
_start:

    mov  eax, cs
    push rax             ; push cs is gone in x86-64
    lea  rax, [foo]
    push rax
    call far [rsp]
Run Code Online (Sandbox Code Playgroud)
$ nasm -felf64 farjmp.asm          # or yasm
$ gcc -nostdlib -static-pie farjmp.o  -o farjmp
$ ./farjmp
or  gdb ./farjmp
Run Code Online (Sandbox Code Playgroud)
  • 由 YASM 组装(没有 REX.W),它在call far [rsp].
  • 由NASM组装(带有REX.W),成功达到foo:

jmp far ptr16:64不存在,或者ptr16:32ptr16:1664 位模式下不可用。这将是一个 10 字节立即(直接)绝对跳转目标。x86-64 根本无法使用绝对直接跳转:无法将新的 CS 或 RIP 编码到指令中jmp

直接近跳使用rel32rel8,当然它们不能更改 CS。(这就是接近的意思)。

32位模式有jmp far ptr16:32(带有6字节立即数)。

的用例并不多jmp far,尤其是在 64 位模式下。在内核中,您可以使用iretsysret返回 32 位用户空间,并且通常没有其他原因需要切换代码段。我想您可以将内核切换到内核中的 32 位模式。