Poor man’s function_graph trace for C programs

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 is pretty easy, but for plain C, it is trickier. I came up with the following to print nice traces. It uses GCC cleanup variable attribute 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() {
                }
            }
        }
Tags: