如何在 ARM64 mov 指令中对寄存器进行编码?

How register is encoded in an ARM64 mov instruction?

我想了解在ARM64 mov 指令中,哪些位负责寄存器信息。我使用 clang 编译我的代码,目标是 aarch64 架构。

比如我用下面的机器码得到这条指令:

01418C52 MOVZ            W1, #0x6208

查看文档“Arm Architecture Reference Manual Armv8,for Armv8-A architecture profile”第 C6-1123 页

Rd 是包含文档中指定的寄存器信息的字段:

是通用目标寄存器的 32 位名称,编码在“Rd”字段中。
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。

使用网站 armconverter,我更改了寄存器的值。

我按预期获得了以下代码:

02418C52 MOVZ            W2, #0x6208

从左起的十六进制值(最低位)从 0x01 变为 0x02。 看起来代码是小端,但文档是大端。但是,如果我将寄存器的字母从 W 更改为 X,则会移动另一位。

02418CD2 MOVZ            X2, #0x6208

右边的最后一个值从 0xC52 更改为 0xCD2。为什么?

>>> bin(0xCD2)
'0b110011010010'
>>> bin(0xC52)
'0b110001010010'

根据文档,字段 sf 中的最高位负责根据立即值的大小(32b 或 64b)选择寄存器.

32-bit (sf == 0)

MOVZ <Wd>, #<imm>{, LSL #<shift>}
64-bit (sf == 1)

MOVZ <Xd>, #<imm>{, LSL #<shift>}

但是钻头位置不对。也许我使用了错误的文档。我想了解 32 位指令中的哪个字段负责寄存器值。

谢谢

您的困惑完全归结为字节序。

来自the manual

B2.6.2      Instruction endianness

                In Armv8-A, A64 instructions have a fixed length
                of 32 bits and are always little-endian.

另一方面,反汇编程序有显示原始字节的习惯 - 对于 A64,这是一个相当不幸的选择,但我认为它源于处理可变长度指令集,如 x86(_64) 和ARM/Thumb,这确实有意义。

但简而言之,当您的反汇编程序显示 01418C52 时,这些是原始字节,应读作 0x528c4101
或图形显示:

+------+----------+----------+----------+----------+
| Byte |    01    |    41    |    8C    |    52    |
+------+----------+----------+----------+----------+
| Bits | 00000001 | 01000001 | 10001100 | 01010010 |
+------+----------+----------+----------+----------+
                ^                         ^
                |                         |
Least significant bit           Most significant bit

小端字节序就是这样工作的。

GNU 和 LLVM 工具做对了:aarch64-linux-gnu-objdump -d 显示 528c4102,4 个字节的 32 位整数解释。 llvm-objdump -d 显示 02 41 8c 52,原始字节序列。这两者是等价的,没有误导性。

但是 https://armconverter.com/ 愚蠢地将它分组为 02418C52(在其默认的“GDB”模式下)。这是不好的。如果你想手动编码一些 AArch64 shellcode,你可以使用 .long 0x528c4102(在小端汇编器上,例如 x86、AArch64 或其他)来获得 MOVZ W2, #0x6208.[=21 的表示=]

按照惯例,没有空格的单个数字串具有从右到左增加的位值,并表示具有一定宽度的单个整数值。不是你, https://armconverter.com/ 就是问题所在。

armconverter 有一个“GDB/LLDB”开关,可在 LLDB 模式下将其固定为 528C4102,它称为“big endian”。但它不是“big endian”字节序列,没有空格所以它是 32 位整数值。 02418C52 是将 4 个字节解释为大端字节序(与 AArch64 CPU 所做的相反)时得到的整数,528C4102 是对这些字节的正确小端字节序解释4 个字节。

我认为 armconverter 使用“big endian”实际上是指“删除字节之间的空格之前的字节反转”。这是对术语的脑残滥用。 同样,GNU binutils 和 LLVM 反汇编程序都正确,问题纯粹是 armconverter。

跟进之前的评论和答案

对于这条指令,sf 位永远不会位于第 7 位,它始终位于第 31 位,您发布的文档中的 ARM 视图是查看该指令的唯一正确方法。永远不要尝试字节交换指令的视图。修复数据,甚至更好地使用有效的工具而不是 buggy/broken 工具。

so.s

movz w1,#0x6208

gnu binutils

aarch64-none-elf-as so.s -o so.o
aarch64-none-elf-objdump -d so.o

so.o:     file format elf64-littleaarch64


Disassembly of section .text:

0000000000000000 <.text>:
   0:   528c4101    mov w1, #0x6208  

clang/llvm

clang -c so.s -o so.o
llvm.objdump so.o
    
Disassembly of section .text:

0000000000000000 <$x.0>:
    0: 01 41 8c 52      mov w1, #25096

现在这与 01418c52 不同,间距意味着这些是字节而不是一个完整的单词,这可能表明可能涉及一些字节顺序。我不一定同意 disassemblers 字节交换,它们可能在这种情况下显示字节视图与字或半字视图是的。然后,如果是半字视图,您必须知道它们在 memory/to 处理器中显示的顺序:

mov.w r10,r11

0:  ea4f 0a0b   mov.w   r10, r11

0xEA4F 是本例中指令的前半部分。

clang/llvm 和 binutils 使用相同的文件格式,因此您可以使用 binutils

disassemble clang/llvm 生成的二进制文件
aarch64-none-elf-objdump -d so.o
Disassembly of section .text:

0000000000000000 <.text>:
    0:  528c4101    mov w1, #0x6208                 // #25096

大端有不同的形式。如 armv8

所记录

如果我在地址 0x1000 处有 32 位小端 (default/normal) 字 0x11223344 那么小端 BYTES 视图是

0x1000: 0x44
0x1001: 0x33
0x1002: 0x22
0x1000: 0x11

(不是11223344那个是word视图)

对于big endian,同一时间相同数据的BYTE视图是

0x1000: 0x44
0x1001: 0x33
0x1002: 0x22
0x1000: 0x11

这是相同的,称为字节不变或 BE-8。对于 armv6 和更高版本的 big endian 是 BE-8,字节不变。 (ARMv4 和 v5 是字不变 BE-32)

一个词的访问确实如人们所期望的那样有所不同:

0x1000: 0x11223344 little endian DATA
0x1000: 0x44332211 big endian DATA
0x1000: 0x11223344 little endian INSTRUCTION fetch
0x1000: 0x11223344 big endian INSTRUCTION fetch

Instruction endianness

In ARMv8-A, A64 instructions have a fixed length of 32 bits and are always little-endian.

您正在使用的工具已经损坏,如果该工具的目标是 assemble 并向您显示机器代码,反之亦然,它无法完成这个简单的任务(它显然不能) ,那么我会简单地避开整个网站。如果他们不能做这么简单的事情,那么他们对指令集的理解不够好。他们的“gdb”big endian switch 不是解决方案,它只是又坏了一件东西。

ARM 文档正确,binutils 易于使用。 clang/llvm 有点难,如果你愿意,我可以提供一个构建脚本。尽管 binutils objdump 存在问题,但它仍然是此类工作的最佳工具集。你可以 轻松地在汇编语言和机器代码之间来回切换。

movz w1,#0x6208
.inst 0x528c4101

aarch64-none-elf-as so.s -o so.o
aarch64-none-elf-objdump -d so.o
so.o:     file format elf64-littleaarch64

Disassembly of section .text:

0000000000000000 <.text>:
   0:   528c4101    mov w1, #0x6208                 // #25096
   4:   528c4101    mov w1, #0x6208                 // #25096

(也可以 clang/llvm)

Disassembly of section .text:

0000000000000000 <$x.0>:
       0: 01 41 8c 52   mov w1, #25096
       4: 01 41 8c 52   mov w1, #25096

您可以从您发布的文档段中看到指令以 x1010010 开头,它可以是 0x52 或 0xD2(损坏的)工具显示 02418C52,这很快表明他们可能已经字节交换了机器代码(如果需要,则需要进一步调查你看到这样的东西可能是运气不好)如果你没有在数据中看到 0x52 或 0xD2 那么它不是同一条指令,或者还有其他一些问题。

如果您想查看此体系结构的机器代码,只需使用 binutils 或 clang/llvm 或其他一些易于使用且不会损坏的工具。