When studying Linux kernel sources, it’s often helpful to use ftrace’s function_graph tracer, which traces function entry and exit points and clearly shows how functions call each other.

Sometimes, it is useful to get such a trace from other code than the Linux kernel. In case of C++ this https://stackoverflow.com/a/21863962/902448, but for plain C, it is trickier. I came up with the following to print nice traces. It uses https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes to register a function exit callback.

#include <stdio.h>

int trace_indent = 2;
char trace_filler[] = {[0 ... 99] = ' '};

static void trace_end(const char **func)
{
    trace_indent--;
    fprintf(stderr, "%.*s}\n", 4*trace_indent, trace_filler);
}

#define TRACE(format, ...) \
    const char *__tracer __attribute__ ((__cleanup__(trace_end))) = __func__; \
    fprintf(stderr, "%.*s%s(" format ") {\n", 4*trace_indent, trace_filler, __func__, ##__VA_ARGS__); \
    trace_indent++;

It can be used this way:

void bar()
{
        TRACE();
}

void foo(char *str)
{
        TRACE("str=%s", str);
        bar();
}

int main(int argc, char *argv[])
{
        TRACE("argc=%d", argc);
        foo("xxx");
        return 0;
}

When this program is run, it prints:

main(argc=1) {
    foo(str=xxx) {
        bar() {
        }
    }
}