背景
- 软件调试,写的代码不多,不用调试,就知道问题出现在哪里,尤其是必现的RT_ASSERT断言。
- 功能多了,发现出现RT_ASSERT断言后,很难一下子明白哪里出了问题,如rt_free 断言,是哪个地方调用rt_free引起的呢?
- 如何使用Keil MDK5 Debug,快速地位assert断言的真实位置,并调试软件?
案例分析
- 自己之前写了一个BUG程序,测试RT-Thread邮箱的使用,必现assert断言:rt_free,因为使用了动态内存的申请与释放。
- 首先,需要定位问题的所在,不能说rt-thread rt_free函数出了问题!!应该是使用不正确引起的。
- Keil MDK5在Debug模式,可以在调试的窗口【Call Stack】,调用栈窗口,查找函数的调用关系,如调用者。
- 通过【顺藤摸瓜】,找到真正的调用函数:出问题的位置!!
问题排查
- 这个例程的问题,是rt_free 空指针引起的,所以,一定是申请与释放内存的成对操作不当引起的问题。
- 因为一下子没发现问题所在,所以,我在问题代码位置,打上断点,发现,第二次执行时,rt_free的指针有问题,或者说是个野指针
- 引用:extern void list_mem(void); 加入打印,发现内存越变越多了!!(rt_free释放的多!!)
- 出问题的部分代码如下:
struct mb_msg
{
rt_uint8_t *data_ptr;
rt_uint32_t data_size;
};
static void thread1_entry(void *param)
{
struct mb_msg *msg_recv_ptr1;
struct mb_msg *msg_send_ptr1;
char sbuf[6] = {'T', 'a', 's', 'k', '1', '.'};
msg_send_ptr1 = (struct mb_msg *)rt_malloc(sizeof(struct mb_msg)); /* !!!! 申请内存的位置放错了!!!*/
while(1)
{
if (rt_mb_recv(&t1_mb, (rt_ubase_t *)&msg_recv_ptr1, RT_WAITING_FOREVER) == RT_EOK)
{
rt_kprintf("thread 1:[recv=%s], print 1.\n", msg_recv_ptr1->data_ptr);
rt_thread_mdelay(10);
rt_free(msg_recv_ptr1->data_ptr);
rt_free(msg_recv_ptr1);
rt_thread_mdelay(500);
msg_send_ptr1->data_size = sizeof(sbuf);
msg_send_ptr1->data_ptr = (rt_uint8_t *)rt_malloc(msg_send_ptr1->data_size);
rt_memcpy(msg_send_ptr1->data_ptr, sbuf, sizeof(sbuf));
rt_kprintf("thread 1:[send=%s]\n", msg_send_ptr1->data_ptr);
rt_mb_send(&t2_mb, (rt_uint32_t)msg_send_ptr1);
}
}
}
- 原来一处申请内存的位置,放错了,造成这个函数,只有初始化时申请一次内存,使用时,是个野指针。
- 解决方法:把申请内存的那段代码,放在while(1) 循环里面,每次操作再申请内存!!
static void thread1_entry(void *param)
{
struct mb_msg *msg_recv_ptr1;
struct mb_msg *msg_send_ptr1;
char sbuf[6] = {'T', 'a', 's', 'k', '1', '.'};
while(1)
{
if (rt_mb_recv(&t1_mb, (rt_ubase_t *)&msg_recv_ptr1, RT_WAITING_FOREVER) == RT_EOK)
{
rt_kprintf("thread 1:[recv=%s], print 1.\n", msg_recv_ptr1->data_ptr);
rt_thread_mdelay(10);
rt_free(msg_recv_ptr1->data_ptr);
list_mem();
rt_free(msg_recv_ptr1);
list_mem();
rt_thread_mdelay(500);
msg_send_ptr1 = (struct mb_msg *)rt_malloc(sizeof(struct mb_msg));
msg_send_ptr1->data_size = sizeof(sbuf);
msg_send_ptr1->data_ptr = (rt_uint8_t *)rt_malloc(msg_send_ptr1->data_size);
rt_memcpy(msg_send_ptr1->data_ptr, sbuf, sizeof(sbuf));
rt_kprintf("thread 1:[send=%s]\n", msg_send_ptr1->data_ptr);
rt_mb_send(&t2_mb, (rt_uint32_t)msg_send_ptr1);
}
}
}
总结
- RT_ASSERT还是比较好用的,注意正确下断点,调试起来,非常的高效。
- 多总结调试的方法,用于解决实际的问题。