Playing with GCC plugins

Martin Kletzander’s devconf.cz talk about features in modern compilers that we probably don’t use inspired me to play with GCC plugins. I want to see if I can use a plugin to solve a horrible bug we hit at the end of last year.

We had a C struct defined in a header file like this:

struct {
  int foo;
#ifdef HAVE_LIBVIRT
  int bar;
#endif
  int baz;
};

But because of the way HAVE_LIBVIRT is defined, if you used the header file as:

#include <config.h>
#include "mystruct.h"

the struct would be defined with the bar element. But if you used the struct like this:

#include "mystruct.h"

you didn’t get the bar element. Ooops. This silently caused data corruption (when accessing the third member of the struct).

My idea is that we could use a GCC plugin to print out the size of every struct we define when compiling the code, and then you can post-process that to see if there are structs which unexpectedly change size.

The GCC plugin API is not exactly well documented. In fact, without reading a lot of GCC header files and internals, it’s virtually impossible to work out what is going on. Anyway, my plugin below works for me on GCC 6.

You have to compile the plugin using:

gcc -g -I`gcc -print-file-name=plugin`/include \
    -fpic -shared -o structsizes.so structsizes.cc

and then when you compile your source code you have to add:

gcc -fplugin=./structsizes.so \
    -fplugin-arg-structsizes-log=<logfile> ...

The plugin writes simple text records to logfile:

struct 'random_data' has size 384 [bits]
struct 'drand48_data' has size 192 [bits]
...

which you can easily post-process with some shell script. Note you should compile your source with make -j1 so that parallel runs of the compiler don’t try to write to the log file at the same time, since my plugin doesn’t include any locking.

/* structsizes.cc plugin: public domain example code written by
 * Richard W.M. Jones
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gcc-plugin.h>
#include <tree.h>
#include <print-tree.h>

int plugin_is_GPL_compatible;

static FILE *log_fp;

static void
plugin_finish_type (void *event_data, void *user_data)
{
  tree type = (tree) event_data;

  /* We only care about structs, not any other type definition. */
  if (TREE_CODE (type) == RECORD_TYPE) {
#if 0
    /* This is useful for working out how to navigate the tree below. */
    debug_tree (type);
#endif

    /* Struct name? */
    tree name_tree = TYPE_NAME (type);

    /* Ignore unnamed structs. */
    if (!name_tree) {
      fprintf (log_fp, "ignoring unnamed struct\n");
      return;
    }

    const char *name;
    if (TREE_CODE (name_tree) == IDENTIFIER_NODE)
      name = IDENTIFIER_POINTER (name_tree);
    else if (TREE_CODE (name_tree) == TYPE_DECL && DECL_NAME (name_tree))
      name = IDENTIFIER_POINTER (DECL_NAME (name_tree));
    else
      name = "unknown struct name"; /* should never happen? */

    /* If the type is not complete, we can't do anything. */
    if (!COMPLETE_TYPE_P (type)) {
      fprintf (log_fp, "struct '%s' has incomplete type\n", name);
      return;
    }

    /* Get the size of the struct that has been defined. */
    tree size_tree = TYPE_SIZE (type);
    if (TREE_CODE (size_tree) == INTEGER_CST &&
        !TYPE_P (size_tree) && TREE_CONSTANT (size_tree)) {
      size_t size = TREE_INT_CST_LOW (size_tree);
      fprintf (log_fp, "struct '%s' has size %zu [bits]\n", name, size);
    }
    else
      fprintf (log_fp, "struct '%s' has non-constant size\n", name);
  }

  fflush (log_fp);
}

int
plugin_init (struct plugin_name_args *plugin_info,
             struct plugin_gcc_version *version)
{
  const char *logfile = NULL;
  size_t i;

  /* Open the log file. */
  for (i = 0; i < plugin_info->argc; ++i) {
    if (strcmp (plugin_info->argv[i].key, "log") == 0) {
      logfile = plugin_info->argv[i].value;
    }
  }

  if (!logfile) {
    fprintf (stderr, "structsizes plugin: missing parameter: -fplugin-arg-structsizes-log=<logfile>\n");
    exit (EXIT_FAILURE);
  }

  log_fp = fopen (logfile, "a");
  if (log_fp == NULL) {
    perror (logfile);
    exit (EXIT_FAILURE);
  }

  fprintf (log_fp, "Loaded structsizes plugin (GCC %s.%s.%s)\n",
           version->basever, version->devphase, version->revision);

  register_callback (plugin_info->base_name, PLUGIN_FINISH_TYPE,
                     plugin_finish_type, NULL);

  return 0;
}

5 Comments

Filed under Uncategorized

5 responses to “Playing with GCC plugins

  1. Laszlo Ersek

    Nice! I’ll have to watch Martin’s presentation. Thanks.

  2. Frank Ch. Eigler

    If you used append mode / unbuffered stdio, you wouldn’t need to worry about make -jNNN, right?

    • rich

      Yeah the plugin is really just a proof of concept. It would be even better if it actually closed log_fp using a PLUGIN_FINISH callback. I just wrote the plugin to look to see if libvirt or libguestfs has any more incorrectly defined structs. Now I have my answer — they don’t.

  3. Hi,

    Instead of a GCC plugin, you could have use pahole on all object files to dump structures layout, then compare each dump to find differences.

    Regards

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.