定位分析boot+app带freertos跳转跑飞

描述情况:

boot程序存储memory,起始地址0x08000000前4 Bytes为堆栈地址(0x20003d30),boot运行程序地址(0x08008a59)
在这里插入图片描述app程序存储memory,0x0800a010起始地址前4 Bytes为堆栈地址(0x20003e50),app运行程序地址(0x0801830d)
在这里插入图片描述boot跳转前的操作:复位开启过的外设,关闭总中断,设置MSP和PSP指向app的堆栈地址,执行跳转到app的运行程序地址(0x0801830d)也就是Reset_Handler地址。关于程序在main函数前都执行了哪些操作和中断向量表,本文不做叙述。

/* boot跳转前操作 */
void HalOTAJumpApp(uint32 addr)
{
  typedef void(*pfun)(void);
  static pfun jumpToApp;  
  __IO uint32 jumpAddr;

  /* 逐个复位开启过的外设 */
//  HAL_DeInit();
//  HAL_RCC_DeInit();
//  HAL_ADC_DeInit(&hadc);
//  HAL_UART_DeInit(&huart3);
//  HAL_CAN_DeInit(&hcan);
  __disable_irq();
   
  if (((*(__IO uint32 *)addr) & 0x2FFE0000) == 0x20000000)
  {
    jumpAddr = *(__IO uint32 *)(addr + 4);
    jumpToApp = (pfun)jumpAddr;
    
    __set_MSP(*(__IO uint32 *)addr);
    __set_PSP(*(__IO uint32 *)addr); 

    jumpToApp();
  }
}
    /* app进入main函数后操作 */
    VectorTableOffset(); /* 重映射中断表 */
    __enable_irq();  	 /* 使能总中断 */

仿真调试boot程序查看,执行跳转app运行程序起始地址jumpToApp();后发现app程序没有正常跑起来,暂停仿真运行看看程序现在是停在哪个地方?通过查看Disassembly窗口程序停留在0x08017fd2地址位置。
在这里插入图片描述根据app程序在memory是从0x0800a010起,也就是说boot跳转到app中程序的某个位置。让我们仿真app程序看看这个位置是哪里?结果一看是进入了app的HardFault_Handler(0x08017D2)
在这里插入图片描述
为什么进入HardFault_Handler(0x08017D2)呢?app程序在进入main函数后先会执行中断表重映射和使能总中断。app程序搬运新的中断向量表这是必做的,否则app产生中断服务程序无法正常跳转到对应的中断入口。而此时跳转过后进入HardFault_Handler(0x08017D2)说明程序已经运行到app中,但却因为未知原因导致运行异常。

测试验证:
  • 既然进入app后发送异常,那不开启app总中断使能来关闭中断触发是否可行?奇迹发生了,居然能正常跳转到app程序运行起来了。但这是什么原因呢?查看在开启总中断使能后app程序执行了哪些会引起中断触发或许能发现些什么?关注到与中断异常可能有关函数HAL_Init();SystemClock_Config();先看HAL_Init();功能描述:Reset of all peripherals, Initializes the Flash interface and the Systick. 进入HAL_InitTick(TICK_INT_PRIORITY);函数看到先是配置TIM6_DAC_IRQn等其他中断而后在SystemClock_Config();中最后去设置SysTick_IRQn作为系统时基。这与CubeMX默认生成的boot工程在HAL_Init();中直接配置SysTick_IRQn为系统时基不一样。

    解释了之前测试跳转新建app工程能够跳转成功的原因是不会因为配置了不同的中断导致因找不到中断入口而异常,因为boot工程也是由CubeMX生成的配置,而原来的app工程有做了配置更改。

    解释了为什么在app中把设置中断表和总中断开启都注释后,会跳转卡死在TIM6_DAC_IRQn上,但该中断地址却是在boot地址范围内,猜测是进入到app后堆栈指针没有重新初始化,继续使用的是boot中的堆栈指针,否则就算没有新的中断表也只是进入app的HardFault_Handler(0x08017D2)不会跳回boot的中断向量表上。
    在这里插入图片描述
    在这里插入图片描述

  • 跳转函数前注释__disable_irq();跳转过去app后又进入到HardFault_Handler(0x08017D2),进入到app中后会开启总中断,在HAL_Init();SystemClock_Config();中会配置各种中断使能,正常来说跳转过来后触发中断后就会跳转到新的中断表上,但是却依然进入异常。而通过仿真调试app就可以正常运行。那原因只有可能是堆栈指针未初始化,依然使用boot的堆栈指针,可能与app程序的全局变量混到一起,执行情况没法预知就会出现异常现象。

  • 跳转函数前注释__set_PSP(*(__IO uint32 *)addr);也出现了进入HardFault_Handler(0x08008952),但这个HardFault_Handler非上一个HardFault_Handler,看Disassembly窗口停留的地址是属于boot范围,说明这次跳转没跳到app程序就异常了。在boot程序用了freertos,而且不是在中断里跳转到app,属于在某个任务里跳转,在任务里执行属于PSP模式,如果boot程序是在使用PSP的任务里跳转app程序,那么只设置__set_MSP(*(__IO uint32 *)addr);并没有使PSP指向app的堆栈地址,即跳转时还是使用boot中的PSP,所以HardFault_Handler(0x08008952)产生是boot中。

    解释了在使用裸机运行程序时只设置了MSP指向app的堆栈地址就可以正常跳转运行,因为当前是在MSP模式下跳转。

验证结论:

根据上述的验证猜测,修改boot程序跳转前的堆栈指针设置,之前的设置PSP和MSP只是考虑将两个值设置成app的堆栈地址,依然会出现跳转到app后进入了HardFault_Handler(0x08017D2)而无法运行,原因是在跳转到app后设置的中断服务程序使用的堆栈指针是在MSP模式,可能改写了主程序使用的堆栈中的值(PSP模式),所以在boot跳转到app之前,在调用__set_MSP(*(__IO uint32 *)addr);之前把当前使用的堆栈指针由PSP改回MSP后在设置MSP比较好些,跳转到app后,无论主程序还是中断服务程序都是使用MSP,也就不会冲突了。

void HalOTAJumpApp(uint32 addr)
{
  typedef void(*pfun)(void);
  static pfun jumpToApp;  
  __IO uint32 jumpAddr;

  /* 逐个关闭开启的外设 */
  HAL_DeInit();
  HAL_RCC_DeInit();
  HAL_ADC_DeInit(&hadc);
  HAL_UART_DeInit(&huart3);
  HAL_CAN_DeInit(&hcan);
  __disable_irq();
   
  if (((*(__IO uint32 *)addr) & 0x2FFE0000) == 0x20000000)
  {
    jumpAddr = *(__IO uint32 *)(addr + 4);
    jumpToApp = (pfun)jumpAddr;
    
    /* key point !!! */
    __set_PSP(*(__IO uint32 *)addr); 
    __set_CONTROL(0);
    __set_MSP(*(__IO uint32 *)addr);
 
    jumpToApp();
  }
}

结尾总结:

通过上述的分析得出了使用boot程序带了系统在跳转前需要注意PSP模式的存在,不单单是只有MSP模式的设置就可以正常跳转。而跳转前复位开启的外设和关总中断也是严谨的做法,虽然非本次异常跑飞的主要原因。分析问题时,先要梳理好需要的验证项而不能病急乱投医,这样也导致了排查问题效率低下容易错过正确的解决方法。记录此篇定位分析文档用以时刻提醒我:做事不要慌!!!做事不要慌!!!做事不要慌!!!

  • 21
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值