跳转到内容

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_declgem_start_function 在函数或变量声明/定义开始时调用。
  • 钩子 gem_build_function_call 允许修改函数调用的名称和参数。
  • 钩子 gem_finish_function 插入到 finish_function() 中,该函数从语法文件中调用。编译器扩展在函数体被翻译成 RTL 之前接收函数体。
  • 钩子 gem_output_asm_insngem_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 添加到函数序言和尾声中。
华夏公益教科书