对于规模大一些的 C 语言项目,往往会存在不好查的 bug,比如某些底层函数在检查参数时遇到不符合预期的情况,但由于调用路径很多,不能有效且快速的找出完整的调用链。此时,如果能在出问题的点打印出调用链,那将会有助于快速定位问题。
之前在看 mariadb 的源码时,发现里面实现了自定义的调用栈跟踪功能,下面将其中的关键代码简化出来,以供大家参考。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <pthread.h>
#define DBUG_DUMP _db_dump_
#define DBUG_LEAVE do { \
_db_stack_frame_.line = __LINE__; \
_db_return_(&_db_stack_frame_); \
_db_stack_frame_.line = 0; \
} while (0)
#define DBUG_ENTER(a) struct _db_stack_frame_ _db_stack_frame_; \
_db_enter_(a, __FILE__, __LINE__, &_db_stack_frame_)
#define DBUG_RETURN(a1) do {DBUG_LEAVE; return (a1);} while(0)
#define DBUG_VOID_RETURN do {DBUG_LEAVE; return;} while(0)
pthread_key_t db_key;
struct _db_stack_frame_ {
const char *func;
const char *file;
int level;
int line;
struct _db_stack_frame_ *prev;
};
struct _db_code_state_ {
struct _db_stack_frame_ *framep;
int level;
/* any other variables */
};
void _db_enter_(const char *_func_, const char *_file_,
int _line_, struct _db_stack_frame_ *_stack_frame_)
{
struct _db_code_state_ *cs;
cs = pthread_getspecific(db_key);
assert(cs != NULL);
_stack_frame_->func = _func_;
_stack_frame_->file = _file_;
_stack_frame_->line = _line_;
_stack_frame_->level = ++cs->level;
_stack_frame_->prev = cs->framep;
cs->framep = _stack_frame_;
}
void _db_return_(struct _db_stack_frame_ *_stack_frame_)
{
struct _db_code_state_ *cs;
cs = pthread_getspecific(db_key);
assert(cs != NULL);
assert(cs->framep != NULL);
cs->framep = cs->framep->prev;
if (cs->level > 0)
--cs->level;
}
void _db_dump_(void)
{
struct _db_stack_frame_ *_stack_frame_;
struct _db_code_state_ *cs;
cs = pthread_getspecific(db_key);
assert(cs != NULL);
_stack_frame_ = cs->framep;
while (_stack_frame_) {
printf("#%d %s() at %s:%d\n", _stack_frame_->level,
_stack_frame_->func, _stack_frame_->file,
_stack_frame_->line);
_stack_frame_ = _stack_frame_->prev;
}
}
int is_even(int num)
{
return !(num & 0x1);
}
int fab(int num)
{
int a, b, c, i;
DBUG_ENTER(__func__);
if (num <= 2)
DBUG_RETURN(1);
a = b = 1;
for (i = 2; i < num; i++) {
c = a + b;
a = b;
b = c;
}
if (!is_even(c)) {
DBUG_DUMP();
printf("Fatal error encountered!\n");
exit(-1);
}
DBUG_RETURN(c);
}
int foo3(int num)
{
int ret;
DBUG_ENTER(__func__);
ret = fab(num);
DBUG_RETURN(ret);
}
int foo2(void)
{
int ret;
DBUG_ENTER(__func__);
ret = foo3(5);
DBUG_RETURN(ret);
}
void foo1(void)
{
int ret;
DBUG_ENTER(__func__);
ret = foo2();
printf("foo2 return: %d\n", ret);
DBUG_VOID_RETURN;
}
void do_test(void)
{
DBUG_ENTER(__func__);
foo1();
DBUG_VOID_RETURN;
}
int main(void)
{
struct _db_code_state_ *cs;
cs = (struct _db_code_state_ *)malloc(sizeof(struct _db_code_state_));
cs->framep = NULL;
cs->level = 0;
pthread_key_create(&db_key, NULL);
pthread_setspecific(db_key, cs);
do_test();
return 0;
}
运行效果如下:
# gcc -o main main.c -lpthread
# ./main
#5 fab() at main.c:90
#4 foo3() at main.c:114
#3 foo2() at main.c:124
#2 foo1() at main.c:134
#1 do_test() at main.c:144
Fatal error encountered!