前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从一次core dump现场说开来

从一次core dump现场说开来

原创
作者头像
果冻虾仁
修改2023-06-12 22:50:39
2.1K0
修改2023-06-12 22:50:39
举报
文章被收录于专栏:后台公论后台公论

导语

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

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

本文通过之前线上遇到的一个coredump来浅析一下coredump分析的经验技巧。

相关源码已经脱敏,如果你看到代码中有guodongxiaren、guodong、demo、test、996、icu、pua等字样不要感到奇怪

其实这次coredump原因并不难猜想,只是借此机会做一下延伸。以供各位后续定位更复杂coredump问题时做参考。

coredump现场

话说某个周五的下午,线上某服务突然出现coredump报警。补充一下,我们这边的服务,默认不生成真实的core文件,只是会把core栈的第一现场的作为文本保存下来(无法调试),俗称minicore,可以登录机器查看,也可以通过网页查看。这次的minicore内容主要部分是:

代码语言:txt
复制
[0x351d7] :0 \_\_GI\_raise 
代码语言:txt
复制
[0x368c8] :0 \_\_GI\_abort 
代码语言:txt
复制
[0x2e146] :0 \_\_assert\_fail\_base 
代码语言:txt
复制
[0x2e1f2] :0 \_\_GI\_\_\_assert\_fail 
代码语言:txt
复制
[0x521bd5f] json\_value.cpp:1072 Json::Value::operator[]() 
代码语言:txt
复制
[0x2661e28] searcher.cpp:169 guodongxiaren::Searcher::PreSearch() 

代码问题推测

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

代码语言:c++
复制
    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类型的对象。咋看之下,这个datai的下标 i 在 for循环中不可能越界的,并且data是局部变量也不可能并发,所以不应该出现越界才对。

再看整段代码的逻辑是解析了一个json字符串(it->second->value()),并且将其理解为json的数组,然后开始遍历数组。这个json字符串是前端生成经过多个模块一路透传过来的。这段代码存在明显缺陷:当前端发送的字符串不是数组形式的json字符串时,那么按照下标来遍历data,是有问题的!

看下jsoncpp的中Json::Value的源码,其中size()函数在Value是数组类型(arrayValue)或者对象类型(objectValue)的时候,都是存在非0返回值的,但含义不同。如果是json对象类型的size,表示的是对象中有多少字段,但这时候不能按照下标来遍历!

代码语言:c++
复制
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则是这个变量的引用:

代码语言:c++
复制
auto& kv_map = ctx->comm_ctx_.kv_map_;

查看kv_map_的类型定义:

代码语言:c++
复制
  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文件路径

代码语言:c++
复制
gdb 二进制路径 core.39057

显示栈帧

输入bt(或where)

代码语言:txt
复制
(gdb) bt
代码语言:txt
复制
#0  0x00007ff0bdab91d7 in raise () from /lib64/libc.so.6
代码语言:txt
复制
#1  0x00007ff0bdabaa08 in abort () from /lib64/libc.so.6
代码语言:txt
复制
#2  0x00007ff0bdab2146 in __assert_fail_base () from /lib64/libc.so.6
代码语言:txt
复制
#3  0x00007ff0bdab21f2 in __assert_fail () from /lib64/libc.so.6
代码语言:txt
复制
#4  0x000000000521bd5f in Json::Value::operator[] (this=this@entry=0x7feeea7a67f0, index=index@entry=0)
代码语言:txt
复制
    at mm3rd/jsoncpp/src/json_value.cpp:1072
代码语言:txt
复制
#5  0x0000000002661e28 in guodongxiaren::Searcher::PreSearch (this=0x7fef48fa5b10)
代码语言:txt
复制
    at guodong/demo/module/searcher.cpp:169

栈帧4是jscocpp的Value对象内部,栈帧5是业务代码调用jsoncpp 运算符的地方。

切换栈帧

输入 f 5 切换到栈帧5:

代码语言:txt
复制
(gdb) f 5
代码语言:txt
复制
#5  0x0000000002661e28 in guodongxiaren::Searcher::PreSearch (this=0x7fef48fa5b10)
代码语言:txt
复制
    at guodong/demo/module/searcher.cpp:169
代码语言:txt
复制
169     guodong/demo/module/searcher.cpp: No such file or directory.

输出变量

输出一下当前对象:

代码语言:txt
复制
(gdb) p this
代码语言:txt
复制
$1 = (guodongxiaren::Searcher * const) 0x7fef48fa5b10

当前有成员变量ctx_,它是指针类型,通过加* 输出具体对象内容:

代码语言:txt
复制
$2 = (guodongxiaren::Context *) 0x7fef48c115e8
代码语言:txt
复制
(gdb) p *ctx
代码语言:txt
复制
$3 = {_vptr.Context = 0x575fc90 <vtable for guodongxiaren::RecallContext<guodongxiaren::ItemContext, void>+16>, rid_ = 993855933, 
代码语言:txt
复制
  search_id_ = 2864970758387329845, timebegin_ = 1665731120388130, req_ = 0x7fef48c73840, rsp_ = 0x7fef497db100, sdk_ = {
代码语言:txt
复制
    impl_ = 0x7fef48c88e00, helper_ = {rid_ = 993855933, only_kv_ = false, 
代码语言:txt
复制
...
代码语言:txt
复制
... 内容太多了,这里省略

设置变量别名

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

代码语言:txt
复制
(gdb) set $a=*ctx

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

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

代码语言:txt
复制
(gdb) p $a.search_id_
代码语言:txt
复制
$5 = 2864970758387329845

当然,我们的重点是那个kv_map,所以同样定义一个别名,然后尝试输出:

代码语言:txt
复制
(gdb) set $m=$a.comm_ctx_.kv_map_
代码语言:txt
复制
(gdb) p $m
代码语言:txt
复制
$6 = {<std::__allow_copy_cons<true>> = {<No data fields>}, 
代码语言:txt
复制
  _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>> = ...
代码语言:txt
复制
... 太长了,这里省略

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

std::unordered_map美化输出

查找printers.py

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

代码语言:shell
复制
find / -name "printers.py" 2> /dev/null

find输出结果是:

代码语言:shell
复制
/usr/share/gcc-4.8.2/python/libstdcxx/v6/printers.py

新建.gdbinit

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

代码语言:text
复制
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文件:

代码语言:shell
复制
/usr/bin/gdb 二进制 core.39057

再次输出std::unordered_map

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

代码语言:txt
复制
    (gdb) f 5
代码语言:txt
复制
    ... 省略
代码语言:txt
复制
    (gdb) set $a=*ctx
代码语言:txt
复制
    (gdb) set $m=$a.comm_ctx_.kv_map_

此时再次执行p命令:

代码语言:txt
复制
(gdb) p $m
代码语言:txt
复制
$1 = std::unordered_map with 11 elements = {["puaType"] = 0x7fef48c93df0, ["icuType"] = 0x7fef48bd3a70, ["996Type"] = 0x7fef48441250, ...

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

代码语言:txt
复制
["puaType"] = 0x7fef48c93df0

0x7fef48c93df0指向的是这个unordered_map的value的内存地址,它的类型是 guodong::CommKvInfo*

输出protobuf的Message对象

可以将内存地址按照它指向对象的真实类型进行转型,然后输出:

代码语言:txt
复制
(gdb) set $c =*(guodong::CommKvInfo*)0x7fef48c93df0
代码语言:txt
复制
(gdb) p $c
代码语言:txt
复制
$2 = {<google::protobuf::Message> = {<google::protobuf::MessageLite> = {
代码语言:txt
复制
      _vptr.MessageLite = 0x59e7eb0 <vtable for guodong::CommKvInfo+16>}, <No data fields>}, static kIndexInFileMessages = 8, 
代码语言:txt
复制
  static kKeyFieldNumber = 1, static kTextValueFieldNumber = 3, static kUintValueFieldNumber = 2, 
代码语言:txt
复制
  _internal_metadata_ = {<google::protobuf::internal::InternalMetadataWithArenaBase<google::protobuf::UnknownFieldSet, google::protobuf::internal::InternalMetadataWithArena>> = {ptr_ = 0x0, static kPtrTagMask = <optimized out>, 
代码语言:txt
复制
      static kPtrValueMask = <optimized out>}, <No data fields>}, _has_bits_ = {has_bits_ = {3}}, _cached_size_ = 0, key_ = {
代码语言:txt
复制
    ptr_ = 0x7fef491cb6a0}, value_ = {ptr_ = 0x7fef491cb640} }

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

代码语言:text
复制
message CommKvInfo
{
     required bytes key = 1;
     optional bytes value = 2;
}

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

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

代码语言:txt
复制
(gdb) p $c.value_
代码语言:txt
复制
$3 = {ptr_ = 0x7fef491cb640}

实锤

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

代码语言:txt
复制
(gdb) p *(std::string*)0x7fef491cb640
代码语言:txt
复制
$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变量)内部,我们可以调试一下它玩玩。

代码语言:txt
复制
#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,输出一下当前对象:

代码语言:txt
复制
(gdb) f 4
代码语言:txt
复制
#4  0x000000000521bd5f in Json::Value::operator[] (this=this@entry=0x7feeea7a67f0, index=index@entry=0)
代码语言:txt
复制
    at mm3rd/jsoncpp/src/json_value.cpp:1072
代码语言:txt
复制
1072    mm3rd/jsoncpp/src/json_value.cpp: No such file or directory.
代码语言:txt
复制
(gdb) p *this
代码语言:txt
复制
$1 = {static null = {static null = <same as static member of an already seen type>, static minLargestInt = -9223372036854775808, 
代码语言:txt
复制
    static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648, 
代码语言:txt
复制
    static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807, 
代码语言:txt
复制
    static maxUInt64 = 18446744073709551615, value_ = {int_ = 0, uint_ = 0, real_ = 0, bool_ = false, string_ = 0x0, map_ = 0x0}, 
代码语言:txt
复制
    type_ = Json::nullValue, allocated_ = 0, comments_ = 0x0}, static minLargestInt = -9223372036854775808, 
代码语言:txt
复制
  static maxLargestInt = 9223372036854775807, static maxLargestUInt = 18446744073709551615, static minInt = -2147483648, 
代码语言:txt
复制
  static maxInt = 2147483647, static maxUInt = 4294967295, static minInt64 = -9223372036854775808, static maxInt64 = 9223372036854775807, 
代码语言:txt
复制
  static maxUInt64 = 18446744073709551615, value_ = {int_ = 140665696073680, uint_ = 140665696073680, real_ = 6.9498087978351207e-310, 
代码语言:txt
复制
    bool_ = 208, string_ = 0x7fef48d8b7d0 "", map_ = 0x7fef48d8b7d0}, type_ = Json::objectValue, allocated_ = -1, comments_ = 0x0}

set print美化输出

看着有点乱,再加一些gdb美化输出的设置:

代码语言:txt
复制
set print pretty on
代码语言:txt
复制
set print object on
代码语言:txt
复制
set print static-members on
代码语言:txt
复制
set print vtbl on
代码语言:txt
复制
set print demangle on
代码语言:txt
复制
set demangle-style gnu-v3
代码语言:txt
复制
set print sevenbit-strings off

再输出一下当前对象的内容

代码语言:txt
复制
(gdb) p *this
代码语言:txt
复制
$2 = {
代码语言:txt
复制
  static null = {
代码语言:txt
复制
    static null = <same as static member of an already seen type>, 
代码语言:txt
复制
    static minLargestInt = -9223372036854775808, 
代码语言:txt
复制
    static maxLargestInt = 9223372036854775807, 
代码语言:txt
复制
    static maxLargestUInt = 18446744073709551615, 
代码语言:txt
复制
    static minInt = -2147483648, 
代码语言:txt
复制
    static maxInt = 2147483647, 
代码语言:txt
复制
    static maxUInt = 4294967295, 
代码语言:txt
复制
    static minInt64 = -9223372036854775808, 
代码语言:txt
复制
    static maxInt64 = 9223372036854775807, 
代码语言:txt
复制
    static maxUInt64 = 18446744073709551615, 
代码语言:txt
复制
    value_ = {
代码语言:txt
复制
      int_ = 0, 
代码语言:txt
复制
      uint_ = 0, 
代码语言:txt
复制
      real_ = 0, 
代码语言:txt
复制
      bool_ = false, 
代码语言:txt
复制
      string_ = 0x0, 
代码语言:txt
复制
      map_ = 0x0
代码语言:txt
复制
    }, 
代码语言:txt
复制
    type_ = Json::nullValue, 
代码语言:txt
复制
    allocated_ = 0, 
代码语言:txt
复制
    comments_ = 0x0
代码语言:txt
复制
  }, 
代码语言:txt
复制
  static minLargestInt = -9223372036854775808, 
代码语言:txt
复制
  static maxLargestInt = 9223372036854775807, 
代码语言:txt
复制
  static maxLargestUInt = 18446744073709551615, 
代码语言:txt
复制
  static minInt = -2147483648, 
代码语言:txt
复制
  static maxInt = 2147483647, 
代码语言:txt
复制
  static maxUInt = 4294967295, 
代码语言:txt
复制
  static minInt64 = -9223372036854775808, 
代码语言:txt
复制
  static maxInt64 = 9223372036854775807, 
代码语言:txt
复制
  static maxUInt64 = 18446744073709551615, 
代码语言:txt
复制
  value_ = {
代码语言:txt
复制
    int_ = 140665696073680, 
代码语言:txt
复制
    uint_ = 140665696073680, 
代码语言:txt
复制
    real_ = 6.9498087978351207e-310, 
代码语言:txt
复制
    bool_ = 208, 
代码语言:txt
复制
    string_ = 0x7fef48d8b7d0 "", 
代码语言:txt
复制
    map_ = 0x7fef48d8b7d0
代码语言:txt
复制
  }, 
代码语言:txt
复制
  type_ = Json::objectValue, 
代码语言:txt
复制
  allocated_ = -1, 
代码语言:txt
复制
  comments_ = 0x0
代码语言:txt
复制
}

可以直接看出 type_ 的值是:Json::objectValue,如果是数组,则type_ 应该是 Json::arrayValue 。到这里也可以停止了。但还可以继续看看。value_ 成员存储具体的数组,它是一个union类型:

代码语言:c++
复制
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_就可以了。

代码语言:txt
复制
(gdb) set $a=(*this).value_.map_
代码语言:txt
复制
$3 = (Json::Value::ObjectValues *) 0x7fef48d8b7d0

指针类型,我们解一下指针,存成新变量:

代码语言:txt
复制
(gdb) set $b=*$a
代码语言:txt
复制
(gdb) p $b
代码语言:txt
复制
$4 = std::map with 9 elements = {
代码语言:txt
复制
  [{
代码语言:txt
复制
    cstr_ = 0x7fef48a40f30 "offset", 
代码语言:txt
复制
    index_ = 1
代码语言:txt
复制
  }] = {
代码语言:txt
复制
    static null = <same as static member of an already seen type>, 
代码语言:txt
复制
    static minLargestInt = -9223372036854775808, 
代码语言:txt
复制
    static maxLargestInt = 9223372036854775807, 
代码语言:txt
复制
    static maxLargestUInt = 18446744073709551615, 
代码语言:txt
复制
    static minInt = -2147483648, 
代码语言:txt
复制
    static maxInt = 2147483647, 
代码语言:txt
复制
    static maxUInt = 4294967295, 
代码语言:txt
复制
    static minInt64 = -9223372036854775808, 
代码语言:txt
复制
    static maxInt64 = 9223372036854775807, 
代码语言:txt
复制
    static maxUInt64 = 18446744073709551615, 
代码语言:txt
复制
    value_ = {
代码语言:txt
复制
      int_ = 0, 
代码语言:txt
复制
      uint_ = 0, 
代码语言:txt
复制
      real_ = 0, 
代码语言:txt
复制
      bool_ = false, 
代码语言:txt
复制
      string_ = 0x0, 
代码语言:txt
复制
      map_ = 0x0
代码语言:txt
复制
    }, 
代码语言:txt
复制
    type_ = Json::intValue, 
代码语言:txt
复制
    allocated_ = 0, 
代码语言:txt
复制
    comments_ = 0x0
代码语言:txt
复制
  },
代码语言:txt
复制
  [{
代码语言:txt
复制
    cstr_ = 0x7fef48b7ffa0 "type", 
代码语言:txt
复制
    index_ = 1
代码语言:txt
复制
  }] = {
代码语言:txt
复制
    static null = <same as static member of an already seen type>, 
代码语言:txt
复制
    static minLargestInt = -9223372036854775808, 
代码语言:txt
复制
    static maxLargestInt = 9223372036854775807, 
代码语言:txt
复制
... 省略

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

另外一个 .gdbinit

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

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

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

pmap输出std::map

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

代码语言:txt
复制
pmap <variable_name> <left_element_type> <right_element_type>

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

代码语言:c++
复制
  typedef std::map<CZString, Value> ObjectValues;

所以这样用:

代码语言:txt
复制
(gdb) pmap $b CZString Value
代码语言:txt
复制
elem[0].left: $2 = {
代码语言:txt
复制
  cstr_ = 0x7fef48a40f30 "offset", 
代码语言:txt
复制
  index_ = 1
代码语言:txt
复制
}
代码语言:txt
复制
elem[0].right: $3 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d8b6e0
代码语言:txt
复制
elem[1].left: $4 = {
代码语言:txt
复制
  cstr_ = 0x7fef48b7ffa0 "type", 
代码语言:txt
复制
  index_ = 1
代码语言:txt
复制
}
代码语言:txt
复制
elem[1].right: $5 = {void (Json::Value * const, Json::ValueType)} 0x7fef48b7ff80
代码语言:txt
复制
elem[2].left: $6 = {
代码语言:txt
复制
  cstr_ = 0x7fef48b80010 "buffer", 
代码语言:txt
复制
  index_ = 1
代码语言:txt
复制
}
代码语言:txt
复制
elem[2].right: $7 = {void (Json::Value * const, Json::ValueType)} 0x7fef48b7fff0
代码语言:txt
复制
elem[3].left: $8 = {
代码语言:txt
复制
  cstr_ = 0x7fef48b800d0 "icu", 
代码语言:txt
复制
  index_ = 1
代码语言:txt
复制
}
代码语言:txt
复制
elem[3].right: $9 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d50060
代码语言:txt
复制
elem[4].left: $10 = {
代码语言:txt
复制
  cstr_ = 0x7fef48d500d0 "test", 
代码语言:txt
复制
  index_ = 1
代码语言:txt
复制
}
代码语言:txt
复制
elem[4].right: $11 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d500b0
代码语言:txt
复制
elem[5].left: $12 = {
代码语言:txt
复制
  cstr_ = 0x7fef48d50160 "demo", 
代码语言:txt
复制
  index_ = 1
代码语言:txt
复制
}
代码语言:txt
复制
elem[5].right: $13 = {void (Json::Value * const, Json::ValueType)} 0x7fef48d50140
代码语言:txt
复制
elem[6].left: $14 = {
代码语言:txt
复制
  cstr_ = 0x7fef48d3fcd0 "cnt", 
代码语言:txt
复制
  index_ = 1
代码语言:txt
复制
}
代码语言:txt
复制
... 省略

可以看到好像比之前的.gdbinit的输出要干净的多,感觉清晰了不少。它也准确地输出解析后json对象字符串的Key和Value。除了std::map,还有如下指令可以输出其他STL容器:

代码语言:txt
复制
#       std::vector<T> -- via pvector command
代码语言:txt
复制
#       std::list<T> -- via plist or plist_member command
代码语言:txt
复制
#       std::map<T,T> -- via pmap or pmap_member command
代码语言:txt
复制
#       std::multimap<T,T> -- via pmap or pmap_member command
代码语言:txt
复制
#       std::set<T> -- via pset command
代码语言:txt
复制
#       std::multiset<T> -- via pset command
代码语言:txt
复制
#       std::deque<T> -- via pdequeue command
代码语言:txt
复制
#       std::stack<T> -- via pstack command
代码语言:txt
复制
#       std::queue<T> -- via pqueue command
代码语言:txt
复制
#       std::priority_queue<T> -- via ppqueue command
代码语言:txt
复制
#       std::bitset<n> -- via pbitset command
代码语言:txt
复制
#       std::string -- via pstring command
代码语言:txt
复制
#       std::widestring -- via pwstring command

pvector输出std::vector

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

代码语言:txt
复制
(gdb) f 5
代码语言:txt
复制
...
代码语言:txt
复制
(gdb) pvector kKeyList
代码语言:txt
复制
elem[0]: $2 = "PTSD"
代码语言:txt
复制
elem[1]: $3 = "ICU"
代码语言:txt
复制
elem[2]: $4 = "996"
代码语言:txt
复制
Vector size = 3
代码语言:txt
复制
Vector capacity = 3
代码语言:txt
复制
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 输出都比较方便了。我把合成版本放到这里了:https://gist.github.com/guodongxiaren/30e4928e25cb20ea6d158a81d3fadfcd

各位可以下载然后保存到~/.gdbinit

切换线程

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

显示当前线程

代码语言:txt
复制
info thread

输出:

代码语言:txt
复制
Id   Target Id         Frame 
代码语言:txt
复制
  103  Thread 0x7fee651be700 (LWP 39142) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6
代码语言:txt
复制
  102  Thread 0x7feebb578700 (LWP 39127) 0x00007ff0bdaff82f in malloc_consolidate () from /lib64/libc.so.6
代码语言:txt
复制
  101  Thread 0x7fee2f0b0700 (LWP 39152) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6
代码语言:txt
复制
  100  Thread 0x7fee81bf8700 (LWP 39137) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6
代码语言:txt
复制
... 省略
代码语言:txt
复制
  72   Thread 0x7fee8353d700 (LWP 39136) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6
代码语言:txt
复制
  71   Thread 0x7fef8d610700 (LWP 39086) 0x00007ff0bdb7bd23 in epoll_wait () from /lib64/libc.so.6
代码语言:txt
复制
  70   Thread 0x7fef8fe15700 (LWP 39067) 0x00007ff0bdb4267d in nanosleep () from /lib64/libc.so.6
代码语言:txt
复制
  69   Thread 0x7fef767fc700 (LWP 39091) 0x0000000003191811 in guodong::demo::Item::Clear (this=0x7fef643da790)

切换到另外线程

看到线程ID 69 也是一个当时正在进行业务处理的线程。切换进去:

代码语言:txt
复制
(gdb) thread 69
代码语言:txt
复制
[Switching to thread 69 (Thread 0x7fef767fc700 (LWP 39091))]
代码语言:txt
复制
#0  0x0000000003191811 in guodong::demo::Item::Clear (this=0x7fef643da790)

查看当前线程的栈帧:

代码语言:txt
复制
(gdb) bt
代码语言:txt
复制
#0  0x0000000003191811 in guodong::demo::Item::Clear (this=0x7fef643da790)
代码语言:txt
复制
... 省略代码路径
代码语言:txt
复制
#1  0x0000000003196888 in guodong::demo::Item::CopyFrom (this=0x7fef643da790, from=...)
代码语言:txt
复制
... 省略代码路径
代码语言:txt
复制
#2  0x000000000263ed70 in guodongxiaren::DocAdapter::SetDocBoxItem (this=this@entry=0x7fef647bdd40, box=box@entry=0x7fef645262d0, 
代码语言:txt
复制
... 省略代码路径
代码语言:txt
复制
#3  0x000000000263f591 in guodongxiaren::DocAdapter::Process (this=0x7fef647bdd40)
代码语言:txt
复制
... 省略代码路径
代码语言:txt
复制
#4  0x0000000002a7ae00 in guodongxiaren::BaseProcessor::Logtrace_Process (this=0x7fef647bdd40)
代码语言:txt
复制
... 省略代码路径
代码语言:txt
复制
#5  0x0000000002a77d89 in guodongxiaren::StageMgr::Process (this=this@entry=0x7fef648427a0, stage=<optimized out>)
代码语言:txt
复制
... 省略代码路径
代码语言:txt
复制
#6  0x0000000002a788cd in guodongxiaren::StageMgr::Process (this=this@entry=0x7fef648427a0)
代码语言:txt
复制
... 省略代码路径

切到栈帧2,这里可能拿到这个线程的ctx_对象:

代码语言:txt
复制
(gdb) f 2
代码语言:txt
复制
...
代码语言:txt
复制
(gdb) set $a=*ctx

ctx_中有一个复杂对象sub_ctxs_:

代码语言:txt
复制
std::map<std::string, std::shared_ptr<SubContext>> sub_ctxs_;

输出sub_ctxs_:

代码语言:txt
复制
(gdb) p $a.sub_ctxs_
代码语言:txt
复制
$11 = std::map with 2 elements = {
代码语言:txt
复制
  ["PuaSubContext"] = std::shared_ptr (count 1, weak 0) 0x7fef65127d40,
代码语言:txt
复制
  ["DocSubContext"] = std::shared_ptr (count 1, weak 0) 0x7fef64535c10
代码语言:txt
复制
}

这个map的value是std::shared_ptr类型,该如何输出呢?

输出std::shared_ptr持有对象的地址

其实不难,把std::shared_ptr持有对象的地址,直接按裸指针的类型来转型就可以了。比如上面的Value本是std::shared_ptr<SubContext>类型,可以:

代码语言:txt
复制
(gdb) set $nd=*(SubContext*)0x7fef64535c10
代码语言:txt
复制
(gdb) p $nd
代码语言:txt
复制
$12 = (guodongxiaren::DocSubContext ?) {
代码语言:txt
复制
  <guodongxiaren::DocSubContext> = {
代码语言:txt
复制
    <guodongxiaren::SubContext> = {
代码语言:txt
复制
      <guodongxiaren::Reflection> = {
代码语言:txt
复制
        _vptr.Reflection = 0x57ee058 <vtable for guodongxiaren::DocSubContext+24>, 
代码语言:txt
复制
        m_kv_items = {
代码语言:txt
复制
          <google::protobuf::internal::RepeatedPtrFieldBase> = {
代码语言:txt
复制
            static kInitialSize = 0, 
代码语言:txt
复制
            arena_ = 0x0, 
代码语言:txt
复制
            current_size_ = 0, 
代码语言:txt
复制
            total_size_ = 0, 
代码语言:txt
复制
            static kRepHeaderSize = 8, 
代码语言:txt
复制
            rep_ = 0x0
代码语言:txt
复制
          }, <No data fields>}, 
代码语言:txt
复制
        kv_map_ = std::map with 0 elements, 
代码语言:txt
复制
... 省略

但实际这个map存的都是SubContext子类的shared_ptr,比如"DocSubContext"这个Key指向的Value就是std::shared_ptr<DocSubContext>

代码语言:c++
复制
class DocSubContext : public SubContext {
 public:
... 省略

 public:
  const guodong::demo::DemoReq* doc_req_ = nullptr;
  guodong::demo::DemoResp* doc_rsp_ = nullptr;
... 省略

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

代码语言:txt
复制
(gdb) set $ndbs=*(DocSubContext*)0x7fef64535c10

DocSubContext 比父类多一些成员变量,比如doc_req_,来看一下能否正确输出

先存成新变量:

代码语言:txt
复制
(gdb) p $ndbs.doc_req_
代码语言:txt
复制
$14 = (const guodong::mbu::DemoReq *) 0x7fef643ed478
代码语言:txt
复制
(gdb) set $r=*($ndbs.doc_req_)

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

代码语言:txt
复制
(gdb) p $r.begid_
代码语言:txt
复制
$19 = 0
代码语言:txt
复制
(gdb) p $r.endid_
代码语言:txt
复制
$20 = 35
代码语言:txt
复制
(gdb) p $r.sn_
代码语言:txt
复制
$21 = 3
代码语言:txt
复制
(gdb) p $r.se_
代码语言:txt
复制
$22 = 1
代码语言:txt
复制
(gdb) p $r.search_id_
代码语言:txt
复制
$23 = {
代码语言:txt
复制
  ptr_ = 0x7fef64d77af0
代码语言:txt
复制
}

search_id是一个字符串,继续查看:

代码语言:txt
复制
(gdb) p *(std::string*)0x7fef64d77af0
代码语言:txt
复制
$24 = "6955223450700033389"

可以看到具体的值

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导语
    • coredump现场
      • 代码问题推测
      • 分析(实锤coredump原因)
        • 按部就班
          • 执行gdb命令
          • 显示栈帧
          • 切换栈帧
          • 输出变量
          • 设置变量别名
        • std::unordered_map美化输出
          • 查找printers.py
          • 新建.gdbinit
          • 使用合适的gdb
          • 再次输出std::unordered_map
          • 输出protobuf的Message对象
          • 实锤
      • 延伸(和本次coredump原因无关)
        • 调试Json::Value
          • set print美化输出
          • 输出std::map
          • 另外一个 .gdbinit
          • pmap输出std::map
          • pvector输出std::vector
          • 美中不足
        • 切换线程
          • 显示当前线程
          • 切换到另外线程
        • 输出std::shared_ptr持有对象的地址
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
        http://www.vxiaotou.com