汇编 32 位寻址大小而不是 64 位模式下的 64 位

Assembly 32-bit addressing size instead of 64-bit in 64-bit Mode

我对 64 位模式下的 32 位寻址大小而不是 64 位寻址大小有疑问

func:
    movzx    eax, al  ; instead of movzx rax, al
    mov      eax, DWORD [4 * eax + .data]  ; instead of mov rax, QWORD [8 * rax + .data]
    ret

    .data:
         DD .DATA1     ; instead of DQ
         DD .DATA2     ; instead of DQ
         DD .DATA3     ; instead of DQ
         DD .DATA4     ; instead of DQ

.DATA1     DB 'HEY1', 0x00
.DATA2     DB 'HEY2', 0x00
.DATA3     DB 'HEY3', 0x00
.DATA4     DB 'HEY4', 0x00

它在 64 位 中是否安全?因为我认为在 64 位和这样寻址,没有问题! (我这样做是因为 .data)

我认为 .data 如果程序大小(可执行文件)小于 32 位寄存器,每个项目地址都适合大约 100 Mb 总是 !

例如,这在 x86-64 MacOS 或 Linux PIE executable 上是不安全的。程序大小不是唯一的因素,因为它不是从虚拟地址 0 开始加载的。您的程序的第一个字节可能位于 0x555555555000 之类的位置,因此无论您的程序有多小,将地址截断为 32 位都会破坏您的代码。

(在这种情况下使用 [.data + rax*4] 会导致无效的重定位链接器错误,不过,只是将 .data 用作绝对值 disp3232-bit absolute addresses no longer allowed in x86-64 Linux? ).但是,如果您在 RDI 中将 [edi + eax*4] 与有效指针一起使用,您可以编写 assemble 但在 PIE executable 或 MacOS executable 中崩溃的代码。 )

但是是的,默认的非 PIE Linux 代码模型将所有代码和静态数据放在虚拟地址的低 2GiB space 所以 32 位绝对符号-或零扩展数字可以表示地址。


无论您如何寻址,内存中的数据都是相同的大小,因此您的替代方案是

 movzx    eax, al
 mov      eax, DWORD [4 * eax + table_of_32bit_pointers]  ; pointless
 mov      eax, DWORD [4 * rax + table_of_32bit_pointers]  ; good

 ; RAX holds a zero-extended pointer.

mov rax, QWORD [8 * rax + .data] 将从不同的位置加载 8 个字节。您仍在混淆地址大小和操作数大小。

在内存中使用紧凑的 32 位指针并不意味着您在加载它们时必须使用 32 位地址大小。

在使用 movzx eax, al. 将索引零扩展到 64 位后,没有理由使用 32 位地址大小(顺便说一句, prefer movzx ecx, al; mov-elimination 只在不同的寄存器之间起作用。)

顺便说一句,如果你的字符串都是相同的长度,或者你可以廉价地将它们填充到固定长度,你不需要 table 指针。您可以改为只从第一个字符串 + 缩放索引的开头计算地址。例如p = .DATA1 + idx*5 在这种情况下,每个字符串的长度为 5 个字节。

lea  eax, [.DATA1 + RAX + RAX*4]    ; 4+1 = 5
; eax points at the selected 5-byte string buffer

此外,不要使用 .data 作为符号名称。这是一个部分的名称,所以会让人感到困惑。