首页 > 代码库 > 基于内存查看STL常用容器内容
基于内存查看STL常用容器内容
有时候在线上使用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<xxx>)=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<xx>)=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<xx>)=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
完
基于内存查看STL常用容器内容