最近看ARM的反汇编,计算地址时搞得有点头晕,总结了两天,把总结的结果记录下来。还是那句话,网上的资料很零碎,多不可信。
在ARM的代码中,经常会用到相对偏移,换句话说就是基于PC的寻址,以PC为基准,加上偏移后找到相对地址。听起来似乎是个很简单的事情,但由于ARM指令的多样性,从ARM的指令机器码推算出偏移地址并不是一件简单的事情。
今天用一个例子讨论三种基于PC的寻址方式,相关的规则等等。看一段Thumb指令:
先看0x8964处的指令:BL sub_8B84,对应的机器码是:F90EF000.
第一个问题来了,Thumb指令不是16bit吗?为何这个指令是32bit?原因是ARMv7中引入了新的指令模式Thumb-2,Thumb-2允许16bit和32bit的指令混合使用,添加了一些新指令,即兼顾了ARM体积小速度快的优点,又兼顾了Thumb低功耗的有点。在Thumb-2的模式下如果指令bit[15:11]=11101/11110/11111, 那么这条指令就是32bit,否则就是16bit. 这个case中bit[15:8] = 0xF0, bit[15:11] = 11110, 属于32bit指令。
那么如何通过0xF90EF000实现从0x8964到0x8B84的偏移的?先要查阅Thumb-2中32bit BL指令的格式。经查询,这条指令符合如下格式:
套这个格式的时候,左边的16bit对应指令的低16位,右边的对应高16位。这条指令的参数:S=0,imm10=0,J1=1,J2=1,imm11=0x10E
根据公式算出 imm32 = 0x10E<<1 = 0x21C,因为S=0所以这是一个正数,向后跳转。为何要左移一位?因为Thumb-2指令的长度是2字节或者4字节,那么指令的开始地址最后一位一定是0,所以在指令中把要存储的地址右移一位,节省空间。如果是表示ARM指令的地址,那么就要左移两位,原理是一样的。
回过头来再看PC的值和要跳转的地址:0x8B84-0x8964=0x220,什么?居然不是0x21C?原因是在执行这条指令的时候PC的值并不是0x8964. 由于ARM处理器采用的是流水线技术,使用指令预取,在执行一条指令的时候PC的值是下下条指令的地址。那么下下条指令地址要加多少呢?在ARM模式下是4*2=8,在Thumb模式下是2*2=4. 那么在Thumb-2模式下是多少?也是2*2=4,虽然Thumb-2有些指令是4字节,但依然按照2*2=4来算。
所以0x21C+4=0x220,一切都能说的通了。
再看另外一条指令, 0x895E: BEQ loc_8A40, 机器码:0xD06F, 这是一个16bit的Thumb-2指令。CPU怎么知道这是个16bit的指令的?前面已经说过了,如果这是个32bit的指令,那么bit[15:11]=11010,显然不符合标准。因而这是个16bit指令。有了前面的基础,这个就好推算多了。先查ARM指令表,符合这个格式:
cond=0, imm8=0x6F
imm32 = 0x6F<<1 = 0xDE == 0x8A40-(0x895E+4)
解释圆满
最后看0x895A处的指令:LDR R3,=0x2711,机器码:0x4B3D, 这也是一个16bit的Thumb-2指令。显而易见的是0x2711超过了16bit指令所能忍受的立即数大小,因而0x2711不能直接放在指令里,而是要放在其他地方,用指令里存储的偏移地址来索引。还是一样先查ARM指令表,找到符合的格式:
Rt=3, imm8=0x3D
imm32=0x3D<<2=0xF4
按此推论存放0x2711的地址:0x895A+4+0xF4=0x8A52
查看一下具体地址:
居然是0x8A50,差了两个字节,怎么回事?
再仔细查看ARM指令说明,找到这么一行:
原来这个指令在寻址的时候要求字对齐(4字节),因而会强制把bit[1]设为0,不巧的是我们这个PC原来的值bit[1]刚好是1,所以PC的值被减去了2,又一次真相大白了。
总的来说ARM指令的寻址多种多样,对于每一种寻址方式都需要仔细查阅官方指令表,认真推算才能不出错。
|Archiver|手机版|小黑屋|软路由 ( 渝ICP备15001194号-1|渝公网安备 50011602500124号 )
GMT+8, 2024-5-10 05:22 , Processed in 0.074431 second(s), 5 queries , Gzip On, Redis On.
Powered by Discuz! X3.5 Licensed
© 2001-2023 Discuz! Team.