gccinstrumentationgcc-plugins

Creating custom gcc attribute to instrument specific functions: whitelisting, not blacklisting


I'm using gcc's -finstrument-functions option. To minimize the overhead, I want to instrument only a few functions. However, gcc only lets you blacklist functions (with the no_instrument_function attribute, or by providing a list of paths). It doesn't let you whitelist functions.

So I wrote a small gcc plugin adding an instrument_function attribute. This lets me set the instrumentation "flag" for a specific function (or, rather, clear the no instrumentation flag):

tree handle_instrument_function_attribute(
    tree * node,
    tree name,
    tree args,
    int flags,
    bool * no_add_attrs)
{
    tree decl = *node;
    DECL_NO_INSTRUMENT_FUNCTION_ENTRY_EXIT(decl) = 0;
    return NULL_TREE;
}

However, from my understanding, this does not work. Looking at the gcc source, for this flag to actually do anything, you need to also use -finstrument-functions. See gcc/gimplify.c:14436:

...
  /* If we're instrumenting function entry/exit, then prepend the call to
     the entry hook and wrap the whole function in a TRY_FINALLY_EXPR to
     catch the exit hook.  */
  /* ??? Add some way to ignore exceptions for this TFE.  */
  if (flag_instrument_function_entry_exit
      && !DECL_NO_INSTRUMENT_FUNCTION_ENTRY_EXIT (fndecl)
      /* Do not instrument extern inline functions.  */
      && !(DECL_DECLARED_INLINE_P (fndecl)
       && DECL_EXTERNAL (fndecl)
       && DECL_DISREGARD_INLINE_LIMITS (fndecl))
      && !flag_instrument_functions_exclude_p (fndecl))
...

It first checks that the global -finstrument-functions flag is enabled. Then it checks a specific function's flag, which, from what I understand, is enabled by default. So all other functions that don't have my instrument_function attribute would still be instrumented.

Is there a way to clear this flag for all functions first, then handle my instrument_function attribute to set the flag for those functions only?


Solution

  • The trick was only defining the attribute, but not actually using any handling function, and do the processing elsewhere.

    We still use -finstrument-functions to enable instrumentation for all functions at first. We can register a callback for PLUGIN_FINISH_PARSE_FUNCTION, which checks everything. For every function declaration, it checks its attributes. If it has the instrument_function attribute, it sets the flag for the instrumentation to be added later as usual. If the function doesn't have the attribute, it clears the flag.

    #include <stdio.h>
    
    #include "gcc-plugin.h"
    #include "plugin-version.h"
    #include "tree.h"
    
    int plugin_is_GPL_compatible;
    
    static struct plugin_info info = {
        "0.0.1",
        "This plugin provides the instrument_function attribute.",
    };
    
    static struct attribute_spec instrument_function_attr =
    {
        "instrument_function",
        0,
        -1,
        false,
        false,
        false,
        NULL,  // No need for a handling function
    };
    
    static void register_attributes(void * event_data, void * data)
    {
        register_attribute(&instrument_function_attr);
    }
    
    void handle(void * event_data, void * data)
    {
        tree fndecl = (tree) event_data;
        // Make sure it's a function
        if (TREE_CODE(fndecl) == FUNCTION_DECL)
        {
            // If the function has our attribute, enable instrumentation,
            // otherwise explicitly disable it
            if (lookup_attribute("instrument_function", DECL_ATTRIBUTES(fndecl)) != NULL_TREE)
            {
                printf("instrument_function: (%s:%d) %s\n",
                    DECL_SOURCE_FILE(fndecl),
                    DECL_SOURCE_LINE(fndecl),
                    get_name(fndecl));
                DECL_NO_INSTRUMENT_FUNCTION_ENTRY_EXIT(fndecl) = 0;
            }
            else
            {
                DECL_NO_INSTRUMENT_FUNCTION_ENTRY_EXIT(fndecl) = 1;
            }
        }
    }
    
    int plugin_init(
        struct plugin_name_args * plugin_info,
        struct plugin_gcc_version * version)
    {
        register_callback(
            plugin_info->base_name,
            PLUGIN_INFO,
            NULL,
            &info);
    
        register_callback(
            plugin_info->base_name,
            PLUGIN_FINISH_PARSE_FUNCTION,
            handle,
            NULL);
    
        register_callback(
            plugin_info->base_name,
            PLUGIN_ATTRIBUTES,
            register_attributes,
            NULL);
        return 0;
    }