最近遇到一个很棘手的bug,值得记录一下。
这是在stm32f103vct6单片机上运行的ucos程序,我根据一些书上的描述移植的(很简单),使用标准库和usb驱动库,因为板卡的USART1没有直接的串口接口,所以使用usb与主机通信并调试。开发环境是eclipse,其实是用makefile+arm-none-eabi-gcc工具链编译的,集成得不好,就是写代码方便。
我创建了两个任务,大致代码如下:
OSTaskCreate(task_manager, NULL, (OS_STK*)&task_manager_stk[STK_SIZE], 20);
OSTaskCreate(task1, NULL, (OS_STK*)&task1_stk[STK_SIZE], 30);
void task_manager(void *pdata)
{
printf("task_manager\n");
while(!is_usb_buffer_empty());
while(1){
OSTimeDly(1000);
printf("task delay 1s\n");
while(!is_usb_buffer_empty());
}
}
void task1()
{
printf("task1\n");
while(!is_usb_buffer_empty());
while(1);
}
问题就出在task_manager延迟够1s,被唤醒时。systick时钟中断先调用OSTimeTick(),再调用OSIntExit,进行可能的任务切换。
OSIntExit会在临界区检查是否需要任务切换,需要则调用OSIntCtxSw()函数。OSIntCtxSw被实现如下:
.thumb_func
OSCtxSw:
OSIntCtxSw:
LDR R0, =NVIC_SCB_ICSR
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
这里的OSIntCtxSw和OSCtxSw功能一致,都是设置pendsv异常阻塞标志,实际的任务切换发生在PendSV_Handler中。
我开始是怀疑任务堆栈出了问题,在task_manager被换入和换出时打印了它的上下文,发现是相同的。
因为usb基本只能在中断情况下通信,而且它优先级又无法高过一些系统异常,所以即使我怀疑是产生了某些系统异常,也没办法用usb输出信息来验证。
这里其实可以用板卡上的led灯验证是进入了那个异常,但我选择了另一种方法,就是用led灯显示确定在哪里产生了异常。最大范围是从OSIntExit
到task_manager重新运行,采用二分法(悲剧的板卡上只有1个led灯)。
最后我终于确定,它竟然没有进入PendSV_Handler,也不是在调用OSIntCtxSw之前,那问题就集中在OSIntCtxSw这段仅有4条指令的代码上。
因为之前从task_manager切换到task1是正常的,所以OSCtxSw可以正常工作,但OSIntCtxSw却不行。我注意到OSCtxSw前面比OSIntCtxSw多了
一个.thumb_func,或许是它产生了影响。
我再回头去分别检查调用OSCtxSw和OSIntCtxSw的反汇编代码。
调用OSCtxSw的指令如下:
bl 0x800f46a
调用OSIntCtxSw的指令如下:
blx 0x800f46a
这显然是问题所在,bl指令不切换thumb2状态,但blx会切换,而且后面0x800f46a第0位为0,会尝试切换到arm状态,在cortex-m3上自然死定了。
所以问题浮出了水面,只需做出如下修改:
.thumb_func
OSCtxSw:
.thumb_func
OSIntCtxSw:
LDR R0, =NVIC_SCB_ICSR
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
这个调试过程说起来简单,但中间也很艰辛。也只能怪自己学东西急躁,写代码时没完全搞清楚.thumb_func的问题就,调试时也没有第一时间冷静地去看代码。
这是首次在usb通信的情况下调试,还是比用串口时麻烦一些。在调试的时候,不停地鄙视自己落后的调试手段,不但连断点、单步什么的都没有,就是想输出些
东西都要受各种限制,真是吃了苦头。其实调试就是脑子中不断涌出各种猜想,动手去实验验证,获得反馈,又有新的猜想,再验证。这个bug虽然不算好找,但
场景简单,可稳定重现,即使在如此落后的调试手段下,也可以找出并修正。