相关文章推荐
光明磊落的卤蛋  ·  IntelliJ ...·  4 月前    · 
坏坏的西瓜  ·  Why does ...·  1 年前    · 
玉树临风的楼房  ·  C++ IntelliSense - ...·  1 年前    · 
首发于 后台公论
从一次coredump现场说开来

从一次coredump现场说开来

导语

纵观C++程序员一生,其实都是在和下面几件事打交道:

  1. coredump
  2. 内存泄露
  3. CPU狂飙
  4. 编译失败

本文通过之前线上遇到的一个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()

代码问题推测

看起来是jsconcpp的 [] 运算符越界导致的。看一下这个searcher.cpp 文件的169行:

it = kv_map.find(kPuaType);
    if (it != kv_map.end() && !it->second->value().empty()) {
      Json::Value data;
      if (Json::Reader().parse(it->second->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类型的对象。咋看之下,这个 data[i] 的下标 i 在 for循环中不可能越界的,并且data是局部变量也不可能被其他线程修改,所以不应该出现越界才对。

再看整段代码的逻辑是解析了一个json字符串(it->second->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 + 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->value() 这个迭代器 it 是kv_map的find(kPuaType)的返回值。变量kPuaType是字符串 "puaType" ,kv_map则是这个变量的引用:

auto& kv_map = ctx->comm_ctx_.kv_map_;

查看kv_map_的类型定义:

using KvMap = std::unordered_map<std::string, const guodong::CommKvInfo*> ;
  KvMap kv_map_;

可知kv_map是一个unordered_map类型。我们可以通过gdb调试真实的core文件,输出kv_map中"puaType"对应的CommKvInfo中的value()返回的字符串,去确认一下它不是数组形式的json字符串就可以了。

执行gdb命令

开始用gdb调试coredump,命令形式:gdb bin文件路径 core文件路径

gdb 二进制路径 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

栈帧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:169
169     guodong/demo/module/searcher.cpp: No such file or directory.

输出变量

输出一下当前对象:

(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_ = 993855933, 
  search_id_ = 2864970758387329845, timebegin_ = 1665731120388130, req_ = 0x7fef48c73840, rsp_ = 0x7fef497db100, sdk_ = {
    impl_ = 0x7fef48c88e00, helper_ = {rid_ = 993855933, only_kv_ = false, 
... 内容太多了,这里省略

设置变量别名

为了调试方便我们可给变量起别名,其实就是定义一个名字更短的变量:

(gdb) set $a=*ctx

我们自己定义的变量一定要是 $ 开头的。

然后可以直接用比较短的变量去查看ctx_中的成员,比如search_id:

(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>> = ...
... 太长了,这里省略

发现基本不可读。因为kv_map是一个unordered_map。有没有更直观的输出unordered_map的方法呢。有的。其实gdb自带一个python脚本,可以美化gdb的输出。

std::unordered_map美化输出

查找printers.py

我们暂时退出gdb,先find一下,找一下gdb自带的python脚本的路径:

find / -name "printers.py" 2> /dev/null

find输出结果是:

/usr/share/gcc-4.8.2/python/libstdcxx/v6/printers.py

新建.gdbinit

然后在home目录新建一个文件,名为 .gdbinit 这个是gdb在执行时会加载的指令文件。在.gdbinit 中写入:

python
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。这里要注意一下,有的机器上有多个gdb,有的gdb并不能加载python脚本,启动gdb会报错。这时候可以更换机器上的其他gdb,比如/usr/bin/gdb,这里也请各位以自己机器的实际情况为准。在准备好.gdbinit 后,我用/usr/bin/gdb重新打开coredump文件:

/usr/bin/gdb 二进制 core.39057

再次输出std::unordered_map

继续之前的操作得到存储kv_map的变量 $m

(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 = {["puaType"] = 0x7fef48c93df0, ["icuType"] = 0x7fef48bd3a70, ["996Type"] = 0x7fef48441250, ...

kv_map的内容一览无余。可以看到:

["puaType"] = 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}, value_ = {ptr_ = 0x7fef491cb640}}

可以看到里面有个成员变量是 value_ ,就是我们想要的内容。即使你没有一眼看到,也没关系。CommKvInfo是一个protobuf的Message类型:

message CommKvInfo
     required bytes key = 1;
     optional bytes value = 2;
}

protobuf 生成的C++类是严格遵守『 谷歌C++代码规范 』的,在该规范中,类成员变量的命名规范都是要以下划线为后缀的: zh-google-styleguide.readthedocs.io

所以proto文件中定义的字段加上下划线,就是其在C++类中的实际字段。来输出一下value_:

(gdb) p $c.value_
$3 = {ptr_ = 0x7fef491cb640}

实锤

value_指向的也是指针,也就是一个内存地址。由于我们知道这个变量是std::string类型,所以可以直接转型,然后输出:

(gdb) p *(std::string*)0x7fef491cb640
$4 = "{\"offset\":0,\"type\":2,\"buffer\":\"abcdefghigklmn\",\"icu\":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)

切换到栈帧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: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 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
}

可以直接看出 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_;

输出std::map

如果是json对象类型的话,我们关注 map_就可以了。

(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, 
... 省略

可以看出这个map对象的key中保护的字符串。说明确实是json对象,而非json数组。

另外一个 .gdbinit

其实网上还有一个经典流传的 .gdbinit配置『STL GDB evaluators/views/utilities - 1.03』很多网站都能找到副本,比如这里: gist.github.com/apetres

我们下载下来,替换掉之前的 .gdbinit试试,这个.gdbinit,已经有 set print 那几个美化输出的命令了,所以不需要我们在gdb启动之后,再输入了。

重新加载coredump文件,沿用前面步骤,得到指向 map_的变量 \$b。

pmap输出std::map

这个.gdbinit 配置中有pmap命令,可以方便的输出std::map类型的变量。用法是:

pmap <variable_name> <left_element_type> <right_element_type>

这个命令有三个参数,第一个参数是变量,第二个参数是左边的元素类型(map key的类型),第三个参数是右边元素的类型(map value的类型),map_的类型定义是:

typedef std::map<CZString, Value> ObjectValues;

所以这样用:

(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 "icu", 
  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

pvector输出std::vector

在栈帧5中有一个vector变量kKeyList,我们可以试一下vector的输出功能:

(gdb) f 5
(gdb) pvector kKeyList
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 输出都比较方便了。我把合成版本放到这里了: gist.github.com/guodong 各位可以下载然后保存到 ~/.gdbinit

切换线程

有时候出现coredump并不是当前线程的处理出了什么问题,而是其他线程把内存踩乱了,导致虽然core在当前线程,但并不是问题代码所在的第一现场!此时需要切换线程来看。当然,本文案例中遇到的coredump并没有那么棘手。下面的内容和本次coredump没什么关系,权作演示,给各位做分析参考。

显示当前线程

info thread

输出:

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) 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

ctx_中有一个复杂对象sub_ctxs_:

std::map<std::string, std::shared_ptr<SubContext>> sub_ctxs_;

输出sub_ctxs_:

(gdb) p $a.sub_ctxs_
$11 = std::map with 2 elements = {
  ["PuaSubContext"] = 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, 
... 省略

但实际这个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;
... 省略

其实也可以直接按照实际类型转:

(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_)

再尝试输出doc_req_中的成员变量:

(gdb) p $r.begid_
$19 = 0
(gdb) p $r.endid_
$20 = 35
(gdb) p $r.sn_
$21 = 3
(gdb) p $r.se_