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; }
Nice! I’ll have to watch Martin’s presentation. Thanks.
The slides are a bit hard to read in the Youtube video, but Martin has uploaded them here: https://people.redhat.com/~mkletzan/dc16.pdf
If you used append mode / unbuffered stdio, you wouldn’t need to worry about make -jNNN, right?
Yeah the plugin is really just a proof of concept. It would be even better if it actually closed
log_fp
using aPLUGIN_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.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