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 PL/SQL(procedure language/SQL)程序设计之游标cursors

    游标 Cursors--Conception 每一条被Oracle服务器执行的SQL语句都有一个独立的游标与之相关联:隐式游标 Implicit cursors: 用于所有的DML和PL/SQL的SE ...

  2. JQ笔记

    参数形式$("input:text",document.forms[0])选择form[0]所有input=text$("<p>123</p>&q ...

  3. [Excel操作]Microsoft Office Excel 不能访问文件

    最近,客户服务器迁移,因操作系统环境变化而引起的的环境问题一堆,遇到的问题并解决方法在“[Excel]操作”类别会体现. Microsoft Office Excel 不能访问文件“C:\\LMSEx ...

  4. Javascript addEventListener dispatchEvent

    测试代码:分别在嵌套的元素body,div#level1,div#level2,div#level3上附加事件,仅在chrome中测试通过. <!DOCTYPE html> <htm ...

  5. Part 15 Scalar user defined functions in sql server

    Scalar user defined functions in sql server Inline table valued functions in sql server Multi statem ...

  6. MVC 使用Jquery实现AJax

    View <script type="text/javascript"> function GetTime() { $.get("Home/GetTime&q ...

  7. JavaScript之放大镜效果2

    在放大图片效果的同时,我们怎么原图和放大窗体增加间隔呢? 我们只需应用一个table就行了: 源码上: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML ...

  8. Swiper之初识

    何为Swiper?Swiper是一款免费以及轻量级的移动设备触控滑块的框架,使用硬件加速过渡(如果该设备支持的话).主要使用与移动端的网站.网页应用程序(web apps),以及原生的应用程序(nat ...

  9. FMS服务器在centos下安装

    首先当然得先下载安装包了 http://pan.baidu.com/s/1jGL1Nvw #要先安装一下这个包,否则会提收提示错误,缺少libcap yum install compat-libcap ...

  10. 在ASP.NET中发送电子邮件的实例教程

    首先.导入命名空间: 代码如下 复制代码 using System.Net.Mail; 定义发送电子邮件的方法[网上很多不同的,可以对比着看一下,WinForm的也适用]: 代码如下 复制代码 /// ...