导语
纵观C++程序员一生,其实都是在和下面几件事打交道:
- coredump
- 内存泄露
- CPU狂飙
- 编译失败
本文通过之前线上遇到的一个coredump来浅析一下coredump分析的经验技巧。
相关源码已经脱敏,如果你看到代码中有guodongxiaren、guodong、demo、test、996、icu、pua等字样不要感到奇怪。
其实这次coredump原因并不难猜想,只是借此机会做一下延伸。以供各位后续定位更复杂coredump问题时做参考。
coredump现场
话说某个周五的下午,线上某服务突然出现coredump报警。补充一下,我们这边的服务,默认不生成真实的core文件,只是会把core栈的第一现场的作为文本保存下来(无法调试),俗称minicore,可以登录机器查看,也可以通过网页查看。这次的minicore内容主要部分是:
[0x351d7] :0 \_\_GI\_raise[0x368c8] :0 \_\_GI\_abort[0x2e146] :0 \_\_assert\_fail\_base[0x2e1f2] :0 \_\_GI\_\_\_assert\_fail[0x521bd5f] json\_value.cpp:1072 Json::Value::operator[]()[0x2661e28] searcher.cpp:169 guodongxiaren::Searcher::PreSearch()[0x351d7] :0 \_\_GI\_raise [0x368c8] :0 \_\_GI\_abort [0x2e146] :0 \_\_assert\_fail\_base [0x2e1f2] :0 \_\_GI\_\_\_assert\_fail [0x521bd5f] json\_value.cpp:1072 Json::Value::operator[]() [0x2661e28] searcher.cpp:169 guodongxiaren::Searcher::PreSearch()[0x351d7] :0 \_\_GI\_raise [0x368c8] :0 \_\_GI\_abort [0x2e146] :0 \_\_assert\_fail\_base [0x2e1f2] :0 \_\_GI\_\_\_assert\_fail [0x521bd5f] json\_value.cpp:1072 Json::Value::operator[]() [0x2661e28] searcher.cpp:169 guodongxiaren::Searcher::PreSearch()
代码问题推测
看起来是jsconcpp的[]
运算符越界导致的。看一下这个searcher.cpp 文件的169行:
it = kv_map.find(kPuaType);if (it != kv_map.end() && !it->second->text_value().empty()) {Json::Value data;if (Json::Reader().parse(it->second->text_value(), data)) {int pua_type;for (uint32_t i = 0, size = data.size(); i < size; ++i) {if (StringToNumber(data[i].asString(), &pua_type)) { ///////////// Core指向这一行sub_ctx_->pua_type_ = pua_type;... // 省略}}}it = kv_map.find(kPuaType); if (it != kv_map.end() && !it->second->text_value().empty()) { Json::Value data; if (Json::Reader().parse(it->second->text_value(), data)) { int pua_type; for (uint32_t i = 0, size = data.size(); i < size; ++i) { if (StringToNumber(data[i].asString(), &pua_type)) { ///////////// Core指向这一行 sub_ctx_->pua_type_ = pua_type; ... // 省略 } } }it = kv_map.find(kPuaType); if (it != kv_map.end() && !it->second->text_value().empty()) { Json::Value data; if (Json::Reader().parse(it->second->text_value(), data)) { int pua_type; for (uint32_t i = 0, size = data.size(); i < size; ++i) { if (StringToNumber(data[i].asString(), &pua_type)) { ///////////// Core指向这一行 sub_ctx_->pua_type_ = pua_type; ... // 省略 } } }
data是jsoncpp的Json::Value类型的对象。咋看之下,这个datai的下标 i 在 for循环中不可能越界的,并且data是局部变量也不可能并发,所以不应该出现越界才对。
再看整段代码的逻辑是解析了一个json字符串(it->second->text_value()),并且将其理解为json的数组,然后开始遍历数组。这个json字符串是前端生成经过多个模块一路透传过来的。这段代码存在明显缺陷:当前端发送的字符串不是数组形式的json字符串时,那么按照下标来遍历data,是有问题的!
看下jsoncpp的中Json::Value的源码,其中size()函数在Value是数组类型(arrayValue)或者对象类型(objectValue)的时候,都是存在非0返回值的,但含义不同。如果是json对象类型的size,表示的是对象中有多少字段,但这时候不能按照下标来遍历!
ArrayIndex Value::size() const {switch (type()) {case nullValue:case intValue:case uintValue:case realValue:case booleanValue:case stringValue:return 0;case arrayValue: // size of the array is highest index + 1if (!value_.map_->empty()) {ObjectValues::const_iterator itLast = value_.map_->end();--itLast;return (*itLast).first.index() + 1;}return 0;case objectValue:return ArrayIndex(value_.map_->size());}JSON_ASSERT_UNREACHABLE;return 0; // unreachable;}ArrayIndex Value::size() const { switch (type()) { case nullValue: case intValue: case uintValue: case realValue: case booleanValue: case stringValue: return 0; case arrayValue: // size of the array is highest index + 1 if (!value_.map_->empty()) { ObjectValues::const_iterator itLast = value_.map_->end(); --itLast; return (*itLast).first.index() + 1; } return 0; case objectValue: return ArrayIndex(value_.map_->size()); } JSON_ASSERT_UNREACHABLE; return 0; // unreachable; }ArrayIndex Value::size() const { switch (type()) { case nullValue: case intValue: case uintValue: case realValue: case booleanValue: case stringValue: return 0; case arrayValue: // size of the array is highest index + 1 if (!value_.map_->empty()) { ObjectValues::const_iterator itLast = value_.map_->end(); --itLast; return (*itLast).first.index() + 1; } return 0; case objectValue: return ArrayIndex(value_.map_->size()); } JSON_ASSERT_UNREACHABLE; return 0; // unreachable; }
当然这是猜测,虽然已经八九不离十了,但还可以实锤一下。
分析(实锤coredump原因)
按部就班
开启线上服务的coredump,让服务生成可调试的coredump文件。然后得到了一个core文件:core.39057
回看业务代码中存储json字符串的 it->second->text_value() 这个迭代器 it 是kv_map的find(kPuaType)的返回值。变量kPuaType是字符串**”puaType”**,kv_map则是
auto& kv_map = ctx->comm_ctx_.kv_map_;auto& kv_map = ctx->comm_ctx_.kv_map_;auto& kv_map = ctx->comm_ctx_.kv_map_;
其类型为:
using KvMap = std::unordered_map<std::string, const guodong::CommKvInfo*> ;...KvMap kv_map_;using KvMap = std::unordered_map<std::string, const guodong::CommKvInfo*> ; ... KvMap kv_map_;using KvMap = std::unordered_map<std::string, const guodong::CommKvInfo*> ; ... KvMap kv_map_;
所以kv_map是一个unordered_map类型。我们可以通过gdb调试真实的core文件,输出kv_map中”docSuperType”对应的CommKvInfo中的text_value()返回的字符串,去确认一下它不是数组形式的json字符串就可以了。
执行gdb命令
开始用gdb调试coredump,命令形式:gdb bin文件路径 core文件路径
gdb /路径/mmsearchdocmixersvr core.39057gdb /路径/mmsearchdocmixersvr core.39057gdb /路径/mmsearchdocmixersvr core.39057
显示栈帧
输入bt(或where)
(gdb) bt#0 0x00007ff0bdab91d7 in raise () from /lib64/libc.so.6#1 0x00007ff0bdabaa08 in abort () from /lib64/libc.so.6#2 0x00007ff0bdab2146 in __assert_fail_base () from /lib64/libc.so.6#3 0x00007ff0bdab21f2 in __assert_fail () from /lib64/libc.so.6#4 0x000000000521bd5f in Json::Value::operator[] (this=this@entry=0x7feeea7a67f0, index=index@entry=0)at mm3rd/jsoncpp/src/json_value.cpp:1072#5 0x0000000002661e28 in guodongxiaren::Searcher::PreSearch (this=0x7fef48fa5b10)at guodong/demo/module/searcher.cpp:169(gdb) bt #0 0x00007ff0bdab91d7 in raise () from /lib64/libc.so.6 #1 0x00007ff0bdabaa08 in abort () from /lib64/libc.so.6 #2 0x00007ff0bdab2146 in __assert_fail_base () from /lib64/libc.so.6 #3 0x00007ff0bdab21f2 in __assert_fail () from /lib64/libc.so.6 #4 0x000000000521bd5f in Json::Value::operator[] (this=this@entry=0x7feeea7a67f0, index=index@entry=0) at mm3rd/jsoncpp/src/json_value.cpp:1072 #5 0x0000000002661e28 in guodongxiaren::Searcher::PreSearch (this=0x7fef48fa5b10) at guodong/demo/module/searcher.cpp:169(gdb) bt #0 0x00007ff0bdab91d7 in raise () from /lib64/libc.so.6 #1 0x00007ff0bdabaa08 in abort () from /lib64/libc.so.6 #2 0x00007ff0bdab2146 in __assert_fail_base () from /lib64/libc.so.6 #3 0x00007ff0bdab21f2 in __assert_fail () from /lib64/libc.so.6 #4 0x000000000521bd5f in Json::Value::operator[] (this=this@entry=0x7feeea7a67f0, index=index@entry=0) at mm3rd/jsoncpp/src/json_value.cpp:1072 #5 0x0000000002661e28 in guodongxiaren::Searcher::PreSearch (this=0x7fef48fa5b10) at guodong/demo/module/searcher.cpp:169
栈帧4是jscocpp的Value对象内部,栈帧5是业务代码调用jsoncpp 运算符的地方。
切换栈帧
输入 f 5 切换到栈帧5:
(gdb) f 5#5 0x0000000002661e28 in guodongxiaren::Searcher::PreSearch (this=0x7fef48fa5b10)at guodong/demo/module/searcher.cpp:169169 guodong/demo/module/searcher.cpp: No such file or directory.(gdb) f 5 #5 0x0000000002661e28 in guodongxiaren::Searcher::PreSearch (this=0x7fef48fa5b10) at guodong/demo/module/searcher.cpp:169 169 guodong/demo/module/searcher.cpp: No such file or directory.(gdb) f 5 #5 0x0000000002661e28 in guodongxiaren::Searcher::PreSearch (this=0x7fef48fa5b10) at guodong/demo/module/searcher.cpp:169 169 guodong/demo/module/searcher.cpp: No such file or directory.
输出变量
输出一下当前对象:
(gdb) p this$1 = (guodongxiaren::Searcher * const) 0x7fef48fa5b10(gdb) p this $1 = (guodongxiaren::Searcher * const) 0x7fef48fa5b10(gdb) p this $1 = (guodongxiaren::Searcher * const) 0x7fef48fa5b10
当前有成员变量ctx_,它是指针类型,通过加* 输出具体对象内容:
$2 = (guodongxiaren::Context *) 0x7fef48c115e8(gdb) p *ctx$3 = {_vptr.Context = 0x575fc90 <vtable for guodongxiaren::RecallContext<guodongxiaren::ItemContext, void>+16>, rid_ = 193855933,search_id_ = 2864970758387329845, timebegin_ = 1665731120388130, req_ = 0x7fef48c73840, rsp_ = 0x7fef497db100, sdk_ = {impl_ = 0x7fef48c88e00, helper_ = {rid_ = 993855933, only_kv_ = false,...... 内容太多了,这里省略$2 = (guodongxiaren::Context *) 0x7fef48c115e8 (gdb) p *ctx $3 = {_vptr.Context = 0x575fc90 <vtable for guodongxiaren::RecallContext<guodongxiaren::ItemContext, void>+16>, rid_ = 193855933, search_id_ = 2864970758387329845, timebegin_ = 1665731120388130, req_ = 0x7fef48c73840, rsp_ = 0x7fef497db100, sdk_ = { impl_ = 0x7fef48c88e00, helper_ = {rid_ = 993855933, only_kv_ = false, ... ... 内容太多了,这里省略$2 = (guodongxiaren::Context *) 0x7fef48c115e8 (gdb) p *ctx $3 = {_vptr.Context = 0x575fc90 <vtable for guodongxiaren::RecallContext<guodongxiaren::ItemContext, void>+16>, rid_ = 193855933, search_id_ = 2864970758387329845, timebegin_ = 1665731120388130, req_ = 0x7fef48c73840, rsp_ = 0x7fef497db100, sdk_ = { impl_ = 0x7fef48c88e00, helper_ = {rid_ = 993855933, only_kv_ = false, ... ... 内容太多了,这里省略
设置变量别名
为了调试方便我们可给变量起别名,其实就是定义一个名字更短的变量:
(gdb) set $a=*ctx(gdb) set $a=*ctx(gdb) set $a=*ctx
我们自己定义的变量一定要是$开头的。
然后可以直接用比较短的变量去查看ctx_中的成员,比如search_id:
(gdb) p $a.search_id_$5 = 2864970758387329845(gdb) p $a.search_id_ $5 = 2864970758387329845(gdb) p $a.search_id_ $5 = 2864970758387329845
当然,我们的重点是那个kv_map,所以同样定义一个别名,然后尝试输出:
(gdb) set $m=$a.comm_ctx_.kv_map_(gdb) p $m$6 = {<std::__allow_copy_cons<true>> = {<No data fields>},_M_h = {<std::__detail::_Hashtable_base<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, guodong::CommKvInfo const*>, std::__detail::_Select1st, std::equal_to<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Hashtable_traits<true, false, true> >> = {<std::__detail::_Hash_code_base<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, guodong::CommKvInfo const*>, std::__detail::_Select1st, std::hash<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, true>> = {<std::__detail::_Hashtable_ebo_helper<0, std::__detail::_Select1st, true>> = ...... 太长了,这里省略(gdb) set $m=$a.comm_ctx_.kv_map_ (gdb) p $m $6 = {<std::__allow_copy_cons<true>> = {<No data fields>}, _M_h = {<std::__detail::_Hashtable_base<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, guodong::CommKvInfo const*>, std::__detail::_Select1st, std::equal_to<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Hashtable_traits<true, false, true> >> = {<std::__detail::_Hash_code_base<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, guodong::CommKvInfo const*>, std::__detail::_Select1st, std::hash<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, true>> = {<std::__detail::_Hashtable_ebo_helper<0, std::__detail::_Select1st, true>> = ... ... 太长了,这里省略(gdb) set $m=$a.comm_ctx_.kv_map_ (gdb) p $m $6 = {<std::__allow_copy_cons<true>> = {<No data fields>}, _M_h = {<std::__detail::_Hashtable_base<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, guodong::CommKvInfo const*>, std::__detail::_Select1st, std::equal_to<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Hashtable_traits<true, false, true> >> = {<std::__detail::_Hash_code_base<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, guodong::CommKvInfo const*>, std::__detail::_Select1st, std::hash<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, true>> = {<std::__detail::_Hashtable_ebo_helper<0, std::__detail::_Select1st, true>> = ... ... 太长了,这里省略
发现基本不可读。因为kv_map是一个unordered_map。有没有更直观的输出unordered_map的方法呢。有的。其实gdb自带一个python脚本,可以美化gdb的输出。
std::unordered_map美化输出
查找printers.py
我们暂时退出gdb,先find一下,找一下gdb自带的python脚本的路径:
find / -name "printers.py" 2> /dev/nullfind / -name "printers.py" 2> /dev/nullfind / -name "printers.py" 2> /dev/null
find输出结果是:
/usr/share/gcc-4.8.2/python/libstdcxx/v6/printers.py/usr/share/gcc-4.8.2/python/libstdcxx/v6/printers.py/usr/share/gcc-4.8.2/python/libstdcxx/v6/printers.py
新建.gdbinit
然后在home目录新建一个文件,名为 .gdbinit 这个是gdb在执行时会加载的指令文件。在.gdbinit 中写入:
pythonimport syssys.path.insert(0, '/usr/share/gcc-4.8.2/python')from libstdcxx.v6.printers import register_libstdcxx_printersregister_libstdcxx_printers (None)endpython import sys sys.path.insert(0, '/usr/share/gcc-4.8.2/python') from libstdcxx.v6.printers import register_libstdcxx_printers register_libstdcxx_printers (None) endpython import sys sys.path.insert(0, '/usr/share/gcc-4.8.2/python') from libstdcxx.v6.printers import register_libstdcxx_printers register_libstdcxx_printers (None) end
注意sys.path.insert()的那个路径指向我们找到的printers.py的之前的python目录。
使用合适的gdb
接着可以重新gdb。这里要注意一下,WXG的线上机器中默认的gdb命令的路径是 /home/qspace/bin/gdb,这个gdb命令并不能加载python脚本。机器中另外有一个gdb(/usr/bin/gdb)是可以加载python脚本的。在准备好.gdbinit 后,我们用/usr/bin/gdb重新打开coredump文件:
/usr/bin/gdb /路径/mmsearchdocmixersvr core.mt_worker.39057.tm_1665731120.uid_1000.sig_6/usr/bin/gdb /路径/mmsearchdocmixersvr core.mt_worker.39057.tm_1665731120.uid_1000.sig_6/usr/bin/gdb /路径/mmsearchdocmixersvr core.mt_worker.39057.tm_1665731120.uid_1000.sig_6
再次输出std::unordered_map
继续之前的操作得到存储kv_map的变量$m
(gdb) f 5... 省略(gdb) set $a=*ctx(gdb) set $m=$a.comm_ctx_.kv_map_(gdb) f 5 ... 省略 (gdb) set $a=*ctx (gdb) set $m=$a.comm_ctx_.kv_map_(gdb) f 5 ... 省略 (gdb) set $a=*ctx (gdb) set $m=$a.comm_ctx_.kv_map_
此时再次执行p命令:
(gdb) p $m$1 = std::unordered_map with 11 elements = {["netType"] = 0x7fef48a63b30, ["reqStartTime"] = 0x7fef48bd3a70, ["reportQuery"] = 0x7fef48441250,["requestId"] = 0x7fef48b8cea0, ["currentPage"] = 0x7fef492a9fe0, ["newCookies"] = 0x7fef493016c0, ["widgetVersion"] = 0x7fef484ec860,["docSuperType"] = 0x7fef48c93df0, ["docSortType"] = 0x7fef488cce90, ["queryInput"] = 0x7fef4998c8a0, ["isCareMode"] = 0x7fef48441130}(gdb) p $m $1 = std::unordered_map with 11 elements = {["netType"] = 0x7fef48a63b30, ["reqStartTime"] = 0x7fef48bd3a70, ["reportQuery"] = 0x7fef48441250, ["requestId"] = 0x7fef48b8cea0, ["currentPage"] = 0x7fef492a9fe0, ["newCookies"] = 0x7fef493016c0, ["widgetVersion"] = 0x7fef484ec860, ["docSuperType"] = 0x7fef48c93df0, ["docSortType"] = 0x7fef488cce90, ["queryInput"] = 0x7fef4998c8a0, ["isCareMode"] = 0x7fef48441130}(gdb) p $m $1 = std::unordered_map with 11 elements = {["netType"] = 0x7fef48a63b30, ["reqStartTime"] = 0x7fef48bd3a70, ["reportQuery"] = 0x7fef48441250, ["requestId"] = 0x7fef48b8cea0, ["currentPage"] = 0x7fef492a9fe0, ["newCookies"] = 0x7fef493016c0, ["widgetVersion"] = 0x7fef484ec860, ["docSuperType"] = 0x7fef48c93df0, ["docSortType"] = 0x7fef488cce90, ["queryInput"] = 0x7fef4998c8a0, ["isCareMode"] = 0x7fef48441130}
kv_map的内容一览无余。可以看到:
["docSuperType"] = 0x7fef48c93df0["docSuperType"] = 0x7fef48c93df0["docSuperType"] = 0x7fef48c93df0
0x7fef48c93df0指向的是这个unordered_map的value的内存地址,它的类型是 guodong::CommKvInfo*
输出protobuf的Message对象
可以将内存地址按照它指向对象的真实类型进行转型,然后输出:
(gdb) set $c =*(guodong::CommKvInfo*)0x7fef48c93df0(gdb) p $c$2 = {<google::protobuf::Message> = {<google::protobuf::MessageLite> = {_vptr.MessageLite = 0x59e7eb0 <vtable for guodong::CommKvInfo+16>}, <No data fields>}, static kIndexInFileMessages = 8,static kKeyFieldNumber = 1, static kTextValueFieldNumber = 3, static kUintValueFieldNumber = 2,_internal_metadata_ = {<google::protobuf::internal::InternalMetadataWithArenaBase<google::protobuf::UnknownFieldSet, google::protobuf::internal::InternalMetadataWithArena>> = {ptr_ = 0x0, static kPtrTagMask = <optimized out>,static kPtrValueMask = <optimized out>}, <No data fields>}, _has_bits_ = {has_bits_ = {3}}, _cached_size_ = 0, key_ = {ptr_ = 0x7fef491cb6a0}, text_value_ = {ptr_ = 0x7fef491cb640}, uint_value_ = 0}(gdb) set $c =*(guodong::CommKvInfo*)0x7fef48c93df0 (gdb) p $c $2 = {<google::protobuf::Message> = {<google::protobuf::MessageLite> = { _vptr.MessageLite = 0x59e7eb0 <vtable for guodong::CommKvInfo+16>}, <No data fields>}, static kIndexInFileMessages = 8, static kKeyFieldNumber = 1, static kTextValueFieldNumber = 3, static kUintValueFieldNumber = 2, _internal_metadata_ = {<google::protobuf::internal::InternalMetadataWithArenaBase<google::protobuf::UnknownFieldSet, google::protobuf::internal::InternalMetadataWithArena>> = {ptr_ = 0x0, static kPtrTagMask = <optimized out>, static kPtrValueMask = <optimized out>}, <No data fields>}, _has_bits_ = {has_bits_ = {3}}, _cached_size_ = 0, key_ = { ptr_ = 0x7fef491cb6a0}, text_value_ = {ptr_ = 0x7fef491cb640}, uint_value_ = 0}(gdb) set $c =*(guodong::CommKvInfo*)0x7fef48c93df0 (gdb) p $c $2 = {<google::protobuf::Message> = {<google::protobuf::MessageLite> = { _vptr.MessageLite = 0x59e7eb0 <vtable for guodong::CommKvInfo+16>}, <No data fields>}, static kIndexInFileMessages = 8, static kKeyFieldNumber = 1, static kTextValueFieldNumber = 3, static kUintValueFieldNumber = 2, _internal_metadata_ = {<google::protobuf::internal::InternalMetadataWithArenaBase<google::protobuf::UnknownFieldSet, google::protobuf::internal::InternalMetadataWithArena>> = {ptr_ = 0x0, static kPtrTagMask = <optimized out>, static kPtrValueMask = <optimized out>}, <No data fields>}, _has_bits_ = {has_bits_ = {3}}, _cached_size_ = 0, key_ = { ptr_ = 0x7fef491cb6a0}, text_value_ = {ptr_ = 0x7fef491cb640}, uint_value_ = 0}
可以看到里面有个成员变量是 text_value_ ,就是我们想要的内容。即使你没有一眼看到,也没关系。CommKvInfo是一个protobuf的Message类型:
message CommKvInfo{required bytes key = 1;optional bytes value = 2;}message CommKvInfo { required bytes key = 1; optional bytes value = 2; }message CommKvInfo { required bytes key = 1; optional bytes value = 2; }
protobuf 生成的C++类是严格遵守『谷歌C++代码规范』的,在该规范中,类成员变量的命名规范都是要以下划线为后缀的:zh-google-styleguide.readthedocs.io/en/latest/g…
所以proto文件中定义的字段加上下划线,就是其在C++类中的实际字段。来输出一下value_:
(gdb) p $c.value_$3 = {ptr_ = 0x7fef491cb640}(gdb) p $c.value_ $3 = {ptr_ = 0x7fef491cb640}(gdb) p $c.value_ $3 = {ptr_ = 0x7fef491cb640}
实锤
value_指向的也是指针,也就是一个内存地址。由于我们知道这个变量是std::string类型,所以可以直接转型,然后输出:
(gdb) p *(std::string*)0x7fef491cb640$4 = "{\"offset\":0,\"type\":2,\"buffer\":\"abcdefghigklmn\",\"doffset\":0,\"test\":\"\",\"demo\":0,\"cnt\":1,\"query\":\"气\",\"sn\":85}\n"(gdb) p *(std::string*)0x7fef491cb640 $4 = "{\"offset\":0,\"type\":2,\"buffer\":\"abcdefghigklmn\",\"doffset\":0,\"test\":\"\",\"demo\":0,\"cnt\":1,\"query\":\"气\",\"sn\":85}\n"(gdb) p *(std::string*)0x7fef491cb640 $4 = "{\"offset\":0,\"type\":2,\"buffer\":\"abcdefghigklmn\",\"doffset\":0,\"test\":\"\",\"demo\":0,\"cnt\":1,\"query\":\"气\",\"sn\":85}\n"
可以发现确实是并非是json数组字符串,而是json对象字符串!
其实到这里,问题已经明确了:一是前端出现了不符合预期的json字符串,二是我们代码自身鲁棒性不够,没有做防御式编程,应该在实际处理之前,去判断一下jsoncpp的Value对象是不是数组类型的。
这并不是一个特别刁钻的coredump,不过我们可以借此再延伸一下。
延伸(和本次coredump原因无关)
调试Json::Value
回看coredump,栈帧4是jsoncpp的Value对象(data变量)内部,我们可以调试一下它玩玩。
#0 0x00007ff0bdab91d7 in raise () from /lib64/libc.so.6#1 0x00007ff0bdabaa08 in abort () from /lib64/libc.so.6#2 0x00007ff0bdab2146 in __assert_fail_base () from /lib64/libc.so.6#3 0x00007ff0bdab21f2 in __assert_fail () from /lib64/libc.so.6#4 0x000000000521bd5f in Json::Value::operator[] (this=this@entry=0x7feeea7a67f0, index=index@entry=0)at mm3rd/jsoncpp/src/json_value.cpp:1072#5 0x0000000002661e28 in guodongxiaren::Searcher::PreSearch (this=0x7fef48fa5b10)at#0 0x00007ff0bdab91d7 in raise () from /lib64/libc.so.6 #1 0x00007ff0bdabaa08 in abort () from /lib64/libc.so.6 #2 0x00007ff0bdab2146 in __assert_fail_base () from /lib64/libc.so.6 #3 0x00007ff0bdab21f2 in __assert_fail () from /lib64/libc.so.6 #4 0x000000000521bd5f in Json::Value::operator[] (this=this@entry=0x7feeea7a67f0, index=index@entry=0) at mm3rd/jsoncpp/src/json_value.cpp:1072 #5 0x0000000002661e28 in guodongxiaren::Searcher::PreSearch (this=0x7fef48fa5b10) at#0 0x00007ff0bdab91d7 in raise () from /lib64/libc.so.6 #1 0x00007ff0bdabaa08 in abort () from /lib64/libc.so.6 #2 0x00007ff0bdab2146 in __assert_fail_base () from /lib64/libc.so.6 #3 0x00007ff0bdab21f2 in __assert_fail () from /lib64/libc.so.6 #4 0x000000000521bd5f in Json::Value::operator[] (this=this@entry=0x7feeea7a67f0, index=index@entry=0) at mm3rd/jsoncpp/src/json_value.cpp:1072 #5 0x0000000002661e28 in guodongxiaren::Searcher::PreSearch (this=0x7fef48fa5b10) at
切换到栈帧4,输出一下当前对象:
(gdb) f 4#4 0x000000000521bd5f in Json::Value::operator[] (this=this@entry=0x7feeea7a67f0, index=index@entry=0)at mm3rd/jsoncpp/src/json_value.cpp:10721072 mm3rd/jsoncpp/src/json_value.cpp: No such file or directory.(gdb) p *this$1 = {static null = {static null = <same as static member of an already seen type>, static minLargestInt = -9223372036854775808,static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648,static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807,static maxUInt64 = 18446744073709551615, value_ = {int_ = 0, uint_ = 0, real_ = 0, bool_ = false, string_ = 0x0, map_ = 0x0},type_ = Json::nullValue, allocated_ = 0, comments_ = 0x0}, static minLargestInt = -9223372036854775808,static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648,static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807,static maxUInt64 = 18446744073709551615, value_ = {int_ = 140665696073680, uint_ = 140665696073680, real_ = 6.9498087978351207e-310,bool_ = 208, string_ = 0x7fef48d8b7d0 "", map_ = 0x7fef48d8b7d0}, type_ = Json::objectValue, allocated_ = -1, comments_ = 0x0}(gdb) f 4 #4 0x000000000521bd5f in Json::Value::operator[] (this=this@entry=0x7feeea7a67f0, index=index@entry=0) at mm3rd/jsoncpp/src/json_value.cpp:1072 1072 mm3rd/jsoncpp/src/json_value.cpp: No such file or directory. (gdb) p *this $1 = {static null = {static null = <same as static member of an already seen type>, static minLargestInt = -9223372036854775808, static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648, static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807, static maxUInt64 = 18446744073709551615, value_ = {int_ = 0, uint_ = 0, real_ = 0, bool_ = false, string_ = 0x0, map_ = 0x0}, type_ = Json::nullValue, allocated_ = 0, comments_ = 0x0}, static minLargestInt = -9223372036854775808, static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648, static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807, static maxUInt64 = 18446744073709551615, value_ = {int_ = 140665696073680, uint_ = 140665696073680, real_ = 6.9498087978351207e-310, bool_ = 208, string_ = 0x7fef48d8b7d0 "", map_ = 0x7fef48d8b7d0}, type_ = Json::objectValue, allocated_ = -1, comments_ = 0x0}(gdb) f 4 #4 0x000000000521bd5f in Json::Value::operator[] (this=this@entry=0x7feeea7a67f0, index=index@entry=0) at mm3rd/jsoncpp/src/json_value.cpp:1072 1072 mm3rd/jsoncpp/src/json_value.cpp: No such file or directory. (gdb) p *this $1 = {static null = {static null = <same as static member of an already seen type>, static minLargestInt = -9223372036854775808, static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648, static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807, static maxUInt64 = 18446744073709551615, value_ = {int_ = 0, uint_ = 0, real_ = 0, bool_ = false, string_ = 0x0, map_ = 0x0}, type_ = Json::nullValue, allocated_ = 0, comments_ = 0x0}, static minLargestInt = -9223372036854775808, static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648, static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807, static maxUInt64 = 18446744073709551615, value_ = {int_ = 140665696073680, uint_ = 140665696073680, real_ = 6.9498087978351207e-310, bool_ = 208, string_ = 0x7fef48d8b7d0 "", map_ = 0x7fef48d8b7d0}, type_ = Json::objectValue, allocated_ = -1, comments_ = 0x0}
set print美化输出
看着有点乱,再加一些gdb美化输出的设置:
set print pretty onset print object onset print static-members onset print vtbl onset print demangle onset demangle-style gnu-v3set print sevenbit-strings offset print pretty on set print object on set print static-members on set print vtbl on set print demangle on set demangle-style gnu-v3 set print sevenbit-strings offset print pretty on set print object on set print static-members on set print vtbl on set print demangle on set demangle-style gnu-v3 set print sevenbit-strings off
再输出一下当前对象的内容
(gdb) p *this$2 = {static null = {static null = <same as static member of an already seen type>,static minLargestInt = -9223372036854775808,static maxLargestInt = 9223372036854775807,static maxLargestUInt = 18446744073709551615,static minInt = -2147483648,static maxInt = 2147483647,static maxUInt = 4294967295,static minInt64 = -9223372036854775808,static maxInt64 = 9223372036854775807,static maxUInt64 = 18446744073709551615,value_ = {int_ = 0,uint_ = 0,real_ = 0,bool_ = false,string_ = 0x0,map_ = 0x0},type_ = Json::nullValue,allocated_ = 0,comments_ = 0x0},static minLargestInt = -9223372036854775808,static maxLargestInt = 9223372036854775807,static maxLargestUInt = 18446744073709551615,static minInt = -2147483648,static maxInt = 2147483647,static maxUInt = 4294967295,static minInt64 = -9223372036854775808,static maxInt64 = 9223372036854775807,static maxUInt64 = 18446744073709551615,value_ = {int_ = 140665696073680,uint_ = 140665696073680,real_ = 6.9498087978351207e-310,bool_ = 208,string_ = 0x7fef48d8b7d0 "",map_ = 0x7fef48d8b7d0},type_ = Json::objectValue,allocated_ = -1,comments_ = 0x0}(gdb) p *this $2 = { static null = { static null = <same as static member of an already seen type>, static minLargestInt = -9223372036854775808, static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648, static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807, static maxUInt64 = 18446744073709551615, value_ = { int_ = 0, uint_ = 0, real_ = 0, bool_ = false, string_ = 0x0, map_ = 0x0 }, type_ = Json::nullValue, allocated_ = 0, comments_ = 0x0 }, static minLargestInt = -9223372036854775808, static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648, static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807, static maxUInt64 = 18446744073709551615, value_ = { int_ = 140665696073680, uint_ = 140665696073680, real_ = 6.9498087978351207e-310, bool_ = 208, string_ = 0x7fef48d8b7d0 "", map_ = 0x7fef48d8b7d0 }, type_ = Json::objectValue, allocated_ = -1, comments_ = 0x0 }(gdb) p *this $2 = { static null = { static null = <same as static member of an already seen type>, static minLargestInt = -9223372036854775808, static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648, static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807, static maxUInt64 = 18446744073709551615, value_ = { int_ = 0, uint_ = 0, real_ = 0, bool_ = false, string_ = 0x0, map_ = 0x0 }, type_ = Json::nullValue, allocated_ = 0, comments_ = 0x0 }, static minLargestInt = -9223372036854775808, static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648, static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807, static maxUInt64 = 18446744073709551615, value_ = { int_ = 140665696073680, uint_ = 140665696073680, real_ = 6.9498087978351207e-310, bool_ = 208, string_ = 0x7fef48d8b7d0 "", map_ = 0x7fef48d8b7d0 }, type_ = Json::objectValue, allocated_ = -1, comments_ = 0x0 }
可以直接看出 type_ 的值是:Json::objectValue,如果是数组,则type_ 应该是 Json::arrayValue 。到这里也可以停止了。但还可以继续看看。value_ 成员存储具体的数组,它是一个union类型:
union ValueHolder {LargestInt int_;LargestUInt uint_;double real_;bool bool_;char* string_; // if allocated_, ptr to { unsigned, char[] }.ObjectValues* map_;} value_;union ValueHolder { LargestInt int_; LargestUInt uint_; double real_; bool bool_; char* string_; // if allocated_, ptr to { unsigned, char[] }. ObjectValues* map_; } value_;union ValueHolder { LargestInt int_; LargestUInt uint_; double real_; bool bool_; char* string_; // if allocated_, ptr to { unsigned, char[] }. ObjectValues* map_; } value_;
输出std::map
如果是json对象类型的话,我们关注 map_就可以了。
(gdb) set $a=(*this).value_.map_$3 = (Json::Value::ObjectValues *) 0x7fef48d8b7d0(gdb) set $a=(*this).value_.map_ $3 = (Json::Value::ObjectValues *) 0x7fef48d8b7d0(gdb) set $a=(*this).value_.map_ $3 = (Json::Value::ObjectValues *) 0x7fef48d8b7d0
指针类型,我们解一下指针,存成新变量:
(gdb) set $b=*$a(gdb) p $b$4 = std::map with 9 elements = {[{cstr_ = 0x7fef48a40f30 "offset",index_ = 1}] = {static null = <same as static member of an already seen type>,static minLargestInt = -9223372036854775808,static maxLargestInt = 9223372036854775807,static maxLargestUInt = 18446744073709551615,static minInt = -2147483648,static maxInt = 2147483647,static maxUInt = 4294967295,static minInt64 = -9223372036854775808,static maxInt64 = 9223372036854775807,static maxUInt64 = 18446744073709551615,value_ = {int_ = 0,uint_ = 0,real_ = 0,bool_ = false,string_ = 0x0,map_ = 0x0},type_ = Json::intValue,allocated_ = 0,comments_ = 0x0},[{cstr_ = 0x7fef48b7ffa0 "type",index_ = 1}] = {static null = <same as static member of an already seen type>,static minLargestInt = -9223372036854775808,static maxLargestInt = 9223372036854775807,... 省略(gdb) set $b=*$a (gdb) p $b $4 = std::map with 9 elements = { [{ cstr_ = 0x7fef48a40f30 "offset", index_ = 1 }] = { static null = <same as static member of an already seen type>, static minLargestInt = -9223372036854775808, static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648, static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807, static maxUInt64 = 18446744073709551615, value_ = { int_ = 0, uint_ = 0, real_ = 0, bool_ = false, string_ = 0x0, map_ = 0x0 }, type_ = Json::intValue, allocated_ = 0, comments_ = 0x0 }, [{ cstr_ = 0x7fef48b7ffa0 "type", index_ = 1 }] = { static null = <same as static member of an already seen type>, static minLargestInt = -9223372036854775808, static maxLargestInt = 9223372036854775807, ... 省略(gdb) set $b=*$a (gdb) p $b $4 = std::map with 9 elements = { [{ cstr_ = 0x7fef48a40f30 "offset", index_ = 1 }] = { static null = <same as static member of an already seen type>, static minLargestInt = -9223372036854775808, static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648, static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807, static maxUInt64 = 18446744073709551615, value_ = { int_ = 0, uint_ = 0, real_ = 0, bool_ = false, string_ = 0x0, map_ = 0x0 }, type_ = Json::intValue, allocated_ = 0, comments_ = 0x0 }, [{ cstr_ = 0x7fef48b7ffa0 "type", index_ = 1 }] = { static null = <same as static member of an already seen type>, static minLargestInt = -9223372036854775808, static maxLargestInt = 9223372036854775807, ... 省略
可以看出这个map对象的key中保护的字符串。说明确实是json对象,而非json数组。
另外一个 .gdbinit
其实网上还有一个经典流传的 .gdbinit配置『STL GDB evaluators/views/utilities – 1.03』很多网站都能找到副本,比如这里:gist.github.com/apetresc/43…
我们下载下来,替换掉之前的 .gdbinit试试,这个.gdbinit,已经有 set print 那几个美化输出的命令了,所以不需要我们在gdb启动之后,再输入了。
重新加载coredump文件,沿用前面步骤,得到指向 map_的变量 $b。
pmap输出std::map
这个.gdbinit 配置中有pmap命令,可以方便的输出std::map类型的变量。用法是:
pmap <variable_name> <left_element_type> <right_element_type>pmap <variable_name> <left_element_type> <right_element_type>pmap <variable_name> <left_element_type> <right_element_type>
这个命令有三个参数,第一个参数是变量,第二个参数是左边的元素类型(map key的类型),第三个参数是右边元素的类型(map value的类型),map_的类型定义是:
typedef std::map<CZString, Value> ObjectValues;typedef std::map<CZString, Value> ObjectValues;typedef std::map<CZString, Value> ObjectValues;
所以这样用:
(gdb) pmap $b CZString Valueelem[0].left: $2 = {cstr_ = 0x7fef48a40f30 "offset",index_ = 1}elem[0].right: $3 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d8b6e0elem[1].left: $4 = {cstr_ = 0x7fef48b7ffa0 "type",index_ = 1}elem[1].right: $5 = {void (Json::Value * const, Json::ValueType)} 0x7fef48b7ff80elem[2].left: $6 = {cstr_ = 0x7fef48b80010 "buffer",index_ = 1}elem[2].right: $7 = {void (Json::Value * const, Json::ValueType)} 0x7fef48b7fff0elem[3].left: $8 = {cstr_ = 0x7fef48b800d0 "doffset",index_ = 1}elem[3].right: $9 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d50060elem[4].left: $10 = {cstr_ = 0x7fef48d500d0 "test",index_ = 1}elem[4].right: $11 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d500b0elem[5].left: $12 = {cstr_ = 0x7fef48d50160 "demo",index_ = 1}elem[5].right: $13 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d50140elem[6].left: $14 = {cstr_ = 0x7fef48d3fcd0 "cnt",index_ = 1}... 省略(gdb) pmap $b CZString Value elem[0].left: $2 = { cstr_ = 0x7fef48a40f30 "offset", index_ = 1 } elem[0].right: $3 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d8b6e0 elem[1].left: $4 = { cstr_ = 0x7fef48b7ffa0 "type", index_ = 1 } elem[1].right: $5 = {void (Json::Value * const, Json::ValueType)} 0x7fef48b7ff80 elem[2].left: $6 = { cstr_ = 0x7fef48b80010 "buffer", index_ = 1 } elem[2].right: $7 = {void (Json::Value * const, Json::ValueType)} 0x7fef48b7fff0 elem[3].left: $8 = { cstr_ = 0x7fef48b800d0 "doffset", index_ = 1 } elem[3].right: $9 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d50060 elem[4].left: $10 = { cstr_ = 0x7fef48d500d0 "test", index_ = 1 } elem[4].right: $11 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d500b0 elem[5].left: $12 = { cstr_ = 0x7fef48d50160 "demo", index_ = 1 } elem[5].right: $13 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d50140 elem[6].left: $14 = { cstr_ = 0x7fef48d3fcd0 "cnt", index_ = 1 } ... 省略(gdb) pmap $b CZString Value elem[0].left: $2 = { cstr_ = 0x7fef48a40f30 "offset", index_ = 1 } elem[0].right: $3 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d8b6e0 elem[1].left: $4 = { cstr_ = 0x7fef48b7ffa0 "type", index_ = 1 } elem[1].right: $5 = {void (Json::Value * const, Json::ValueType)} 0x7fef48b7ff80 elem[2].left: $6 = { cstr_ = 0x7fef48b80010 "buffer", index_ = 1 } elem[2].right: $7 = {void (Json::Value * const, Json::ValueType)} 0x7fef48b7fff0 elem[3].left: $8 = { cstr_ = 0x7fef48b800d0 "doffset", index_ = 1 } elem[3].right: $9 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d50060 elem[4].left: $10 = { cstr_ = 0x7fef48d500d0 "test", index_ = 1 } elem[4].right: $11 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d500b0 elem[5].left: $12 = { cstr_ = 0x7fef48d50160 "demo", index_ = 1 } elem[5].right: $13 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d50140 elem[6].left: $14 = { cstr_ = 0x7fef48d3fcd0 "cnt", index_ = 1 } ... 省略
可以看到好像比之前的.gdbinit的输出要干净的多,感觉清晰了不少。它也准确地输出解析后json对象字符串的Key和Value。除了std::map,还有如下指令可以输出其他STL容器:
# std::vector<T> -- via pvector command# std::list<T> -- via plist or plist_member command# std::map<T,T> -- via pmap or pmap_member command# std::multimap<T,T> -- via pmap or pmap_member command# std::set<T> -- via pset command# std::multiset<T> -- via pset command# std::deque<T> -- via pdequeue command# std::stack<T> -- via pstack command# std::queue<T> -- via pqueue command# std::priority_queue<T> -- via ppqueue command# std::bitset<n> -- via pbitset command# std::string -- via pstring command# std::widestring -- via pwstring command# std::vector<T> -- via pvector command # std::list<T> -- via plist or plist_member command # std::map<T,T> -- via pmap or pmap_member command # std::multimap<T,T> -- via pmap or pmap_member command # std::set<T> -- via pset command # std::multiset<T> -- via pset command # std::deque<T> -- via pdequeue command # std::stack<T> -- via pstack command # std::queue<T> -- via pqueue command # std::priority_queue<T> -- via ppqueue command # std::bitset<n> -- via pbitset command # std::string -- via pstring command # std::widestring -- via pwstring command# std::vector<T> -- via pvector command # std::list<T> -- via plist or plist_member command # std::map<T,T> -- via pmap or pmap_member command # std::multimap<T,T> -- via pmap or pmap_member command # std::set<T> -- via pset command # std::multiset<T> -- via pset command # std::deque<T> -- via pdequeue command # std::stack<T> -- via pstack command # std::queue<T> -- via pqueue command # std::priority_queue<T> -- via ppqueue command # std::bitset<n> -- via pbitset command # std::string -- via pstring command # std::widestring -- via pwstring command
pvector输出std::vector
在栈帧5中有一个vector变量kTransferKeyList,我们可以试一下vector的输出功能:
(gdb) f 5...(gdb) pvector kTransferKeyListelem[0]: $2 = "PTSD"elem[1]: $3 = "ICU"elem[2]: $4 = "996"Vector size = 3Vector capacity = 3Element type = std::_Vector_base<std::string, std::allocator<std::string> >::pointer(gdb) f 5 ... (gdb) pvector kTransferKeyList elem[0]: $2 = "PTSD" elem[1]: $3 = "ICU" elem[2]: $4 = "996" Vector size = 3 Vector capacity = 3 Element type = std::_Vector_base<std::string, std::allocator<std::string> >::pointer(gdb) f 5 ... (gdb) pvector kTransferKeyList elem[0]: $2 = "PTSD" elem[1]: $3 = "ICU" elem[2]: $4 = "996" Vector size = 3 Vector capacity = 3 Element type = std::_Vector_base<std::string, std::allocator<std::string> >::pointer
美中不足
看着这个.gdbinit功能挺强大,但美中不足的是这个 .gdbinit的配置是2010年之前诞生的,所以他不支持C++11引入的std::unordered_map类型。所以能不能把两个 .gdbinit 统一呢? 当然能。直接把他们合成到一个文件就可以。这样不管是std::map 还是 std::unordered_map 输出都比较方便了。我把合成版本放到附件中了,使用的时候记得重命名成.gdbinit
切换线程
有时候出现coredump并不是当前线程的处理出了什么问题,而是其他线程把内存踩乱了,导致虽然core在当前线程,但并不是问题代码所在的第一现场!此时需要切换线程来看。当然,本文案例中遇到的coredump并没有那么棘手。下面的内容和本次coredump没什么关系,权作演示,给各位做分析参考。
显示当前线程
info threadinfo threadinfo thread
输出:
Id Target Id Frame103 Thread 0x7fee651be700 (LWP 39142) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6102 Thread 0x7feebb578700 (LWP 39127) 0x00007ff0bdaff82f in malloc_consolidate () from /lib64/libc.so.6101 Thread 0x7fee2f0b0700 (LWP 39152) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6100 Thread 0x7fee81bf8700 (LWP 39137) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6... 省略72 Thread 0x7fee8353d700 (LWP 39136) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.671 Thread 0x7fef8d610700 (LWP 39086) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.670 Thread 0x7fef8fe15700 (LWP 39067) 0x00007ff0bdb4267d in nanosleep () from /lib64/libc.so.669 Thread 0x7fef767fc700 (LWP 39091) 0x0000000003191811 in guodong::demo::Item::Clear (this=0x7fef643da790)Id Target Id Frame 103 Thread 0x7fee651be700 (LWP 39142) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6 102 Thread 0x7feebb578700 (LWP 39127) 0x00007ff0bdaff82f in malloc_consolidate () from /lib64/libc.so.6 101 Thread 0x7fee2f0b0700 (LWP 39152) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6 100 Thread 0x7fee81bf8700 (LWP 39137) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6 ... 省略 72 Thread 0x7fee8353d700 (LWP 39136) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6 71 Thread 0x7fef8d610700 (LWP 39086) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6 70 Thread 0x7fef8fe15700 (LWP 39067) 0x00007ff0bdb4267d in nanosleep () from /lib64/libc.so.6 69 Thread 0x7fef767fc700 (LWP 39091) 0x0000000003191811 in guodong::demo::Item::Clear (this=0x7fef643da790)Id Target Id Frame 103 Thread 0x7fee651be700 (LWP 39142) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6 102 Thread 0x7feebb578700 (LWP 39127) 0x00007ff0bdaff82f in malloc_consolidate () from /lib64/libc.so.6 101 Thread 0x7fee2f0b0700 (LWP 39152) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6 100 Thread 0x7fee81bf8700 (LWP 39137) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6 ... 省略 72 Thread 0x7fee8353d700 (LWP 39136) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6 71 Thread 0x7fef8d610700 (LWP 39086) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6 70 Thread 0x7fef8fe15700 (LWP 39067) 0x00007ff0bdb4267d in nanosleep () from /lib64/libc.so.6 69 Thread 0x7fef767fc700 (LWP 39091) 0x0000000003191811 in guodong::demo::Item::Clear (this=0x7fef643da790)
切换到另外线程
看到线程ID 69 也是一个当时正在进行业务处理的线程。切换进去:
(gdb) thread 69[Switching to thread 69 (Thread 0x7fef767fc700 (LWP 39091))]#0 0x0000000003191811 in guodong::demo::Item::Clear (this=0x7fef643da790)(gdb) thread 69 [Switching to thread 69 (Thread 0x7fef767fc700 (LWP 39091))] #0 0x0000000003191811 in guodong::demo::Item::Clear (this=0x7fef643da790)(gdb) thread 69 [Switching to thread 69 (Thread 0x7fef767fc700 (LWP 39091))] #0 0x0000000003191811 in guodong::demo::Item::Clear (this=0x7fef643da790)
查看当前线程的栈帧:
(gdb) bt#0 0x0000000003191811 in guodong::demo::Item::Clear (this=0x7fef643da790)... 省略代码路径#1 0x0000000003196888 in guodong::demo::Item::CopyFrom (this=0x7fef643da790, from=...)... 省略代码路径#2 0x000000000263ed70 in guodongxiaren::DocAdapter::SetDocBoxItem (this=this@entry=0x7fef647bdd40, box=box@entry=0x7fef645262d0,... 省略代码路径#3 0x000000000263f591 in guodongxiaren::DocAdapter::Process (this=0x7fef647bdd40)... 省略代码路径#4 0x0000000002a7ae00 in guodongxiaren::BaseProcessor::Logtrace_Process (this=0x7fef647bdd40)... 省略代码路径#5 0x0000000002a77d89 in guodongxiaren::StageMgr::Process (this=this@entry=0x7fef648427a0, stage=<optimized out>)... 省略代码路径#6 0x0000000002a788cd in guodongxiaren::StageMgr::Process (this=this@entry=0x7fef648427a0)... 省略代码路径(gdb) bt #0 0x0000000003191811 in guodong::demo::Item::Clear (this=0x7fef643da790) ... 省略代码路径 #1 0x0000000003196888 in guodong::demo::Item::CopyFrom (this=0x7fef643da790, from=...) ... 省略代码路径 #2 0x000000000263ed70 in guodongxiaren::DocAdapter::SetDocBoxItem (this=this@entry=0x7fef647bdd40, box=box@entry=0x7fef645262d0, ... 省略代码路径 #3 0x000000000263f591 in guodongxiaren::DocAdapter::Process (this=0x7fef647bdd40) ... 省略代码路径 #4 0x0000000002a7ae00 in guodongxiaren::BaseProcessor::Logtrace_Process (this=0x7fef647bdd40) ... 省略代码路径 #5 0x0000000002a77d89 in guodongxiaren::StageMgr::Process (this=this@entry=0x7fef648427a0, stage=<optimized out>) ... 省略代码路径 #6 0x0000000002a788cd in guodongxiaren::StageMgr::Process (this=this@entry=0x7fef648427a0) ... 省略代码路径(gdb) bt #0 0x0000000003191811 in guodong::demo::Item::Clear (this=0x7fef643da790) ... 省略代码路径 #1 0x0000000003196888 in guodong::demo::Item::CopyFrom (this=0x7fef643da790, from=...) ... 省略代码路径 #2 0x000000000263ed70 in guodongxiaren::DocAdapter::SetDocBoxItem (this=this@entry=0x7fef647bdd40, box=box@entry=0x7fef645262d0, ... 省略代码路径 #3 0x000000000263f591 in guodongxiaren::DocAdapter::Process (this=0x7fef647bdd40) ... 省略代码路径 #4 0x0000000002a7ae00 in guodongxiaren::BaseProcessor::Logtrace_Process (this=0x7fef647bdd40) ... 省略代码路径 #5 0x0000000002a77d89 in guodongxiaren::StageMgr::Process (this=this@entry=0x7fef648427a0, stage=<optimized out>) ... 省略代码路径 #6 0x0000000002a788cd in guodongxiaren::StageMgr::Process (this=this@entry=0x7fef648427a0) ... 省略代码路径
切到栈帧2,这里可能拿到这个线程的ctx_对象:
(gdb) f 2...(gdb) set $a=*ctx(gdb) f 2 ... (gdb) set $a=*ctx(gdb) f 2 ... (gdb) set $a=*ctx
ctx_中有一个复杂对象sub_ctxs_:
std::map<std::string, std::shared_ptr<SubContext>> sub_ctxs_;std::map<std::string, std::shared_ptr<SubContext>> sub_ctxs_;std::map<std::string, std::shared_ptr<SubContext>> sub_ctxs_;
输出m_box_session:
(gdb) p $a.sub_ctxs_$11 = std::map with 2 elements = {["DocFilterSession"] = std::shared_ptr (count 1, weak 0) 0x7fef65127d40,["DocSubContext"] = std::shared_ptr (count 1, weak 0) 0x7fef64535c10}(gdb) p $a.sub_ctxs_ $11 = std::map with 2 elements = { ["DocFilterSession"] = std::shared_ptr (count 1, weak 0) 0x7fef65127d40, ["DocSubContext"] = std::shared_ptr (count 1, weak 0) 0x7fef64535c10 }(gdb) p $a.sub_ctxs_ $11 = std::map with 2 elements = { ["DocFilterSession"] = std::shared_ptr (count 1, weak 0) 0x7fef65127d40, ["DocSubContext"] = std::shared_ptr (count 1, weak 0) 0x7fef64535c10 }
这个map的value是std::shared_ptr类型,该如何输出呢?
输出std::shared_ptr持有对象的地址
其实不难,把std::shared_ptr持有对象的地址,直接按裸指针的类型来转型就可以了。比如上面的Value本是std::shared_ptr<SubContext>
类型,可以:
(gdb) set $nd=*(SubContext*)0x7fef64535c10(gdb) p $nd$12 = (guodongxiaren::DocSubContext ?) {<guodongxiaren::DocSubContext> = {<guodongxiaren::SubContext> = {<guodongxiaren::Reflection> = {_vptr.Reflection = 0x57ee058 <vtable for guodongxiaren::DocSubContext+24>,m_kv_items = {<google::protobuf::internal::RepeatedPtrFieldBase> = {static kInitialSize = 0,arena_ = 0x0,current_size_ = 0,total_size_ = 0,static kRepHeaderSize = 8,rep_ = 0x0}, <No data fields>},kv_map_ = std::map with 0 elements,... 省略(gdb) set $nd=*(SubContext*)0x7fef64535c10 (gdb) p $nd $12 = (guodongxiaren::DocSubContext ?) { <guodongxiaren::DocSubContext> = { <guodongxiaren::SubContext> = { <guodongxiaren::Reflection> = { _vptr.Reflection = 0x57ee058 <vtable for guodongxiaren::DocSubContext+24>, m_kv_items = { <google::protobuf::internal::RepeatedPtrFieldBase> = { static kInitialSize = 0, arena_ = 0x0, current_size_ = 0, total_size_ = 0, static kRepHeaderSize = 8, rep_ = 0x0 }, <No data fields>}, kv_map_ = std::map with 0 elements, ... 省略(gdb) set $nd=*(SubContext*)0x7fef64535c10 (gdb) p $nd $12 = (guodongxiaren::DocSubContext ?) { <guodongxiaren::DocSubContext> = { <guodongxiaren::SubContext> = { <guodongxiaren::Reflection> = { _vptr.Reflection = 0x57ee058 <vtable for guodongxiaren::DocSubContext+24>, m_kv_items = { <google::protobuf::internal::RepeatedPtrFieldBase> = { static kInitialSize = 0, arena_ = 0x0, current_size_ = 0, total_size_ = 0, static kRepHeaderSize = 8, rep_ = 0x0 }, <No data fields>}, kv_map_ = std::map with 0 elements, ... 省略
但实际这个map存的都是SubContext子类的shared_ptr,比如”DocSubContext”这个Key指向的Value就是std::shared_ptr
class DocSubContext : public SubContext {public:... 省略public:const guodong::demo::DemoReq* doc_req_ = nullptr;guodong::demo::DemoResp* doc_rsp_ = nullptr;... 省略class DocSubContext : public SubContext { public: ... 省略 public: const guodong::demo::DemoReq* doc_req_ = nullptr; guodong::demo::DemoResp* doc_rsp_ = nullptr; ... 省略class DocSubContext : public SubContext { public: ... 省略 public: const guodong::demo::DemoReq* doc_req_ = nullptr; guodong::demo::DemoResp* doc_rsp_ = nullptr; ... 省略
其实也可以直接按照实际类型转:
(gdb) set $ndbs=*(DocSubContext*)0x7fef64535c10(gdb) set $ndbs=*(DocSubContext*)0x7fef64535c10(gdb) set $ndbs=*(DocSubContext*)0x7fef64535c10
DocSubContext 比父类多一些成员变量,比如doc_req_,来看一下能否正确输出
先存成新变量:
(gdb) p $ndbs.doc_req_$14 = (const guodong::mbu::DemoReq *) 0x7fef643ed478(gdb) set $r=*($ndbs.doc_req_)(gdb) p $ndbs.doc_req_ $14 = (const guodong::mbu::DemoReq *) 0x7fef643ed478 (gdb) set $r=*($ndbs.doc_req_)(gdb) p $ndbs.doc_req_ $14 = (const guodong::mbu::DemoReq *) 0x7fef643ed478 (gdb) set $r=*($ndbs.doc_req_)
再尝试输出doc_req_中的成员变量:
(gdb) p $r.begid_$19 = 0(gdb) p $r.endid_$20 = 35(gdb) p $r.sn_$21 = 3(gdb) p $r.se_$22 = 1(gdb) p $r.search_id_$23 = {ptr_ = 0x7fef64d77af0}(gdb) p $r.begid_ $19 = 0 (gdb) p $r.endid_ $20 = 35 (gdb) p $r.sn_ $21 = 3 (gdb) p $r.se_ $22 = 1 (gdb) p $r.search_id_ $23 = { ptr_ = 0x7fef64d77af0 }(gdb) p $r.begid_ $19 = 0 (gdb) p $r.endid_ $20 = 35 (gdb) p $r.sn_ $21 = 3 (gdb) p $r.se_ $22 = 1 (gdb) p $r.search_id_ $23 = { ptr_ = 0x7fef64d77af0 }
search_id是一个字符串,继续查看:
(gdb) p *(std::string*)0x7fef64d77af0$24 = "6955223450700033389"(gdb) p *(std::string*)0x7fef64d77af0 $24 = "6955223450700033389"(gdb) p *(std::string*)0x7fef64d77af0 $24 = "6955223450700033389"