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

判定通过条件:

  1. 插件加载成功,无版本错误;
  2. 每个函数名恰好打印一次,顺序与GCC处理顺序一致(通常为源码定义顺序);
  3. 编译正常生成 test.o,无编译错误。

若输出中缺少函数名,检查 PLUGIN_FINISH_DECL 回调是否注册成功,以及 gcc_data 是否为 FUNCTION_DECL 节点。

常见问题

  • 找不到 gcc-plugin.h:确认plugin-dev包已安装,且 -I 路径中的版本号与 gcc --version 一致。
  • 版本检查失败:插件必须与调用它的GCC主版本完全匹配,重新用当前GCC编译插件。
  • 无任何输出:确认 -fplugin= 后跟的是 .so 文件的完整路径,且文件有执行权限。