我们都知道x86中的寻址相对于arm稍微简单一些,arm因无法直接操作内存地址,所以需要使用成对的ldr和str指令完成操作,因此,在操作一些常量或者字符串的时候,免不了要寻址,常见的教程版本大概是这样子讲的(摘抄):
.data /*.data段是动态创建的,无法预测 */
var1: .word 3 /* 内存中的变量var1=3*/
var2: .word 4 /* 内存中的变量var2=4*/
.text /* 代码段开始位置 */
.global _start
_start:
ldr r0, adr_var1 @ 通过标签adr_var1获得变量var1的地址,并加载到R0。
ldr r1, adr_var2 @ 通过标签adr_var2获得变量var2的地址,并加载到R1。
ldr r2, [r0] @ 通过R0内的地址获取到该地址处的值(0x03),加载到R2。
str r2, [r1] @ 将R2内的值(0x03)存储到R1中的地址处。
bkpt
adr_var1: .word var1 /* 变量var1的地址位置 */
adr_var2: .word var2 /* 变量var2的地址位置 */
汇编器编译后,反汇编看大概是长这样的:
gef> disassemble _start
Dump of assembler code for function _start:
0x00008074 <+0>: ldr r0, [pc, #12] ; 0x8088 <adr_var1>
0x00008078 <+4>: ldr r1, [pc, #12] ; 0x808c <adr_var2>
0x0000807c <+8>: ldr r2, [r0]
0x00008080 <+12>: str r2, [r1]
0x00008084 <+16>: bx lr
End of assembler dump.
乍一看没什么问题吧?ldr r0, [pc, #12],PC + 12的地址就是变量地址,那么我们找一个真实世界里面的例子,随便一个android arm的so文件好了,原因呢,是因为有人问我ida里面为什么用这种方式表示:
说实话我也只是知道这里是把字符串加载进来,但从来没较真过这个问题,结果认真就输了,问了几个大佬都答得含含糊糊,因为理解不了这个并不影响你写汇编代码,这LDR的反汇编代码为:
LDR R1, [PC, #0x388]
看起来也没什么问题吧,但实际调试的时候你会发现,R1里面是一个偏移!!!而不是真正的内存地址,继续往下看,还有一行ADD R1, PC, R1才完成寻址,这个时候R1的地址才是真正的字符串的地址!!!
IDA这种表示方式只是让人更容易理解,其他反汇编器也好,实际上表示的是加载一个label进来(如下是arm开发手册里写的):
LDR RX, =place
=> LDR RX, [PC, offset_to_litpool]
涉及到字符串池,如果你想快速理解汇编到底做了什么的话,心中默念,上面的汇编代码都是伪指令,右键:
就变成了人能理解的版本,加载字符串池的偏移,add pc完成寻址。