有时候在线上使用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
>
class vector : protected _Vector_base<_Tp, _Alloc>
...
template
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
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
vec; vec.push_back(0x11); vec.push_back(0x22); vec.push_back(0x33); ds_vec_i
(&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
struct _List_node : public _List_node_base
{
_Tp _M_data; ///< User's data.
};
template
class _List_base { ... struct _List_impl : public _Node_alloc_type { _List_node_base _M_node; ... }; _List_impl _M_impl; template
> class list : protected _List_base