IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    基于内存查看STL常用容器内容

    Kevin Lynx发表于 2014-12-03 00:00:00
    love 0

    有时候在线上使用gdb调试程序core问题时,可能没有符号文件,拿到的仅是一个内存地址,如果这个指向的是一个STL对象,那么如何查看这个对象的内容呢?

    只需要知道STL各个容器的数据结构实现,就可以查看其内容。本文描述了SGI STL实现中常用容器的数据结构,以及如何在gdb中查看其内容。

    string

    string,即basic_string bits/basic_string.h:

    mutable _Alloc_hider  _M_dataplus;
        ... 
          const _CharT*
          c_str() const
          { return _M_data(); }
        ...    
          _CharT*
          _M_data() const 
          { return  _M_dataplus._M_p; }
    
        ...
          struct _Alloc_hider : _Alloc
          {
        _Alloc_hider(_CharT* __dat, const _Alloc& __a)
        : _Alloc(__a), _M_p(__dat) { }
    
        _CharT* _M_p; // The actual data.
          };
       
          size_type
          length() const
          { return _M_rep()->_M_length; }
    
          _Rep*
          _M_rep() const
          { return &((reinterpret_cast<_Rep*> (_M_data()))[-1]); }
    
          ...
           struct _Rep_base
          {
        size_type       _M_length;
        size_type       _M_capacity;
        _Atomic_word        _M_refcount;
          };
    
          struct _Rep : _Rep_base

    即,string内有一个指针,指向实际的字符串位置,这个位置前面有一个_Rep结构,其内保存了字符串的长度、可用内存以及引用计数。当我们拿到一个string对象的地址时,可以通过以下代码获取相关值:

    void ds_str_i(void *p) {
            char **raw = (char**)p;
            char *s = *raw;
            size_t len = *(size_t*)(s - sizeof(size_t) * 3);
            printf("str: %s (%zd)\n", s, len);
        }
    
        size_t ds_str() {
            std::string s = "hello";
            ds_str_i(&s);
            return s.size();
        }

    在gdb中拿到一个string的地址时,可以以下打印出该字符串及长度:

    (gdb) x/1a p
    0x7fffffffe3a0: 0x606028
    (gdb) p (char*)0x606028
    $2 = 0x606028 "hello"
    (gdb) x/1dg 0x606028-24
    0x606010:       5
    

    vector

    众所周知vector实现就是一块连续的内存,bits/stl_vector.h。

    template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
        class vector : protected _Vector_base<_Tp, _Alloc>
    
        ...
        template<typename _Tp, typename _Alloc>
        struct _Vector_base
        {
          typedef typename _Alloc::template rebind<_Tp>::other _Tp_alloc_type;
    
          struct _Vector_impl
          : public _Tp_alloc_type
          {
        _Tp*           _M_start;
        _Tp*           _M_finish;
        _Tp*           _M_end_of_storage;
        _Vector_impl(_Tp_alloc_type const& __a)
        : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0)
        { }
          };
    
    
          _Vector_impl _M_impl;

    可以看出sizeof(vector)=24,其内也就是3个指针,_M_start指向首元素地址,_M_finish指向最后一个节点+1,_M_end_of_storage是可用空间最后的位置。

    iterator
          end()
          { return iterator (this->_M_impl._M_finish); }
          const_iterator
          ...
          begin() const
          { return const_iterator (this->_M_impl._M_start); }
          ...
          size_type
          capacity() const
          { return size_type(const_iterator(this->_M_impl._M_end_of_storage)
                 - begin()); }

    可以通过代码从一个vector对象地址输出其信息:

    template <typename T>
        void ds_vec_i(void *p) {
            T *start = *(T**)p;
            T *finish = *(T**)((char*)p + sizeof(void*));
            T *end_storage = *(T**)((char*)p + 2 * sizeof(void*));
            printf("vec size: %ld, avaiable size: %ld\n", finish - start, end_storage - start); 
        }
    
        size_t ds_vec() {
            std::vector<int> vec;
            vec.push_back(0x11);
            vec.push_back(0x22);
            vec.push_back(0x33);
            ds_vec_i<int>(&vec);
            return vec.size();
        }

    使用gdb输出一个vector中的内容:

    (gdb) p p
    $3 = (void *) 0x7fffffffe380
    (gdb) x/1a p
    0x7fffffffe380: 0x606080
    (gdb) x/3xw 0x606080
    0x606080:       0x00000011      0x00000022      0x00000033
    

    list

    众所周知list被实现为一个链表。准确来说是一个双向链表。list本身是一个特殊节点,其代表end,其指向的下一个元素才是list真正的第一个节点:

    bits/stl_list.h

    bool
          empty() const
          { return this->_M_impl._M_node._M_next == &this->_M_impl._M_node; }
    
          const_iterator
          begin() const
          { return const_iterator(this->_M_impl._M_node._M_next); }
    
          iterator
          end()
          { return iterator(&this->_M_impl._M_node); }
    
          ...
    
        struct _List_node_base
        {
            _List_node_base* _M_next;   ///< Self-explanatory
            _List_node_base* _M_prev;   ///< Self-explanatory
            ...
        };
             
        template<typename _Tp>
        struct _List_node : public _List_node_base
        {
          _Tp _M_data;                ///< User's data.
        };
          
        template<typename _Tp, typename _Alloc>
        class _List_base
        {
            ...
          struct _List_impl
          : public _Node_alloc_type
          {
        _List_node_base _M_node;
            ...
          };
    
          _List_impl _M_impl;
    
              
        template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
        class list : protected _List_base<_Tp, _Alloc>

    所以sizeof(list)=16,两个指针。每一个真正的节点首先是包含两个指针,然后是元素内容(_List_node)。

    通过代码输出list的内容:

    #define NEXT(ptr, T) do { \
            void *n = *(char**)ptr; \
            T val = *(T*)((char**)ptr + 2); \
            printf("list item %p val: 0x%x\n", ptr, val); \
            ptr = n; \
        } while (0)
    
        template <typename T>
        void ds_list_i(void *p) {
            void *ptr = *(char**)p;
    
            NEXT(ptr, T);
            NEXT(ptr, T);
            NEXT(ptr, T);
        }
    
        size_t ds_list() {
            std::list<int> lst;
            lst.push_back(0x11);
            lst.push_back(0x22);
            lst.push_back(0x33);
            ds_list_i<int>(&lst);
            return lst.size();
        }

    在gdb中可以以下方式遍历该list:

    (gdb) p p
    $4 = (void *) 0x7fffffffe390
    (gdb) x/1a p
    0x7fffffffe390: 0x606080
    (gdb) x/1xw 0x606080+16         # 元素1 
    0x606090:       0x00000011
    (gdb) x/1a 0x606080
    0x606080:       0x6060a0
    (gdb) x/1xw 0x6060a0+16         # 元素2
    0x6060b0:       0x00000022
    

    map

    map使用的是红黑树实现,实际使用的是stl_tree.h实现:

    bits/stl_map.h

    typedef _Rb_tree<key_type, value_type, _Select1st<value_type>,
                   key_compare, _Pair_alloc_type> _Rep_type;
        ...
         _Rep_type _M_t;
        ... 
    
          iterator
          begin()
          { return _M_t.begin(); }

    bits/stl_tree.h

    struct _Rb_tree_node_base
          {
            typedef _Rb_tree_node_base* _Base_ptr;
            typedef const _Rb_tree_node_base* _Const_Base_ptr;
    
            _Rb_tree_color  _M_color;
            _Base_ptr       _M_parent;
            _Base_ptr       _M_left;
            _Base_ptr       _M_right;
            
            ...
          };
    
        template<typename _Val>
        struct _Rb_tree_node : public _Rb_tree_node_base
        {
          typedef _Rb_tree_node<_Val>* _Link_type;
          _Val _M_value_field;
        };
    
    
        template<typename _Key_compare,
               bool _Is_pod_comparator = std::__is_pod<_Key_compare>::__value>
            struct _Rb_tree_impl : public _Node_allocator
            {
          _Key_compare      _M_key_compare;
          _Rb_tree_node_base    _M_header;
          size_type         _M_node_count; // Keeps track of size of tree.
          ...
            }
        
        _Rb_tree_impl<_Compare> _M_impl;
        ...
    
          iterator
          begin()
          {
        return iterator(static_cast<_Link_type>
                (this->_M_impl._M_header._M_left));
          }

    所以可以看出,大部分时候(取决于_M_key_compare) sizeof(map)=48,主要的元素是:

    _Rb_tree_color  _M_color; // 节点颜色
            _Base_ptr       _M_parent; // 父节点
            _Base_ptr       _M_left; // 左节点
            _Base_ptr       _M_right; // 右节点
            _Val            _M_value_field // 同list中节点技巧一致,后面是实际的元素

    同list中的实现一致,map本身作为一个节点,其不是一个存储数据的节点,

    _Rb_tree::end

    iterator
          end()
          { return iterator(static_cast<_Link_type>(&this->_M_impl._M_header)); }

    由于节点值在_Rb_tree_node_base后,所以任意时候拿到节点就可以偏移这个结构体拿到节点值,节点的值是一个pair,包含了key和value。

    在gdb中打印以下map的内容:

    size_t ds_map() {
            std::map<std::string, int> imap;
            imap["abc"] = 0xbbb;
            return imap.size();
        }
    (gdb) p/x &imap;
    $7 = 0x7fffffffe370
    (gdb) x/1a (char*)&imap;+24       # _M_left 真正的节点
    0x7fffffffe388: 0x606040          
    (gdb) x/1xw 0x606040+32+8        # 偏移32字节是节点值的地址,再偏移8则是value的地址
    0x606068:       0x00000bbb
    (gdb) p *(char**)(0x606040+32)   # 偏移32字节是string的地址
    $8 = 0x606028 "abc"
    

    或者很多时候没有必要这么装逼+蛋疼:

    (gdb) p *(char**)(imap._M_t._M_impl._M_header._M_left+1)
    $9 = 0x606028 "abc"
    (gdb) x/1xw (char*)(imap._M_t._M_impl._M_header._M_left+1)+8
    0x606068:       0x00000bbb
    

    完

    原文地址: http://codemacro.com/2014/12/03/gdb_stl/
    written by Kevin Lynx posted at http://codemacro.com



沪ICP备19023445号-2号
友情链接