刚刚解决了一个自认为十分诡异的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
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 }
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。