强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

16 - GCC 插件开发

16 - GCC 插件开发

了解 GCC 插件架构,学习如何开发自定义编译器插件,在编译流程中注入自定义分析和变换。


16.1 GCC 插件概述

GCC 插件允许开发者在不修改 GCC 源码的情况下扩展编译器功能。

插件的典型应用场景

场景说明
静态分析自定义的代码检查规则
代码变换自动的代码生成或优化
代码度量圈复杂度、代码行数统计
安全检查自定义安全规则
编码规范强制团队编码规范
日志注入自动插入日志/性能计数代码

插件架构

┌────────────────────────────────────────────┐
│               GCC 编译器                    │
│  ┌────────────────────────────────────────┐│
│  │         Plugin API (libgccplugin)       ││
│  │  - 事件注册                            ││
│  │  - GIMPLE/RTL 访问                     ││
│  │  - Tree 操作                           ││
│  └──────────┬─────────────────────────────┘│
│             │                              │
│  ┌──────────▼──────────┐                   │
│  │     自定义插件       │                   │
│  │  (共享库 .so 文件)   │                   │
│  └──────────────────────┘                   │
│                                             │
│  编译流程:                                   │
│  Parse → GIMPLE → Passes → RTL → Assembly  │
│             ↑                               │
│         插件挂接点                           │
└────────────────────────────────────────────┘

16.2 开发环境准备

# 安装 GCC 插件开发头文件
sudo apt install gcc-13-plugin-dev

# 验证插件 API 头文件
ls /usr/lib/gcc/x86_64-linux-gnu/13/plugin/include/

# 检查插件 ABI 版本
grep -r "GCC_PLUGIN_VERSION" /usr/lib/gcc/x86_64-linux-gnu/13/plugin/include/plugin-version.h

16.3 第一个 GCC 插件

插件源码

// hello_plugin.c
#include <gcc-plugin.h>
#include <plugin-version.h>
#include <tree.h>
#include <tree-pass.h>
#include <function.h>
#include <stringpool.h>
#include <diagnostic.h>

// 插件必须声明这个全局结构
int plugin_is_GPL_compatible;

// 在每个函数编译前执行
static void callback_start_unit(void *gcc_data, void *user_data) {
    fprintf(stderr, "Hello GCC Plugin! Compiling file: %s\n",
            main_input_filename);
}

// 在每个函数开始解析时执行
static void callback_pre_genericize(void *gcc_data, void *user_data) {
    tree fndecl = (tree)gcc_data;
    const char *name = IDENTIFIER_POINTER(DECL_NAME(fndecl));
    fprintf(stderr, "  Processing function: %s\n", name);
}

// 插件初始化入口
int plugin_init(struct plugin_name_args *plugin_info,
                struct plugin_gcc_version *version) {
    // 检查 GCC 版本兼容性
    if (!plugin_default_version_check(version, &gcc_version)) {
        error("GCC version mismatch: plugin built for %s, running %s",
              gcc_version.basever, version->basever);
        return 1;
    }

    fprintf(stderr, "Plugin '%s' initialized (GCC %s)\n",
            plugin_info->base_name, version->basever);

    // 注册编译单元开始事件
    register_callback(plugin_info->base_name,
                      PLUGIN_START_UNIT,
                      callback_start_unit,
                      NULL);

    // 注册函数泛型化前事件
    register_callback(plugin_info->base_name,
                      PLUGIN_PRE_GENERICIZE,
                      callback_pre_genericize,
                      NULL);

    return 0;
}

编译和使用插件

# 编译插件
gcc-13 -shared -fPIC -I/usr/lib/gcc/x86_64-linux-gnu/13/plugin/include \
    -o hello_plugin.so hello_plugin.c

# 使用插件编译
gcc-13 -fplugin=./hello_plugin.so -o hello main.c

# 输出:
# Plugin 'hello_plugin' initialized (GCC 13.2.0)
# Hello GCC Plugin! Compiling file: main.c
#   Processing function: main

16.4 GCC 插件事件

可用事件一览

事件触发时机数据
PLUGIN_START_UNIT编译单元开始NULL
PLUGIN_FINISH_UNIT编译单元结束NULL
PLUGIN_PRE_GENERICIZE函数泛型化前tree fndecl
PLUGIN_FINISH_TYPE类型完成tree type
PLUGIN_FINISH_DECL声明完成tree decl
PLUGIN_PASS_MANAGER_SETUPPass 管理器设置NULL
PLUGIN_FINISH_PARSE_FUNCTION函数解析完成tree fndecl
PLUGIN_ALL_PASSES_START所有 pass 开始前NULL
PLUGIN_ALL_PASSES_END所有 pass 结束后NULL
PLUGIN_INFO插件信息查询NULL

注册 Pass

// 自定义 GIMPLE Pass 示例
#include <gcc-plugin.h>
#include <plugin-version.h>
#include <tree.h>
#include <tree-pass.h>
#include <gimple.h>
#include <gimple-iterator.h>
#include <diagnostic.h>

int plugin_is_GPL_compatible;

// Pass 相关数据
const pass_data my_pass_data = {
    GIMPLE_PASS,       // pass 类型
    "my_custom_pass",  // pass 名称
    OPTGROUP_NONE,     // 优化组
    TV_NONE,           // 时间变量
    PROP_gimple_any,   // 所需属性
    0,                 // 提供的属性
    0,                 // 不需要的属性
    0,                 // 需要的属性
};

// Pass 类定义
struct my_pass : gimple_opt_pass {
    my_pass(gcc::context *ctx)
        : gimple_opt_pass(my_pass_data, ctx) {}

    // 判断是否应该执行此 pass
    virtual bool gate(function *fun) override {
        return true;  // 对所有函数执行
    }

    // 执行 pass
    virtual unsigned int execute(function *fun) override {
        // 遍历函数中的所有 GIMPLE 语句
        basic_block bb;
        FOR_EACH_BB_FN(bb, fun) {
            gimple_stmt_iterator gsi;
            for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
                gimple *stmt = gsi_stmt(gsi);

                // 检查是否是函数调用
                if (is_gimple_call(stmt)) {
                    tree fndecl = gimple_call_fndecl(stmt);
                    if (fndecl) {
                        const char *name = IDENTIFIER_POINTER(
                            DECL_NAME(fndecl));
                        // 检测 malloc 没有对应的 free
                        if (strcmp(name, "malloc") == 0) {
                            warning_at(gimple_location(stmt), 0,
                                "found malloc call in %s",
                                function_name(fun));
                        }
                    }
                }
            }
        }
        return 0;  // 0 = 无额外属性变化
    }
};

// 插件初始化
int plugin_init(struct plugin_name_args *plugin_info,
                struct plugin_gcc_version *version) {
    struct register_pass_info pass_info;

    pass_info.pass = new my_pass(g);
    pass_info.reference_pass_name = "ssa";    // 在 SSA pass 之后执行
    pass_info.ref_pass_instance_number = 1;
    pass_info.pos_op = PASS_POS_INSERT_AFTER;

    register_callback(plugin_info->base_name,
                      PLUGIN_PASS_MANAGER_SETUP,
                      NULL,
                      &pass_info);

    return 0;
}

16.5 GIMPLE 和 RTL

GIMPLE 中间表示

GIMPLE 是 GCC 的主要中间表示,三地址码形式,简化了优化分析。

// 原始 C 代码
int compute(int a, int b) {
    int c = a + b;
    int d = c * 2;
    return d;
}

// GIMPLE 表示(简化)
// compute (int a, int b)
// {
//   int c;
//   int d;
//   int _1;
//   int _2;
//
//   _1 = a + b;        // GIMPLE 赋值
//   c = _1;
//   _2 = c * 2;
//   d = _2;
//   return d;
// }

遍历 GIMPLE 语句

static void dump_function_gimple(function *fun) {
    basic_block bb;
    FOR_EACH_BB_FN(bb, fun) {
        fprintf(stderr, "Basic Block %d:\n", bb->index);
        gimple_stmt_iterator gsi;
        for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
            gimple *stmt = gsi_stmt(gsi);
            print_gimple_stmt(stderr, stmt, 0, TDF_VOPS | TDF_MEMSYMS);
        }
    }
}

16.6 实用插件示例

函数调用计数器插件

// call_counter.c
#include <gcc-plugin.h>
#include <plugin-version.h>
#include <tree.h>
#include <tree-pass.h>
#include <gimple.h>
#include <gimple-iterator.h>
#include <diagnostic.h>

int plugin_is_GPL_compatible;

static int call_count = 0;

// 在每个 GIMPLE 语句中查找函数调用
static unsigned int count_calls_execute(function *fun) {
    basic_block bb;
    FOR_EACH_BB_FN(bb, fun) {
        gimple_stmt_iterator gsi;
        for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
            gimple *stmt = gsi_stmt(gsi);
            if (is_gimple_call(stmt)) {
                call_count++;
                tree fndecl = gimple_call_fndecl(stmt);
                if (fndecl && DECL_NAME(fndecl)) {
                    fprintf(stderr, "  Call to: %s at %s:%d\n",
                            IDENTIFIER_POINTER(DECL_NAME(fndecl)),
                            gimple_filename(stmt),
                            gimple_lineno(stmt));
                }
            }
        }
    }
    return 0;
}

// GIMPLE Pass 定义
static const pass_data count_calls_pass_data = {
    GIMPLE_PASS, "call_counter", OPTGROUP_NONE,
    TV_NONE, PROP_gimple_any, 0, 0, 0
};

struct count_calls_pass : gimple_opt_pass {
    count_calls_pass(gcc::context *ctx)
        : gimple_opt_pass(count_calls_pass_data, ctx) {}

    bool gate(function *) override { return true; }
    unsigned int execute(function *fun) override {
        fprintf(stderr, "Function: %s\n", function_name(fun));
        return count_calls_execute(fun);
    }
};

// 文件完成时输出统计
static void finish_unit(void *, void *) {
    fprintf(stderr, "\nTotal function calls: %d\n", call_count);
}

int plugin_init(struct plugin_name_args *info,
                struct plugin_gcc_version *ver) {
    struct register_pass_info pass_info;
    pass_info.pass = new count_calls_pass(g);
    pass_info.reference_pass_name = "ssa";
    pass_info.ref_pass_instance_number = 1;
    pass_info.pos_op = PASS_POS_INSERT_AFTER;

    register_callback(info->base_name, PLUGIN_PASS_MANAGER_SETUP,
                      NULL, &pass_info);
    register_callback(info->base_name, PLUGIN_FINISH_UNIT,
                      finish_unit, NULL);
    return 0;
}

16.7 调试插件

# 使用 GDB 调试插件
gcc -fplugin=./my_plugin.so -o hello main.c
# 如果插件崩溃,GDB 会停在崩溃位置

# 添加 -v 输出详细信息
gcc -v -fplugin=./my_plugin.so -o hello main.c

# 转储 GIMPLE
gcc -fdump-tree-all -fplugin=./my_plugin.so -o hello main.c

# 转储 RTL
gcc -fdump-rtl-all -fplugin=./my_plugin.so -o hello main.c

16.8 插件 API 常用函数

函数说明
register_callback()注册事件回调
warning_at()输出警告信息
error_at()输出错误信息
inform()输出信息
IDENTIFIER_POINTER()获取标识符名称
DECL_NAME()获取声明名称
gimple_call_fndecl()获取函数调用的目标声明
gimple_location()获取语句的源码位置
gimple_filename()获取文件名
gimple_lineno()获取行号
function_name()获取函数名

要点回顾

要点核心内容
插件格式共享库 (.so),实现 plugin_init() 入口
事件机制register_callback() 注册事件处理函数
GIMPLE编译器主要中间表示,三地址码形式
Pass 体系可以插入自定义 GIMPLE Pass 和 RTL Pass
应用静态分析、代码变换、编码规范检查

注意事项

GCC 版本依赖: 插件 ABI 可能随 GCC 版本变化,插件需要针对特定 GCC 版本编译。

GPL 兼容性: 使用 GCC 插件 API 的插件必须声明 plugin_is_GPL_compatible

Pass 顺序: 自定义 Pass 的执行位置会影响分析结果,需要理解现有 Pass 的执行顺序。

GIMPLE 不稳定: GIMPLE API 可能随 GCC 版本变化,需要检查版本兼容性。


扩展阅读


下一步

17 - CMake 集成:学习在 CMake 项目中配置 GCC 编译器标志和工具链。