Finely grained dynamic debug print statements with GCC
Introduction
Any reasonably sized program needs some half decent debug logging mechanism. At a minimum we want something that can allow us to enable debug prints at compile time for production builds, but often we want more than that – we need to handle different verbosity levels, and once we have a number of components we probably want to be able to selectively enable some debug. The other part of debug logging is where we store the information – it may be stdout, a file, syslog, but for now lets assume stdout for simplicity.
A debug mechanism needs to be flexible – enabling too much debug can often prevent the problem you’re trying to debug, especially if it’s timing related, so sometimes it can be really handy to selectively enable debug statements at a more finely grained level than module or verbosity. With a few GCC extensions we can achieve exactly that.
The debug metadata
To enable us to control what we enable, we need to associate some metadata with each statement that will allow us to find it. The source filename, line number and function name should be sufficient for that, so we can create a structure like:
struct debug_key {
const char *filename;
const char *function;
unsigned int line_no;
bool enabled;
};
with an additional flag to say whether the statement is enabled or not. Now, we can create a macro to instantiate everything:
#define debug(fmt, ...) ({ \
static struct debug_key __key = { \
.filename = __FILE__, \
.function = __func__, \
.line_no = __LINE__, \
}; \
if (__key.enabled) \
printf(fmt, ##__VA_ARGS__); \
})
Now we have a debug key for the statement, and some conditional code that will only print when the key’s enabled field is true. Now we just need a way to manipulate that field.
GNU extensions to the rescue
The GNU toolchain has a really need feature whereby if you create a new ELF
section that has a valid C identifier as it’s name then you get some handy
variables thrown in for free. For example, if you have a section named foo
,
then you’ll have some extern variables named __start_foo
and __stop_foo
that you can use to find the section’s address in memory.
Using a new section we can build up an array of pointers to the debug keys then iterate over them. So we can revise our debug macro to:
#define __dbgkey __attribute__((section("dbgkey"), __used__))
#define debug(fmt, ...) ({ \
static struct debug_key __key = { \
.filename = __FILE__, \
.function = __func__, \
.line_no = __LINE__, \
}; \
static struct debug_key *__dbgkey __key_addr = &__key; \
if (__key.enabled) \
printf(fmt, ##__VA_ARGS__); \
})
which is the same as before, but with the addition of the __key_addr
variable
that holds the address of the debug key. This is marked with __dbgkey
which
I’ve defined to tell the toolchain to put the variable in the dbgkey
section
and to tell GCC that the variable really is used so that it doesn’t get
optimized out or give us build warnings. Storing the address of the key in
this section rather than the key itself means that we don’t have to worry about
the padding and alignment of the key if we decide to change it at any point,
though it could be done that way to save some bytes.
Putting it all together
Now that we have our table of debug key pointers, we can selectively enable debug keys by iterating over that table. For example, to enable all debug statements in the function foo() in the file foo.c:
static void enable_keys(void)
{
extern struct debug_key *__start_dbgkey[];
extern struct debug_key *__stop_dbgkey[];
struct debug_key **addr;
for (addr = __start_dbgkey; addr < __stop_dbgkey; ++addr) {
struct debug_key *key = *addr;
if (!strcmp(key->filename, "foo.c") &&
!strcmp(key->function, "foo"))
key->enabled = true;
}
}
Obviously we wouldn’t want to open code this sort of thing into our application, but this could easily grab descriptions from files, environment variables or the command line.
This could also easily be extended to include verbosity levels, modules etc, but this is a pretty self-contained example to illustrate the principles.