65.9K
CodeProject 正在变化。 阅读更多。
Home

简单的 C++ 函数调用堆栈跟踪实用程序

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.02/5 (23投票s)

2006年2月4日

CPOL

1分钟阅读

viewsIcon

236198

downloadIcon

1951

如何记录函数调用堆栈以进行调试

引言

在 UNIX/Linux 平台上,程序崩溃时会生成一个 core 文件,我们可以分析 core 文件来确定程序的问题所在。core 文件记录了函数调用堆栈(例如,在 gdb 中可以使用 bt 命令查看回溯),这对于我们进行故障排除非常有帮助。

当我调试程序时,我尝试将函数调用堆栈记录到程序调试日志中,以帮助我诊断程序缺陷。幸运的是,C++ 提供了一种简单的方法来实现这一点。

原理

只需使用 C++ 构造函数/析构函数的语义,我们就可以创建自己的跟踪类。在函数入口处,我们声明一个局部变量,它会立即调用构造函数,在离开函数时,局部变量会被销毁并调用析构函数。就是这样。我们需要做的就是实现一个类的构造函数和析构函数。这个原理很简单,但有时可以帮助我们很多。

完整的代码如下

// trace.hpp
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdarg.h>
#include <string>

#define CM_TRACE_FILE(trace_file)	cm::Trace::LogToFile(trace_file)

#define CM_TRACE_FUNC(func_name)    cm::Trace __CM_TRACE__(func_name, "()")
#define CM_TRACE_FUNC_ARG1(func_name, argfmt, arg)   \
            cm::Trace __CM_TRACE__(func_name, argfmt, arg)
#define CM_TRACE_FUNC_ARG2(func_name, argfmt, arg1, arg2)   \
            cm::Trace __CM_TRACE__(func_name, argfmt, arg1, arg2)
// more macros define....

namespace	cm
{
    class	Trace
    {
    public:
    	explicit Trace(char *func_name, const char* argsfmt, ...)
    	{
            char fmt[256] ={0};
            sprintf(fmt, "%s%s", func_name, argsfmt);
    	    va_list arglist;
    	    va_start(arglist, argsfmt);
    		LogMsg(depth_, depth_ * 2, fmt,  arglist);
    		va_end(arglist);
    		++depth_;
    	}

    	~Trace()
    	{
    		--depth_;
    	}

    	/// special the global log file.
    	void static LogToFile(const char *trace_file)
    	{
    		trace_file_ = trace_file;
    	}

    private:
    	void LogMsg(int depth, int align, const char *fmt, va_list args)
    	{
    		FILE	*fp = fopen(trace_file_.c_str(), "a+");
    		if (fp == NULL)
    		{
    			return;
    		}


    		time_t		curTime;
    		time(&curTime);

    		char	timeStamp[32] = { 0 };
    		strftime(timeStamp, sizeof(timeStamp), 
				"%Y%m%d.%H%M%S", localtime(&curTime));

    		// only log the timestamp when the time changes
    		unsigned int len = fprintf( fp, "%s %*.*s> (%d)",
    				(last_invoke_time_ != curTime) ? 
				timeStamp : "               ",
    				2 * depth,
    				2 * depth,
    				nest_,
    				depth);
    		last_invoke_time_ = curTime;
    		len += vfprintf(fp, fmt, args);
    		len += fwrite("\n", 1, 1, fp);
    		fflush(fp);
    		fclose(fp);
    	}

    private:
    	// the debug trace filename
    	static std::string	trace_file_;

    	// function call stack depth
    	static int			depth_;
    	static const char*  nest_;
    	static time_t       last_invoke_time_;
    };

    std::string Trace::trace_file_  = "";
    int         Trace::depth_       = 0;

    // arbitrarily support nesting 34 deep for no particular reason
    const char* Trace::nest_        = 
	"| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ";
    time_t      Trace::last_invoke_time_ = 0;
}	// end namespace cm
#endif // CM_TRACE_20060209_HPP

Using the Code

首先你需要调用 CM_TRACE_FILE() 来指定调试日志文件名,然后在函数入口处调用 CM_TRACE()。这里有一个例子。

#include "cm/trace.hpp"

void foo()
{
	CM_TRACE_FUNC("foo");
}

void bar(int x)
{
	CM_TRACE_FUNC_ARG1("bar", "(%d)", x);
	foo();
}

void foobar(int x, const char* s)
{
    CM_TRACE_FUNC_ARG2("foobar", "(%d, %s)", x, s);
    bar(789);
}

void foobar3(int x, int y, double z)
{
    CM_TRACE_FUNC_ARG3("foobar3", "(%d, %d, %f)", x, y, z);
    foobar(123, "4546");
}

int main()
{
    CM_TRACE_FILE("./trace.log");
    CM_TRACE_FUNC("main");
    foo();
    bar(23);
    foobar(33, "char");
    foobar3(12, 23, 34.45);
    return 0;
}

然后打开 `trace.log',使用你喜欢的编辑器。在文件末尾,你将看到以下内容

20060211.132431 > (0)main()
                | > (1)foo()
                | > (1)bar(23)
                | | > (2)foo()
                | > (1)foobar(33, char)
                | | > (2)bar(789)
                | | | > (3)foo()
                | > (1)foobar3(12, 23, 34.450000)
                | | > (2)foobar(123, 4546)
                | | | > (3)bar(789)
                | | | | > (4)foo()

结论

本文的原理很简单,但我们可以编写简单的代码来做有用的事情。

修订历史

  • v1.00 2005年2月4日。Mockey
    • 原文
  • v1.01 2005年2月11日。Mockey
    • fstream 更改为 FILE*
    • string 更改为 sprintf 以提高效率。感谢 Jon Wold 的评论
    • CM_TRACE 重命名为 CM_TRACE_FUN 并添加 CM_TRACE_FUN_ARGx 宏。
    • 添加记录函数调用参数的功能。
© . All rights reserved.