GCC 完全指南 / 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_SETUP | Pass 管理器设置 | 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 版本变化,需要检查版本兼容性。
扩展阅读
- GCC Plugins Wiki — 插件开发 Wiki
- GCC Internals Manual — GCC 内部 API
- MELT — GCC 插件的高级 DSL
- Walking the GIMPLE Tree — GIMPLE 遍历
下一步
→ 17 - CMake 集成:学习在 CMake 项目中配置 GCC 编译器标志和工具链。