Libcwd is a C++ library, written by Carlo Wood, to add run-time debugging support for C++ applications, particularly for code developed with the GNU Compiler Collection. The functionality that the library adds to an application can be divided into three categories:
- Ostream-based debug output.
- Run-time access to debug information.
- Run-time access to memory allocation administration.
Although the library code itself attempts to be strictly ISO C++, and conform to POSIX as much as possible, in order to achieve points 2 and 3, rather specialized code is needed, specific to the architecture the application runs on. Libcwd restricts itself to a narrow architecture for this reason: It has to be compiled with the GNU compiler, and demands the object code to be 32 or 64 bits ELF and the compiler generated debug information to be DWARF-2.
Compiling libcwd results in two libraries: one that is thread-safe (libcwd_r) and a slightly faster version (libcwd) without thread support. The thread-safe version depends on even more architecture specific details (namely, the GNU C library). As a result, a full featured libcwd is basically only suitable for development on Linux platforms.
However, libcwd may be configured to drop thread support, memory allocation debugging and/or reading the ELF and DWARF-2 debugging information—until only the ostream debug support is left. This way one can use it to develop an application on linux until it is robust, and still have the debug output on other (POSIX) platforms, even though a full-fledged libcwd isn't available there—provided no thread-safety is needed for the debug output on those platforms: two or more threads writing debug output to the same ostream might cause a rather messy output where the output of one line starts in the middle of another, without thread-support.
Ostream based debug output
Libcwd provides several macros that are easily extensible, allowing the user to basically do anything that one can normally do with ostreams. However, if one just wants to write debug output, two macros will suffice: Dout and DoutFatal. The latter is to be used for fatal debug output, after which the application needs to be terminated. For example:
if (error) DoutFatal(dc::fatal, "An unrecoverable error occurred.");
The difference with Dout is that when the application is compiled without debug code, the macro Dout() is replaced with nothing, while DoutFatal() is replaced with code that prints its output and terminates (in a way that the user can define).
Simple debug output is written by using Dout, as follows:
Dout(dc::notice, "called from " << location_ct(CALL_ADDR));
where the second parameter is allowed to contain '<<' to write any type or object to the debug output stream (a location_ct in this case).
The 'dc::fatal' and 'dc::notice' are debug 'channels', which can be turned on or off. The user can create and use any number of custom debug channels, in addition to the default ones. It is also possible to create more than just the default debug output ostream object 'libcw_do', and thus to write output to more than one ostream. Each debug object, representing an ostream, can in turn be separately turned on and off.
Run-time access to debug information
This information includes the possibility to lookup source file / line number locations, and function names. As a result it is for example possible to write debug output that prints who the caller is of a given function, or to print the name of the current function, even if that function is a complex template. For example,
PERSIST : PersistXML::serialize_builtin<std::string>("M_hostname", @0xbfc1f214) is called.
Run-time access to memory allocation administration
Libcwd keeps an internal administration of memory allocations. This allows one to do things like memory leak checking, printing out an overview of allocated memory (in a very powerful way, allowing one to filter on about anything: regular expressions for library names, function names (demangled or not) and/or time intervals during which allocations were made).
The library also provides a few global functions that can be called from within a debugger, like gdb, allowing the developer to quickly find out which allocation a given pointer is pointing at. For example,
(gdb) call cwdebug_alloc(0x8a19450) 0x8a19450 points inside a memory allocation that starts at 0x8a19448 start: 0x8a19448 size: 12 type: char** description: Array of commandline arguments passed to libcw_app_ct::options_done_event. location: libcw_app.cc:304 in function: libcw_app_ct::libcw_init(int, char* const*) when: 00:31:09.888760 (gdb) l libcw_app.cc:304