跳轉到內容

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 新增到函式序言和尾聲中。
華夏公益教科書