4. GCC插件开发入门:第一个GCC插件
任务目标
编写一个GCC插件,在编译C代码时,每当GCC处理完一个函数定义,就在编译日志中打印该函数的名称。插件通过 -fplugin 选项加载,不修改GCC自身源码。
环境与依赖
- GCC 4.5+(插件API自4.5起稳定),建议GCC 7或更高版本。
- 需安装GCC插件开发头文件:
gcc-<版本>-plugin-dev(Debian/Ubuntu)或gcc-plugin-devel(RedHat/Fedora)。 - 确认头文件存在:
/usr/lib/gcc/<target>/<version>/plugin/include/gcc-plugin.h
插件完整代码
创建文件 print_fnname.c:
#include <gcc-plugin.h>
#include <plugin-version.h>
#include <tree.h>
#include <c-family/c-common.h>
#include <string.h>
int plugin_is_GPL_compatible;
/* 在GCC处理完每个函数定义后调用 */
static void handle_function(unsigned int event, void *gcc_data, void *user_data) {
tree decl = (tree)gcc_data;
if (decl && TREE_CODE(decl) == FUNCTION_DECL) {
const char *name = IDENTIFIER_POINTER(DECL_NAME(decl));
fprintf(stderr, "[GCC Plugin] function: %s\n", name);
}
}
int plugin_init(struct plugin_name_args *info, struct plugin_gcc_version *version) {
/* 版本兼容性检查 */
if (!plugin_default_version_check(version, &gcc_version)) {
fprintf(stderr, "[GCC Plugin] version mismatch\n");
return 1;
}
/* 注册回调:函数定义完成事件 */
register_callback(info->base_name,
PLUGIN_FINISH_DECL,
handle_function,
NULL);
fprintf(stderr, "[GCC Plugin] loaded: %s\n", info->base_name);
return 0;
}
编译与加载
将插件编译为共享库(以GCC 12为例,其他版本替换版本号):
gcc -I/usr/lib/gcc/x86_64-linux-gnu/12/plugin/include \
-fPIC -shared -o print_fnname.so print_fnname.c
编写待分析的测试文件 test.c:
int add(int a, int b) {
return a + b;
}
int main(void) {
return add(1, 2);
}
加载插件编译:
gcc -fplugin=./print_fnname.so -c test.c -o test.o
验证方法与判定标准
编译时,stderr 应输出:
[GCC Plugin] loaded: print_fnname
[GCC Plugin] function: add
[GCC Plugin] function: main
判定通过条件:
- 插件加载成功,无版本错误;
- 每个函数名恰好打印一次,顺序与GCC处理顺序一致(通常为源码定义顺序);
- 编译正常生成
test.o,无编译错误。
若输出中缺少函数名,检查 PLUGIN_FINISH_DECL 回调是否注册成功,以及 gcc_data 是否为 FUNCTION_DECL 节点。
常见问题
- 找不到
gcc-plugin.h:确认plugin-dev包已安装,且-I路径中的版本号与gcc --version一致。 - 版本检查失败:插件必须与调用它的GCC主版本完全匹配,重新用当前GCC编译插件。
- 无任何输出:确认
-fplugin=后跟的是.so文件的完整路径,且文件有执行权限。