我正在阅读这篇文章 "汇编挑战:不使用寄存器跳转到非相对地址"。
我需要按照他在这里提出的方法(在不使用寄存器的情况下跳转到一个非相对地址)实现,但是我需要用intel语法而不是att。
他为att语法找到的解决方案是:
jmp *0f(%eip)
0: .int 0x12345678
这个在Intel语法中会是什么样子?
我正在阅读这篇文章 "汇编挑战:不使用寄存器跳转到非相对地址"。
我需要按照他在这里提出的方法(在不使用寄存器的情况下跳转到一个非相对地址)实现,但是我需要用intel语法而不是att。
他为att语法找到的解决方案是:
jmp *0f(%eip)
0: .int 0x12345678
jmp *0f(%eip)
。但是这是错误的;在32位模式下没有EIP相对寻址,因此这不是有效的32位汇编。看起来clang 6.0及更早版本存在缺陷,它仍然会接受jmp *0f(%eip)
,但输出显示它实际汇编的指令是jmp *0
,即尝试从绝对地址0(而不是*0f
,即您放置了一些数据的本地标签的地址)加载跳转目标。这不会起作用,只会崩溃,假设页面0未映射,这在正常操作系统下是情况。jmp label(%eip)
使用label
的位移作为绝对地址,这是从下一条指令中获取的,永远不会有用。即在32位模式下编码,好像EIP相对寻址在工作; 在64位模式下,同样的机器码将使用这4个字节的机器码作为rel32相对位移而不是disp32绝对地址。但是x86-64无法改变32位机器码的工作方式,同时保持向后兼容性。因此,作者在这方面是错误的,可能没有真正测试他们提出的代码。
jmp *0f(%rip)
0:
.quad 0x1234567890
是有效的。请注意使用64位程序计数器rip
和使用.quad
获取64位地址。(在这里使用eip
实际上是一个有效的指令,对应于0x67
地址大小覆盖,但它会导致加载地址被截断为32位,这不太可能是所需的。)
RIP相对寻址的英特尔语法因汇编器而异。在NASM中,您将编写:
jmp [rel label]
label:
dq 0x1234567890
jmp [label]
汇编为RIP相关的。请查看您想使用的汇编器的手册。
push $0xdeadbeef # push the constant
ret # pop it into EIP
push 0xdeadbeef
,这是一个内存源操作数,从该绝对地址加载4个字节。mov %eax,0xdeadbeef
(将EAX存储到绝对地址),然后jmp %eax
(GAS将其汇编为jmp *%eax
,警告缺少间接跳转的*
)。.intel_syntax noprefix
可以避免转换为AT&T。该博客引用了他们提出的SO问题,其中出现了相同的示例。@fuz在那里的回答确实纠正了AT&T语法。0:
作为标签名称,那么GAS.intel_syntax noprefix
版本将是jmp [RIP + 0f]
。x86-64 GAS Intel-syntax中类似于"[RIP + _a]"的RIP相对变量引用是如何工作的? - Peter Cordes.text
jmp *0f(%eip)
0: .int 0x12345678
$ gcc -c so_72135694.S
$ objdump -d so_72135694.o
so_72135694.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 67 ff 25 00 00 00 00 jmpq *0x0(%eip) # 0x7
7: 78 56 js 0x5f
9: 34 12 xor $0x12,%al
为什么使用gcc
而不是直接使用as
- 嗯,我太懒了,不想记住as
的选项。
然后,称之为Intel风格解码:
$ objdump -d -Mintel-syntax so_72135694.o
so_72135694.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 67 ff 25 00 00 00 00 jmp QWORD PTR [eip+0x0] # 0x7
7: 78 56 js 0x5f
9: 34 12 xor al,0x12
让我们回头来比较一下:
$ cat so_72135694.intel.S
.intel_syntax noprefix
.text
jmp QWORD PTR [eip+0x0]
0: .int 0x12345678
$ gcc -c so_72135694.intel.S
$ objdump -d so_72135694.intel.o
so_72135694.intel.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 67 ff 25 00 00 00 00 jmpq *0x0(%eip) # 0x7
7: 78 56 js 0x5f
9: 34 12 xor $0x12,%al
$ objdump -d -Mintel-syntax so_72135694.intel.o
so_72135694.intel.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 67 ff 25 00 00 00 00 jmp QWORD PTR [eip+0x0] # 0x7
7: 78 56 js 0x5f
9: 34 12 xor al,0x12
你可以很容易地看出它们是相同的,而且你可以针对所有类似的问题使用此方法。
注1:注意,Unix binutils 对于“Intel语法”的解释与Intel本身的想法(甚至在语法基础上,如0x1234
vs. 1234h
)以及像NASM或FASM等广泛流行的工具有微小的差异。在这里,我假设如果你谈到AT&T语法,则使用最典型的Binutils包(GNU one)(我的系统是Ubuntu 20.04 / x86-64,几乎是最流行的) 。如果我在这里错了,请随意探索其他工具的细节。
注2:你代码中真正令人困惑的事情是使用EIP相对寻址。这种寻址方式只能用于64位模式,但在这种情况下使用EIP很奇怪。将其编译为32位模式(例如使用.code32
)自然会失败。
0x12345678
应该是64位。 - Nate Eldredgejmp dword ptr [RIP + 0f]
汇编为66 ff 2d 00 00 00 00 ljmpw *0x0(%rip)
-一个16位操作数大小的far
跳转,加载新的CS:IP! NASM拒绝汇编jmp dword [rel foo]
。这是有道理的,因为https://www.felixcloutier.com/x86/jmp显示,在64位模式下不支持`jmp r/m32`。 - Peter Cordes%eip
导致地址大小覆盖可能是OP的错误,应该只使用%rip
。 - Nate Eldredgejmp 0f(%eip)
,这是完全错误的。对于64位模式,他们使用带有64位地址的jmp 0f(%rip)
,一切都很合理。 - Nate Eldredge
jmp dword [rel foo]
。 (在32位模式下不可用PC相关寻址) - Peter Cordesjmp *0f(%eip)
。但是它不起作用,因为在32位模式下没有EIP相对寻址可用。反汇编显示,汇编器实际上给出的只是jmp *0x0
,即跳转目标从绝对地址0加载,这当然会崩溃,因为空指针引用。(可能有一个重定位要求链接器填写“0f”标签的绝对地址,但如果需要位置无关代码,则这并没有帮助。) - Nate Eldredgejmp *0f(%rip) ; 0: .quad 0x1234567890
是可以的,但请注意它引用了rip
而不是eip
(应该这样做,32位地址大小是可能的,但几乎肯定不是您想要的),并使用.quad
来组装完整的64位地址。 - Nate Eldredge