NASM x86_64在32位模式下组装:为什么该指令产生RIP相对寻址代码?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NASM x86_64在32位模式下组装:为什么该指令产生RIP相对寻址代码?相关的知识,希望对你有一定的参考价值。
[bits 32]
global _start
section .data
str_hello db "HelloWorld", 0xa
str_hello_length db $-str_hello
section .text
_start:
mov ebx, 1 ; stdout file descriptor
mov ecx, str_hello ; pointer to string of characters that will be displayed
mov edx, [str_hello_length] ; count outputs Relative addressing
mov eax, 4 ; sys_write
int 0x80 ; linux kernel system call
mov ebx, 0 ; exit status zero
mov eax, 1 ; sys_exit
int 0x80 ; linux kernel system call
这里的基本要点是我需要将hello字符串的长度传递给linux的sys_write系统调用。现在,我很清楚我可以使用EQU,它会工作正常,但我真的想了解这里发生了什么。
所以,基本上当我使用EQU时,它会加载值,这很好。
str_hello_length equ $-str_hello
...
...
mov edx, str_hello_length
但是,如果我将此行与DB一起使用
str_hello_length db $-str_hello
...
...
mov edx, [str_hello_length] ; of course, without the brackets it'll load the address, which I don't want. I want the value stored at that address
而不是像我期望的那样在该地址加载值,汇编器输出RIP相对寻址,如gdb调试器中所示,我只是想知道为什么。
mov 0x6000e5(%rip),%edx # 0xa001a5
现在,我尝试使用eax寄存器(然后将eax移动到edx),但后来我遇到了另一个问题。我最终得到了一个分段错误,如gdb中所述:
movabs 0x4b8c289006000e5,%eax
显然,不同的寄存器产生不同的代码。我想我需要以某种方式截断高32位,但我不知道该怎么做。
虽然有点找到了'解决方案',但它是这样的:用str_hello_length的地址加载eax,然后加载eax指向的地址内容,一切都是hunky dory。
mov eax, str_hello_length
mov edx, [eax] ; count
; gdb disassembly
mov $0x6000e5,%eax
mov (%rax),%edx
显然试图从mem地址间接加载一个值产生不同的代码?我真的不知道。
我只需要帮助理解这些指令的语法和操作,这样我就可以更好地理解为什么如何加载有效地址。是的,我想我可以切换到EQU并以我的快乐方式,但我真的觉得我不能继续,直到我理解DB声明发生了什么并从它的地址加载。
答案是不是。 x86-64在32位仿真模式下没有RIP相关寻址(这应该是显而易见的,因为RIP不存在于32位中)。正在发生的事情是,nasm正在编译一些你试图以64位运行的可爱的32位操作码。 GDB将您的32位操作码拆分为64位,并告诉您在64位中,这些字节意味着RIP相对的mov。 x86-64上的64位和32位操作码与硅片中的常见解码逻辑重叠很多,你会感到困惑,因为GDB反汇编的代码看起来类似于你写的32位代码,但实际上你只是在处理器上扔垃圾字节。
这与nasm没有任何关系。您正在使用错误的体系结构进行处理。要么在32位进程中使用32位nasm,要么为[BITS 64]编译汇编代码。
您要求汇编程序以32位模式(使用bits 32
)为目标,但是您将该32位机器代码放入64位目标文件中,然后查看将其作为x86-64机器进行反汇编时会发生什么码。
所以你看到了x86-32和x86-64中指令编码之间的差异。即,将32位机器代码解码为64位时会发生这种情况。
mov 0x6000e5(%rip),%edx # 0xa001a5
在这种情况下,关键的一点是32位x86有两种冗余方式来编码32位绝对地址(没有寄存器):有或没有SIB字节。 32位模式没有RIP相关(或EIP相对)寻址。
x86-64将较短的(ModR/M + disp32
)形式重新用作RIP相对寻址模式,而使用较长的ModR/M + SIB + disp32
编码仍然可以使用32位绝对寻址。 (当然,SIB字节不编码基址寄存器而不编码索引寄存器)。
请注意,RIP的偏移量实际上是放置数据的绝对静态地址(以64位代码形式),0x6000e5
。
评论是反汇编程序显示有效的绝对地址; RIP相对寻址从指令之后的字节开始计数,即下一条指令的开始。
movabs 0x4b8c289006000e5,%eax
当目标寄存器是EAX时,汇编程序(在32位模式下)选择较短的mov
编码,该编码从没有ModR / M字节的32位绝对地址加载eax
,只需A1 disp32
。 Intel's manual calls this a moffs
(内存偏移)而不是有效地址。
在x86-64模式下,该操作码采用64位绝对地址。 (并且能够从64位绝对(不是RIP相关)地址加载/存储而不首先将地址放入寄存器中是唯一的)。因此,解码消耗下一条指令的一部分作为64位地址的一部分,而这就是地址中某些高字节的来源。低32位的0x6000e5
是正确的,它是如何解码为32位机器码。
将
[bits 32]
改为[bit 64]
见What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?。
如果您不打算使用本机64位系统调用,最好构建一个32位可执行文件。使用nasm -felf32
,并与gcc -m32 -nostdlib -static
联系。
问题可能是str_hello_length
的偏移量大于32位。 IA-32不支持大于32位的位移。解决方法是使用RIP相对寻址,在(通常是正确的)假设下,RIP与您尝试到达的地址之间的距离适合32位。在这种情况下,基数是RIP
,索引是指令长度,因此如果指令已经有一个基数或索引,则不能使用RIP-Relative。
让我们来看看你的各种尝试:
str_hello_length equ $-str_hello
...
...
mov edx, str_hello_length
这里没有内存访问权限,只需简单地移动即可,因此根本没有寻址。
下一个:
mov eax, str_hello_length
mov edx, [eax] ; count
现在第一条指令是一个立即移动,它仍然不是一个内存访问。第二条指令有一个内存访问,但它使用eax
作为基础,并且没有位移。 RIP相对仅在存在位移时才相关,因此这里没有RIP相关。
最后:
str_hello_length db $-str_hello
...
...
mov edx, [str_hello_length] ; of course, without the brackets it'll load the address, which I don't want. I want the value stored at that address
在这里你使用str_hello_length
作为你的位移。如上所述,这将导致RIP相对寻址。
以上是关于NASM x86_64在32位模式下组装:为什么该指令产生RIP相对寻址代码?的主要内容,如果未能解决你的问题,请参考以下文章
is incompatible with i386:x86-64 output报错