`uxtx` 和 `sxtx` 扩展对于 32 位 AArch64 `adds` 指令意味着什么?

What do the `uxtx` and `sxtx` extensions mean for 32-bit AArch64 `adds` instruction?

我正在查看以下反汇编的 AArch64 指令:

65 6E 20 2B    adds w5, w19, w0, uxtx #3

根据 ARM 手册,uxtxw0 零扩展为无符号 64 位值,然后再将其添加到 w19 中的值。但是w19x19的32位“切片”,结果存储在x5的32位切片中。也就是说,操作值的大小不同。

问题不限adds;其他 AArch64 指令,如 addsub 表现出相同的编码。该问题也适用于 64 位 sxtx 签名扩展,由于符号扩展问题,很可能预计其行为与 32 位 sxtw.

不同

当与 32 位寄存器片一起使用时,uxtxsxtx 的行为是否分别与 uxtwsxtx 完全一样?如果是这样,ARM 通过为这些看似相同的操作同时支持 [us]xtw[us]xtx 扩展编码提供了什么价值?如果不是,是否存在用户程序可见的差异?

他们都做同样的事情,即什么都不做。

正如您所说,从逻辑上讲,对宽度大于操作数大小的值进行符号或 zero-extending 实际上不应影响所使用的值,这是正确的。您可以通过仔细阅读 Architecture Reference Manual 中的伪代码来确认它。在 ExtendReg 的代码中,注意行 len = Min(len, N - shift)。这里 N 是 32,所以 len 是 32 还是 64 没有区别。

同样,对于 32 位或 64 位指令,uxtxsxtx 都是 no-ops。

所以下面的指令都具有完全相同的架构效果,执行操作w0 = w1 + (w2 << 3)。我实际上用 select 的选择和随机输入对它们进行了测试,验证了所有五个的结果和标志是相同的。

   0:   2b224c20    adds    w0, w1, w2, uxtw #3
   4:   2b22cc20    adds    w0, w1, w2, sxtw #3
   8:   2b226c20    adds    w0, w1, w2, uxtx #3
   c:   2b22ec20    adds    w0, w1, w2, sxtx #3
  10:   2b020c20    adds    w0, w1, w2, lsl #3

但是请注意,它们的编码不同。

这也是为什么他们对扩展操作使用不同的助记符:ARM64 汇编语言的原则之一是每个合法的二进制编码 都应该有自己的明确汇编.因此,如果出于某种不明确的原因,您关心是否获得了编码 0x2b224c200x2b226c20——假设您正试图在某些字节被禁止的地方编写 shellcode——您可以指定 uxtwuxtx 到 select 你想要的那个。这也意味着如果你 disassemble 和 reassemble 一段代码,你将取回你放入的相同二进制文件。

(对比 x86 汇编语言中的情况,其中冗余编码不会获得不同的助记符。因此 add edx, ecx 可能 assemble 到 01 ca(“存储形式”)或03 d1(“加载表单”),而 assemblers 通常不会给你任何方式来选择哪个。同样,这两种编码都会将 assemble 改为 add edx, ecx,所以如果你 disassemble 和 reassemble 你可能不会得到与开始时相同的二进制文件。请参阅 How to resolve ambivalence in x64 assembly? 及其重复链接。)

扩展运算符的助记符反映了编码结构,这也有助于解释为什么首先存在冗余编码。扩展类型在指令的第 13-15 位的 3 位“选项”字段中进行编码。第 13-14 位指定要扩展的值的宽度:

  • 00 = 8 位字节 B
  • 01 = 16 位半字 H
  • 10 = 32 位字 W
  • 11 = 64 位双字 X

请注意,X 总是有效的“无扩展名”。然后第 15 位指定符号:0 = 无符号 U,1 = 有符号 S。所以 010 = uxtw011 = uxtx 因为这是他们逻辑上指定的,即使对于 32 位操作,两者都具有相同的实际效果(即 none)。

这似乎是对指令 space 的浪费,但据推测它允许解码器硬件比其他冗余编码 select 一些不同的操作更简单。

上面列出的最后一个选项 adds w0, w1, w2, lsl #3 具有完全不同的编码,因为它 select 是“添加(移位寄存器)”操作码,而不是“添加(扩展寄存器)”操作码就像前四个一样。所以这是另一个冗余;没有扩展的加法,左移 0-4 位,可以用任一操作码完成。不过,这也不是完全没用,因为“扩展寄存器”形式可以使用栈指针寄存器sp作为操作数,而“移位寄存器”可以使用零寄存器xzr/wzr。两个寄存器都被编码为“寄存器 31”,因此每个操作码都必须指定它是将“寄存器 31”解释为堆栈指针还是零寄存器。因此,这两个操作码具有重叠效果这一事实使指令集可以使用堆栈指针或零寄存器提供加法,否则只能支持一个或另一个。


sxt/uxt 语法出现在 ARM64 汇编语言的其他几个地方,每种情况下的细节略有不同。

  1. sxt*/uxt* 指令,它只是简单地用符号或 zero-extend 将一个寄存器写入另一个寄存器。它们是 sbfm/ubfm 位域移动指令特殊情况的别名。 sxtb, sxth, uxtb, uxth 适用于 32 位或 64 位目标,sxtw x0, w1 仅适用于 64 位目标。

    GNU assembler 至少也支持 uxtw w0, w1uxtw x0, w1,尽管官方的 Architecture Reference Manual 没有记录它们。但它们都只是 mov w0, w1 的别名,因为写入 32 位寄存器总是将相应 64 位寄存器的高半部分置零。 (一个有趣的事实是 mov w0, w1 本身就是 orr w0, wzr, w1 的别名,与零寄存器按位或。)

    简单的 uxtx, sxtx 没有助记符,这只是一个 64 位移动。我想逻辑上 uxtx x0, x1 可能是 ubfm x0, x1, #0, #63 的别名,编码为 0xd340fc20,但他们懒得支持它。 addsuxtx 运算符是必需的,因为其他ise 将无法 assemble 0x2b226c20,但由于 0xd340fc20 已经可以通过 ubfm 获得,因此不需要另一个冗余名称。 (实际上好像ubfm x0, x1, #0, #63 disassembles as lsr x0, x1, #0,因为立即移位指令也是bitfield move的别名。)同样,无用的sxtw w0, w1也被拒绝了assembler.

  2. 加载、存储和预取指令的extended-register寻址模式。它们通常采用 64 位基址和变址寄存器 ldr x0, [x1, x2],但也可以将变址指定为具有零或符号扩展名的 32 位寄存器:ldr x0, [x1, w2, uxtw]ldr x0, [x1, w2, sxtw].

    这里又出现了冗余编码。这些指令包含一个 3 位“选项”字段,其位置和格式与 add 和朋友的相同,但此处不支持字节和 half-word 版本,因此位 14 = 0 的编码是不明确的。在其余四种组合中,uxtw (010)sxtw (110) 非常合理。另外两个使用64位索引,没有扩展名,效果一样,但需要指定不同的汇编语法。 110 编码,逻辑上可能是 uxtx,被指定为“首选”编码,并且不使用运算符编写为 ldr x0, [x1, x2],或 ldr x0, [x1, x2, lsl #3] 表示 shifted-index 转移的版本。冗余 111 编码然后 select 与 ldr x0, [x1, x2, sxtx]ldr x0, [x1, x2, sxtx #3]

    编辑
  3. uxtl/sxtl 扩展长 SIMD 指令,其中零或 sign-extend 向量的元素使其原始宽度加倍。这些实际上是 ushll/sshll 长移位指令的别名,移位计数为 0。但除此之外,它们的编码没有任何异常。