c語言調試接口

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

  在C語言程序設計中,常會出現各類各樣的bug:段錯誤、參數異常等等。咱們須要儘快定位錯誤,輸出異常信息,出錯位置及調用層次等,這對於解決bug問題是很是方便的,因此設計了以下調試接口。node

調試級別:共有三級,不一樣的級別對於錯誤採起不一樣的處理方法,如異常退出仍是函數返回仍是僅僅輸出錯誤信息,調試級別越高,給出的錯誤信息越詳細。web

    最高調試級別assert,當斷言失效時打印最詳細的出錯信息,包括斷言語句位置(文件,函數,代碼行數)、出錯緣由,並引起SIGTERM信號,由該信號處理函數打印。程序的函數調用層次(從main開始)。函數

    如由assert(n> 0, "invalid n : %d", n); 引起異常以下:ui

 

     最上面幾行是樁輸出,樁可在程序中隨處插入,當程序運行到該行時就會打印出樁輸出信息。下面第二部分就是斷言錯誤信息,包括斷言條件,補充的錯誤信息等。下面第三部分就是打印函數調用層次,從main函數開始一直到斷言所在函數。spa

     第二級爲return,當條件失效時打印詳細出錯信息並返回。.net

     如由 return_val_if_fail(n >5, -1, "invalid n = %d", n); 引起異常以下:debug

     第三級爲warn,當條件失效時打印詳細出錯信息,並繼續執行下面的語句並不返回。設計

     除此以外,還對可能出現的段錯誤進行了處理,自動調用assert級別,並由SIGSEGV信號處理函數打印程序的函數調用層次。unix

     如由*(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)

相關文章
相關標籤/搜索