[GDB] 使用 gdb 的巨集指令,印出 STL container (如 map) 的儲存內容
今天在用 gdb 查一個 crash 的問題,看到 backtrace 像下面這樣:
(gdb) bt #1 0x00002b9872b3ec88 in GetFileType ( szSrcFilePath=0x163243c "/Normal.dotm", pFileType=0x1b2dc70) at ./FileTypeLib.cpp:488 #2 0x00002b986e671ca8 in ffi_call_unix64 () from /opt/python/lib/python2.6/lib-dynload/_ctypes.so #3 0x00002b986e671af4 in ffi_call () from /opt/python/lib/python2.6/lib-dynload/_ctypes.so #4 0x00002b986e66c2f4 in _CallProc () from /opt/python/lib/python2.6/lib-dynload/_ctypes.so #5 0x00002b986e664118 in ?? () from /opt/python/lib/python2.6/lib-dynload/_ctypes.so #6 0x0000000000417d2d in PyObject_Call () #7 0x000000000048fc7a in PyEval_EvalFrameEx () ...... #30 0x00000036d120683d in start_thread () from /lib64/libpthread.so.0 #31 0x00000036d06d4fcd in clone () from /lib64/libc.so.6
嗯… 因為有程式的原始碼,所以可以去查一下 FileTypeLib.cpp 在寫什麼,
懷疑是一個 STL map 的變數 (本例中是 m_mapType) 儲存的值有問題…
用 print 來印看看,基本上看不出什麼,
只能從 _M_node_count 來猜這個 map 裡面有 3 組資料,但內容完全沒提示:
(gdb) print FileTypeManager::s_pInstance->m_mapType $3 = {_M_t = { _M_impl = {<std::allocator<std::_Rb_tree_node<std::pair<const EnumType, PairData> > >> = {<__gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<const EnumType, PairData> > >> = {<No data fields>}, <No data fields>}, _M_key_compare = {<std::binary_function<EnumType,EnumType,bool>> = {<No data fields>}, <No data fields>}, _M_header = {_M_color = std::_S_red, _M_parent = 0x2b988032f730, _M_left = 0x24c3990, _M_right = 0x2b988037fa80}, _M_node_count = 3}}}
要怎麼查 STL map 儲存的值呢?
找了一下,有人說新版的 gdb 的 print 是可以直接印出 STL map 的儲存內容的,
不過我們專案程式跑的平台的 gdb 沒有那麼新…
幸好網路上也有好心人士提供了給 gdb 使用的巨集,
讓我們可以很方便地檢視 STL container 儲存的資料 (如 map, list, vector 等等)~
參考資料:stackoverflow: Inspecting standard container (std::map) contents with gdb
1. 下載 stl-views.gdb 巨集檔
下載這個巨集檔案 stl-views.gdb 到 gdb 所在的機器,
可以先瞄一下內容,開頭的部分有列出可以用的巨集指令,
像是 pvector 可以印出 vector 的內容,而 map 的內容可以用 pmap 來印出來:
# The following STL containers are currently supported: # # std::vector<T> -- via pvector command # std::list<T> -- via plist command # std::map<T,T> -- via pmap command # std::multimap<T,T> -- via pmap command # std::set<T> -- via pset command # std::multiset<T> -- via pset command # std::deque<T> -- via pdequeue command # std::stack<T> -- via pstack command # std::queue<T> -- via pqueue command # std::priority_queue<T> -- via ppqueue command # std::bitset<n> -- via pbitset command # std::string -- via pstring command # std::widestring -- via pwstring command
2. 使用 stl-views.gdb 巨集指令
首先用 gdb 開啟 crash dump,接著執行 source 指令,將巨集檔案載入:
(gdb) source stl-views.gdb
用 pmap 指令來印出 m_mapType 的內容看看… 在 pmap 後面要給的是 map 的位址:
(gdb) pmap &(FileTypeManager::s_pInstance->m_mapType) Map type = MAP_TYPE_TO_PAIR_DATA * Use pmap <variable_name> <left_element_type> <right_element_type> to see the elements in the map. Map size = 3
gdb 說這個 map 的資料型態是 MAP_TYPE_TO_PAIR_DATA,
這個 MAP_TYPE_TO_PAIR_DATA 事實上是一個 std::map<EnumType, PairData> 的 typedef,
因此 gdb 提示我們要把 map 的 key/value 的資料型態明確指定上去,
修正過後的指令如下:
(gdb) pmap &(FileTypeManager::s_pInstance->m_mapType) EnumType PairData elem[0]->left: $5 = CONTEXT_FILE elem[0]->right: $6 = { nBase = 167460362554190744, nDiff = 1 } elem[1]->left: $7 = CONTEXT_URL elem[1]->right: $8 = { nBase = -9207449046900266088, nDiff = 2 } elem[2]->left: $9 = CONTEXT_MEMORY elem[2]->right: $10 = { nBase = -9208743309524860926, nDiff = 3 } Map size = 3
可以看到 pmap 指令成功地將 map 的內容印出來了,
像是第一個元素 elem[0] 的 key 是 CONTEXT_FILE,
對應的值是一個 struct,裡面有 nBase 和 nDiff 兩個成員~
用這個方法就能很快檢視 STL map 的儲存資料,進而找出造成 crash 的錯誤的值囉~