http://blog.chinaunix.net/uid-10106787-id-2985587.html

在C语言程序设计中,常会出现各种各样的bug:段错误、参数异常等等。我们需要尽快定位错误,输出异常信息,出错位置及调用层次等,这对于解决bug问题是非常方便的,所以设计了如下调试接口。

调试级别:共有三级,不同的级别对于错误采取不同的处理方法,如异常退出还是函数返回还是仅仅输出错误信息,调试级别越高,给出的错误信息越详细。

最高调试级别assert,当断言失效时打印最详细的出错信息,包括断言语句位置(文件,函数,代码行数)、出错原因,并引发SIGTERM信号,由该信号处理函数打印。程序的函数调用层次(从main开始)。

如由assert(n> 0, "invalid n : %d", n); 引发异常如下:

最上面几行是桩输出,桩可在程序中随处插入,当程序运行到该行时就会打印出桩输出信息。下面第二部分就是断言错误信息,包括断言条件,补充的错误信息等。下面第三部分就是打印函数调用层次,从main函数开始一直到断言所在函数。

第二级为return,当条件失效时打印详细出错信息并返回。

如由 return_val_if_fail(n >5, -1, "invalid n = %d", n); 引发异常如下:

第三级为warn,当条件失效时打印详细出错信息,并继续执行下面的语句并不返回。

除此之外,还对可能出现的段错误进行了处理,自动调用assert级别,并由SIGSEGV信号处理函数打印程序的函数调用层次。

如由*(int*)0x32 = 10; 引发段错误如下:

所有的调试语句都可用宏开关进行控制,可随时注销

接下来谈谈实现,在调试接口内部保存函数调用层次,在每个函数开头都要插入ENTER__,并且函数返回都用RETURN,这样就能记录函数调用信息。

为了最大程度降低对程序效率的影响,将所有的实现尽可能用宏完成,而将很少一部分工作利用接口函数完成。

/* 函数调用信息结构体*/

struct debug_function_info{
         constchar * filename;
         constchar * funcname;
         unsigned int   line;
};

typedef unsigned int dbgsize_t;
typedef unsigned int offset_t;

/*函数调用栈*/
struct debug_struct {
         dbgsize_t stack_size;
         offset_t stack_offset;
         dbgsize_t stack_unitsz;
         struct debug_function_info *stack_buff;
         struct debug_function_info   dbg_funcinf_array[INITIAL_STACK_SIZE];
};

#define ENTER__ENTER_FUNCTION(__FILE__, __FUNCTION__, __LINE__);
#define ENTER_FUNCTION(x, y, z) \
        do { \

                   if(unlikely(global_debug_infop == NULL)) \

                            debug_initialize();\

                   struct debug_struct *p = global_debug_infop;\
                   if (unlikely(p->stack_offset >= p->stack_size)) \
                            debug_stack_resize();\
                   offset_t* t = &p->stack_offset; \
                   p->dbg_funcinf_array[*t].filename = (x);\
                   p->dbg_funcinf_array[*t].funcname = (y);\
                   p->dbg_funcinf_array[*t].line = (z);\
                   p->stack_offset ;\  

         } while(0)

宏ENTER_FUNCTION用于将调用信息压栈,开头几行用于检测对栈初始化和扩充。

static struct debug_structglobal_debug_info = {

         .stack_size = INITIAL_STACK_SIZE,

         .stack_offset = 0,

         .stack_unitsz = sizeof(struct debug_function_info),

         .stack_buff = global_debug_info.dbg_funcinf_array
};

栈在接口内部定义为静态变量,栈的初始化函数如下

void debug_initialize(void)
{
         global_debug_infop = &global_debug_info;
         signal(SIGSEGV, exception_handler);
         signal(SIGTERM, exception_handler);
}

可见仅仅是指定信号处理函数。

当函数返回时,利用RETURN宏从栈中弹出一个调用记录

#define RETURN(...)\
         do{\
                  global_debug_infop->stack_offset--; \
                   return __VA_ARGS__;  \
         }while(0)

宏D__为桩语句,打印函数运行路径

#define D__\
         do{\
                   STACK_PUSH_LINE(__LINE__);  \
                   DUMP_MSG(stdout, "\nRunning over %s() at %s: %d", \
                   __FUNCTION__, __FILE__,  __LINE__);  \
         }while(0);

对于assert调试级别会引发SIGTERM信号,对段错误会引发SIGSEGV信号。

#define assert(p,  ...) \
         do {\
                   if (!!!(p)){ \
                            charerr_msg[DEBUG_ERRMSG_LEN] = {0}; \
                            MAKE_ERROR_MSG(err_msg, __VA_ARGS__);  \
                            DUMP_DEBUG_MSG(p);  \
                            DUMP_ERROR_MSG("Error Msg",err_msg);  \
                            STACK_PUSH_LINE(__LINE__); \
                            raise(SIGTERM); \
                   } \
         }while(0)

以下是两种信号共有的处理函数

static void exception_handler(int signo)
{
         fprintf(stdout, "\n\nCaught signal %s...", 
                   signo== SIGSEGV ? "SIGSEGV" : "SIGTERM"); 
         ((void)signo);
         EXCEPTION_DUMP_STACK();
         abort();
}

宏EXCEPTION_DUMP_STACK用于弹出调用栈

#define EXCEPTION_DUMP_STACK()\
         do {\
                  int i; \
                   intoffset = global_debug_infop->stack_offset; \                       

                   struct debug_struct *p = global_debug_infop; \
                   for(i = offset - 1; i >= 0; i--) { \
                            pdbg_nodet = p->stack_buff i; \
                            DUMP_MSG(stdout, "\n%s %s() at %s: %d", \
                            i== offset - 1 ? "Raised in" : "Called from", \
                            t->funcname, t->filename,  t->line); \
                   }\
                   DUMP_MSG(stdout,"\n"); \
         }while(0)

对于debug_ret级别,相关调试宏如下:

#define debug_ret_series(p, ...) \
         do {\
                   charerr_msg[DEBUG_ERRMSG_LEN] = {0}; \
                   MAKE_ERROR_MSG(err_msg, __VA_ARGS__); \
                   DUMP_DEBUG_MSG(p); \
                   DUMP_ERROR_MSG("ErrorMsg", err_msg); \
                   STACK_PUSH_LINE(__LINE__); \
                   DUMP_LAST_ERROR_MSG(); \
         }while(0)
#define debug_retv(p, ret, ...) \
         do {\
                   debug_ret_series(p, __VA_ARGS__); \
                   RETURN(ret); \
         }while(0)

#define retv_if(p, ret,...) \
         do {\
                   if(!!(p)){ \
                            debug_retv(p, ret, __VA_ARGS__); \
                   }\
         }while(0)
#define retv_if0(p, ret, ...) \
         do {\
                   if(!!!(p)){ \
                            debug_retv(p, ret, __VA_ARGS__); \
                   }\
         }while(0)

还有一些额外的宏可用于辅助调试

#define Show_Value(x, u) \
                   DUMP_MSG(stdout, "\nCalled From %s() at %s : %d, " \
                   "TheValue of "#x" is %"#u"\n", __FUNCTION__, __FILE__, __LINE__, x)

c语言调试接口的更多相关文章

  1. GDAL库调试(包括跨语言调试)

    很多时候都需要调试GDAL库,尤其是像学习GDAL库中的某些算法是如何实现的时候,调试就必不可少了. 首先说明用C++的调试.以VS2008为例进行说明. 编译DEBUG版本的GDAL库,这个可以参考 ...

  2. 浅论各种调试接口(SWD、JTAG、Jlink、Ulink、STlink)的区别

    JTAG协议 JTAG(Joint Test Action Group,联合测试行动小组)是一种国际标准测试协议(IEEE 1149.1兼容),主要用于芯片内部测试.现在多数的高级器件都支持JTAG协 ...

  3. R语言数据接口

    R语言数据接口 R语言处理的数据一般从外部导入,因此需要数据接口来读取各种格式化的数据 CSV # 获得data是一个数据帧 data = read.csv("input.csv" ...

  4. [日常] Go语言圣经--接口约定习题

    Go语言圣经-接口1.接口类型是对其它类型行为的抽象和概括2.Go语言中接口类型的独特之处在于它是满足隐式实现的3.Go语言中还存在着另外一种类型:接口类型.接口类型是一种抽象的类型4.一个类型可以自 ...

  5. 用SWD调试接口测量代码运行时间 ( SWO )

    用SWD调试接口测量代码运行时间 关于时间测量的种种问题 在嵌入式中,我们经常需要测量某段代码的执行时间或测量事件触发的时间,常规的思路是: 1:在测量起始点,反转电平2:在测量结束点,再次反转电平 ...

  6. FFI (语言交互接口(Foreign Function Interface))

    FFI(Foreign Function Interface)是用来与其它语言交互的接口, 在有些语言里面称为语言绑定(language bindings), Java 里面一般称为 JNI(Java ...

  7. 调试接口,返回的json数据,我定义了一个类,用来序列化,其中有一个字段定义为string 然后序列化的时候报错

    调试接口,返回的json数据,我定义了一个类,用来序列化,其中有一个字段定义为string 然后序列化的时候报错 在需要解析的类型类上加上声明 eg:

  8. 调试接口你还在用postman吗

    作者 | 陈凯玲 来源 | my.oschina.net/keking/blog/3104972 接口调试是每个软件开发从业者必不可少的一项技能,一个项目的的完成,可能接口测试调试的时间比真正开发写代 ...

  9. js 调试接口

    在我们做完前端的工作后,很多情况下需要把我们的数据与后端得接口进行对接,说以我们就得掌握调试接口的方法 一.建立对象数组(一般是后端的工作) 代码如下: [ {"name":&qu ...

随机推荐

  1. Oracle数据库SQL优化

    SQL优化的最终目的是用户体验-在最短时间内让用户在页面数据.因此,执行时间是SQL优化的重要指标.在SQL查询中,I/O操作最占系统性能的.I/O操作量越大,时间越久.减少I/O操作量是优化SQL的 ...

  2. ASP.NET后台注册JS的方法

    1. 用Response.Write方法 代码如下: Response.Write("<script type='text/javascript'>alert("hel ...

  3. MySQL双主配置

    MySQL双主配置 准备环境:服务器操作系统为RHEL6.4 x86_64,为最小化安装.主机A和主机B均关闭防火墙和SELINUX ,IP地址分别为192.168.131.129和192.168.1 ...

  4. jQuery选择器之基本过滤选择器Demo

    测试代码: 03-基本过滤选择器.html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" &q ...

  5. Every student in every school should have the opportunity to learn to code

    “I think everybody in this country should learn how to program a computerbecause it teaches you how ...

  6. 【Cocos2d入门教程四】Cocos2d-x菜单篇

    游戏世界多姿多彩,无论多靓丽的游戏,多耐玩的游戏,在与游戏用户交互上的往往是菜单. 上一章我们已经大概了解了导演.节点.层.精灵.这一章以菜单为主题. 菜单(Menu)包含以下内容: 1.精灵菜单项( ...

  7. ios 中的block应用

    在这个大冬天里默默敲着键盘,勿喷.今天学习swift过程中,学习到闭包,发现闭包和oc的block中有很多的相同之处,又重新学习了一下并且学习了一些高级点的用法,内容如下: 1.block格式说明:( ...

  8. hdu 1686 Oulipo KMP匹配次数统计

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1686 分析:典型的KMP算法,统计字符串匹配的次数. 用Next数组压缩时间复杂度,要做一些修改. / ...

  9. 杭电ACM2085--核反应堆

    http://acm.hdu.edu.cn/showproblem.php?pid=2085 这也是一个简单的的递推. a[i][0] = 3*a[i-1][0]+2*a[i-1][1]; a[i][ ...

  10. USB时钟为什么是48MHz

        在学习2440的USB配置时钟中,发现它的时钟需要设置成48MHz固定的,这个我就来兴趣了,为什么这里面USB的时钟一定要是48M呢?在网上找了众多文章,都是讲解如何配置2440的MPLL和U ...