为了深入理解函数的调用过程,我在STM32上使用JTAG来单步调试,观察通用寄存器和函数调用栈帧;
进入bsp_InitUart()之前,PC的值为0x08002B86,等于即将执行的指令地址(此时只是停在了断点处,还没有执行该指令)
进入bsp_InitUart()后,第一条指令:push {r4, lr}
意思是依次将lr, r4寄存器的内容压栈(按照约定,序号高的寄存器放在高地址),lr中保存的是函数bsp_InitUart()退出后的返回地址。
此时lr & pc寄存器的内容如图:
执行 push {r4, lr}指令后,sp -= 8(cortex栈是从高地址往低地址增长的),我们查看sp=0x20009770处的内容:
当bsp_InitUart()函数执行完后,最后一条指令是pop {r4, pc}:
(目的是从栈中读回,进入该函数时压栈保存的返回地址,让PC等于bsp_InitUart()函数的下一条指令,之后sp += 8)
执行完pop {r4, pc}指令后:
(1)sp的值 +=8 变为0x20009778,符合预期
(2)r4的值为0x08007174,符合预期
(3)而pc的值为何变为了0x08002B8A,而不是存在栈中的0x08002B8B???
查阅Cortex M3权威指南:
在第一张图里发生函数调用时,LR的值比预期+1 ,(下一条指令bsp_InitLed()地址为0x08002B8A,但是压入栈中的LR却是0x08002B8B)因为在分支时,无论是直接写 PC 的值还是使用分支指令,都必须保证加载到 PC 的数值是奇数(即 LSB=1),用以表明这是在 Thumb 状态下执行. 因为这个原因,所以在压栈LR时,LR等于下一条指令地址+1.