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)