Contiki开发3:调试平台与Debug系统

时间:2022-08-13 03:33:21

Contiki开发3:调试平台与Debug系统

曾经掉进的坑

7年前,我们在PC104(Intel x86系统中面向嵌入式的CPU)平台开发Linux+miniGUI的产品,奇怪的是,每当测试人员多次点击触摸屏后,程序崩溃了。当然,是应用进程崩溃,Linux还运行良好。附带说一句,像Linux这种多进程系统还是很稳定的,哪怕一个用户进程挂掉,其他进程仍正常运行;在这方面,一般的RTOS无法比拟。

当时这种bug极大地打击我们的士气,记得我给家人打电话:程序员这碗饭,可能吃不下去了。直到部门经理请来一个高手,他说,miniGUI有一个测试版本和日志打印,你们使能这2个Debug功能。果真,他这个方法,让我们很快找到错误根源----miniGUI的调色板有一个指针为空!要知道,这个函数已经被调用到了第8层,如果没有自动捕捉,不敢想象如何解决它。

这个事件,让我们汲取了宝贵的经验,优秀的系统和生命一样:它能运行在测试或正式版本(如:人们正常工作和生病住院),它能捕捉异常(如:人体疼痛和发烧),它能与人对话(如:患者把自己不适之处告诉医生)。直到今天,我们依然保持如下的调试和Debug,它给我们带来的红利是:信心,质量和速度。

1 维护2个版本

尽管现代的IDE(集成开发环境)给嵌入式开发带来诸多便捷(如断点白盒测试,软件性能剖析等),然而还有如下需求:

1. 系统希望能够自动捕捉一些致命的错误,这需要设计一个ASSERT系统;

2. 像黑盒和集成测试它更希望系统持续运行,这样日志打印能清晰反馈系统运行轨迹和关键数据的计算;

当然,以上DEBUG系统仅包含在“测试版本”中,不应该包含在“发布版本”。因此,需要建立全局控制宏:0=测试版本,1=发布版本。

#define REL_VER    0 /* 0=debug; 1=release */

2 捕捉异常

当嵌入式软件遇到一些“它认为不可能发生”的异常时,包括:指针为空、除数为0、数组下标越界、硬件紊乱等,不但需要报警,还需要停止系统运行。这种情况下,ASSERT系统可以有效捕捉此类异常,当然它仅包含在“测试版本中”。(一个高质量产品的“发布版本”软件是绝对不允许此类异常存在)。

#undef ASSERT     /* remove existing definition */

#if REL_VER

   #define ASSERT(test)  NULL

#else

   extern void _AssertUART(const char *, unsigned int);

   #if (UART == PRINT_WAY)

       #define ASSERT(test)    \

       if (test)    \

           NULL;    \

       else    \

           _AssertUART(__FILE__, __LINE__)

   #else

       #define ASSERT(test)    assert(test)

   #endif

#endif

从上面代码清单可知:

仅当“测试版本”时ASSERT()才编译成执行代码,如果定向到UART,一旦捕捉到异常,它将通过UART打印“文件名”和“行号”;如果定向到标准C,它将直接调用assert()库函数。

_AssertUART()函数将异常所在的“文件名”和“行号”解释成字符串,并通过UART口打印。

3 让程序说话

嵌入式软件程序员一定遇到这样的需求:

1. 测试某个模块时,希望开启打印,查看程序执行流和数据计算;而该模块测试完毕后,希望关闭打印,这称为日志打印的“模块控制”。

2. 当系统加入新代码后运行异常,需要对这些没有彻底测试的新代码开启日志打印;还需要对程序状态和执行流进行打印,这称为日志打印的“类型控制”。

3. 有时候,希望程序打印后停止运行,以方便检查上下文,即“停止系统”。

4. 异常是分级别的,包括:“警告/严重/致命”,希望能打印该级别的缺陷报告,这称之为日志打印的“级别控制”。

锐米通信的日志打印满足上述要求,它和普通的C语言库函数printf()相似:

实例1

RIME_DBG( WAKE_TIME_DBG,

"current=%ldms, the next rtc alarm=%ldms.\r\n",

lCurMs,

lAlarmMs);

希望使能上述日志打印:#defineWAKE_TIME_DBG    RIME_DBG_ON

希望关闭上述日志打印:#defineWAKE_TIME_DBG    RIME_DBG_OFF

 

实例2(级别=警告)

RIME_DBG( RIME_DBG_LEVEL_WARNING,

"SafeBlockThread(): event buffer is full.\r\n" );

 

实例3(级别=严重)

RIME_DBG( RIME_DBG_LEVEL_SERIOUS,

"network_AlarmByRTC(): set RTC alarm error!\r\n" );

我们一起来看看上述日志打印的实现。

3.1 打印开关

日志打印使用了8-bit的控制,如下图所示,它们一起实现4种打印开关:模块开关、类型控制、停止系统和级别控制

Contiki开发3:调试平台与Debug系统


模块开关的宏定义如下:

/* flag for RIME_DBG to enable that debugmessage */

#define RIME_DBG_ON            0x80U

/* flag for RIME_DBG to disable that debugmessage */

#define RIME_DBG_OFF           0x00U

 

类型控制的宏定义如下:

/* flag for RIME_DBG indicating a tracingmessage (to follow program flow) */

#define RIME_DBG_TRACE         0x40U

/* flag for RIME_DBG indicating a statedebug message (to follow module states) */

#define RIME_DBG_STATE         0x20U

/* flag for RIME_DBG indicating newly addedcode, not thoroughly tested yet */

#define RIME_DBG_FRESH         0x10U

 

停止系统的宏定义如下:

/** flag for RIME_DBG to halt afterprinting this debug message */

#define RIME_DBG_HALT          0x08U

 

级别控制的宏定义如下:

/** lower two bits indicate debug level

 * -0 all

 * -1 warning

 * -2 serious

 * -3 severe

 */

#define RIME_DBG_LEVEL_ALL     0x00

#define RIME_DBG_LEVEL_WARNING 0x01 /* badchecksums, dropped packets, ... */

#define RIME_DBG_LEVEL_SERIOUS 0x02 /*memory allocation failures, ... */

#define RIME_DBG_LEVEL_SEVERE  0x03

#define RIME_DBG_MASK_LEVEL    0x03

3.2 打印端口

一般说来,嵌入式设备的日志打印定向到UART口(也有定向到Ethernet),因此该函数与设备相关联,为方便移植将其分成2部分:解析变参和端口输出,如下代码所示,它定向到UART口打印日志。

#include <stdarg.h>

#define SIZE_DBG_BUF    128

static INT8U    s_abyDbgBuf[SIZE_DBG_BUF];

void dbg_PrintfArg(const char *p_chFormat, ...)

{

   ASSERT(NULL != p_chFormat);

 

   INT8U    byLen;

   va_list    args;

 

   if (!s_bEnablePrint)

    {

       return; /* Exit procedure if DISABLE print */

    }

 

   /* Process a variable number of arguments into array */

   va_start(args, p_chFormat);

   byLen = vsnprintf((char *)s_abyDbgBuf, SIZE_DBG_BUF, p_chFormat, args);

   va_end(args);

 

   /* TX debug string through COMM */

   dp_Tx(s_abyDbgBuf, byLen);

   return;

}

3.3 日志打印宏定义

#if DBG_VER

/** print debug message only if debugmessage type is enabled...

 *  ANDis of correct type AND is at least RIME_DBG_LEVEL

 */

#define RIME_DBG(dbg, fmt, args...)    \

   do    \

   {    \

       if ( ((dbg) & RIME_DBG_ON) &&    \

              ((dbg) & RIME_DBG_TYPES_ON)&&    \

              (RIME_DBG_MIN_LEVEL <=(int16_t)((dbg) & RIME_DBG_MASK_LEVEL)) )   \

       {    \

           dbg_PrintfArg(fmt, ##args);    \

       }    \

       if ((dbg) & RIME_DBG_HALT)   \

       {    \

           while (1) ;    \

       }    \

    }while (0)

#else

#define RIME_DBG(dbg, fmt, args...)

#endif

4 测试之过

理想情况下,测试不能干扰系统的运行,而日志打印的特点包括2个:可能突发地打印一批信息;打印信息总会使系统运行变慢。

大部分嵌入式设备使用UART口执行日志打印,UART口是慢速通信设备,如果前一条日志没有打印完毕,打印当前日志的进程只能阻塞等待,这样就会干扰软件的正常运行。

为此,需要设计一个环形缓冲区,它结合DMA可以实现“无阻塞日志打印”。

详情请链接:http://blog.csdn.net/jiangjunjie_2005/article/details/50807498