3
[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-Relative Addressing,如 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

显然试图从内存地址间接加载一个值会产生不同的代码?我真的不知道。

我只需要帮助理解这些指令的语法和操作,这样我就可以更好地理解为什么要加载有效地址。是的,我想我本可以切换到 EQU 并继续我的快乐之路,但我真的觉得我无法继续,直到我了解 DB 声明和从它的地址加载的情况。

4

3 回答 3

7

答案是它不是。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] 编译您的汇编代码。

于 2012-04-03T12:20:40.360 回答
3

您要求汇编程序以 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 相对寻址模式,而 32 位绝对寻址仍可用于较长的ModR/M + SIB + disp32编码。(当然,SIB 字节不编码基址寄存器和索引寄存器。有趣的事实:在这种情况下未使用比例字段,但 AMD64 选择不对这些位做任何事情)。

rbp 不允许作为 SIB 基础?详细说明了 ModRM 编码中的“转义码”,它可以编码特殊情况,如 SIB-present 或 no-base-reg。

请注意,从 RIP 解码的偏移量实际上是放置数据的绝对静态地址(在 64 位代码中)0x6000e5,.

注释是向您显示有效绝对地址的反汇编程序;RIP 相对寻址从指令后的字节开始计数,即下一条指令的开始。


movabs 0x4b8c289006000e5,%eax

当目标寄存器是 EAX 时,您的汇编器(在 32 位模式下)选择从没有 ModR/M 字节的 32 位绝对地址mov加载的较短编码,只是. 英特尔的手册将此称为(内存偏移)而不是有效地址。eaxA1 disp32moffs

在 x86-64 模式下,该操作码采用 64 位绝对地址。(并且能够从 64 位绝对(非 RIP 相对)地址加载/存储而无需先将地址放入寄存器中是独一无二的)。因此,解码消耗下一条指令的一部分作为 64 位地址的一部分,这就是地址中一些高字节的来源。低 32 位是正确的0x6000e5,它是如何解码为 32 位机器代码的。


改为[bits 32]_[bit 64]

请参阅如果在 64 位代码中使用 32 位 int 0x80 Linux ABI 会发生什么?.

如果您不打算使用本机 64 位系统调用,最好构建一个 32 位可执行文件。使用nasm -felf32,并与 链接gcc -m32 -nostdlib -static

于 2018-03-06T01:48:43.217 回答
0

问题可能是偏移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-relative 仅在有位移时才相关,因此这里没有 RIP-relative。

最后:

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-Relative 寻址。

于 2012-04-03T08:30:08.047 回答