Cortex-M7——函数及中断异常调用过程
小狼@http://blog.csdn.net/xiaolangyangyang
一、函数调用过程
ARMv8架构下,函数参数是通过R0~R4寄存器传递的,如果参数超过4个,就要借助于栈进行参数传递。
int func(int a1, int a2, int a3, int a4, int a5, int a6)
{
return a1 + a2 + a3 + a4 + a5 + a6;
}
void main(void)
{
func(1, 2, 3, 4, 5, 6);
}
main()函数的汇编如下:
movs r2, #0x6 ; 参数赋值,用于压栈
movs r3, #0x5 ; 参数赋值,用于压栈
sub sp, sp, #0x0C ; 预留栈空间给参数
str r2, [sp, #0x4] ; 参数保存到栈
str r3, [sp] ; 参数保存到栈
movs r2, #0x3 ; 参数赋值给r3
movs r3, #0x4 ; 参数赋值给r2
movs r1, #0x2 ; 参数赋值给r1
movs r0, #0x1 ; 参数赋值给r0
bl 0x80A278 ; 调用func
func()函数的汇编如下:
push {r4-r5, lr} ; 函数用到r4/r5,将原值压栈,lr压栈
ldr r4, [sp, #0x8] ; 将栈保存的参数赋给r4
ldr r5, [sp, #0x0C] ; 将栈保存的参数赋给r5
add r4, r5 ; r4=r4+r5
add r3, r4 ; r3=r3+r4
add r2, r3 ; r2=r2+r3
add r1, r2 ; r1=r1+r2
add r0, r1 ; r0=r0+r1
pop {r4-r5, pc} ; 恢复r4/r5,lr赋值给pc
以上函数对应点的栈结构图如下:
综上所述,ARMv8体系结构下,函数调用流程如下:
1、func调用前:将参数写入R0~R3,大于4个以后的参数压栈;
2、func调用后:将LR、R4/R5压栈,将上个栈帧保存的参数复制到R4/R5进行使用;
3、func返回前:将返回值放入R0,恢复R4/R5,POP LR到PC(即函数返回);
4、func返回后:main函数读取R0中的返回值。
函数调用时硬件不会压栈,进行软件压栈:
1、进入函数后,首先将LR压栈(如果没有调用子函数,则LR不压栈);
2、参数大于4个时,调用函数将多余的参数压入本函数栈帧供被调用函数使用;
3、局部变量较少时,R寄存器处理,不开辟栈空间,变量较多时,调整SP指针预留空间;
4、函数退出时出栈,如果局部变量较多使用了栈,先调整SP释放变量预留空间;
5、函数退出时将栈中的LR值出栈到PC(如果没有调用子函数,直接将LR赋值给PC)。
二、中断异常调用过程
中断异常时硬件自动压栈:
1、中断和异常会进行硬件自动压栈,压栈顺序:xPSR、PC、LR、R12、R0~R3;
2、硬件压栈是在进入中断函数前进行,所以硬件压栈的LR寄存器是原LR,并非进入异常后的LR值(进异常后LR=EXC_RETURN,EXC_RETURN的值包括如下,其他值为非法值);
0xFFFFFFF1:中断返回时从MSP恢复寄存器值,返回后进入Handler模式,使用MSP(中断返回到另一个中断);
0xFFFFFFF9:中断返回时从MSP恢复寄存器值,返回后进入线程模式,使用MSP(不使用PSP只使用MSP堆栈的情况);
0xFFFFFFFD:中断返回时从PSP恢复寄存器值,返回后进入线程模式,使用PSP(OS处理完中断后返回用户程序)。
3、中断函数退出时,执行# bx R14(LR)指令,该指令将LR赋值给PC,PC根据LR判断出栈到MSP还是PSP,恢复CPU模式,出栈内容与压栈内容相同(即将进入中断函数前硬件自动压栈内容出栈,其中包括LR和PC)。