頭文件和.a(庫文件不匹配)導致虛函數錯位,進而導致的bug
該bug的快速定位技巧
bt查看函數調用堆棧. 如果堆棧的順序錯亂, 則可能是虛函數表錯亂導致.
本例中, GetTPLContent 的上層很明顯不應該是 DealInput, 因爲DealInput沒有調用GetTPLContent
故障回顧:
某日遇到一個奇葩bug, 進程core, 查看堆棧, core的位置沒有任何異常, 在一個不可能出問題的地方出錯了. 只根據core文件在生產環境無法排查.
在開發機生把cgi編譯成可執行文件, 用gdb跟蹤, 發現在 單步跟進 BeforeProcess() 函數時, 卻意外的跳到了 GetTPLContent()函數接下來就是core了.
中斷現場如下:
rogram received signal SIGSEGV, Segmentation fault.
[Switching to Thread -1211028832 (LWP 10060)]
0xb7ef2422 in std::string::compare () from /usr/lib/libstdc++.so.6
(gdb) bt
#0 0xb7ef2422 in std::string::compare () from /usr/lib/libstdc++.so.6
#1 0x08055e5c in std::operator==<char, std::char_traits<char>, std::allocator<char> > (__lhs=@0xbfcd60ec, __rhs=0x82a080e "json")
at /usr/include/c++/4.1.0/bits/basic_string.h:2163
#2 0x0804eea0 in CMyCGI::GetTPLContent (this=0xbfcd505c) at src/mb_get_app_friends.cpp:165
#3 0x08260eb0 in COpenAPIBase::DealInput (this=0xbfcd4128,
params=@0xbfcd505c) at src/openapi_base.cpp:239
#4 0x0821b9af in CCGIEx::Run (this=0xbfcd4128, pszTemplateFile=0x0) at cgiex.cpp:248
#5 0x08051205 in main () at src/mb_get_app_friends.cpp:386
把棧幀定位到COpenAPIBase::DealInput上
(gdb) frame 3
#3 0x08260eb0 in COpenAPIBase::DealInput (this=0xbfcd4128,
params=@0xbfcd505c) at src/openapi_base.cpp:239
239 BeforeProcess(params);
(gdb) l
234 //把參數轉化成值
235 TransParams(params);
236 //填充出authinfo
237 FillOpenAPIInfo();
238
239 BeforeProcess(params);
240 m_mod_ret = Process(params);
241 AfterProcess(params);
242 return true;
243 }
查看彙編
(gdb) disassemble
Dump of assembler code for function _ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE:
0x08260e76 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+0>: push %ebp
0x08260e77 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+1>: mov %esp,%ebp
0x08260e79 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+3>: push %esi
0x08260e7a <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+4>: push %ebx
0x08260e7b <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+5>: sub $0x10,%esp
0x08260e7e <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+8>: mov 0x8(%ebp),%ebx
0x08260e81 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+11>: mov 0xc(%ebp),%esi
0x08260e84 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+14>: mov (%ebx),%eax
0x08260e86 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+16>: mov %esi,0x4(%esp)
0x08260e8a <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+20>: mov %ebx,(%esp)
0x08260e8d <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+23>: call *0x10(%eax) // 虛函數 GetDftParams
0x08260e90 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+26>: mov %esi,0x4(%esp)
0x08260e94 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+30>: mov %ebx,(%esp)
0x08260e97 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+33>: call 0x8260684 <_ZN12COpenAPIBase11TransParamsERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE>
0x08260e9c <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+38>: mov %ebx,(%esp)
0x08260e9f <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+41>: call 0x825fbcc <_ZN12COpenAPIBase15FillOpenAPIInfoEv>
0x08260ea4 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+46>: mov (%ebx),%eax
0x08260ea6 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+48>: mov %esi,0x4(%esp)
0x08260eaa <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+52>: mov %ebx,(%esp)
0x08260ead <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+55>: call *0x18(%eax) // 虛函數 BeforeProcess
0x08260eb0 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+58>: mov (%ebx),%eax
0x08260eb2 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+60>: mov %esi,0x4(%esp)
0x08260eb6 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+64>: mov %ebx,(%esp)
0x08260eb9 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+67>: call *0x20(%eax) // 虛函數 Process
0x08260ebc <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+70>: mov %eax,0xf94(%ebx)
0x08260ec2 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+76>: mov (%ebx),%eax
0x08260ec4 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+78>: mov %esi,0x4(%esp)
0x08260ec8 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+82>: mov %ebx,(%esp)
0x08260ecb <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+85>: call *0x1c(%eax) // 虛函數 AfterProcess
0x08260ece <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+88>: mov $0x1,%eax
0x08260ed3 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+93>: add $0x10,%esp
0x08260ed6 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+96>: pop %ebx
0x08260ed7 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+97>: pop %esi
0x08260ed8 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+98>: pop %ebp
0x08260ed9 <_ZN12COpenAPIBase9DealInputERSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE+99>: ret
這個時候, 如果嗅覺敏銳就已經可以看到問題了, TransParams 在頭文件裏是個虛函數, 理論上是無法在反彙編裏直接看到函數符號的 (因爲虛函數的具體的地址, 只有在運行時才能決定)
進一步驗證
單步執行時, 當執行到BeforeProcess時, s跳進去, 代碼卻跳到 GetTPLContent, 因此嚴重懷疑是 我使用的頭文件 和 .a庫不匹配導致.
頭文件更新了, .a庫卻沒有更新, 如果頭文件中新增虛函數, 將導致虛函數表中各個虛函數的位置發生改變, 而.a卻使用舊的虛函數表!
重新編譯.a庫後bug消失.
這個bug讓我想起了以前在windows環境下遭遇的著名的"dll地獄"bug, 都是由於虛函數表錯位導致的.