【问题标题】:Assembly - Moving through a register/array with an offset of 5装配 - 通过偏移量为 5 的寄存器/数组移动
【发布时间】:2020-03-24 05:54:09
【问题描述】:

快速提问。此代码将无法编译:

mov eax, dword [rbx+rsi*5]

我不希望它,因为 mov 和乘法是两种不同的 CPU 操作。它可以实现的唯一原因是通过位移。

但是,这确实可以编译:

mov eax, dword [lst+rsi*5]

“lst”是一个变量数组。在上下文中使用时它也会产生输出(因此代码编译并运行)。为什么这有效的解释是什么?

yasm -Worphan-labels -g dwarf2 -f elf64 NAME.asm -l NAME.lst

【问题讨论】:

    标签: assembly x86-64 addressing-mode yasm


    【解决方案1】:

    x86 寻址模式必须符合[base + idx*scale + disp0/8/32] 的形式。 (或 RIP 相关的。)

    *scale 实际上被编码为 2 位移位计数,因此它可以是 1、2、4 或 8。参见 A couple of questions about [base + index*scale + disp]Referencing the contents of a memory location. (x86 addressing modes)

    这里发生的事情是你的汇编器分解了[lst + rsi*5]
    为您输入[lst + rsi + rsi*4]
    (或1 + (1<<0..3) 形式的其他比例因子)
    (其中lst 是一个 4 字节(32 位)绝对地址,可以符号扩展为 64 位。是的,这适用于Linux non-PIE executables;静态代码+数据恰好位于虚拟地址空间的低 2GiB 中所以这可以工作。)

    但是,如果您已经有一个基址寄存器,则无法将其拆分并仍然具有可编码的寻址模式。 [rbx + rsi + rsi*4] 是不可能的。

    同样,NASM 和 YASM 允许您编写 vaddps xmm0, [rbp] 之类的东西,而不是 vaddps xmm0, xmm0, [rbp+0](即使 RBP 作为基址寄存器是 not encodeable without a displacement。当它与目标相同时也省略第一个源操作数)。或者例如写 [rbp + rax] 而不是 [rbp + rax*1] - 寻址模式每个基数或索引最多只能有 1 个。

    当您的代码表达的操作是明确的并且以某种方式可编码时,汇编程序有时具有方便的功能,可以让源代码看起来不同于机器代码/您从反汇编中获得的代码。


    mov 和乘法是两种不同的 CPU 操作

    寻址模式包括加法和移位,尽管shladd 也是单独的指令。这不是为什么。此外,imul ecx, [lst + rsi + rsi*4], 12345 是有效指令。使用内存源或目标操作数进行类似的移位或加法也是如此。

    但是是的,x86 寻址模式不能编码任意乘法,只能编码 2 位移位计数。


    循环遍历任意步幅/元素大小的数组:

    通常你会在寄存器中得到一个指针并在循环内递增它

    add  rsi, 5*4      ; 5*4 = 20 as an assemble time constant expression
    add  eax, [rsi]
    

    这基本上是缩放的强度降低,将乘法或转换为加法。这意味着您可以使用更有效的简单非索引寻址模式(代码大小,并避免 Sandybridge 系列的分层。)

    【讨论】:

    • 谢谢,非常深入!
    猜你喜欢
    • 2018-04-03
    • 2016-11-11
    • 1970-01-01
    • 2014-09-28
    • 2018-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多