C语言调用子函数时入/出栈(保护/恢复现场)全过程分析:以Cortex-M3为例
C语言调用子函数时入/出栈保护/恢复现场全过程分析以Cortex-M3为例0 参考资料工具Cortex M3权威指南(中文).pdf keil5用于仿真查看寄存器、栈变化复制1 C语言调用子函数时出入/出栈保护/恢复现场全过程分析使用C语言调用子函数是如何保护/恢复现场的呢本文以Cortex-M3为例逐行汇编代码分析C语言调用子函数时入/出栈保护/恢复现场全过程。准备工作1使用keil5新建一个基于stm32f103的工程2将栈底值设置为0x20000400Cortex-M3栈从上往下生长也就是将栈大小设置为0x400在分析之前我们需要对寄存器的功能有所了解ATPCS即ARM-THUMB procedure call standardARM-Thumb过程调用标准的简称。ATPCS规定了应用程序的函数可以如何分开地写分开地编译最后将它们连接在一起所以它实际上定义了一套有关过程函数调用者与被调用者之间的协议。规则如下1子程序通过寄存器R0~R3来传递参数.2在子程序中,使用R4~R11来保存局部变量3寄存器R12用作子程序间scratch寄存器,记作ip4寄存器R13用作数据栈指针,记做SP5寄存器R14用作连接寄存器,记作lr ; 它用于保存子程序的返回地址6寄存器R15是程序计数器,记作PC1.1 C代码本文用来调试分析的C代码如下typedef unsigned int u32; u32 fun2(u32 p1, u32 p2, u32 p3, u32 p4) { return p1 p2 p3 p4; } /** * 主函数 */ int main(void) { u32 x; fun2(1, 2, 3, 4); x 0x778899AA; } void SystemInit(void) { }复制说明使用主函数调用fun2子函数查看Cortex-M3的保护、恢复现场过程。1.2 C代码编译生成的汇编指令以上C代码使用0级优化不优化生成的汇编指令如下我们重点关注执行fun2函数前后的操作。1执行fun2函数前传递变量执行fun2函数前首先将第4、3、2、1个参数依次传递给r3、r2、r1、r0寄存器。2跳转到fun2函数参数传递完之后跳转到fun2函数。之所以不先保护现场而是先跳转到fun2函数是因为使用BL指令会自动将跳转指令的下一条指令地址保存到LR。BL function1 ;使用“分支并连接”指令呼叫 function1 ; PC function1并且 LRmain的下一条指令地址也就是说在跳转到子函数前LR寄存器会被设置为子函数下一条指令地址这里保存到LR的并非是0x0800021A也就是fun2函数的下一条指令反而保存的是0x0800021B。这样操作的原因可以参考Cortex M3权威指南(中文).pdf也就是说每次使用跳转指令跳转到子函数时LR保存的实际上是子函数下一条指令地址1 避免产生fault 异常。地址1的值写入PCPC会自动将最低位设置为0。3执行fun2函数前保护现场在执行fun2函数前CPU会执行一条入栈指令PUSH至少会将lr寄存器程序链接寄存器保存了子函数返回地址入栈。使用keil的单步仿真功能我们观察执行这条指令前后栈的变化3.1执行到fun2函数保护现场前栈内从顶到底依次保存了局部变量x此时还未赋值栈内值为0x08000234、mian函数返回地址0x080001D9。此时栈指针值为0x200003F8。3.2PUSH指令依次将lr、r4的值压入栈内。也就是依次向栈顶写入值0x0800021B、0x08000234。此时栈指针值为0x200003F0。此时寄存器组内容如下4执行完fun2函数后恢复现场执行POP指令出栈从栈顶开始依次将栈值写入r4、PC。执行完该语句后栈的内容如下可以看到出入栈的部分已经被绿色标记出来至此就算完成了C语言调用子函数保护/恢复现场操作。此时寄存器组内容如下2 总结本例C语言在调用函数时保护/恢复现场操作如下1保护现场1.1传递函数参数1.2使用BL指令跳转到子函数自动将子函数下一条指令地址保存到LR1.3保护现场至少要将LR程序链接寄存器保存有子函数返回地址入栈2恢复现场至少要将LR程序链接寄存器保存有子函数返回地址出栈将LR的值写入到PC跳转到子函数下一条指令位置继续执行https://download.csdn.net/blog/column/12617739/141761244