【问题标题】:Encoding JMP FAR and CALL FAR in x86-64在 x86-64 中编码 JMP FAR 和 CALL FAR
【发布时间】:2019-01-20 18:43:57
【问题描述】:

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

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

【问题讨论】:

    标签: assembly x86-64 machine-code instruction-encoding


    【解决方案1】:

    “encode”通常表示机器码字节。但是我认为您是在询问汇编程序语法,因为英特尔的手册对机器代码很清楚。 (请参阅 jmp 的条目,或 Intel's vol.2 instruction set reference manual 的其余部分,了解有关条目的格式和含义的更多信息。)


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

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

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

    NASM 语法:

    jmp far [rdi]        ; for YASM, you need a manual REX prefix somehow
    

    AT&T 语法:

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

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

      400080:       48 ff 2f        rex.W jmp FWORD PTR [rdi]
    

    或者从llvm-objdump -d转换成AT&T语法:

      400080:  48 ff 2f             ljmpq   *(%rdi)
    

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

    FWORD (48-bit far-word = 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
    

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


    NASM 知道 far jmp 需要 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]
    

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

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

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


    实验测试:远跳/呼叫需要 REX 前缀

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

    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]
    
    $ nasm -felf64 farjmp.asm          # or yasm
    $ gcc -nostdlib -static-pie farjmp.o  -o farjmp
    $ ./farjmp
    or  gdb ./farjmp
    
    • 由 YASM 组装(没有 REX.W),在 call far [rsp] 上出现段错误。
    • 由NASM组装,(带有REX.W),成功到达foo:

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

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

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

    jmp far 的用例并不多,尤其是在 64 位模式下。在内核中,您可以使用iretsysret 来返回32 位用户空间,并且通常没有其他理由来切换代码段。我猜你可以让你的内核在内核中切换到 32 位模式。

    【讨论】:

    • 英特尔手册说 JMP far ptr16:16 和 JMP far ptr16:32 在 64 位模式下均不可用。我只是使用 64 位来使事情变得更容易。它说 m16:16、m16:32 和 m16:64 都是可用的……据我所知,近跳不需要指定偏移量以外的任何东西?我到底是如何编码那个 CS 值的?您还可以使用寄存器跳远吗?还是地址?好困惑
    • @RyanBrown:是的,在 64 位模式下没有直接的远跳转。 CS 值不进入指令流,您将其存储在内存中并使用内存间接跳转。像mov [rsp], rdi / mov [rsp+8], si / jmp far [rsp] 做 CS=si 和 RIP=rdi。 (没有寄存器间接远跳转,因为cs:rip 不适合单个 GP 寄存器)。没错,手册将jmp m16:32 列为可在 64 位模式下编码,所以也许您确实需要 REX.W 来获得普通的m16:64。否则手册有误。
    • 所以 JMP m16:64 意味着我必须使用我之前定义的标签 (CS)?是用来跳转其他程序的吗?妈的,这很复杂!远的 JMP 16 和 32 使用相同的操作码,我如何区分它们?
    • 我还有一个简单的问题。是经常使用 Call 还是手动使用堆栈函数?
    • ok 最后一个问题... m16:64 和 m16:r/m64 一样吗?还是只有内存地址?
    猜你喜欢
    • 2018-11-10
    • 1970-01-01
    • 1970-01-01
    • 2013-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多