MOV(wide immediate)
MOV 可以将一个立即数移动到寄存器中。
.text:0000000000000834 80 46 82 D2 MOV X0, #0x1234 ; Keypatch modified this from:
MOV X0, #0x1234
对应的汇编代码为:80 46 82 D2
看手册可知,0x1234 的值储存在 5 - 20 位里面。
汇编指令对应的二进制为:
80 46 82 D2 ->
D2 82 46 80 ->
11010010 10000010 01000110 10000000 ->
1 10 100101 00 0001 0010 0011 0100 00000
其中最低5为都是0,所以寄存器是 X0 寄存器。
5-20 位的值分别是 4 3 2 1,刚好就是 0x1234。
这种 mov 指令还是比较常见的,但是由于指令的限制,它只能描述16位的立即数。
看另外的一个例子:
.text:0000000000000834 00 00 B0 D2 MOV X0, #0x80000000
MOV X0, #0x80000000
这条指令的立即数显然已经超过了16位,这又是咋回事呢?
这是 MOV 指令的另一种形式,由于 #0x80000000
这样的立即数实际的有效位数很少,所以ARM指令集对其表示做了优化。
手册里面有说到,MOV 实际上等同于 MOVZ:
而 MOVZ 是支持 shift 写法的:
MOVZ <Xd>, #<imm>{, LSL #<shift>}
所以上面的汇编实际上等同于:
MOVZ X0, #0x8000, LSL #16
汇编指令对应的二进制为:
00 00 B0 D2 ->
D2 B0 00 00 ->
11010010 10110000 00000000 00000000 ->
1 10 100101 01 1000 0000 0000 0000 00000
看其 hw 对应的位是 01,所以需要将 0x8000左移 1*16 位,得到 0x80000000。
hw 表示的是左移的位数,为 16 * (0~3)。
MOV (register)
MOV 可以将寄存器的值赋值给另一个寄存器。
Rm 与 Rd 都占据了5位,刚好可以描述所有的寄存器,MOV <Xd>, <Xm>
。
shift 有2位,这里固定是零值。imm6 与 Rn 也都是固定的值。
MOV 有个别名指令,ORR,它可以用到 shift,imm6,Rn的值。
看一个指令,MOV X0, X1
:
E0 03 01 AA MOV X0, X1
汇编指令对应的二进制为:
E0 03 01 AA ->
AA 01 03 E0 ->
10101010 00000001 00000011 11100000 ->
1 01 01010 00 0 00001 000000 11111 00000
所以,Rd 的值是 0,表示 X0 寄存器,Rm 的值是1,表示 X1 寄存器。
顺便说一下 ORR 指令:
是将寄存器中值按位做“或”操作。
ORR <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
Xm还可以做移位操作,具体移动多少位由 imm6 决定,怎么移动由 shift 决定。
MOVK
由于ARM64指令集是定长的4字节指令,这就决定了它无法在指令里面描述一个任意的数字,比如:0x123456789,这个就无法在指令中表示。
那么,当我们在程序中使用到这个数字的时候,arm64指令集是如何处理的呢?
我们写一个例子看看:
uint64_t num = 0x123456789abc;
printf("num = %llx\n", num);
查看汇编代码:
.text:00000000000008AC 81 57 93 D2 MOV X1, #0x9ABC
.text:00000000000008B0 00 00 00 90 ADRP X0, #aNumLlx@PAGE ; "num = %llx\n"
.text:00000000000008B4 01 CF AA F2 MOVK X1, #0x5678,LSL#16
.text:00000000000008B8 00 00 24 91 ADD X0, X0, #aNumLlx@PAGEOFF ; "num = %llx\n"
.text:00000000000008BC 81 46 C2 F2 MOVK X1, #0x1234,LSL#32
我们只看 X1 相关的指令,它分为3个步骤:
MOV X1, #0x9ABC
MOVK X1, #0x5678,LSL#16
MOVK X1, #0x1234,LSL#32
通过数据的拼接来达成目标,非常的合理。