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。
函数 Prolog/Epilog
[编辑源代码]汇编指令被写入汇编文件
#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 添加到函数序言和尾声中。 |