通过Keil5,在stm32H743非中断模式下,使用主堆栈指针下的汇编语句查看。理解这个汇编逻辑后,将有利于更自由的进行程序调试,查找BUG。
文章涉及的汇编指令可以在工程中左侧:book→Device Data Books→Cortex-M7 Generic User Guide中查找。
目录
通过调试模式下的Memory窗口,可以看到:
1、单片机是小端模式,
2、入栈是从后往前压,显然出栈应该是从前往后出。
3、栈是向下增长的。
4、从地址上还能看出,栈是存储在RAM中的
这是C语言:
HAL_StatusTypeDef HAL_Init(void)
{
/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
/* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
if(HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
{
return HAL_ERROR;
}
/* Init the low level hardware */
HAL_MspInit();
/* Return function status */
return HAL_OK;
}
这是C语言对应的汇编:
首先,调用者通过跳转指令修改了PC、LR寄存器。
然后,被调用者内如果会调用其他函数,则会把LR寄存器压入栈中,如果有相关运算还需要使用寄存器,则也把这些寄存器压到栈中。程序运行结束时,把通用寄存器推出,并把有LR寄存器压入栈的值推导PC寄存器中。
状态 |
C语言 (箭头表示程序中断的位置) |
黑色汇编,蓝色为说明 |
函数进入 |
→HAL_Init(); |
BL.W HAL_Init(0x08004818) PC=0x800AC5E MSP=0x240268D8 下一指令:0x800AC62 |
函数开始 |
HAL_StatusTypeDef HAL_Init(void) →{ |
PUSH {r4,lr} PC=0x8004818 LR=0x800AC63 MSP=0x240268D8 |
退出结束 |
→ return HAL_OK; }
return HAL_OK; →}
→return HAL_ERROR; } |
MOVS r0,#0x00 B 0x0800482A
POP {r4,pc}
MOVS r0,#0x01 |
可见,第一个返回指令后紧接着一个出栈指令,其他后续返回处用跳转指令到这个第一个出栈指令处。出栈指令对应C语言中的函数结束出的“}”。入栈指令对应函数入口处的“{”。
在汇编窗口处的单步和在C窗口处的单步是不一样的。汇编处的单步可能会比C语言处的步长短一些或同等长度。
这是C语言:
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* Configure the SysTick to have interrupt in 1ms time basis*/
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
/* Return function status */
return HAL_OK;
}
这是C语言对应的汇编:
状态 |
C语言 (箭头表示程序中断的位置) |
黑色汇编,蓝色为说明 |
函数进入 |
→if(HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK) |
MOVS r0,#0x0F BL.W HAL_InitTick (0x08004834) PC=0x08004822 下一条指令:0x8004826 MSP=0x240269D0 |
函数开始 |
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority) →{ |
PUSH {r4-r6,lr} LR:0x8004827 PC:0x08004834 |
退出结束 |
→ return HAL_ERROR; }
return HAL_OK; →}
→ return HAL_OK; }
|
MOVS r0,#0x01
POP {r4-r6,pc}
MOVS r0,#0x00 B 0x08004856 ;即错误返回下面的一个出栈指令处 |
退出后 |
→if(HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK) |
CBZ r0,0x0900482c |
图片来自Arm Cortx-M3与Cortx-M4 权威指南 第三版的第p189页。
1、若参数小于4个,则调用者直接把参数一次写到R0,R1,R2,R3中;参数不足4个时,后面几个寄存器就不修改。若参数大于4个,则调用者通过压栈的方式来存参数输入参数,被调用者通过出栈的方式来获取输入参数。返回值根据其类型,选择使用R0或R1来返回参数。
2、被调用者需要使用的寄存器数自己心里有数。因此可以提前把需要修改的被调用者保存寄存器(R4~R11)压如栈中,返回时再把这些寄存器压出栈。也就是说被调用者对上图右侧的深色寄存器的修改是免责的,包括输入参数所在的寄存器。若调用者的后续程序还需要使用这些不被被调用者保存的寄存器(称为调用者保存寄存器),则需自行保存。
那么返回后调用者的寄存器内的值和被调用前是一致的,因此可以继续运行调用者剩余的程序。
全局变量自增:
全局变量从文本池获取全局变量的地址后,读内存,再修改值,最后协会内存。
/* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
if(HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
{
return HAL_ERROR;
}
/* Init the low level hardware */
HAL_MspInit();
int i =0,j=0;
for(i=0;i<10;i++)
{
j++;
}
i=j;
1、订正了没有区分调用者寄存器和被调用者寄存器的错误。订正单片机名称撰写笔误。
2、添加修改全局变量的例子