ART世界探险(4) - 数据传送指令和桶型移位器
来源:互联网 发布:尼古丁的好处 知乎 编辑:程序博客网 时间:2024/04/30 01:19
ART世界探险(4) - 数据传送指令和桶型移位器
数据传送指令
将数在寄存器之间传递,或者将立即数传给寄存器。所谓的立即数,就是直接写在指令里的数,比如MOV X0,100,这个100就是立即数。立即数会存在指令的参数中。
将立即数传给寄存器:MOV
命令格式:MOV 寄存器, 立即数
AArch64状态下例:
MOV X0,100MOV W1,200
AArch32状态例:
MOV R2,50
立即数取非送到寄存器:MVN
格式:MVN 寄存器,立即数
MVN X0,1,相当于X0=~1
我们写一个函数取非,看看编译成汇编指令是什么样子的.源代码如下:
long mvn(long value){ return ~value;}
反编译之后的代码:
; __int64 __fastcall mvn(__int64)EXPORT _Z3mvnl_Z3mvnlMVN X0, X0RET
编译得真棒,就两条指令!
在AArch32下是这样的:
; _DWORD __fastcall mvn(__int32)EXPORT _Z3mvnl_Z3mvnlMVNS R0, R0BX LR
因为long在64位和32位系统上定义不同,所以在AArch64下是64位的,而编成AArch32下变成32位了。
然后我们看看同样的功能用java写出来之后,再通过ART编译之后的结果:
源码:
public static long mvn(long value){ return ~value; }
java字节码:
public static long mvn(long); Code: 0: lload_0 1: ldc2_w #2 // long -1l 4: lxor 5: lreturn
- lload_0,从栈里将函数的实参取出来。
- ldc2_w,从常量池中读出参数-1l.
- 做异或运算。
- 返回长整型。
Dalvik字节码:
DEX CODE: 0x0000: 1600 ffff | const-wide/16 v0, #-1 0x0002: c220 | xor-long/2addr v0, v2 0x0003: 1000 | return-wide v0
Dalvik指令显示优势了,不用去查常量池了,直接立即数放在const-wide指令中。
OAT代码:
CODE: (code_offset=0x005027fc size_offset=0x005027f8 size=80)... 0x005027fc: d1400bf0 sub x16, sp, #0x2000 (8192) 0x00502800: b940021f ldr wzr, [x16] suspend point dex PC: 0x0000
首先将传进来的参数保存一下,x1是传进来的参数那个value,我们把它暂时存在sp+40位置。因为x1还要计算用。
LR的值也先存一下,函数返回的时候还得用呢。存到sp+24中。
0x00502804: f81e0fe0 str x0, [sp, #-32]! 0x00502808: f9000ffe str lr, [sp, #24] 0x0050280c: f90017e1 str x1, [sp, #40]
下面这两句是判断当前状态。
cbnz指令是不为0则跳转,跳转到pTestSuspend过程中去。
0x00502810: 79400250 ldrh w16, [tr] (state_and_flags) 0x00502814: 35000170 cbnz w16, #+0x2c (addr 0x502840)
常量-1,传给x16.
stur,将这个常量-1存到sp+12的内存。
然后再从sp+12x内存把这个常量重新读出来到x0中。
sp+40这个值是我们刚开始进来时将第1个参数保存的地方,大家还记得吧?现在再把它重新装回x1里。
0x00502818: 92800010 mov x16, #0xffffffffffffffff 0x0050281c: f800c3f0 stur x16, [sp, #12] 0x00502820: f840c3e0 ldur x0, [sp, #12] 0x00502824: f94017e1 ldr x1, [sp, #40]
参数终于凑齐了,可以开始进行异或运算了。
ARM中的异或指令的助记符是EOR: eor x2, x0, x1
0x00502828: ca010002 eor x2, x0, x1
异或的结果在x2里,把它暂时保存在sp+12中。
然后再从sp+12中把刚才x2那个计算结果放到x0中。因为返回参数要放在x0中。
从sp+24中再把LR的值读回来,恢复一下栈指针,然后就可以返回了。
0x0050282c: f800c3e2 stur x2, [sp, #12] 0x00502830: f840c3e0 ldur x0, [sp, #12] 0x00502834: f9400ffe ldr lr, [sp, #24] 0x00502838: 910083ff add sp, sp, #0x20 (32) 0x0050283c: d65f03c0 ret
后面这段代码是给前面讲到的cbnz跳转用的。
0x00502840: f9421e5e ldr lr, [tr, #1080] (pTestSuspend) 0x00502844: d63f03c0 blr lr suspend point dex PC: 0x0000 0x00502848: 17fffff4 b #-0x30 (addr 0x502818)
庆祝一下,虽然简单,但是我们已经看懂了一段真正OAT编译生成的代码了!
MOV操作SP
MOV指令操作SP或WSP,其实是ADD X1/SP, X2/SP, #0命令的别名。
例如:
MOV X0,SP
实际上是在执行:
ADD X0,SP,#0
MOV操作通用寄存器
这种情况下的MOV操作,实际上是ORR操作的别名。
例如:
MOV X0,X1MOV W2,W3
分别相当于:
ORR X0,XZR,X1ORR W2,WZR,W3
桶形移位器
在讲其它计算相关的指类之前,我们先看一个ARM芯片中特有的有趣的东西,叫做桶形移位器。
一般的计算操作,都是在算术逻辑单元ALU中完成的。但是ARM芯片在ALU之外,还有一个桶形的移位器,可以对数据进行移位的预处理,再送入到ALU中进行运算。
请注意,这个额外的移位操作是与ALU运算在同一个指令周期中完成的,桶形移位器的加入,增加了数据处理指令的灵活性。
在AArch64状态下,桶形移位器支持4种操作:
* LSL:逻辑左移
* LSR:逻辑右移
* ASR:算术右移
* ROR:循环右移
在AArch32状态下,还支持第5种操作:
* RRX:扩展的循环右移
LSL逻辑左移
左移最省事,不用管符号,就相当于C语言中的:
unsigned long l_shift(unsigned long x0, unsigned long x1){ return x0 << x1;}
我们看看AArch64下反汇编的结果:
; __int64 __fastcall l_shift(unsigned __int64, unsigned __int64)EXPORT _Z7l_shiftmm_Z7l_shiftmmLSL X0, X0, X1RET
太完美了!除了RET,就LSL一句话。
再来看看AArch32模式下的:
; _DWORD __fastcall l_shift(unsigned __int32, unsigned __int32)EXPORT _Z7l_shiftmm_Z7l_shiftmmLSLS R0, R1BX LR
LSL后面的S意思是修改标志位的状态,也就是说,比如移出的位是1,将更新C进位标志。
Java中的左移
我们看看对应的java写法,在java字节码,Dalvik字节码和OAT生成的代码中是什么样子吧:
Java字节码:
Code: 0: lload_1 1: lload_3 2: l2i 3: lshl 4: lreturn
lshl左移指令第二个参数需要int型,所以要多做一步l2i的操作。
Dalvik字节码:
6: long com.yunos.xulun.testcppjni2.TestART.l_shift(long, long) (dex_method_idx=16779) DEX CODE: 0x0000: 8460 | long-to-int v0, v6 0x0001: a300 0400 | shl-long v0, v4, v0 0x0003: 1000 | return-wide v0
OAT代码:
前面的压栈备份,和检测是不是suspend状态的cbnz,上例已经讲过了,这里就不再讲了。
CODE: (code_offset=0x0050295c size_offset=0x00502958 size=92)... 0x0050295c: d1400bf0 sub x16, sp, #0x2000 (8192) 0x00502960: b940021f ldr wzr, [x16] suspend point dex PC: 0x0000 GC map objects: v3 ([sp + #40]) 0x00502964: f81e0fe0 str x0, [sp, #-32]! 0x00502968: f9000ffe str lr, [sp, #24] 0x0050296c: b9002be1 str w1, [sp, #40] 0x00502970: f802c3e2 stur x2, [sp, #44] 0x00502974: f80343e3 stur x3, [sp, #52] 0x00502978: 79400250 ldrh w16, [tr] (state_and_flags) 0x0050297c: 35000190 cbnz w16, #+0x30 (addr 0x5029ac)
sp+52是第二个长整数参数,读到x0中。
然后调用sbfx,带符号的扩展,取32位,结果放到w1中。这就完成了一次long-to-int的计算。
接着再把w1中的值存到栈里,sp+8中。
把第一个参数从sp+44中读出来,放到x0中。
再把刚存进sp+8中的long-to-int的值重新读回w1中,白折腾两趟,哈哈
终于可以运行lsl了,结果在x2中。
把x2存到sp+8中,再从sp+8折腾到x0,准备返回。
0x00502980: f84343e0 ldur x0, [sp, #52] 0x00502984: 13007c01 sbfx w1, w0, #0, #32 0x00502988: b9000be1 str w1, [sp, #8] 0x0050298c: f842c3e0 ldur x0, [sp, #44] 0x00502990: b9400be1 ldr w1, [sp, #8] 0x00502994: 9ac12002 lsl x2, x0, x1 0x00502998: f90007e2 str x2, [sp, #8] 0x0050299c: f94007e0 ldr x0, [sp, #8] 0x005029a0: f9400ffe ldr lr, [sp, #24] 0x005029a4: 910083ff add sp, sp, #0x20 (32) 0x005029a8: d65f03c0 ret
后面还是pTestSuspend的调用。
0x005029ac: f9421e5e ldr lr, [tr, #1080] (pTestSuspend) 0x005029b0: d63f03c0 blr lr suspend point dex PC: 0x0000 GC map objects: v3 ([sp + #40]) 0x005029b4: 17fffff3 b #-0x34 (addr 0x502980)
LSR逻辑右移
相当于C语言中的无符号右移,我们用C语言模拟一下:
unsigned long r_shift(unsigned long x0, unsigned long x1){ return x0 >> x1;}
我们看看翻译的结果:
AArch64的:
; __int64 __fastcall r_shift(unsigned __int64, unsigned __int64)EXPORT _Z7r_shiftmm_Z7r_shiftmmLSR X0, X0, X1RET
果然就被处理成LSR了!
再看AArch32的:
; _DWORD __fastcall r_shift(unsigned __int32, unsigned __int32)EXPORT _Z7r_shiftmm_Z7r_shiftmmLSRS R0, R1BX LR
LSRS,带置位的LSR,非常棒。
ASR算术右移
就是带符号的右移,我们用C模拟一下:
public static long r_shift(long x0, long x1){ return x0 >> x1; }
不出我们所料,就直接是ASR啊。
; __int64 __fastcall r_shift2(__int64, __int64)EXPORT _Z8r_shift2ll_Z8r_shift2llASR X0, X0, X1RET
AArch32下,也就是ASRS:
; _DWORD __fastcall r_shift2(__int32, __int32)EXPORT _Z8r_shift2ll_Z8r_shift2llASRS R0, R1BX LR
Java中对应的指令是lshr,Dalvik指令shr-long,其它跟左移都一样。
循环右移ROR
这个就不多讲了,将右移出去的值补到左边。
扩展循环右移
就是C作为符号位放到最左边。
传送数据与桶移位同时进行
ARM的魔法开始了,我们可以将一个寄存器中的值左移两位,再送到另一个寄存器中:
MVN X0,X1,LSL #2
只需要一个指令周期哟。
以后我们学习了计算指令如加减之类的,也照样可以使用这个桶形移位器,比如一个数加另一个数乘以2的倍数,只要一条加法就搞定了。
小结
这次探险先到这里,我们学习了桶形移位器,这是个可以在ALU运算前对第二个操作数进行操作的神奇器件。
同时也学习了MOV, MVN和几个可以单独使用的移位指令,以及他们对应的java指令。
- ART世界探险(4) - 数据传送指令和桶型移位器
- ART世界探险(5) - 计算指令
- ART世界探险(6) - 流程控制指令
- ART世界探险(7) - 数组
- ART世界探险(18) InlineMethod
- ART世界探险(14) - 快速编译器和优化编译器
- ART探险(1) - oatdump看到的世界
- ART世界探险(8) - 面向对象编程
- ART世界探险(9) - 同步锁
- ART世界探险(10) - 异常处理
- ART世界探险(11) - OAT文件格式分析
- ART世界探险(13) - 初入dex2oat
- ART世界探险(2) - 从java byte code说起
- ART世界探险(15) - CompilerDriver,ClassLinker,Runtime三大组件
- ART世界探险(16) - 快速编译器下的方法编译
- ART世界探险(17) - 中层中间代码MIR
- ART世界探险(19) - 优化编译器的编译流程
- ART世界探险(20) - Android N上的编译流程
- 使用IntelliJ IDEA 15和Maven 开发Spring Data JPA + MVC Web应用
- define,const与inlin
- tjut 4882
- 并查集——求无向图的所有连通子图
- HDU 3709 Balanced Number
- ART世界探险(4) - 数据传送指令和桶型移位器
- Find MaxXorSum 字典树+DP
- MQ学习网站
- Ant+jmeter 实现自动化性能测试
- 二叉搜索树的java实现
- Ubuntu 14.04 安装eclipse4及oracle-java8
- Java NIO使用及原理分析 (四)
- 「前端知识框架」 -- 较全的前端知识
- 有趣的算法