gcc多平台va_list重载问题

刚刚解决了一个自认为十分诡异的BUG,其根本原因是自己的技术深度不够,没有考虑不同平台下gcc中内置变量的定义不同所造成的。

现象为Koala LINUX客户端在64位平台工作正常,在32位平台会出现core dump,对core文件进行分析,调用堆栈如下所示:

#0  0x00322701 in vfprintf () from /lib/libc.so.6
#1  0x003475f0 in vsnprintf () from /lib/libc.so.6
#2  0x0805c23b in inspsys::InspLog::InternalDispatch (this=0x838e050, 
    format=0xbfef57a0 "[AGFSMONITOR][DEBUG]begin handle message %s", 
    vars=0x8062c73 "why") at insplog.cpp:100
#3  0x0805bfdf in inspsys::InspLog::Debug (this=0x838e050, 
    fmt=0x8062c77 "begin handle message %s", vars=0x8062c73 "why")
    at insplog.cpp:26
#4  0x08053658 in FSMonitorService::HandleMessage (this=0x806f660)
    at fsmonitorservice.cpp:191
#5  0x0804b789 in main () at agfsmonitor.cpp:50

可见,在HandleMessage中调用了InspLog进行日志打印,进而调用vsnprintf进行不定参的解析时出现core dump。其中,InspLog的定义简要如下所示:

class InspLog
{
public:
    //不定参日志接口
    void Debug(const char *format, ...);
    void Debug(const char *format, va_list vars);
private:
    void InternalDispatch(const char *format, va_list vars);
};
写入日志时,其调用过程应为Debug(const char *format,...)->Debug(const char *format, va_list vars)->InternalDispatch(oncst char *format, va_list vars).

其中,Debug(const char *format, ...)对可变参数进行第一步处理,在日志信息中增加写入日志的模块名;

            Debug(const char *format, va_list vars)进行第二步处理,在日志信息中增加写入日志的时间;

            InternalDispatch(const char *format, va_list vars)进行日志的最终拼接及写入。

对比上面的core dump,可以发现其中少了对于Debug(const char *format, ...)的调用,而直接进行了Debug(const char *format, va_list vars)的调用,进而造成了core dump。

为什么呢?难道va_list在32位平台上的定义为char *,故而gcc对于Debug的函数覆盖进行的最佳选择为Debug(const char *format, va_list vars)而不是Debug(const char *format, ...)。

通过下面的代码进行了验证:

#include 
#include 
#include 
#include 
int main()
{
    printf("va_list type name is %s\n", typeid(va_list).name());
    printf("char * type name is %s\n", typeid(char *).name());
    return 0;
}
以上代码在CentOS_X86_64_6.3上执行时,输出结果为
va_list type name is A1_13__va_list_tag
char * type name is Pc
在CentOS_X86_32_5.7上执行时,输出结果为

va_list type name is Pc
char * type name is Pc
通过上面的测试,可见在32位机器上将va_list解析成了char*,导制在写入一个参数且为字符型的日志,会错误地调用Debug(const char *format, va_list vars)。


进一步对va_list的定义进行分析,va_list被定义成了__builtin_va_list,而__builtin_va_list是gcc的内置定义。下面对__built_va_list的实现进行分析。

__built_va_list为gcc的内置变量,其定义在gcc代码的c-common.c中,具体如下:

4355   lang_hooks.decls.pushdecl
4356     (build_decl (TYPE_DECL, get_identifier ("__builtin_va_list"),
4357          va_list_type_node));
可见,其类型通过va_list_type_node进行获取,而va_list_type_node的初始化在tree.c中,具体如下:
    tree t = targetm.build_builtin_va_list ();

    /* Many back-ends define record types without setting TYPE_NAME.
       If we copied the record type here, we'd keep the original
       record type without a name.  This breaks name mangling.  So,
       don't copy record types and let c_common_nodes_and_builtins()
       declare the type to be __builtin_va_list.  */
    if (TREE_CODE (t) != RECORD_TYPE)
      t = build_variant_type_copy (t);

    va_list_type_node = t;

可见,va_list_type_node由targettm.build_buildin_va_list()获取得到,而对于X86平台,build_builtin_va_list的实现如下:

 7023 static tree
 7024 ix86_build_builtin_va_list_abi (enum calling_abi abi)
 7025 {
 7026   tree f_gpr, f_fpr, f_ovf, f_sav, record, type_decl;
 7027 
 7028   /* For i386 we use plain pointer to argument area.  */
 7029   if (!TARGET_64BIT || abi == MS_ABI)
 7030     return build_pointer_type (char_type_node);
 7031 
 7032   record = (*lang_hooks.types.make_type) (RECORD_TYPE);
 7033   type_decl = build_decl (TYPE_DECL, get_identifier ("__va_list_tag"), record);
 7034 
 7035   f_gpr = build_decl (FIELD_DECL, get_identifier ("gp_offset"),
 7036               unsigned_type_node);
 7037   f_fpr = build_decl (FIELD_DECL, get_identifier ("fp_offset"),
 7038               unsigned_type_node);
 7039   f_ovf = build_decl (FIELD_DECL, get_identifier ("overflow_arg_area"),
 7040               ptr_type_node);
 7041   f_sav = build_decl (FIELD_DECL, get_identifier ("reg_save_area"),
 7042               ptr_type_node);
 7043 
 7044   va_list_gpr_counter_field = f_gpr;
 7045   va_list_fpr_counter_field = f_fpr;
 7046 
 7047   DECL_FIELD_CONTEXT (f_gpr) = record;
 7048   DECL_FIELD_CONTEXT (f_fpr) = record;
 7049   DECL_FIELD_CONTEXT (f_ovf) = record;
 7050   DECL_FIELD_CONTEXT (f_sav) = record;
 7051 
 7052   TREE_CHAIN (record) = type_decl;
 7053   TYPE_NAME (record) = type_decl;
 7054   TYPE_FIELDS (record) = f_gpr;
 7055   TREE_CHAIN (f_gpr) = f_fpr;
 7056   TREE_CHAIN (f_fpr) = f_ovf;
 7057   TREE_CHAIN (f_ovf) = f_sav;
 7058 
 7059   layout_type (record);
 7060 
 7061   /* The correct type is an array type of one element.  */
 7062   return build_array_type (record, build_index_type (size_zero_node));
 7063 }
 
 7068 static tree
 7069 ix86_build_builtin_va_list (void)                                            
 7070 { 
 7071   tree ret = ix86_build_builtin_va_list_abi (DEFAULT_ABI);                   
 7072   
 7073   /* Initialize abi specific va_list builtin types.  */                      
 7074   if (TARGET_64BIT)                                                          
 7075     { 
 7076       tree t;
 7077       if (DEFAULT_ABI == MS_ABI)                                             
 7078         { 
 7079           t = ix86_build_builtin_va_list_abi (SYSV_ABI);                     
 7080           if (TREE_CODE (t) != RECORD_TYPE)
 7081             t = build_variant_type_copy (t);                                 
 7082           sysv_va_list_type_node = t;                                        
 7083         }
 7084       else                                                                   
 7085         { 
 7086           t = ret;
 7087           if (TREE_CODE (t) != RECORD_TYPE)
 7088             t = build_variant_type_copy (t);                                 
 7089           sysv_va_list_type_node = t;                                        
 7090         }
 7091       if (DEFAULT_ABI != MS_ABI)                                             
 7092         { 
 7093           t = ix86_build_builtin_va_list_abi (MS_ABI);                       
 7094           if (TREE_CODE (t) != RECORD_TYPE)
 7095             t = build_variant_type_copy (t);                                 
 7096           ms_va_list_type_node = t;                                          
 7097         }
 7098       else
 7099         {
 7100           t = ret;
 7101           if (TREE_CODE (t) != RECORD_TYPE)
 7102             t = build_variant_type_copy (t);
 7103           ms_va_list_type_node = t;
 7104         }
 7105     }
 7106 
 7107   return ret;
 7108 }

可见,在32位机器上,通过 
return build_pointer_type (char_type_node);
将其声明为"char *"类型,而在64位机器上,通过
7033   type_decl = build_decl (TYPE_DECL, get_identifier ("__va_list_tag"), record);
初始化了新的类型__va_list_tag。


综上,对于va_list的重载需要注意,其在32位平台上被定义为char *,而在64位机器上才是一个特有的类型__va_list_tag。


你可能感兴趣的:(gcc多平台va_list重载问题)