最近回家学驾照了,没有什么时候学习,趁着空隙看下arm平台的逆向,可一上来就发现arm平台的函数调用和x86还是有很大的区别的。
ARM有16个32位的寄存器(r0到r15)。
R15充当程序寄存器PC
R14(link register)存储子程序的返回地址
R13存储的是堆栈地址
ARM有一个当前程序状态寄存器:CPSR
一些寄存器(r13,r14)在异常发生时会产生新的instances,比如IRQ处理器模式,这时处理器使用r13_irq和r14_irq
ARM的子程序调用是很快的,因为子程序的返回地址不需要存放在堆栈中。
1、 ARM处理器共有37个寄存器,其中包括:
i. 31个通用寄存器,包括程序计数器(PC)在内。都是32位寄存器
ii. 6个状态寄存器,都是32位寄存器,但目前只使用了其中12位
2、 ARM处理器有7种不同的处理器模式,在每一种处理器模式中有一组相应的寄存器组。任意时刻(也就是任意的处理器模式下),可见的寄存器包括15个通用寄存器(R0~R14)、一个或两个状态寄存器及程序计数器(PC)。在所有的寄存器中,有些是各模式共用的同一个物理寄存器,有些是各模式自己拥有的独立的物理寄存器。
3、通用寄存器可以分为3类:未备份寄存器(R0~R7)、备份寄存器(R8~R14)和程序计数器PC(R15)。对于每一个未备份寄存器来说,在所有的处理器模式下指的都是同一个物理寄存器。对应备份寄存器R8~R12来说,每个寄存器对应两个不同的物理寄存器,这使得中断处理非常简单。例如,仅仅使用R8~R14寄存器时,FIQ处理程序可以不必执行保存和恢复中断现场的指令,从而使中断处理过程非常迅速。对于备份寄存器R13和R14来说,每个寄存器对应6个不同的物理寄存器,其中的一个是用户模式和系统模式共用的,另外的5个对应于其他5种处理器模式。
4、 每一种异常模式拥有自己的物理的R13。应用程序初始化该R13,使其指向该异常模式专用的栈地址。当进入异常模式时,可以将需要使用的寄存器保存在R13所指的栈中;当退出异常处理程序时,将保存在R13所指的栈中的寄存器值弹出。这样就使异常处理程序不会破坏被其中断程序的运行现场。
5、 寄存器R14又被称为连接寄存器(Link Register,LR),在ARM体系中具有下面两种殊的作用:
i. 每一种处理器模式自己的物理R14中存放当前子程序的返回地址。当通过BL或BLX指令调用子程序时,R14被设置成该子程序的返回地址。在子程序中,当把R14的值复制到程序计数器PC中时,子程序即返回。
ii. 当异常中断发生时,该异常模式特定的物理R14被设置成该异常模式将要返回的地址,对于有些异常模式,R14的值可能与将返回的地址有一个常数的偏移量。具体的返回方式与子程序返回方式基本相同。
int func(int a, int b, int c, int d)
{
return 1;
}
int main()
{
int i = 1, j = 2;
func(i, j, 3, 4);
return 0;
}
使用arm-linux-gcc编译后,使用ida打开:
.text:000083D0 EXPORT main
.text:000083D0 main ; DATA XREF: .text:000082C4o
.text:000083D0 ; .text:off_82DCo
.text:000083D0
.text:000083D0 b = -0x14
.text:000083D0 a = -0x10
.text:000083D0
.text:000083D0 IP = R12
.text:000083D0 FP = R11
.text:000083D0 MOV IP, SP
.text:000083D4 STMFD SP!, {FP,IP,LR,PC}
.text:000083D8 SUB FP, IP, #4
.text:000083DC SUB SP, SP, #8
.text:000083E0 MOV R3, #1
.text:000083E4 STR R3, [FP,#a]
.text:000083E8 MOV R3, #2
.text:000083EC STR R3, [FP,#b]
.text:000083F0 LDR R0, [FP,#a]
.text:000083F4 LDR R1, [FP,#b]
.text:000083F8 MOV R2, #3
.text:000083FC MOV R3, #4
.text:00008400 BL func
.text:00008404 MOV R3, #0
.text:00008408 MOV R0, R3
.text:0000840C SUB SP, FP, #0xC
.text:00008410 LDMFD SP, {FP,SP,PC}
.text:00008410 ; End of function main
可以发现,在main函数中,使用IP(R12)暂时保存栈指针sp,然后使用堆栈操作指令stmfd将栈帧(FP)、IP、程序返回地址(LR)、程序计数器(PC)压栈,以保护现场,然后使用sub fp,ip,#4使fp指向当前函数栈帧的栈底,sub sp,sp,#8,为当前函数局部变量分配看空间。接下来通过寄存器传递参数r1,r2,r3,r4。使用BL指令调用函数,BL指令同时也会将当前指令的下一条指令地址赋给LR,以跳转回来。最后使用ldmfd恢复现场。
.text:000083A0 ; =============== S U B R O U T I N E =======================================
.text:000083A0
.text:000083A0 ; Attributes: bp-based frame
.text:000083A0
.text:000083A0 EXPORT func
.text:000083A0 func ; CODE XREF: main+30p
.text:000083A0
.text:000083A0 var_1C = -0x1C
.text:000083A0 var_18 = -0x18
.text:000083A0 var_14 = -0x14
.text:000083A0 var_10 = -0x10
.text:000083A0
.text:000083A0 MOV R12, SP
.text:000083A4 STMFD SP!, {R11,R12,LR,PC}
.text:000083A8 SUB R11, R12, #4
.text:000083AC SUB SP, SP, #0x10
.text:000083B0 STR R0, [R11,#var_10]
.text:000083B4 STR R1, [R11,#var_14]
.text:000083B8 STR R2, [R11,#var_18]
.text:000083BC STR R3, [R11,#var_1C]
.text:000083C0 MOV R3, #1
.text:000083C4 MOV R0, R3
.text:000083C8 SUB SP, R11, #0xC
.text:000083CC LDMFD SP, {R11,SP,PC}
.text:000083CC ; End of function func
.text:000083CC
.text:000083D0
.text:000083D0 ; =============== S U B R O U T I N E =======================================
子函数func与上面的main函数差不多,这里可以注意下函数中将寄存器传递过来的参数存储在了当前函数的栈帧中。