如何在 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 或其他一些易于使用且不会损坏的工具。
我想了解在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 或其他一些易于使用且不会损坏的工具。