add x1, sp, x2, lsl #1
指令应该是“添加(移位寄存器)”,但我在使用 SP 和 XZR 时区分编码时遇到问题。我对结果感到惊讶。
这是“ADD(移位寄存器)”的编码:
31:1, 30:0, 29:0, 28:0, 27:1, 26:0, 25:1, 24:1, 23..22:shift, 21:0, 20..16:Rm, 15..10:imm6, 9..5:Rn, 4..0:Rd
如果我组装
add x1, xzr, x2, lsl #1
,它匹配上面的编码。只是要说明重要的一点:
E1 07 02 8B add x1, xzr, x2, lsl #1
bit 21 = 0;
shift = 0 (lsl);
imm6 = 1
但是如果我组装
add x1, sp, x2, lsl #1
,编码会有奇怪的变化。变化是:
E1 67 22 8B add x1, sp, x2, lsl #1
bit 21 = 1;
shift = 0;
imm6 = 011001 (lsl 25)
所以,我猜第 21 位表示 SP 而不是 XZR。但为什么 imm6 = 25?那是“lsl 25”!
我是不是看错了指令编码?
如果您在操作数中使用
SP
,则编码的指令从“ADD(移位寄存器)”(§ C6.2.5)更改为“ADD(扩展寄存器)”(§ C6.2.3)。这是必要的,因为只有后者支持在其第一个或第二个操作数中使用 SP
或 WSP
。
我在使用 SP 和 XZR 时区分编码有问题。
说明书上说使用了两者中的哪一个。如果使用
SP
,该字段将显示类似 <Xn|SP>
的内容,文本将显示“通用寄存器或堆栈指针”。当使用 XZR
时,该字段显示类似 <Xn>
的内容,文本仅显示“通用寄存器”。你需要知道你正在解码哪条指令才能知道是解码XZR
还是SP
。请注意,有时会有多个指令共享相同的助记符。例如,ADD
助记符由这些共享(忽略可用于同一指令的不同操作数大小):
ADD <Xd|SP>, <Xn|SP>, <R><m>{, <extend> {#<amount>}}
ADD <Xd|SP>, <Xn|SP>, #imm{, <shift>}
ADD <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
ADD <V><d>, <V><n>, <V><m>
您正确地确定该指令是 ARM64 中的“Add(移位寄存器)”。然而,混淆是由于汇编程序在涉及堆栈指针 (SP) 时使用不同的编码,这不是很明显。
指令
add x1, sp, x2, lsl #1
实际上是使用“Add(扩展寄存器)”编码进行编码的。
Add (extended register) 的编码如下:
31:1, 30:0, 29:0, 28:0, 27:1, 26:1, 25:1, 24:1, 23..22:option, 21:1, 20..16:Rm, 15..13:S, 12..10:type, 9..5:Rn, 4..0:Rd
组装
add x1, sp, x2, lsl #1
时,您会得到:
E1 67 22 8B add x1, sp, x2, lsl #1
让我们分解一下重要的部分:
bit 21 = 1;
option = 0;
S = 001 (which is the shift amount);
type = 001 (which corresponds to LSL).
这个编码变化的原因是因为堆栈指针(SP)被用作基址寄存器。当涉及 SP 时,汇编程序更喜欢“添加(扩展寄存器)”编码,以避免堆栈指针的特殊属性出现潜在问题。
您正在查看“添加(移位寄存器)”情况下的正确指令编码,但是当涉及堆栈指针时,汇编程序改为使用“添加(扩展寄存器)”编码。在这种情况下,移位量的编码不同,这就是为什么您会看到 imm6 = 25.