A Simple C++ Function Call Stack Trace Utility






3.02/5 (23 votes)
How to record your function call stack for debug purposes
Introduction
Under UNIX/Linux platform, program coredump generates a core file, we can analyze the core file and determine what's wrong in our program. The core file records the function call stack (e.g. under gdb you can use bt command to see backtrace), that is mostly information for us to troubleshooting.
When I debug my program, I try to record the function call stack in program debug log to help me diagnose the program's defect. Fortunately, C++ gives us a simple way to implement this.
Rational
Just use C++ constructor/destructor semantic, we can make our own trace class. At the function entrance, we declare a local variable, it will invoke the constructor immediately, when leaving the function, the local variable will be destroyed and call destructor. That's all. what we need to do is implement a class's constructor and destructor. This rational is simple enough, but this way sometimes can help us a lot.
The complete code is as follows:
// 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
First you need to call CM_TRACE_FILE()
to special debug log filename, then at function entrance, call CM_TRACE()
. Here is an example.
#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;
}
Then open `trace.log', use your favourite editor. At the end of file, you will see the following:
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()
Conclusion
The rational in this article is simple, but we can write simple code do useful things.
Revision History
- v1.00 Feb 4, 2005. Mockey
- Original article
- v1.01 Feb 11 2005. Mockey
- Change
fstream
toFILE*
- Change
string
tosprintf
for more efficiency. Thanks Jon Wold's comment - Rename
CM_TRACE
toCM_TRACE_FUN
and addCM_TRACE_FUN_ARGx
macros. - Add feature to log the function call arguments.
- Change