有時(shí)候在線上使用gdb調(diào)試程序core問(wèn)題時(shí),可能沒(méi)有符號(hào)文件,拿到的僅是一個(gè)內(nèi)存地址,如果這個(gè)指向的是一個(gè)STL對(duì)象,那么如何查看這個(gè)對(duì)象的內(nèi)容呢?
只需要知道STL各個(gè)容器的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),就可以查看其內(nèi)容。本文描述了SGI STL實(shí)現(xiàn)中常用容器的數(shù)據(jù)結(jié)構(gòu),以及如何在gdb中查看其內(nèi)容。
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內(nèi)有一個(gè)指針,指向?qū)嶋H的字符串位置,這個(gè)位置前面有一個(gè)_Rep
結(jié)構(gòu),其內(nèi)保存了字符串的長(zhǎng)度、可用內(nèi)存以及引用計(jì)數(shù)。當(dāng)我們拿到一個(gè)string對(duì)象的地址時(shí),可以通過(guò)以下代碼獲取相關(guān)值:
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中拿到一個(gè)string的地址時(shí),可以以下打印出該字符串及長(zhǎng)度:
(gdb) x/1a p
0x7fffffffe3a0: 0x606028
(gdb) p (char*)0x606028
$2 = 0x606028 "hello"
(gdb) x/1dg 0x606028-24
0x606010: 5
vector
眾所周知vector實(shí)現(xiàn)就是一塊連續(xù)的內(nèi)存,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
,其內(nèi)也就是3個(gè)指針,_M_start
指向首元素地址,_M_finish
指向最后一個(gè)節(jié)點(diǎn)+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()); }
可以通過(guò)代碼從一個(gè)vector對(duì)象地址輸出其信息:
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輸出一個(gè)vector中的內(nèi)容:
(gdb) p p
$3 = (void *) 0x7fffffffe380
(gdb) x/1a p
0x7fffffffe380: 0x606080
(gdb) x/3xw 0x606080
0x606080: 0x00000011 0x00000022 0x00000033
list
眾所周知list被實(shí)現(xiàn)為一個(gè)鏈表。準(zhǔn)確來(lái)說(shuō)是一個(gè)雙向鏈表。list本身是一個(gè)特殊節(jié)點(diǎn),其代表end,其指向的下一個(gè)元素才是list真正的第一個(gè)節(jié)點(diǎn):
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
,兩個(gè)指針。每一個(gè)真正的節(jié)點(diǎn)首先是包含兩個(gè)指針,然后是元素內(nèi)容(_List_node
)。
通過(guò)代碼輸出list的內(nèi)容:
#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使用的是紅黑樹實(shí)現(xiàn),實(shí)際使用的是stl_tree.h
實(shí)現(xiàn):
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));
}
所以可以看出,大部分時(shí)候(取決于_M_key_compare
) sizeof(map<xx>)=48
,主要的元素是:
_Rb_tree_color _M_color; // 節(jié)點(diǎn)顏色
_Base_ptr _M_parent; // 父節(jié)點(diǎn)
_Base_ptr _M_left; // 左節(jié)點(diǎn)
_Base_ptr _M_right; // 右節(jié)點(diǎn)
_Val _M_value_field // 同list中節(jié)點(diǎn)技巧一致,后面是實(shí)際的元素
同list中的實(shí)現(xiàn)一致,map本身作為一個(gè)節(jié)點(diǎn),其不是一個(gè)存儲(chǔ)數(shù)據(jù)的節(jié)點(diǎn),
_Rb_tree::end
iterator
end()
{ return iterator(static_cast<_Link_type>(&this->_M_impl._M_header)); }
由于節(jié)點(diǎn)值在_Rb_tree_node_base
后,所以任意時(shí)候拿到節(jié)點(diǎn)就可以偏移這個(gè)結(jié)構(gòu)體拿到節(jié)點(diǎn)值,節(jié)點(diǎn)的值是一個(gè)pair,包含了key和value。
在gdb中打印以下map的內(nèi)容:
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 真正的節(jié)點(diǎn)
0x7fffffffe388: 0x606040
(gdb) x/1xw 0x606040+32+8 # 偏移32字節(jié)是節(jié)點(diǎn)值的地址,再偏移8則是value的地址
0x606068: 0x00000bbb
(gdb) p *(char**)(0x606040+32) # 偏移32字節(jié)是string的地址
$8 = 0x606028 "abc"
或者很多時(shí)候沒(méi)有必要這么裝逼+蛋疼:
(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
完