现在大部分手机cpu架构是ARM v7-A和ARMV8-A,,在ARM-v7A中常使用32位ARM指令集并且支持thumb指令集与arm的切换,而在ARMV8中使用的是64位ARM指令集且不再有thumb指令集状态的切换了。在调用函数时,会有常用的调用方式:BL和B,且分三种情况arm, thumb, aarch64,而对于BLX在arm64指令集中不再有。下面对这三种情况进行讨论。
常见跳转指令机器码:
B:0xEA
BL:0xEB
偏移地址计算过程:
(目标地址 - 指令地址 - 8)/ 4 = 偏移
// 减8,指令流水造成。
// 除4,因为指令定长,存储指令个数差,而不是地址差。
完整指令:
.text:0000D11C D9 51 00 EB BL __set_tls
.text:00021888 __set_tls
计算偏移:
(21888-D11C - 8) /4 = 0x51D9
EB000000 | 0x51D9 = EB0051D9
thumb指令都是2字节。BL看起来像四字节指令,其实是个误解,因为长跳转是由两条跳转指令组成的。
0-11位表示11位地址,具体含义如下:
第11位为0,代表偏移高位
第11位为1,代表偏移低位
完整指令:
.text:0002E3C2 F2 F7 A2 ED BLX nanosleep
.text:0002E3C6 38 B1 CBZ R0, locret_2E3D8
.text:0002E3C8 E6 F7 66 F8 BL __errno
.text:0002E3CC 01 68 LDR R1, [R0]
.text:00020F08 nanosleep
.text:00014498 __errno
先讲BL指令:
0002E3C8 E6 F7 66 F8 BL __errno
00014498 __errno
如何得到目标地址的呢,计算方式如下:
解析偏移
F7E6(1111011111100110) 第11位为0,所以代表高位偏移
F866(1111100001100110)第11位为1,所以代表低位偏移
最高位:F7E6 取后11位 7E6
最低位:F866 取后11位 66
7E6 << 12(十进制) = 7E6000
66 << 1 = CC
7E6000 | CC = 7E60CC
计算出来7E60CC最高位符号位为1,代表向前跳转,需要-1然后取反,得到数值为FFFFFFFFFF819F34,取19F34,
2E3C8+ 4 - 19F34 = 14498
若计算出来的数符号位为0,则直接保留该值,然后后面相加而不是减。
那偏移0xF866F7E6又该如何计算出来呢,计算方式如下:
offset = (目标地址- 源地址 -4) & 0x007fffff = 7E60CC
high = offset >> 12(十进制) = 7E6
low = ( offset & 0x00000fff )>>1 = 66
machineCode = ((0xF800 | low) << 16) | (0xF000 | high)
=F8660000 | F7E6 = 0xF866F7E6
注意F800相当于高偏移取第11位到第15位, F0000相当于取低偏移第11位到第15位。
再讲BLX指令:
0002E3C2 F2 F7 A2 ED BLX nanosleep
00020F08 nanosleep
那偏移0xEDA2 F7F2 如何得到的呢,跟BL算法稍微不同:
offset = (目标地址- 源地址 -4) & 0x007fffff = 7F2B42
high = offset >> 12 = 7F2
low = ( offset & 0x00000fff )>>1 = 5A1
if(low % 2 != 0) {
low++;
}//low=5A2
machineCode = ((0xE800 | low) << 16) | (0xF000 | high);
=EDA20000 | F7F2 = EDA2 F7F2
B:0x17向前跳转,0x14向后跳转
BL:0x97向前跳转 0x94向后跳转
偏移地址计算过程:
(目标地址 - 指令地址)/ 4 = 偏移
// 减8,指令流水造成。
// 除4,因为指令定长,存储指令个数差,而不是地址差。
完整指令:
.text:000000000008CC84 8D B3 FF 97 BL je_arena_malloc_hard
.text:0000000000079AB8 je_arena_malloc_hard
计算偏移:
(79AB8-8CC84) / 4 = FFFFFFFFFFFFB38D
FFB38D | 0x97000000 = 97FFB38D