|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Introduction
What the article/code snippet does, why it's useful, the problem it solves etc.
Under UNIX/Linux platform, when program coredump, it generate a core file, we can analyse the core file and determine what's wrong in our program. The core file record the function call stack(e.g. under gdb you can use bt command to see backtrace), that is the mostly information for us to trouble shooting.
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++ give us a simple way implement this.
Rational
Just use C++ constructor/destuctor semantic, we can make our own trace class. At the function entrance, we declare a local variable, it will be invoke constructor immediately, when leave the function, the local variable will be destroy 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 sometime can help us a lot.
The complete code as following:
#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)
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_;
}
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));
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:
static std::string trace_file_;
static int depth_;
static const char* nest_;
static time_t last_invoke_time_;
};
std::string Trace::trace_file_ = "";
int Trace::depth_ = 0;
const char* Trace::nest_ = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ";
time_t Trace::last_invoke_time_ = 0;
}
#endif
Using the code
A brief desciption of how to use the article or code. The class names, the methods and properties, any tricks or tips.
Blocks of code should be wrapped in <pre> tags like this:
First you need call CM_TRACE_FILE() to special the debug log filename, then at funciton entrance call CM_TRACE(), here is a 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 as 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 relate is simple, but we can simple code do useful things.
Revision History
v1.00 Feb 4, 2005. Mockey
v1.01 Feb 11 2005. Mockey
- Change fstream to FILE*.
- Change stringstring to sprintf for more efficiency. Thanks Jon Wold's comment
- Rename CM_TRACE to CM_TRACE_FUN and add CM_TRACE_FUN_ARGx macros.
- Add feature to log the function call arguments.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 18 of 18 (Total in Forum: 18) (Refresh) | FirstPrevNext |
|
 |
|
|

void gun() { Trace_log("gun"); }
int main() {
SET_FILENAME("./Trace.log"); Trace_log("main"); gun(); Trace_log("main1"); }
k
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I have changed the foobar3 function like this:
void foobar3(int x, int y, double z) { CM_TRACE_FUNC_ARG3("foobar3", "(%d, %d, %s)", x, y, z); // i changed %f to %s foobar(123, "4546"); }
When I compiled this code, there is no warning or error were detected. And the program caused an error when it was executed.
Is there any way to check this kind of typo in LogMsg function?
Thank and regards, Toan Pham Thanh.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
Because it use sprintf internal, so there is no easy way to solve your problem. May be you can try to use gcc compiling with option -W -Wall
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
Wow. Didn't know it. Thanks for the info. I use std::stringstream for formatting operations but in case of real time call stack tracing it can affect performance due to extra memory allocations.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Hi,
I commend you for considering the need to be able to analyse what happened in the event of a defect.
I have written quite a few debugging aids over the years, especially for post-mortem debugging, so hopefully I can offer some constructive advice.
Ultimately I have found methods such as you decribe to be of use only in very limited cases such as platforms where more automated forms of stack and memory examination are not available. In fact most often just the current call stack(s) are all that are needed rather than a history of all calls made.
Facilities such as you describe can sometimes be useful for manually instrumenting small sections of code that have already been narrowed down as suspect by other means.
The main issues I owuld have with your solution are:
- Intrusive/Error prone. T - Not thread safe/aware. - Speed.
These can be improved but please bear in mind that a call history is only rarely useful and we shoudl avoid trying to capture information that is more conveniently and reliably available by other means.
Intrusive/Error prone. =====================
Each function must be explicitly instrumented. Instrumenting all functions can be tedious and requires strict enforcement. The need to explicitly record function name and argument formats is error prone.
Various levels of failure are possible. - fail to instrument function. Log output is deceptive. - fail to name function correctly. Log output is deceptive. - fail to specify function argument formats correctly. Program may crash.
This last one is particularly insidious, particularly as the function may be on an infrequently used code path or may not crash all the time. This means the attempt to improve program quality via instrumentation has in fact added a potentially hard to detect bug.
Threading =========
Most real world applications are multi-threaded.
Your code currently does not take into account the fact that multiple threads may be running the code in question, each thread of course having it's own stack. To remedy this...
- the depth_ member would need to be moved to thread local storage, so that it stores an independent depth count for each thread. - I would recommend adding a thread identifier to the logged messages. - Technically the last_invoke_time_ would need to be mutex protected, although in practice the race condition on the inequality test and assignment should only cause a subsequent spurious timestamp output. - Similarly the trace_file_ member should be mutex protected.
This list may not be exhaustive.
Speed =====
Logging a message on every function scope entrance (or exit) can have a large speed impact. This can affect program behaviour and debugging by altering thread races and/or changing the code path e.g timers fire, remote servers timeout etc.
It is best that if such logging is used it is as speedy as possible. Some issues with the code are: - the mutexes described cost time and force calls to open the file and get the last invoke time to be serialised. Rewriting code to avoid cross thread dependencies can eliminate these. - The logging file is opened, flushed and closed each time a message is logged. This is very costly. Definitely Consider opening once prior to all logging and closing once after all logging. Consider not flushing after every message although this will mean you may lose last few messages if a crash occurs. This dilemma is basically inescapable as file based logging of function calls is incommensurate with speed. - The timestamp is formatted even if (last_invoke_time_ == curTime). Consider rewriting code to initialise timeStamp to the required blank string, get curTime, then test (last_invoke_time_ != curTime) and only format timeStamp and update last_invoke_time_ inside this if clause. - the len variable is assigned and added, but is never used. It can be removed. - The actual string output and formatting is very expensive.
These costs can reduce the speed of the instrumented code by tens, hundreds or even thousands of times.
One way to avoid many of these costs is consider logging in a pure binary format direct to memory. While this means it is difficult to keep a complete trace it is possible to keep a trace of the last 1,000 or 10,000 etc. calls. The description
A brief description of an implementation that ignores the issue of logging function arguments is,
- consider a struct that describes a function call. It may contain the address of the function called, the time called (time_t), the thread id, and bool indicating entry or exit. Such a struct would be at most say 16 bytes. - allocate a static array of such structs. The (fixed) size of the array governs the how many calls can be stored for post mortem debugging. - also declare a pointer to struct initialise to point to the start of the array.
The idea is that this array forms a ring buffer in memory.
Logging a message now requires the following operation; increment the pointer mod the array length, as an _atomic_ (thread safe) operation, and use this value as pointer to the struct for logging.
Note...while thread safety may typically require a mutex to protect this operation, with a little bit of planning this can be done using very fast interlocked operations on most architectures. Alternatively create per thread arrays in thread local storage if it is not required to have a global record of thread interleaving. Please note that doing this and allowing both increment and decrement will effectively be duplicating each threads current call stack manually, information that can be obtained without any manual logging at all !! Having (safely) obtained a pointer, now fill in the struct that is pointed to with the necessary details, eg. curTime, thread id, entry/exit, address of function. Note that we are not formatting any of these as strings. Log an entry value from the Trace ctor, store the thread id and function address as memebers so these can be reusued in dtor, ... and log an exit value from the dtor.
Note that this logging is very fast compared to file based logging. It defers any string formatting until after runtime and it has the bare minimum of thread synchronisation required.
So how is this used in practice ???
If the program crashes the contents of the ring buffer are available in the core dump (or mini dump under windows). This can be examined or dumped to file using the debugger. While in the debugger the function addresses can easily be used to look at the functions called.
Similarly while the program is running the debugger can attach and suspend the process and be used to examine the ring buffer. Note that this is often pointless as the debugger has access to the current live call stacks and a history of calls is rarely needed.
One way of thinking of this is that the debugger is being used to translate the contents of the ring buffer into something human readable i.e. this formatting has been deferred until _after_ runtime.
Equally a custom viewer could be written. This program can take the dump from the debugger, load the ring buffer contents and a map file from the build, and then run through the ring buffer translating function addresses to names, tracking entry/exit to calculate depth and producing output much like your log file. Again all these costs have been deferred until after runtime.
In all this I skipped over the issue of logging argument values. This can be dealt with but is tricky. Even your solution had to hardcode an upper limit on the number of arguments (2) and can only handle argument types that have a corresponding printf format. My point is that this is issue is tricky for any "manual" implementation to solve generally.
Ultimately I hope these last few items illustrate the following point...
While it is possible to write programs that store and decode information about the runtime state of a program e.g. deduce function names from address, display values of arguments, in reality much of this work has already been done and exists inside your debugger.
In summary....
In my experience, needing a history of _all_ function calls is only very rarely needed to successfully debug an issue, most often the current call stacks are sufficient. A record of current call stacks is readily obtained post-mortem via core dumps (in Linux) or mini dumps (Windows). Such dumps are best viewed in a debugger. Equally, attaching a debugger and suspending a process can be used to examine call stacks pre-mortem and is useful to debug deadlocks. It can be very valuable, under windows, to add a code to set a top level filter to catch exceptions (caused by a crash) and perform a minidump to capture the program state for post-mortem analysis. Under linux, core dumps tend to be created automatically in this case. It is also often valuable (under windows and linux) to add a facility to provoke a dump and resume as this can be used to perform post mortem analysis on suspected dead locks or live (soft) locks that may occur in the field when a debugger is not available.
An in-memory call ring buffer may be very occasionally useful for particularly convoluted issues, if a history of calls is required.
I hope you find this feedback useful...as I said I think you should be commended for considering the need to be able to determine what has happened in a program, as this is a crucial requirement for effectively being able to fix bugs, particularly in live applications.
best regards
Andrew
|
| Sign In·View Thread·PermaLink | 5.00/5 (5 votes) |
|
|
|
 |
|
|
The article itself is good in the sense that gives you new ideas of logging. Your comment is also very useful because you pointed out some issues that you must be aware of when you make debugging using code inserts. Together, article and this comment, make a very useful article . Only one add to your comment:
I personally encourage to write as many debug code as you can in 99.99% of the code BUT only it is thread safe and, the most important, can be ALL deactivated at compile time via a simple define. I don't mind having performance trade over debug logs as long as the logging facility can be deactivated in a release version. More over, if you consider to make the logging facility more robust, you can activate/deactivate it at run-time. This is a real bless when it comes to ask the client to send you the debug logs.
And one more thing... Extracted from "Pragmatic Programmer" by Andrew Hunt and David Thomas:
"Dead programs tells no lies!" So crash early using asserts 
Bottom line, excellent article-comment combination!
Thank you
RFC1925
With sufficient thrust, pigs fly just fine. However, this is not necessarily a good idea. It is hard to be sure where they are going to land, and it could be dangerous sitting under them as they fly overhead.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I just wanted to remind again, that it itsn't a stack trace - it's automated scope log or something like that. That's right, i said scope! Consider something like this:
void dummy(int x) { CM_TRACE_FUNC("foo"); if (x > 10) { CM_TRACE_FUNC("foo -> (x > 10)"); // do something with x } else { CM_TRACE_FUNC("foo -> (x <= 10)"); // do something with x } }
So, my suggestion is to edit the library and rename it to something like "scope logger".
"C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off." --Bjarne Stroustrup
-- modified at 20:19 Tuesday 14th February, 2006
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
You should probably rename this article to debug function log utility or something instead.
Keep up the good work.
----------------------------- I am out of scope
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Nice ! I'm strongly in favor of these sorts of itty-bitty classes that take advantage of compiler gen'd dtors to pair up do / undo. Tho in a comparable case I made a few slightly different decisions.
Since my func names aren't changing I eschewed the std::string and instead used just a const char[] -- this avoids the std::string ctor / dtor for what is essentially unchanging text. I also init my member vars via the initialization list (do you have a good reason to init to "" and then set in the body of the ctor ?). Were the initializer list used you could mark the member var const as the func name is not changing for the duration.
Regarding depth_ -- it is already available to the Record() method so why pass it in ? Ditto for the func_name_.
Regarding format, I'd say given the nesting you don't need "Enter" and "Leave" strings -- not only do they require another std::string to be ctor'd / dtor'd, but it should be obvious from the nesting display which case has happened.
Personally I'd not pass in the empty parens in each string -- if they're wanted, add 'em at the lowest level (else a caller may forget).
I'm not much of iostream fan (== iostream idiot =8-O) so I stuck with the printf() family which allows me to turn a nesting level counter into nesting bars via the %*.*s format spec. I also timestamp my calls via code like the following:
// arbitrarily support nesting 34 deep for no particular reason const char nest[] = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | " ; // only log the timestamp when the time changes time_t g_lasttime = 0 ; FILE* g_file = NULL; unsigned g_depth = 0 ;
logmsg( const char fmt[] , va_list pArgs ) { time_t curTime; time( &curTime );
char timeStamp[16]; strftime( timeStamp , sizeof( timeStamp ) , "%m%d%y%H%M%S", localtime( &curTime ) ) ; // only log the date / time when it changes unsigned len = fprintf( g_file , "%s %*.*s%s: " , g_lasttime != curTime ? timeStamp : " " , 2 * g_depth , 2 * g_depth , nest , modname ) ; g_lasttime = curTime ; len += vfprintf( g_file , fmt , pArgs ) ; len += fwrite( "\n" , 1 , 1 , g_file ) ; fflush( g_file ) ; return len ; } // logmsg
Once again, nice job !
Jon
|
| Sign In·View Thread·PermaLink | 3.00/5 (2 votes) |
|
|
|
 |
|
|
 |
|
|
For users of Microsoft's VC++ (at least in Visual Studio .NET 2003, and probably earlier versions), the CM_TRACE macro can be simplified even further by using the predefined macros __FUNCTION__ and __FUNCSIG__. For example, the following changes:
// trace.hpp #define CM_TRACE cm::Trace __CM_TRACE__(__FUNCTION__)
// test_trace.cpp #include "cm/trace.hpp"
void foo() { CM_TRACE; }
void bar() { CM_TRACE; foo(); }
int main() { CM_TRACE_FILE("trace.log"); CM_TRACE; foo(); bar(); return 0; }
produce the following output
(0)[BT]:Enter function <main> (1)[BT]:Enter function <foo> (1)[BT]:Leave function <foo> (1)[BT]:Enter function <bar> (2)[BT]:Enter function <foo> (2)[BT]:Leave function <foo> (1)[BT]:Leave function <bar> (0)[BT]:Leave function <main>
and using __FUNCSIG__ instead of __FUNCTION__ produces:
(0)[BT]:Enter function <int __cdecl main(void)> (1)[BT]:Enter function <void __cdecl foo(void)> (1)[BT]:Leave function <void __cdecl foo(void)> (1)[BT]:Enter function <void __cdecl bar(void)> (2)[BT]:Enter function <void __cdecl foo(void)> (2)[BT]:Leave function <void __cdecl foo(void)> (1)[BT]:Leave function <void __cdecl bar(void)> (0)[BT]:Leave function <int __cdecl main(void)>
Happy debugging 
John
-- PhotoBlog: http://www.photoblog.be/efotografo
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Great idea. But using MACRO are not portable. The standard C/C++ does not have __FUNCTION__ macros, however xlc/gcc/VC extend the language and add a MACRO such as __FUNCTION__. At begin of design, I also expect here is a way to generate a function name automaticly, I use MACRO like you, but when I migrate my code occur error. Maybe a better way is using #ifdef identify compiler and choose the proper MACRO.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Take look at Boost, file current_function.hpp ir into email: http://aspn.activestate.com/ASPN/Mail/Message/boost/1739312
/Pavel
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'm still learning my way around c++, which once you know the semantics' really means re-inventing stuff like this, so thx for teaching me what a stack trace is 
I wonder if theirs anyway to just have a class inherit some "stack trace" class to make it easier to log stuff without worrying about which functions are added in the future. Also would this work if their were multiple classes in differant files... would they all log to the same file, or is multiple logs the desired effect?
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
I am sorry to say that there is not exist the "stack trace" class which you want. Since the Trace class use static variable to record log file, it just all log to one file.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|