Next: LTO, Previous: Type Information, Up: Top
在支持-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目录。
插件由编译器在特定的事件上激活,这些事件在gcc-plugin.h中定义。对于每个感兴趣的事件,插件应该调用register_callback
来指定事件的名字,以及将要处理该事件的回调函数的地址。
头文件gcc-plugin.h必须为第一个包含的gcc头文件。
每个插件应该定义全局符号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;
每个插件都应该导出一个叫做plugin_init
的函数,其在插件刚被加载之后调用。该函数负责注册插件需要的所有回调,并做其它所需要的初始化。
该函数就在调用语法分析器之前,由compile_file
来调用。plugin_init
的参数为:
plugin_info
: 插件调用信息。
version
: GCC版本。
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; }
不过,如果你想进行不太严格的检查,你还可以只检查单独的域。
回调函数具有如下的函数原型:
/* 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
:
char *name
: 插件的名字。
int event
: 事件代码。
plugin_callback_func callback
: 处理event
的函数。
void *user_data
: 指向插件特定数据的指针。
对于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。
需要有一种方法来动态增加/重排序/移除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); ... }
一些插件想要当GGC(GCC Garbage Collector)运行的时候被告知。它们可以为PLUGIN_GGC_START
和PLUGIN_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_MARKING
,PLUGIN_REGISTER_GGC_ROOTS
或者PLUGIN_REGISTER_GGC_CACHES
之前,应该理解GCC内部的内存管理的详细情况。
插件应该给用户提供自身的一些信息。这使用下列结构:
struct plugin_info { const char *version; const char *help; };
这样的一个结构体作为user_data
由插件的初始化例程来传递,并使用register_callback
,PLUGIN_INFO
伪事件和一个空回调。
出于分析(或者其它)目的,能够增加自定义的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”参数。
事件PLUGIN_PASS_EXECUTION将指向被执行的pass的指针,作为gcc_data
传递给回调函数。你还可以通过检查cfun来找到该pass所执行的函数。注意,该事件只有当入口检查(gate check)成功时,才会被调用。你可以在你的插件中使用其它钩子,像PLUGIN_ALL_PASSES_START
,PLUGIN_ALL_PASSES_END
,PLUGIN_ALL_IPA_PASSES_START
,PLUGIN_ALL_IPA_PASSES_END
,PLUGIN_EARLY_GIMPLE_PASSES_START
,和/或PLUGIN_EARLY_GIMPLE_PASSES_END
来操纵全局状态,以便获得pass执行的上下文。
在pass的原始的入口函数被调用之后,其结果,入口状态,作为整数被存储。然后事件PLUGIN_OVERRIDE_GATE
被激起,在回调函数的gcc_data
参数中使用一个指向入口状态的指针。一个非零值的入口状态意味着该pass将被执行。你可以同时通过传递的指针读取和改写入口状态。
当你的插件被加载时,你可以检查各种pass列表来确定哪些pass是可用的。但是,其它插件可能会加入新的pass。而且,GCC将来可能会改成在插件加载之后添加通用的pass。当一个pass首次被增加到一个pass列表时,事件PLUGIN_NEW_PASS
被激起,使用回调参数gcc_data
指向新pass。
如果启用了插件技术,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具有相同的版本。