在 x86-64 中编码 JMP FAR 和 CALL FAR
Encoding JMP FAR and CALL FAR in x86-64
我熟悉 r/m8、r/m16、imm16 等,但如何编码 m16:16、m16:32 和 m16:64?这些在 JMP 和 CALL 指令中...
m16:16是一个地址位置吗?或者它就像一个即时地址?任何帮助将不胜感激!
“编码”通常表示机器代码字节。但我认为你问的是 assembler 语法,因为英特尔的手册对机器代码很清楚。 (请参阅 jmp
, or the rest of Intel's vol.2 instruction set reference manual 的条目,了解有关条目的格式和含义的更多信息。)
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
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 位远字 = m16:32)实际上可能是没有 REX 前缀的正确反汇编,这就是为什么它不是我们想要的以及为什么它在没有 [=120= 的情况下崩溃的原因] 如果指向的内存实际上是 m16:64
。我们需要 48 ff 2f
作为 TWORD (m16:64) 内存操作数。
GAS 不会 assemble 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 知道远跳转需要 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]
Intel 的手动条目列表 jmp m16:64
需要 REX.W 前缀 ,但 GAS / binutils 错误地认为没有必要。另见关于 https://lkml.org/lkml/2012/12/23/164 关于 Linux 内核代码使用 lret
与 rex64 ljmp *initial_code(%rip)
的讨论,以及关于 AMD CPU 是否支持 FF /5
的猜测带有 REX.W 前缀。由于 AMD 文档没有明确提及它。
实验测试:far jump/call
需要 REX 前缀
我在 Intel i7-6700k Skylake 上 GNU/Linux 上的静态可执行文件中测试了这个(因此它会在低 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:32
或 ptr16:16
在 64 位模式下不可用 。那将是一个 10 字节的立即(直接)绝对跳转目标。 x86-64 根本无法使用绝对直接跳转:无法将新的 CS 或 RIP 编码为 jmp
指令。
直接近距离跳跃使用rel32
或rel8
,当然他们不能改变CS。 (这就是接近的意思)。
32 位模式有 jmp far ptr16:32
(带 6 字节立即数)。
jmp far
的用例不多,尤其是在 64 位模式下。在内核中,您将使用 iret
或 sysret
到 return 到 32 位用户-space,并且通常没有其他理由切换代码段。我想您可以在内核中将内核切换到 32 位模式。
我熟悉 r/m8、r/m16、imm16 等,但如何编码 m16:16、m16:32 和 m16:64?这些在 JMP 和 CALL 指令中...
m16:16是一个地址位置吗?或者它就像一个即时地址?任何帮助将不胜感激!
“编码”通常表示机器代码字节。但我认为你问的是 assembler 语法,因为英特尔的手册对机器代码很清楚。 (请参阅 jmp
, or the rest of Intel's vol.2 instruction set reference manual 的条目,了解有关条目的格式和含义的更多信息。)
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
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 位远字 = m16:32)实际上可能是没有 REX 前缀的正确反汇编,这就是为什么它不是我们想要的以及为什么它在没有 [=120= 的情况下崩溃的原因] 如果指向的内存实际上是 m16:64
。我们需要 48 ff 2f
作为 TWORD (m16:64) 内存操作数。
GAS 不会 assemble 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 知道远跳转需要 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]
Intel 的手动条目列表 jmp m16:64
需要 REX.W 前缀 ,但 GAS / binutils 错误地认为没有必要。另见关于 https://lkml.org/lkml/2012/12/23/164 关于 Linux 内核代码使用 lret
与 rex64 ljmp *initial_code(%rip)
的讨论,以及关于 AMD CPU 是否支持 FF /5
的猜测带有 REX.W 前缀。由于 AMD 文档没有明确提及它。
实验测试:far jump/call
需要 REX 前缀我在 Intel i7-6700k Skylake 上 GNU/Linux 上的静态可执行文件中测试了这个(因此它会在低 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:32
或 ptr16:16
在 64 位模式下不可用 。那将是一个 10 字节的立即(直接)绝对跳转目标。 x86-64 根本无法使用绝对直接跳转:无法将新的 CS 或 RIP 编码为 jmp
指令。
直接近距离跳跃使用rel32
或rel8
,当然他们不能改变CS。 (这就是接近的意思)。
32 位模式有 jmp far ptr16:32
(带 6 字节立即数)。
jmp far
的用例不多,尤其是在 64 位模式下。在内核中,您将使用 iret
或 sysret
到 return 到 32 位用户-space,并且通常没有其他理由切换代码段。我想您可以在内核中将内核切换到 32 位模式。