组装 - 在偏移量为 5 的寄存器/数组中移动 [英] Assembly - Moving through a register/array with an offset of 5
问题描述
快速提问.此代码不会编译:
Quick question. This code will not compile:
mov eax, dword [rbx+rsi*5]
我不指望它,并解释说 mov 和乘法是两种不同的 CPU 操作.可以实现的唯一原因是通过位移.
I don't expect it to, with the explaination that mov and multiplication are two different CPU operations. The only reason it can be achieved is through bit-shifting.
但是,这确实可以编译:
However, this does compile:
mov eax, dword [lst+rsi*5]
lst"是一个变量数组.在上下文中使用时它也会产生输出(因此代码编译和运行).为什么会这样?
With "lst" being a variable array. It also produces output when used in context (so the code compiles AND runs). What's the explanation for why this works?
yasm -Worphan-labels -g dwarf2 -f elf64 NAME.asm -l NAME.lst
推荐答案
x86 寻址模式必须符合 [base + idx*scale + disp0/8/32]
的形式.(或相对于 RIP.)
x86 addressing modes have to fit the form [base + idx*scale + disp0/8/32]
. (Or RIP-relative.)
*scale
实际上编码为 2 位移位计数,因此它可以是 1、2、4 或 8.请参阅关于 [base + index*scale + disp] 的几个问题 和 引用内存的内容地点.(x86 寻址模式)
The *scale
is actually encoded as a 2-bit shift count, so it can be 1, 2, 4, or 8. See A couple of questions about [base + index*scale + disp] and 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 非 PIE 可执行文件;静态代码+数据进入虚拟地址空间的低 2GiB正是这样,这可以工作.)
What's happening here is that your assembler decomposes [lst + rsi*5]
into [lst + rsi + rsi*4]
for you. (Or other scale factors of the form 1 + (1<<0..3)
)
(Where lst
is a 4-byte (32-bit) absolute address that gets sign-extended to 64-bit. And yes this works in Linux non-PIE executables; static code+data goes in the low 2GiB of virtual address space exactly so this can work.)
但是如果您已经有一个基址寄存器,则无法将其拆分并仍然具有可编码的寻址模式.[rbx + rsi + rsi*4]
是不可能的.
But if you already have a base register, there's no way to split it up and still have an encodeable addressing mode. [rbx + rsi + rsi*4]
is impossible.
类似地,NASM 和 YASM 允许您编写诸如 vaddps xmm0, [rbp]
而不是 vaddps xmm0, xmm0, [rbp+0]
(即使 RBP 作为基址寄存器没有位移就不能编码.也省略了第一个源操作数当它与目的地相同时).或者例如编写 [rbp + rax]
而不是 [rbp + rax*1]
- 寻址模式每个基数或索引最多只能有 1 个.
Similarly, NASM and YASM let you write things like vaddps xmm0, [rbp]
instead of vaddps xmm0, xmm0, [rbp+0]
(even though RBP as a base register is not encodeable without a displacement. Also omitting the first source operand when it's the same as the destination). Or for example writing [rbp + rax]
instead of [rbp + rax*1]
- an addressing mode can only have at most 1 each base or index.
当您的代码表达的操作明确且可编码时,汇编程序有时会提供方便的功能,让源代码看起来与机器代码/反汇编代码不同.
When the operation expressed by your code is unambiguous and encodeable somehow, assemblers sometimes have convenient features to let the source look like different from the machine code / what you'd get from disassembly.
mov 和乘法是两种不同的 CPU 操作
mov and multiplication are two different CPU operations
寻址模式do包括加法和移位,尽管shl
和add
也是分开的指令.这不是为什么.此外,imul ecx, [lst + rsi + rsi*4], 12345
是一条有效指令.使用内存源或目标操作数进行类似的移位或相加也是如此.
Addressing modes do include addition and shifting, even though shl
and add
are also separate instructions. That's not why. Also, imul ecx, [lst + rsi + rsi*4], 12345
is a valid instruction. So is a similar shift or add with a memory source or destination operand.
但是是的,x86 寻址模式不能编码任意乘法,只能编码 2 位移位计数.
But yes, x86 addressing modes can't encode arbitrary multiplications, just a 2-bit shift count.
通常你会在寄存器中得到一个指针并在循环内递增它
Normally you'd get a pointer in a register and increment it inside the loop
add rsi, 5*4 ; 5*4 = 20 as an assemble time constant expression
add eax, [rsi]
这基本上是将乘法或移位转换为加法的缩放的强度降低.这意味着您可以使用更高效的简单非索引寻址模式(代码大小,并避免在 Sandybridge 系列上分层.)
This is basically a strength-reduction of the scaling that turns multiplication or shifting into addition. It means you can use simple non-indexed addressing modes which are more efficient (code-size, and avoids unlamination on Sandybridge-family.)
这篇关于组装 - 在偏移量为 5 的寄存器/数组中移动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!