GDB实践:一场std::sort引发的coredump

以前只掌握gdb一些基础知识,还没有真正"实战"过。刚好最近同事一个进程coredump了,原因比较深,正好利用这个机会来分析下

// @ 运行:gdb [可执行程序] -c [coredump文件]
gdb edu_info_recommend_svr -c core_edu_info_recomm
// @ 查看堆栈信息:bt (backtrace)
(gdb) bt 10
#0  0x00007fa0809b6144 in __strcmp_sse42 () from /lib64/libc.so.6
#1  0x00000000004aa8f7 in Json::Value::CZString::operator< (this=<value optimized out>, other=<value optimized out>)
    at /data/home/yaaf_proj/workspace/yaaf-ng/deps/libjsoncpp/json_value.cpp:191
#2  0x00000000004abf1a in operator() (this=<value optimized out>, key=<value optimized out>)
    at /usr/lib/gcc/x86_64-redhat-linux/4.4.7/../../../../include/c++/4.4.7/bits/stl_function.h:230
#3  find (this=<value optimized out>, key=<value optimized out>) at /usr/lib/gcc/x86_64-redhat-linux/4.4.7/../../../../include/c++/4.4.7/bits/stl_tree.h:1424
#4  find (this=<value optimized out>, key=<value optimized out>) at /usr/lib/gcc/x86_64-redhat-linux/4.4.7/../../../../include/c++/4.4.7/bits/stl_map.h:659
#5  Json::Value::operator[] (this=<value optimized out>, key=<value optimized out>) at /data/home/yaaf_proj/workspace/yaaf-ng/deps/libjsoncpp/json_value.cpp:1195
#6  0x0000000000438448 in Cmp6 (CourseInfo1=..., CourseInfo2=...) at /data/home/tinshuang/work/edu_proj/edu_info_recommend_svr/worker.cpp:226
#7  0x0000000000448a73 in __unguarded_partition<__gnu_cxx::__normal_iterator<Json::Value*, std::vector<Json::Value, std::allocator<Json::Value> > >, Json::Value, bool (*)(Json::Value const&, Json::Value const&)> (__first=..., __last=..., __depth_limit=10, __comp=0x438430 <Cmp6(Json::Value const&, Json::Value const&)>)
    at /usr/lib/gcc/x86_64-redhat-linux/4.4.7/../../../../include/c++/4.4.7/bits/stl_algo.h:2230
#8  std::__introsort_loop<__gnu_cxx::__normal_iterator<Json::Value*, std::vector<Json::Value, std::allocator<Json::Value> > >, long, bool (*)(Json::Value const&, Json::Value const&)> (__first=..., __last=..., __depth_limit=10, __comp=0x438430 <Cmp6(Json::Value const&, Json::Value const&)>)
    at /usr/lib/gcc/x86_64-redhat-linux/4.4.7/../../../../include/c++/4.4.7/bits/stl_algo.h:2301
#9  0x0000000000448aea in std::__introsort_loop<__gnu_cxx::__normal_iterator<Json::Value*, std::vector<Json::Value, std::allocator<Json::Value> > >, long, bool (*)(Json::Value const&, Json::Value const&)> (__first=..., __last=..., __depth_limit=10, __comp=0x438430 <Cmp6(Json::Value const&, Json::Value const&)>)
    at /usr/lib/gcc/x86_64-redhat-linux/4.4.7/../../../../include/c++/4.4.7/bits/stl_algo.h:2302
(More stack frames follow...)

从总的堆栈信息看,很多和STL与Json.Cpp相关,最终core在了Json.Cpp调用strcmp()上面。STL和Json.Cpp都是久经考验的第三方库,怎么可能会core?所以开始时很疑惑

// @ 切换当前栈:f (frame)
(gdb) f 1
#1  0x00000000004aa8f7 in Json::Value::CZString::operator< (this=<value optimized out>, other=<value optimized out>)
    at /data/home/yaaf_proj/workspace/yaaf-ng/deps/libjsoncpp/json_value.cpp:191
191	      return strcmp( cstr_, other.cstr_ ) < 0;
// @ 打印相关的源代码:l (list)
(gdb) l
187	bool
188	Value::CZString::operator<( const CZString &other ) const
189	{
190	   if ( cstr_ )
191	      return strcmp( cstr_, other.cstr_ ) < 0;
192	   return index_ < other.index_;
193	}
// @ 查看当前栈|函数的入参:info args
(gdb) info args
this = <value optimized out>
other = <value optimized out>

可以看到,堆栈#1core在了json_value.cpp内的strcmp(cstr_, other.cstr_)里,但是打印参数this和other都是<value optimized out>。这是因为程序在编译时加了-O2优化,一些变量会存储在CPU的寄存器中,没有放入内存。所以这里我再往上层的堆栈查看

(gdb) f 2
#2  0x00000000004abf1a in operator() (this=<value optimized out>, key=<value optimized out>)
    at /usr/lib/gcc/x86_64-redhat-linux/4.4.7/../../../../include/c++/4.4.7/bits/stl_function.h:230
230	      { return __x < __y; }
(gdb) l
225	  template<typename _Tp>
226	    struct less : public binary_function<_Tp, _Tp, bool>
227	    {
228	      bool
229	      operator()(const _Tp& __x, const _Tp& __y) const
230	      { return __x < __y; }
231	    };
(gdb) info args
this = 0x3277aa0
__x = @0x7f9fbfeb2250
__y = @0x31c26b0
// @ 打印变量:p (print)
(gdb) p __x
$1 = (const Json::Value::CZString &) @0x7f9fbfeb2250: {
  cstr_ = 0x50477b "sub_bgtime",
  index_ = 0
}
(gdb) p __y
$2 = (const Json::Value::CZString &) @0x31c26b0: {
  cstr_ = 0x0,
  index_ = 0
}

可以看出,这一层堆栈core在了STL比较2个Json::Value::CZString类型变量的大小上。翻了Json.cpp的源代码,其实Json::Value::CZString只有2个变量:

{  const char * cstr_ ; int index_  }

其中cstr_表示CZString的具体字符串内容,但是可以发现,__y的cstr_=0x0,即指向NULL!(不是指向空串,空串是类似cstr_=0x50477b ""的形式)

再结合#0~#5和Json.Cpp的源代码,core的原因就比较清楚了:Json.Cpp调用[]在找"sub_bgtime"这个Key,其内部是用到std::map::find()来查找,std::map内部维护了一棵红黑树,红黑树在拿"sub_bgtime"和原有的Key(可简单看成const char*类型)做比较时,有一个Key=NULL,所以在调用strcmp("sub_bgtime",NULL)时,strcmp内部访问NULL引发了Segmentation fault段错误

所以现在问题就是为什么这个Json会有一个Key=NULL呢?从代码上看,是core在了对vector<Json::value> vec_course_list做std::sort(),以为是vec_course_list里面的Json数据有问题,所以这里从vec_course_list的堆栈内继续分析下去

(gdb) f 13
#13 0x0000000000442132 in InfoWorker::GetWillCourseListOnClient (this=0x15fb1e0, req_body=<value optimized out>)
    at /data/home/tinshuang/work/edu_proj/edu_info_recommend_svr/worker.cpp:894
// @ 设置打印选项:set print [pretty|union|static-members...] [on|off] (这里为了显示方便)
(gdb) set print union on
(gdb) set print static-members off
// @ 打印STL::vector:pvec (原生gdb对打印STL容器支持不是很好,网上这个叫dbinit_stl_views的小插件蛮好用的)
// 可以看到这个vector拥有193个元素(Json),第1个Json值存储的内存地址为value_.map_,即0x13fe450
(gdb) pvec vec_course_list 0
elem[0]: $416 = {
  value_ = {
    int_ = 20964432,
    uint_ = 20964432,
    real_ = 1.0357805635774896e-316,
    bool_ = 80,
    string_ = 0x13fe450 "\020w\r\002",
    map_ = 0x13fe450
  },
  type_ = Json::objectValue,
  allocated_ = -1,
  comments_ = 0x0
}
Vector size = 193
Vector capacity = 256
Element type = Json::Value *
// 按照其数据类型,把这个Json::Value内容打印出来。这里就可以看到这个Json::Value的各个Key了
(gdb) p *(Json::Value::ValueHolder::ObjectValues *) 0x13fe450
$419 = std::map with 18 elements = {
  [{
    cstr_ = 0x19598f0 "apply_num",
    index_ = 1
  }] = {
    value_ = {
      int_ = 1401,
      uint_ = 1401,
      real_ = 6.9218596982358641e-321,
      bool_ = 121,
      string_ = 0x579 <Address 0x579 out of bounds>,
      map_ = 0x579
    },
    type_ = Json::uintValue,
    allocated_ = -1,
    comments_ = 0x0
  },
  ....

当然这里把vector里面193个Json::Value的每个Key都搞出来工作量很大,现在还没什么好方法。所以先结合代码逻辑和程序日志,发现vector内各个Json::Value其实都是正常的,不可能存在Key=NULL的情况。

到这里,就开始怀疑是std::sort有问题了,或者是我们使用不当。看了STL这块的源代码,比较晦涩,所以Google了下"stl sort core",发现这个问题在网上已经被很多人提及到:std::sort()在排序时,如果比较函数对相等的元素返回true,会导致程序coredump

原因分析:std::sort()的排序分2种,当元素个数>16(_S_threshold)时选择快速排序,<=16个则选择插入排序(对象少时快排性能不理想)。按照快排原理,每次都是遍历所有值和一个中间值比较,小的放左边,大的放右边。从下面STL源代码可看出,std::sort()在遍历比较时,是没有边界保护的。如果比较相等的元素返回真,则在极端情况下(如所有元素相等,__pivot为最小|最大值时)会出现访问越界,导致coredump:

/// This is a helper function...
  template<typename _RandomAccessIterator, typename _Tp, typename _Compare>
    _RandomAccessIterator
    __unguarded_partition(_RandomAccessIterator __first,
              _RandomAccessIterator __last,
              _Tp __pivot, _Compare __comp)
    {
      while (true)
    {
      while (__comp(*__first, __pivot))
        ++__first;
      --__last;
      while (__comp(__pivot, *__last))
        --__last;
      if (!(__first < __last))
        return __first;
      std::iter_swap(__first, __last);
      ++__first;
    }
    }
时间: 2024-10-18 11:58:10

GDB实践:一场std::sort引发的coredump的相关文章

std::sort引发的core

#include <stdio.h> #include <vector> #include <algorithm> #include <new> struct foo_t { int size; }; class cmp_t { public: bool operator()(foo_t *a, foo_t *b) { return a->size >= b->size; } }; int main(int argc, char *argv

源码阅读笔记 - 1 MSVC2015中的std::sort

大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格式化,去掉或者展开用于条件编译或者debug检查的宏,依重要程度重新排序函数,但是不会改变命名方式(虽然MSVC的STL命名实在是我不能接受的那种),对于代码块的解释会在代码块前(上面)用注释标明. template<class _RanIt, class _Diff, class _Pr> in

std::sort 学习:一种递归分治方法

// std::sort 学习:一种递归分治方法 今天看了看 stl 的 std::sort 的代码,众所周知,这个函数是在快速排序递归太深的时候使用堆排序防止过度退化,但是今天说的不是这个.我们只看快速排序的部分. 我们一般实现快速排序大概是这样的(本王随意写了个用下标当参数的排序函数,领会意思即可). void quick_sort(int first, int last) // 某个数组的 [first, last) {  if ((last - first) > 1) {  int mi

std::sort要求strict weak ordering

strict weak ordering简单地说就是小于语义,非小于等于语义,也就是说对于相等的或者异常的元素比较应当返回false 后果很严重,在google搜一下violating strict weak ordering make std::sort crash能看到很多种后果, 经测试,当待排序元素大于16个时使用std::sort,若传入的callable违反strict weak ordering,则可能死循环也可能越界访问. 待排序元素小于等于16个不会有问题是因为std::sor

测试std::sort 和std::qsort 的性能, 修改编译器栈大小

根据effective STL中Item 46 提到, C程序员很难接受C++的STL中std::sort(定义于头文件<algorithm>)竟然比C语言的std::qsort(定义与头文件<cstdlib>中)快了670%. 最后Scot Meyer建议我们我们要使用C++的std::sort函数. 我们知道qsort 实现的排序算法是快排, 但是std::sort 实现的排序算法并不知道, 有人说这得看是哪一个STL版本了. std::sort的大部分实现的是quick so

[C/C++标准库]_[初级]_[使用std::sort排序各种类型数据]

std::sort 场景: 1. 在使用sort排序时,有时候需要对对象的某个值进行排序,比如对类对象的某个id的int类型值或者bool类型值,其实bool类型值排序研究了半天.. test_sort.cpp #include <stdlib.h> #include <string.h> #include <string> #include <vector> #include <algorithm> #include <iostream&

非常无聊——STD::sort VS 基数排序

众所周知,Std::sort()是一个非常快速的排序算法,它基于快排,但又有所修改.一般来说用它就挺快的了,代码一行,时间复杂度O(nlogn)(难道不是大叫一声"老子要排序!!"就排好了么...).我们也知道,不基于比较的排序可以达到O(n),比如说基数排序.什么,它是O(n * log(10)( max(n) ) ) 的?NO!!我们可以用sqrt(max(n))来作为进制,这样就是(N*logMax(n))=O(2*n)的了..看起来很不错, 代码量嘛....呵呵 所谓基数排序,

一场由tcp_timestamps 引发的无解追击案

案例描述:我们的合作客户(国内知名电子支付企业)反应有四台机器调用我们的接口服务,但是奇怪的是四台中有两台是通的,有两台是不通的,不通的机器也是偶尔通偶尔不通,这个问题一直断断续续困扰了他们很久,刚开始我们认为是他们系统那里参数配置不对,就没有给予太多关注,毕竟我们还有好多合作客户,却没有问题:这个问题直到有一天,他们实在扛不住,实在找不出原因了,要求我们技术人员现场去帮他们排查,才开始了一场无解破解案. 那天接手了这个问题后,开始一对一让对方网络技术人员配合联合排查,分别从正常机器,不正常机器

将三维空间的点按照座标排序(兼谈为std::sort写compare function的注意事项)

最近碰到这样一个问题:我们从文件里读入了一组三维空间的点,其中有些点的X,Y,Z座标只存在微小的差别,远小于我们后续数据处理的精度,可以认为它们是重复的.所以我们要把这些重复的点去掉.因为数据量不大,这里不准备使用划分包围盒或者建立k-d tree这样的重型武器,我们简单的把点按照其三维坐标进行排序就好. 我们准备使用STL的std::sort来做这个排序.它要求提供一个符合如下签名的比较函数: bool cmp(const Type1 &a, const Type2 &b) 怎么样写这个