Next: , Previous: Type Information, Up: Top


23 插件

23.1 加载插件

在支持-ldl -rdynamic的平台上,插件才被支持。它们由编译器使用dlopen来加载,并在编译过程中预先确定的位置进行调用。

使用

-fplugin=/path/to/NAME.so -fplugin-arg-NAME-<key1>[=<value1>]

来加载插件。

插件的参数由GCC解析,并按照“关键字-值”(key-value pairs)的方式传递给相应的插件。可以通过指定多个-fplugin参数来调用多个插件。

可以直接使用简短的名字(没有点和斜杠)来指定一个插件。当简单的是传递-fplugin=NAME时,插件会从plugin目录下被加载,所以-fplugin=NAME等同于-fplugin=`gcc -print-file-name=plugin`/NAME.so,使用反引号shell语法来查询plugin目录。

23.2 插件的API

插件由编译器在特定的事件上激活,这些事件在gcc-plugin.h中定义。对于每个感兴趣的事件,插件应该调用register_callback来指定事件的名字,以及将要处理该事件的回调函数的地址。

头文件gcc-plugin.h必须为第一个包含的gcc头文件。

23.2.1 插件的版权检查

每个插件应该定义全局符号plugin_is_GPL_compatible来宣称其具有GPL兼容的版权。如果该符号不存在,则编译器会产生一个致命错误,并带着如下错误信息退出:

     fatal error: plugin <name> is not licensed under a GPL-compatible license
     <name>: undefined symbol: plugin_is_GPL_compatible
     compilation terminated

该符号的声明类型应该为int,从而匹配在gcc-plugin.h中为了抑制C++ mangling的前向声明。它其实并不需要位于任何分配的section中。编译器仅仅是使用断言来确保该符号在全局作用域中存在。类似于这样的就可以:

     int plugin_is_GPL_compatible;

23.2.2 插件的初始化

每个插件都应该导出一个叫做plugin_init的函数,其在插件刚被加载之后调用。该函数负责注册插件需要的所有回调,并做其它所需要的初始化。

该函数就在调用语法分析器之前,由compile_file来调用。plugin_init的参数为:

plugin_info结构体的定义如下:

     struct plugin_name_args
     {
       char *base_name;              /* Short name of the plugin
                                        (filename without .so suffix). */
       const char *full_name;        /* Path to the plugin as specified with
                                        -fplugin=. */
       int argc;                     /* Number of arguments specified with
                                        -fplugin-arg-.... */
       struct plugin_argument *argv; /* Array of ARGC key-value pairs. */
       const char *version;          /* Version string provided by plugin. */
       const char *help;             /* Help string provided by plugin. */
     }

如果初始化失败,plugin_init必须翻译一个非零的值。否则,其应该返回0。

加载插件的GCC编译器的版本使用如下结构体来描述:

     struct plugin_gcc_version
     {
       const char *basever;
       const char *datestamp;
       const char *devphase;
       const char *revision;
       const char *configuration_arguments;
     };

函数plugin_default_version_check接受两个指向该结构的指针,并按照域来进行比较。其可以在插件的plugin_init函数中使用。

用来编译插件的GCC版本可以在符号gcc_version中找到,该符号在头文件plugin-version.h中定义。推荐使用如下的方式来进行版本检查:

     #include "plugin-version.h"
     ...
     
     int
     plugin_init (struct plugin_name_args *plugin_info,
                  struct plugin_gcc_version *version)
     {
       if (!plugin_default_version_check (version, &gcc_version))
         return 1;
     
     }

不过,如果你想进行不太严格的检查,你还可以只检查单独的域。

23.2.3 插件回调

回调函数具有如下的函数原型:

     /* The prototype for a plugin callback function.
          gcc_data  - event-specific data provided by GCC
          user_data - plugin-specific data provided by the plug-in.  */
     typedef void (*plugin_callback_func)(void *gcc_data, void *user_data);

回调函数可以在如下预定义的事件上被调用:

     enum plugin_event
     {
       PLUGIN_PASS_MANAGER_SETUP,    /* To hook into pass manager.  */
       PLUGIN_FINISH_TYPE,           /* After finishing parsing a type.  */
       PLUGIN_FINISH_UNIT,           /* Useful for summary processing.  */
       PLUGIN_PRE_GENERICIZE,        /* Allows to see low level AST in C and C++ frontends.  */
       PLUGIN_FINISH,                /* Called before GCC exits.  */
       PLUGIN_INFO,                  /* Information about the plugin. */
       PLUGIN_GGC_START,             /* Called at start of GCC Garbage Collection. */
       PLUGIN_GGC_MARKING,           /* Extend the GGC marking. */
       PLUGIN_GGC_END,               /* Called at end of GGC. */
       PLUGIN_REGISTER_GGC_ROOTS,    /* Register an extra GGC root table. */
       PLUGIN_REGISTER_GGC_CACHES,   /* Register an extra GGC cache table. */
       PLUGIN_ATTRIBUTES,            /* Called during attribute registration */
       PLUGIN_START_UNIT,            /* Called before processing a translation unit.  */
       PLUGIN_PRAGMAS,               /* Called during pragma registration. */
       /* Called before first pass from all_passes.  */
       PLUGIN_ALL_PASSES_START,
       /* Called after last pass from all_passes.  */
       PLUGIN_ALL_PASSES_END,
       /* Called before first ipa pass.  */
       PLUGIN_ALL_IPA_PASSES_START,
       /* Called after last ipa pass.  */
       PLUGIN_ALL_IPA_PASSES_END,
       /* Allows to override pass gate decision for current_pass.  */
       PLUGIN_OVERRIDE_GATE,
       /* Called before executing a pass.  */
       PLUGIN_PASS_EXECUTION,
       /* Called before executing subpasses of a GIMPLE_PASS in
          execute_ipa_pass_list.  */
       PLUGIN_EARLY_GIMPLE_PASSES_START,
       /* Called after executing subpasses of a GIMPLE_PASS in
          execute_ipa_pass_list.  */
       PLUGIN_EARLY_GIMPLE_PASSES_END,
       /* Called when a pass is first instantiated.  */
       PLUGIN_NEW_PASS,
     
       PLUGIN_EVENT_FIRST_DYNAMIC    /* Dummy event used for indexing callback
                                        array.  */
     };

除此之外,插件还可以查找命名事件的枚举变量,并且/或者通过调用函数get_named_event_id来动态生成新的事件。

要注册一个回调,插件使用如下参数来调用register_callback

对于PLUGIN_PASS_MANAGER_SETUP,PLUGIN_INFO,PLUGIN_REGISTER_GGC_ROOTS和PLUGIN_REGISTER_GGC_CACHES这些伪事件,callback应该为空,user_data为特定的。

当PLUGIN_PRAGMAS事件被触发(使用一个空指针,作为来自GCC的数据),插件可以使用函数,像c_register_pragma或者c_register_pragma_with_expansion,来注册它们自己的pragma。

23.3 与pass manager进行交互

需要有一种方法来动态增加/重排序/移除pass。这对分析插件(位于特定pass之后的插件,例如CFG或者IPA pass)和优化插件都很有帮助。

现在已经提供了对插入新的pass或者替换现有pass的基本支持。要注册一个新的pass,插件可以通过使用PLUGIN_PASS_MANAGER_SETUP事件和指向如下定义的struct register_pass_info对象的指针,来调用register_callback

     enum pass_positioning_ops
     {
       PASS_POS_INSERT_AFTER,  // Insert after the reference pass.
       PASS_POS_INSERT_BEFORE, // Insert before the reference pass.
       PASS_POS_REPLACE        // Replace the reference pass.
     };
     
     struct register_pass_info
     {
       struct opt_pass *pass;            /* New pass provided by the plugin.  */
       const char *reference_pass_name;  /* Name of the reference pass for hooking
                                            up the new pass.  */
       int ref_pass_instance_number;     /* Insert the pass at the specified
                                            instance number of the reference pass.  */
                                         /* Do it for every instance if it is 0.  */
       enum pass_positioning_ops pos_op; /* how to insert the new pass.  */
     };
     
     
     /* Sample plugin code that registers a new pass.  */
     int
     plugin_init (struct plugin_name_args *plugin_info,
                  struct plugin_gcc_version *version)
     {
       struct register_pass_info pass_info;
     
       ...
     
       /* Code to fill in the pass_info object with new pass information.  */
     
       ...
     
       /* Register the new pass.  */
       register_callback (plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &pass_info);
     
       ...
     }

23.4 与GCC垃圾搜集器进行交互

一些插件想要当GGC(GCC Garbage Collector)运行的时候被告知。它们可以为PLUGIN_GGC_STARTPLUGIN_GGC_END事件注册回调函数,从而在GCC垃圾搜集开始和结束时被告知。

一些插件可能需要GGC来标记额外的数据。这可以通过为PLUGIN_GGC_MARKING事件注册一个回调函数(使用为空的gcc_data)来完成。这样的回调可以调用ggc_set_mark例程,最好是通过ggc_mark宏(反过来,这些例程通常不应该在插件中,PLUGIN_GGC_MARKING事件之外使用)。

一些插件可能需要增加额外的GGC root表,例如,来处理它们自己的GTY数据。这可以通过使用PLUGIN_REGISTER_GGC_ROOTS伪事件来完成,使用一个空的回调和额外的(struct ggc_root_tab*类型的)root表作为user_data。想要使用if_marked哈希表选项的插件,可以增加额外的GGC cache表,这些是由gengtype使用PLUGIN_REGISTER_GGC_CACHES伪事件,一个空回调和作为user_data的额外的(struct ggc_cache_tab*类型的)cache表,来生成的。运行gengtype -p source-dir file-list plugin*.c ...来产生这些额外的root表。

你在使用PLUGIN_GGC_MARKINGPLUGIN_REGISTER_GGC_ROOTS或者PLUGIN_REGISTER_GGC_CACHES之前,应该理解GCC内部的内存管理的详细情况。

23.5 给出插件的信息

插件应该给用户提供自身的一些信息。这使用下列结构:

     struct plugin_info
     {
       const char *version;
       const char *help;
     };

这样的一个结构体作为user_data由插件的初始化例程来传递,并使用register_callbackPLUGIN_INFO伪事件和一个空回调。

23.6 注册自定义的attributes或者pragmas

出于分析(或者其它)目的,能够增加自定义的attributes或者pragmas是有帮助的。

PLUGIN_ATTRIBUTES回调在属性注册的时候被调用。使用register_attribute函数来注册自定义的属性。

     /* Attribute handler callback */
     static tree
     handle_user_attribute (tree *node, tree name, tree args,
                            int flags, bool *no_add_attrs)
     {
       return NULL_TREE;
     }
     
     /* Attribute definition */
     static struct attribute_spec user_attr =
       { "user", 1, 1, false,  false, false, handle_user_attribute };
     
     /* Plugin callback called during attribute registration.
     Registered with register_callback (plugin_name, PLUGIN_ATTRIBUTES, register_attributes, NULL)
     */
     static void
     register_attributes (void *event_data, void *data)
     {
       warning (0, G_("Callback to register attributes"));
       register_attribute (&user_attr);
     }
     

PLUGIN_PRAGMAS回调在pragmas注册的时候被调用。使用c_register_pragma或者c_register_pragma_with_expansion函数来注册自定义的pragmas。

     /* Plugin callback called during pragmas registration. Registered with
          register_callback (plugin_name, PLUGIN_PRAGMAS,
                             register_my_pragma, NULL);
     */
     static void
     register_my_pragma (void *event_data, void *data)
     {
       warning (0, G_("Callback to register pragmas"));
       c_register_pragma ("GCCPLUGIN", "sayhello", handle_pragma_sayhello);
     }

建议传递"GCCPLUGIN"(或者一个简短的名字来标识你的插件)作为你的pragma的“space”参数。

23.7 记录pass的执行信息

事件PLUGIN_PASS_EXECUTION将指向被执行的pass的指针,作为gcc_data传递给回调函数。你还可以通过检查cfun来找到该pass所执行的函数。注意,该事件只有当入口检查(gate check)成功时,才会被调用。你可以在你的插件中使用其它钩子,像PLUGIN_ALL_PASSES_STARTPLUGIN_ALL_PASSES_ENDPLUGIN_ALL_IPA_PASSES_STARTPLUGIN_ALL_IPA_PASSES_ENDPLUGIN_EARLY_GIMPLE_PASSES_START,和/或PLUGIN_EARLY_GIMPLE_PASSES_END来操纵全局状态,以便获得pass执行的上下文。

23.8 控制将要执行的pass

在pass的原始的入口函数被调用之后,其结果,入口状态,作为整数被存储。然后事件PLUGIN_OVERRIDE_GATE被激起,在回调函数的gcc_data参数中使用一个指向入口状态的指针。一个非零值的入口状态意味着该pass将被执行。你可以同时通过传递的指针读取和改写入口状态。

23.9 跟踪可用的pass

当你的插件被加载时,你可以检查各种pass列表来确定哪些pass是可用的。但是,其它插件可能会加入新的pass。而且,GCC将来可能会改成在插件加载之后添加通用的pass。当一个pass首次被增加到一个pass列表时,事件PLUGIN_NEW_PASS被激起,使用回调参数gcc_data指向新pass。

23.10 构建GCC插件

如果启用了插件技术,GCC会安装需要构建插件的头文件(在安装路径中的某个地方,例如,位于/usr/local)。特别的,plugin/include目录会被安装,其包含了构建插件需要的所有头文件。

在大多系统上,你可以通过命令gcc -print-file-name=plugin(如果需要,将gcc替换为合适的程序路径)来查询该plugin目录。

在插件内部,该plugin目录名可以通过调用default_plugin_dir_name ()来查询。

下列GNU Makefile摘抄片段展示了如何构建一个简单的插件:

     GCC=gcc
     PLUGIN_SOURCE_FILES= plugin1.c plugin2.c
     PLUGIN_OBJECT_FILES= $(patsubst %.c,%.o,$(PLUGIN_SOURCE_FILES))
     GCCPLUGINS_DIR:= $(shell $(GCC) -print-file-name=plugin)
     CFLAGS+= -I$(GCCPLUGINS_DIR)/include -fPIC -O2
     
     plugin.so: $(PLUGIN_OBJECT_FILES)
        $(GCC) -shared $^ -o $@

单个文件的插件可以使用gcc -I`gcc -print-file-name=plugin`/include -fPIC -shared -O2 plugin.c -o plugin.so来构建,使用反引号shell语法来查询plugin目录。

需要使用gengtype的插件需要一个GCC的构建目录,其必须是与将要链接的GCC具有相同的版本。