Skip to content

Experimenting with gcc plugins

Monday, 27 April 2009  |  brad hards

One of the new features proposed for GCC 4.5 is the ability to use plugins.

I'm a big fan of plugin architectures, especially in open source software. I think that plugins provide a really nice starting point for potential developers. So you can start with something simple and well defined, and grow into the rest of the system. Personally, the idea of understanding all of GCC is just overwhelming. But perhaps I could do a really basic plugin that can do an additional static check.

The idea is based on mygcc which allows extensible checkers to be written for gcc. As an example, consider a (C) API that looks something like foo_init(); foo_use1(); foo_use2(); foo_cleanup(); where you have to init() before using any of the use() functions, and then cleanup() when you're done. Its an error / bug if you forget to init(), or cleanup(), or if there is any path that results in the sequence being broken. Its an error if you cleanup() twice. An example of this is something like malloc/free or varargs (va_start() / va_arg() / va_end()), but I'm less interested in general cases (which might be able to be added to gcc) and more interested in cases from specific libraries (e.g. crypto/security stuff like gnutls and libgcrypt, or alternative memory manager libs like talloc). It would be nice if you could get gcc to (optionally) check that you're using the library roughly right when you're not so familiar with the API.

The plugins branch has been merged into gcc trunk, so I spent some time getting it set up over the weekend (hint: it really is supposed to be built with builddir != sourcedir). There are a few surprises (or, I'm still doing it wrong), like the headers not getting installed, but I'm confident that will get sorted before release.

In the mean time, here is some starting points. CMakeLists.txt to build the plugin (without having installed headers): project( MyGccPlugin )

Probably nothing that needs 2.6, but that is what I tested with

cmake_minimum_required(VERSION 2.6)

We need a range of header files (perhaps more than are here)

Some are relative to the build directory, and some to the source directory

set( GCC_SRC_DIR "/home/bradh/devel/gcc-svn" ) set( GCC_BUILD_DIR "/home/bradh/devel/gcc-build" ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) include_directories( ${GCC_SRC_DIR}/gcc ) include_directories( ${GCC_SRC_DIR}/libcpp/include ) include_directories( ${GCC_SRC_DIR}/include ) include_directories( ${GCC_BUILD_DIR}/gcc )

Source files for the plugin

set( my_gcc_plugin_SRCS my_gcc_plugin.c )

build the plugin (share library object)

add_library( my_gcc_plugin MODULE ${my_gcc_plugin_SRCS} )

have the name set to my_gcc_plugin.so (on Linux) rather than libmy_gcc_plugin.so

set_target_properties( my_gcc_plugin PROPERTIES PREFIX "" )

A framework for a module, heavily based on one I extracted from gcc's testsuite: /* A sample plugin example that shows how to use the GCC plugin mechanism. */

#include <stdlib.h> #include "config.h" #include "system.h" #include "coretypes.h" #include "tree.h"
#include "tree-pass.h" #include "intl.h"
#include "gcc-plugin.h"

/* A really simple callback handler */ void handle_end_of_compilation_unit (void *event_data, void *data) {
warning (0, G_("my gcc plugin: End of compilation unit")); }

/* This presumably could be used to enable / disable the plugin */ static bool gate_my_gcc_plugin_example (void)
{
return true;
}

static struct gimple_opt_pass pass_my_gcc_plugin_example = {
{
GIMPLE_PASS,
"my_gcc_plugin_example", /* name / gate_my_gcc_plugin_example, / gate / NULL, / execute / NULL, / sub /
NULL, /
next /
0, /
static_pass_number / 0, / tv_id /
PROP_cfg, /
properties_required / 0, / properties_provided / 0, / properties_destroyed / 0, / todo_flags_start /
TODO_dump_func /
todo_flags_finish */
}
};

/* Initialization function that GCC calls. This plugin takes an argument that specifies the name of the reference pass and an instance number, both of which determine where the plugin pass should be inserted.
*/
int plugin_init (const char *plugin_name, struct plugin_gcc_version *version, int argc, struct plugin_argument *argv) {
struct plugin_pass pass_info;
char *ref_pass_name = NULL;
int ref_instance_number = 0;
int i;
struct plugin_info info = {"0.0a", "my_gcc_plugin help should go here" };

    /* Process the plugin arguments. This plugin takes the following arguments:
       ref-pass-name=<PASS_NAME> and ref-pass-instance-num=<NUM>.  */          
    for (i = 0; i < argc; ++i) {                                               
            if (strcmp (argv[i].key, "ref-pass-name") == 0) {                  
                    if (argv[i].value) {
                            ref_pass_name = argv[i].value;
                    } else {
                            warning (0, G_("option '-fplugin-arg-%s-ref-pass-name' requires a pass name"), plugin_name);
                    }
            } else if (strcmp (argv[i].key, "ref-pass-instance-num") == 0)  {
                    if (argv[i].value) {
                            ref_instance_number = strtol (argv[i].value, NULL, 0);
                    } else {
                            warning (0, G_("option '-fplugin-arg-%s-ref-pass-instance-num' requires an integer value"), plugin_name);
                    }
            } else {
                    warning (0, G_("plugin %qs: unrecognized argument %qs ignored"), plugin_name, argv[i].key);
            }
    }

    if (!ref_pass_name) {
            error (G_("plugin %qs requires a reference pass name"), plugin_name);
            return 1;
    }

    /* Set up the pass information. This controls where we get inserted in gcc's processing */
    pass_info.pass = &pass_my_gcc_plugin_example.pass;
    pass_info.reference_pass_name = ref_pass_name; /* from the command line argument */
    pass_info.ref_pass_instance_number = ref_instance_number; /* from the command line argument, defaults to 0 */
    pass_info.pos_op = PASS_POS_INSERT_AFTER;

    /* Register a callback for the pass_info setup */
    register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &pass_info);

    /* Register a callback for our meta-data (version and help information) */
    register_callback (plugin_name, PLUGIN_INFO, NULL, &info);

    /* Register a callback for the end-of-file handler */
    register_callback (plugin_name, PLUGIN_FINISH_UNIT, handle_end_of_compilation_unit, NULL);

    /* Add other register_callback functions here */

    return 0;

}

You can build it using the normal cmake path-to-sourcedir followed by make.

You can then run gcc/g++ as /opt/gccsvn/bin/g++ -fplugin=./my_gcc_plugin.so -fplugin-arg-my_gcc_plugin-ref-pass-name=cfg my-program-src.c (with changes the pass name depending on what you want, e.g. if you want the SSA form, you can just use ssa instead of cfg.

Clearly that doesn't actually check anything (it just outputs a warning message at the end of each file being built), but I need to understand a lot more gcc before I can actually do a checker.