GCC-3.4.6源代码学习笔记(67)

来源:互联网 发布:知乎和微博 编辑:程序博客网 时间:2024/05/29 14:04
4.3.1.7.8.3.  异常处理句柄

接下来,如果我们没有通过-fno-exception禁止异常机制,C++运行时将为我们建立起异常处理所用的函数声明。在C++中,当抛出一个异常但程序没有捕捉它,那么在全局名字空间,运行时将会捕捉它,并调用std::terminate()来终止程序。这整个功能由定义在文件gcc-3.4.6/libstdc++-v3/libstdsupc++/eh_personality.cc中的运行时函数__cxa_call_unexpected来提供。

而在解析throwcatch语句时,编译器需要建立由exception对象携带的相关的上下文,及为这个上下文调用异常处理逻辑的代码,__cxa_call_unexpected__gxx_personality_v0__gxx_personality_sj0都是这个上下文所引用的处理函数。整个异常处理逻辑及其上下文的建立非常复杂,这里我们不准备深入。在看完后续关于复杂类型的解析及处理后,这部分代码并不难理解。

 

60      void

61      init_exception_processing (void)                                                                      in expt.c

62      {

63        tree tmp;

64     

65        /* void std::terminate (); */

66        push_namespace (std_identifier);

67        tmp = build_function_type (void_type_node, void_list_node);

68        terminate_node = build_cp_library_fn_ptr ("terminate", tmp);

69        TREE_THIS_VOLATILE (terminate_node) = 1;

70        TREE_NOTHROW (terminate_node) = 1;

71        pop_namespace ();

72     

73        /* void __cxa_call_unexpected(void *); */

74        tmp = tree_cons (NULL_TREE, ptr_type_node, void_list_node);

75        tmp = build_function_type (void_type_node, tmp);

76        call_unexpected_node

77          = push_throw_library_fn (get_identifier ("__cxa_call_unexpected"), tmp);

78     

79        eh_personality_libfunc = init_one_libfunc (USING_SJLJ_EXCEPTIONS

80                                            ? "__gxx_personality_sj0"

81                                            : "__gxx_personality_v0");

82     

83        lang_eh_runtime_type = build_eh_type_type;

84        lang_protect_cleanup_actions = &cp_protect_cleanup_actions;

85      }

 

因为std::terminate是运行时库例程,其声明需由build_cp_library_fn_ptr来创建。记住build_cp_library_fn将找出(如果找不到就创建它)声明的具有修饰名的标识符节点,并把它设置为声明节点的汇编名。那么在链接阶段,链接器将在库空间,通过这个汇编名找到这个函数。

 

3350   tree

3351   build_cp_library_fn_ptr (const char* name, tree type)                                        in decl.c

3352   {

3353     return build_cp_library_fn (get_identifier (name), ERROR_MARK, type);

3354   }

 

例程__cxa_call_unexpected是声明为extern “C”,这表示它是一个C形式的函数声明。它的名字将不经过修饰。

 

3393   tree

3394   push_throw_library_fn (tree name, tree type)                                                    in decl.c

3395   {

3396     tree fn = push_library_fn (name, type);

3397     TREE_THIS_VOLATILE (fn) = 1;

3398     TREE_NOTHROW (fn) = 0;

3399     return fn;

3400   }

 

注意在build_library_fn中,声明的语言被设置为C,而且没有设置汇编名,因为在C里,这个名字没有修饰。

 

3359   tree

3360   push_library_fn (tree name, tree type)                                                              in decl.c

3361   {

3362     tree fn = build_library_fn (name, type);

3363     pushdecl_top_level (fn);

3364     return fn;

3365   }

 

接下来pushdecl_top_level将把这个声明加入全局名字空间。

 

3414   tree

3415   pushdecl_top_level (tree x)                                                               in name-lookup.c

3416   {

3417     return pushdecl_top_level_1 (x, NULL);

3418   }

 

下面的push_to_top_level缓存了从当前作用域到全局名字空间的标识符,并停留在全局名字空间。而 pop_from_top_level将恢复这个作用域。

 

3400   static tree

3401   pushdecl_top_level_1 (tree x, tree *init)                                             in name-lookup.c

3402   {

3403     timevar_push (TV_NAME_LOOKUP);

3404     push_to_top_level ();

3405     x = pushdecl_namespace_level (x);

3406     if (init)

3407       cp_finish_decl (x, *init, NULL_TREE, 0);

3408     pop_from_top_level ();

3409     POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, x);

3410   }

 

虽然是尝试把对象加入到当前名字空间,我们有可能是在当前名字空间中的一个作用域里(当然,对于我们的情形,我们一定在全局名字空间里,但从pushdecl_namespace_level的角度,这是有可能的)。因此在3232行,获取了当前作用域。其目的下面可以看到。

 

3229   tree

3230   pushdecl_namespace_level (tree x)                                                     in name-lookup.c

3231   {

3232     struct cp_binding_level *b = current_binding_level;

3233     tree t;

3234  

3235     timevar_push (TV_NAME_LOOKUP);

3236     t = pushdecl_with_scope (x, NAMESPACE_LEVEL (current_namespace));

 

对于把对象加入名字空间,pushdecl_with_scope的行为类似于pushdecl,除了它是为指定的名字空间,而不一定是当前作用域,因此为保险起见,总是缓存当前作用域,并随后恢复。

 

1941   tree

1942   pushdecl_with_scope (tree x, cxx_scope *level)                                   in name-lookup.c

1943   {

1944     struct cp_binding_level *b;

1945     tree function_decl = current_function_decl;

1946  

1947     timevar_push (TV_NAME_LOOKUP);

1948     current_function_decl = NULL_TREE;

1949     if (level->kind == sk_class)

1950     {

1951       b = class_binding_level;

1952       class_binding_level = level;

1953       pushdecl_class_level (x);

1954       class_binding_level = b;

1955     }

1956     else

1957     {

1958       b = current_binding_level;

1959       current_binding_level = level;

1960       x = pushdecl (x);

1961       current_binding_level = b;

1962     }

1963     current_function_decl = function_decl;

1964     POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, x);

1965   }

 

对于每个作用域对象,如果它是一个类作用域,域class_shadowed记录了被绑定的类型声明。而域type_shadowed则是在所有的非名字空间作用域中,记录被该作用域所屏蔽的类型(如果没有屏蔽,则是记录自己)。为什么是非名字空间作用域?因为通过域level_chain,很容易确定指定的标识符,在指定的名字空间中是否可见。这是因为名字空间总是最外层的作用域,并且一直有效。

 

pushdecl_namespace_level (continue)

 

3238     /* Now, the type_shadowed stack may screw us. Munge it so it does

3239       what we want.  */

3240     if (TREE_CODE (x) == TYPE_DECL)

3241     {

3242       tree name = DECL_NAME (x);

3243       tree newval;

3244       tree *ptr = (tree *)0;

3245       for (; !global_scope_p (b); b = b->level_chain)

3246       {

3247         tree shadowed = b->type_shadowed;

3248         for (; shadowed; shadowed = TREE_CHAIN (shadowed))

3249           if (TREE_PURPOSE (shadowed) == name)

3250           {

3251             ptr = &TREE_VALUE (shadowed);

3252             /* Can't break out of the loop here because sometimes

3253               a binding level will have duplicate bindings for

3254               PT names. It's gross, but I haven't time to fix it.  */

3255           }

3256       }

3257       newval = TREE_TYPE (x);

3258      if (ptr == (tree *)0)

3259       {

3260         /* @@ This shouldn't be needed. My test case "zstring.cc" trips

3261           up here if this is changed to an assertion.  --KR  */

3262         SET_IDENTIFIER_TYPE_VALUE (name, x);

3263       }

3264       else

3265       {

3266         *ptr = newval;

3267       }

3268     }

3269     POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, t);

3270   }

 

正如我们在前面提及的,当前作用域可能在当前名字空间里。现在往名字空间里加入了新的类型,那么要检查当前作用域到当前名字空间之间,是否会对这个类型构成屏蔽。在3245行,b就是当前作用域,通过level_chain,一直向上攀升至全局名字空间,不过因为名字空间不使用type_shadowed域,因此,3245行的FOR循环实际找到的是,被最接近当前名字空间的内部作用域所屏蔽的类型,该类型在当前名字空间外的名字空间中(该函数的使用保证了这一点)。毫无疑问,这个屏蔽类型应该更新为当前加入的类型(3262行)。如果内层作用域没有这个名字的类型声明,ptr将是03258行)。

__gxx_personality_sj0__gxx_personality_v0都声明为C形式(extern “C”)。它们的声明节点由init_one_libfunc创建,这个声明类似:int __gxx_personality_sj0()

 

5126   rtx

5127   init_one_libfunc (const char *name)                                                          in optabs.c

5128   {

5129     rtx symbol;

5130  

5131     /* Create a FUNCTION_DECL that can be passed to

5132       targetm.encode_section_info.  */

5133     /* ??? We don't have any type information except for this is

5134       a function. Pretend this is "int foo()".  */

5135     tree decl = build_decl (FUNCTION_DECL, get_identifier (name),

5136                        build_function_type (integer_type_node, NULL_TREE));

5137     DECL_ARTIFICIAL (decl) = 1;

5138     DECL_EXTERNAL (decl) = 1;

5139     TREE_PUBLIC (decl) = 1;

5140  

5141     symbol = XEXP (DECL_RTL (decl), 0);

5142  

5143     /* Zap the nonsensical SYMBOL_REF_DECL for this. What we're left with

5144       are the flags assigned by targetm.encode_section_info.  */

5145     SYMBOL_REF_DECL (symbol) = 0;

5146  

5147     return symbol;

5148   }

 

5141行,DECL_RTL调用make_decl_rtl(其细节,参考为内建函数创建RTX对象)。注意到在该函数的790行,它要获取声明的修饰名,但是我们手上现在没有函数正确的声明(比较unwind-cxx.h中的声明)!

幸运的是,这里我们不需要完全正确的函数声明来得到库函数的入口名。类似__gxx_personality_v0的例程具有C链接性。这样其修饰名与声明名相同。那么只要在运行时提供正确的实参,一切都OK

4.3.1.7.8.4.  检测链接器的行为及其它

GCC支持弱符号(weak symbol)的概念。

同名的2个或以上的全局符号,只有除1个外其他都声明为弱符号时,才不会导致冲突。链接器忽略弱符号的定义,而使用全局符号的定义来解析所有的引用,不过如果没有全局符号,弱符号将被使用。弱符号可以用于命名函数及可被用户代码改写的数据。弱符号亦被称为弱别名(weak alias),或简单地“弱”(weak)。

另外,GCC还能输出下列的汇编器指示(assembler directive)。

linkonce[type]:标记当前段使其只被链接器包含一次,即便在多个模块中出现相同的段。该指示必须在该段的每个实例中出现一次。段通过名字来被选定,因此名字必须唯一。

可选的参数type不使用,将使得重复的段被无声丢弃(默认行为)。如果typeone_only 将每次发现重复时发出一个警告。Typesame_size,则如果重复的段大小不同,将发出警告,如果重复的段。Typesame_contents,则如果重复的段的内容不同,将发出警告。

这一切都是依赖于目标平台的。对于x86elf格式的输出,support_one_only 返回0,因而设置flag_weak0

 

4549   int

4550   supports_one_only (void)                                                                         in varasm.c

4551   {

4552     if (SUPPORTS_ONE_ONLY)

4553       return 1;

4554     return SUPPORTS_WEAK;

4555   }

 

在编程中,我们可以使用__func____FUNCTION____PRETTY__FUNCTION__来获取当前函数名。函数指针make_fname_decl用于保存相应的处理函数,在3129行,被设置为cp_make_fname_decl。函数start_fname_decls准备保存这些函数名的栈。后面可以再次看到这个函数。

4.3.1.8.    设置预处理器

cxx_init_decl_processing返回,前端的C++的运行时已经建立,接下来设置预处理器。首先,创建我们耳熟能详的null对象。正如我们多次在程序中所见,这家伙实际就是整数0,不过注意它的大小正好是一个指针的大小。

 

cxx_init (continue)

 

412   /* Create the built-in __null node.  */

413    null_node = build_int_2 (0, 0);

414    TREE_TYPE (null_node) = c_common_type_for_size (POINTER_SIZE, 0);

415    ridpointers[RID_NULL] = null_node;

416 

417    interface_unknown = 1;

418 

419    if (c_common_init () == false)

420    {

421      pop_srcloc();

422      return false;

423    }

 

下面的cpp_init_iconv初始化iconv描述符,它用于把源字符集转换为执行字符集。我们跳过这部分。flag_preprocess_only如果非0,表示只做预处理,那么至此,进行预处理的条件已经成熟,为之调用1207行的preprocess_file。并由此返回结束文件的编译处理。

 

1186   bool

1187   c_common_init (void)                                                                              in c-opts.c

1188   {

1189     input_line = saved_lineno;

1190  

1191    /* Set up preprocessor arithmetic. Must be done after call to

1192       c_common_nodes_and_builtins for type nodes to be good.  */

1193     cpp_opts->precision = TYPE_PRECISION (intmax_type_node);

1194     cpp_opts->char_precision = TYPE_PRECISION (char_type_node);

1195     cpp_opts->int_precision = TYPE_PRECISION (integer_type_node);

1196     cpp_opts->wchar_precision = TYPE_PRECISION (wchar_type_node);

1197     cpp_opts->unsigned_wchar = TREE_UNSIGNED (wchar_type_node);

1198     cpp_opts->bytes_big_endian = BYTES_BIG_ENDIAN;

1199  

1200     /* This can't happen until after wchar_precision and bytes_big_endian

1201       are known.  */

1202     cpp_init_iconv (parse_in);

1203  

1204     if (flag_preprocess_only)

1205     {

1206       finish_options ();

1207       preprocess_file (parse_in);

1208       return false;

1209     }

1210  

1211    /* Has to wait until now so that cpplib has its hash table.  */

1212     init_pragma ();

1213  

1214     return true;

1215   }

 

如果需要完整的编译,那么对于x86 linux目标,C语言只有2个指示“pack”及“weak”可用(它们将出现在__attribute__()块中),因为#pragma指示是依赖于目标平台的。

 

494    void

495    init_pragma (void)                                                                           in c-pragma.c

496    {

497    #ifdef HANDLE_PRAGMA_PACK

498      c_register_pragma (0, "pack", handle_pragma_pack);

499    #endif

500    #ifdef HANDLE_PRAGMA_WEAK

501      c_register_pragma (0, "weak", handle_pragma_weak);

502    #endif

503    #ifdef HANDLE_PRAGMA_REDEFINE_EXTNAME

504      c_register_pragma (0, "redefine_extname", handle_pragma_redefine_extname);

505    #endif

506    #ifdef HANDLE_PRAGMA_EXTERN_PREFIX

507      c_register_pragma (0, "extern_prefix", handle_pragma_extern_prefix);

508    #endif

509   

510    #ifdef REGISTER_TARGET_PRAGMAS

511       REGISTER_TARGET_PRAGMAS ();

512    #endif

513    }

 

c_register_pragma把指示的标识符插入哈希表ident_hash并与处理句柄绑定。

 

486    void

487    c_register_pragma (const char *space, const char *name,                      in c-pragma.c

488                   void (*handler) (struct cpp_reader *))

489    {

490      cpp_register_pragma (parse_in, space, name, handler);

491    }

4.3.1.9.    完成前端初始化

C++支持更多的#pragma指示,那么在下面初始化这些指示。

 

cxx_init (continue)

 

425    init_cp_pragma ();

426 

427    init_repo (main_input_filename);

428 

429    pop_srcloc();

430    return true;

431  }

 

下面的#pragma vtablegcc不再支持,而#pragma unitgcc并未实质支持。

 

368    static void

369    init_cp_pragma (void)                                                                                    in lex.c

370    {

371      c_register_pragma (0, "vtable", handle_pragma_vtable);

372      c_register_pragma (0, "unit", handle_pragma_unit);

373      c_register_pragma (0, "interface", handle_pragma_interface);

374      c_register_pragma (0, "implementation", handle_pragma_implementation);

375      c_register_pragma ("GCC", "interface", handle_pragma_interface);

376      c_register_pragma ("GCC", "implementation", handle_pragma_implementation);

377      c_register_pragma ("GCC", "java_exceptions", handle_pragma_java_exceptions);

378    }

 

当使用-frepo选项编译代码时,将产生后缀为.rpo的文件,每个文件列出在对应目标文件中所具现的模板。然后调用名为collect2的封装了链接器的工具,使用链接器指令来更新.rpo文件,以在最终的程序里放置模板实例。这个方法唯一的困难在于库的处理——除非提供了相关的.rpo文件,链接库里的模板实例将会失败。

427行,init_repo为主输入文件创建了.rpo后缀的文件。

 

原创粉丝点击