有时候在线上使用gdb调试程序core问题时,可能没有符号文件,拿到的仅是一个内存地址,如果这个指向的是一个STL对象,那么如何查看这个对象的内容呢?
只需要知道STL各个容器的数据结构实现,就可以查看其内容。本文描述了SGI STL实现中常用容器的数据结构,以及如何在gdb中查看其内容。
string
string,即basic_string
bits/basic_string.h
:
即,string内有一个指针,指向实际的字符串位置,这个位置前面有一个_Rep
结构,其内保存了字符串的长度、可用内存以及引用计数。当我们拿到一个string对象的地址时,可以通过以下代码获取相关值:
在gdb中拿到一个string的地址时,可以以下打印出该字符串及长度:
1
2
3
4
5
6
| (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
。
可以看出sizeof(vector<xxx>)=24
,其内也就是3个指针,_M_start
指向首元素地址,_M_finish
指向最后一个节点+1,_M_end_of_storage
是可用空间最后的位置。
可以通过代码从一个vector对象地址输出其信息:
使用gdb输出一个vector中的内容:
1
2
3
4
5
6
| (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
所以sizeof(list<xx>)=16
,两个指针。每一个真正的节点首先是包含两个指针,然后是元素内容(_List_node
)。
通过代码输出list的内容:
在gdb中可以以下方式遍历该list:
1
2
3
4
5
6
7
8
9
10
| (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
bits/stl_tree.h
所以可以看出,大部分时候(取决于_M_key_compare
) sizeof(map<xx>)=48
,主要的元素是:
同list中的实现一致,map本身作为一个节点,其不是一个存储数据的节点,
_Rb_tree::end
由于节点值在_Rb_tree_node_base
后,所以任意时候拿到节点就可以偏移这个结构体拿到节点值,节点的值是一个pair,包含了key和value。
在gdb中打印以下map的内容:
1
2
3
4
5
6
7
8
| (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"
|
或者很多时候没有必要这么装逼+蛋疼:
1
2
3
4
| (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
|
完