认真你就输系列 – Arm间接寻址

我们都知道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完成寻址。

发表评论

邮箱地址不会被公开。 必填项已用*标注