GNU C 编译器内部/GEM 框架 3 4
钩子
[编辑源代码]GEM 框架旨在简化编译器扩展的开发。GEM 的理念类似于 Linux 安全模块 (LSM) 的理念,这是一个在整个 Linux 内核中定义钩子的项目,允许人们强制执行安全策略。
GEM 在 GCC 的源代码中定义了许多钩子。它实现为 GCC 的补丁。使用 GEM,编译器扩展被开发为一个独立的程序。它被编译成一个动态链接模块,该模块在调用 GCC 时被指定为命令行参数。GCC 加载模块并调用其初始化函数。然后,该模块注册其钩子,这些钩子是 GCC 中的回调函数。
除了编译器钩子,GEM 还提供了简化扩展开发的宏和函数。在本章中,我们将首先介绍 GEM 框架添加到 GCC 中的钩子。然后,我们描述扩展编程中的典型问题。
项目主页在 http://research.alexeysmirnov.name/gem
GEM 在 GCC 源代码中添加了几个钩子。根据需要将新钩子添加到 GEM 中。
- 钩子 gem_handle_option 用于函数 handle_option(),该函数处理每个命令行选项。钩子将当前选项作为其参数。如果钩子返回的值为 GEM_RETURN,则 GCC 将忽略该选项。
- 钩子 gem_c_common_nodes_and_builtins 在创建所有标准类型后被调用。GCC 扩展可以创建其他类型。
- 钩子 gem_macro_name 允许保存正在定义的宏的名称。另一个 GEM 钩子 gem_macro_def 在解析宏定义时被调用。使用新宏定义的宏名称,可以重新定义宏。此钩子被添加到函数 create_iso_definition() 中。
- 钩子 gem_start_decl 和 gem_start_function 在函数或变量声明/定义开始时被调用。
- 钩子 gem_build_function_call 允许修改函数调用的名称和参数。
- 钩子 gem_finish_function 被插入到 finish_function() 中,该函数从语法文件调用。编译器扩展在函数主体被翻译成 RTL 之前接收函数主体。
- 钩子 gem_output_asm_insn 和 gem_final_start_function 被添加到函数 output_asm_insn() 中,该函数用于汇编代码的每个指令,以及函数 final_start_function() 中,该函数在汇编代码被写入文件时调用。前一个钩子接收写入文件的文本,允许它修改输出。后一个钩子可以修改函数的序言。
要点: | GEM 钩子主要在 AST 级别定义。一些钩子在汇编级别定义。根据需要添加新的钩子。 |
遍历 AST
[编辑源代码]当函数的 AST 被构建时,可以检测它。GEM 的 gem_finish_function 钩子接收函数的 AST。想法是遍历 AST 并根据需要检测 AST 节点。函数 walk_tree() 获取 AST、回调函数、可选数据(默认情况下为 NULL)以及 walk_subtrees 参数(默认情况下为 NULL)。回调函数在遍历操作数之前,对 AST 的每个节点进行调用。如果回调函数修改了 walk_subtree() 变量,则不会处理操作数。
以下代码演示了这个想法
static tree walk_tree_callback(tree *tp, int *walk_subtrees, void *data) { tree t=*tp; enum tree_code code = TREE_CODE(t); switch (code) { case CALL_EXPR: instrument_call_expr(t); break; case MODIFY_EXPR: instrument_modify_expr(t); break; } } walk_tree(&t_body, walk_tree_callback, NULL, NULL);
要点: | 函数 walk_tree() 遍历 AST,将用户定义的回调函数应用于每个树节点。 |
检测 AST
[编辑源代码]在本节中,我们将描述创建新的树节点的函数以及如何将新节点添加到 AST。
walk_tree 回调函数可以检测 AST。函数 build1() 和 build() 构建新的树节点。前一个函数接收一个操作数,后一个函数接收多个操作数。以下代码计算操作数的地址,与 C 运算符 '&' 相同
t = build1(ADDR_EXPR, TREE_TYPE(t), t);
以下示例是指数组元素 arr[0]
t = build(ARRAY_REF, integer_type_node, arr, integer_zero_node);
以下示例构建一个整数常量
t = build_int_2(value, 0);
构建字符串常量比较困难。以下示例演示了这个想法
tree gem_build_string_literal(int len, const char *str) { tree t, elem, index, type; t = build_string (len, str); elem = build_type_variant (char_type_node, 1, 0); index = build_index_type (build_int_2(len-1, 0)); type = build_array_type (elem, index); T_T(t) = type; TREE_READONLY(t)=1; TREE_STATIC(t)=1; TREE_CONSTANT(t)=1; type=build_pointer_type (type); t = build1 (ADDR_EXPR, type, t); t = build1 (NOP_EXPR, build_pointer_type(char_type_node), t); return t; }
要构建函数调用,需要构建参数列表。然后构建 CALL_EXPR
t_arg1 = build_tree_list(NULL_TREE, arg1); t_arg2 = build_tree_list(NULL_TREE, arg2); ... TREE_CHAIN(t_arg1)=t_arg2; ... TREE_CHAIN(t_argn)=NULL_TREE; t_call = build_function_call(t_func_decl, t_arg1);
构建的树节点被添加为语句或表达式添加到 AST 中。使用 TREE_CHAIN 将语句添加到语句的链接列表中
t_stmt=build_stmt (EXPR_STMT, t_call); TREE_CHAIN(t_cur)=t_stmt;
将树节点添加为表达式与使用 C 运算符 '()' 相同。新表达式被添加为运算符的第一个参数。运算符的结果是其第二个参数的结果
t_res = build(COMPOUND_EXPR, TREE_TYPE(t), t_call, t);
如果要将调用添加到 t 后面,则需要构建一个复合语句。这等效于在 C 中使用花括号
static tree gen_start_scope() { t_hdr = build_stmt (COMPOUND_STMT, NULL_TREE); ss = build_stmt (SCOPE_STMT, NULL_TREE); SCOPE_BEGIN_P(ss)=1; SCOPE_PARTIAL_P(ss)=0; TREE_CHAIN(t_hdr)=NULL_TREE; TREE_OPERAND(t_hdr, 0)=ss; return t_hdr; }
static tree gen_end_scope() { ss = build_stmt (SCOPE_STMT, NULL_TREE); SCOPE_BEGIN_P(ss)=0; SCOPE_PARTIAL_P(ss)=0; return ss; }
static tree scope_stmt(tree t) { t_res=gen_start_scope(); TREE_CHAIN(TREE_OPERAND(t_res, 0)) = t; while (TREE_CHAIN(t)) t = TREE_CHAIN(t); TREE_CHAIN(t)=gen_end_scope(); return t_res; }
要点: | 使用 GCC 的函数构建新的节点并将它们添加到 AST 中,无论是作为语句还是表达式。 |
何时检测
[编辑源代码]在本节中,我们将描述每个 GEM 钩子何时使用。
- 在钩子 gem_c_common_nodes_and_builtins 中添加新的函数和类型声明。
- 在钩子 gem_finish_function 中,在解析 AST 后检测它。
函数序言/结语
[编辑源代码]汇编指令被写入汇编文件
#define OUTPUT_ASM_INST(inst) \ p=inst; \ putc('\t', asm_out_file); \ while (*p++) putc(p, asm_out_file); \ putc('\n', asm_out_file); OUTPUT_ASM_INST("pushl %%eax"); OUTPUT_ASM_INST("popl %%eax");
要点: | 汇编指令被添加到函数序言和结语中,使用钩子 gem_output_asm_insn 和 gem_final_start_function。 |