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; }