編寫服務器端程序,很容易遇到Crash問題,比較幸運的是Linux提供了core file,保留了Crash的現場。有時候,根據當前的調用棧,並且打印出當前棧的變量就可以分析出crash的原因,但是,有時候看到調用棧卻束手無策。下面就介紹自己通過GDB的幾個命令的結合,發現一個crash的原因的過程。
下面讓我們一起進入現場,來逐步發現其中的原因。
首先,還是運行gdb 命令,gdb wbxgs core.5797,來看看現場。
[root@hfgs126 bin]# gdb wbxgs_crash core.5797
GNU gdb Red Hat Linux (6.3.0.0-1.132.EL4rh)
……
#0 0x00000038e8d70540 in strlen () from /lib64/tls/libc.so.6
(gdb) bt
#0 0x00000038e8d70540 in strlen () from /lib64/tls/libc.so.6
#1 0x000000000057cfc0 in T120_Trace::Text_Formator::advance (this=0x7e800a70, lpsz=0x1 <Address 0x1 out of bounds>)
at ./t120trace.cpp:1464
#2 0x000000000057ceb1 in T120_Trace::Text_Formator::operator<< (this=0x7e800a70, lpsz=0x1 <Address 0x1 out of bounds>)
at ./t120trace.cpp:1411
#3 0x0000000000407927 in ~func_tracer (this=0x7e804bd0) at ../h/t120trace.h:381
#4 0x00000000004432fd in CGSSocketServer::readHeader (this=0x8e4130, socketfd=1088,
buf=0x7e806cc0 "GET /detectService?cmd=selfcheck HTTP/1.1/r/nConnection: Close/r/nHost: 10.224.122.94/r/n/r/n", bufsize=1024)
at mgr/gssocketserver.cpp:337
#5 0x0000000000443981 in CGSSocketServer::handle (this=0x8e4130, socketfd=1088, strRet=@0x7e807190) at mgr/gssocketserver.cpp:424
#6 0x0000000000442f5e in CGSSocketServer::readThread (pArg=0x9ae9c0) at mgr/gssocketserver.cpp:304
#7 0x00000038e980610a in start_thread () from /lib64/tls/libpthread.so.0
#8 0x00000038e8dc68b3 in clone () from /lib64/tls/libc.so.6
#9 0x0000000000000000 in ?? ()
通過這個調用棧,可以看出,程序crash在打log的時候。雖然遇到過類似的crash,但是,當時的原因是有死循環,通過review code,沒有發現死循環。但是當前的調用棧對於分析Crash的原因是一點用也沒有,如果分析具體的原因呢?會不會是其他得線程出現錯誤導致程序Crash在這個線程呢?為了找到深一層的原因,嘗試着通過GDB的一些關於線程的命令,來看看其他的線程是否有問題。於是,使用info threads,查看了一下當時線程的情況。
(gdb) info threads
21 process 5797 0x00000038e8d7186d in memset () from /lib64/tls/libc.so.6
20 process 5839 0x00000038e8dc6c8c in epoll_wait () from /lib64/tls/libc.so.6
19 process 5842 0x00000038e8d8f7d5 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
18 process 5845 0x00000038e8d8f7d5 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
17 process 5846 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
16 process 5847 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
15 process 5848 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
14 process 5849 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
13 process 5850 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
12 process 5852 0x00000038e8dbf946 in __select_nocancel () from /lib64/tls/libc.so.6
11 process 5854 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
10 process 5856 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
9 process 5857 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
8 process 5858 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
7 process 5859 0x00000038e8d8f7d5 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
6 process 5861 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
5 process 5862 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
4 process 5863 0x00000038e8d8f7d5 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
3 process 5864 0x00000038e8d8f7d5 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
2 process 5883 0x00000038e8d8f7d5 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
* 1 process 5853 0x00000038e8d70540 in strlen () from /lib64/tls/libc.so.6
對於線程如果停止在sleep或者wait的情況,都是正常的,但是我們看到thread 21有些異常,程序停止在memset,不管是否有問題,都需要看看這樣的線程具體有沒有出錯。
於是通過命令thread 21,進入到thread 21的調用棧。
(gdb) thread 21
[Switching to thread 21 (process 5797)]#0 0x00000038e8d7186d in memset () from /lib64/tls/libc.so.6
(gdb) bt
#0 0x00000038e8d7186d in memset () from /lib64/tls/libc.so.6
#1 0x000000000049da0d in CGSPduFactory::streamStringFrom (is=@0x7fff9b436360, strFrom=@0x2aaaec979760) at common/pdu/gspdu.cpp:422
#2 0x00000000004d1f25 in CGSOthShardUserRspPdu::streamFrom (this=0x2aaaec951650, is=@0x7fff9b436360) at common/pdu/pdugs.cpp:2707
#3 0x000000000049cb2d in CGSPduFactory::derivePdu (is=@0x7fff9b436360, ulPDULen=30506) at common/pdu/gspdu.cpp:79
#4 0x000000000049c78e in CGSPduFactory::streamPduFrom (pDataPacket=0x2aaaeca31d70) at common/pdu/gspdu.cpp:35
#5 0x0000000000449681 in CGSWDMSManager::on_wdms_message_indication (this=0x8e3680, msg=0x2aaae9894360)
at mgr/gswdmsmanager.cpp:344
……
#18 0x0000000000407733 in main (argc=1, argv=0x7fff9b44ac98) at gsmain.cpp:118
(gdb) f 3
#3 0x000000000049cb2d in CGSPduFactory::derivePdu (is=@0x7fff9b436360, ulPDULen=30506) at common/pdu/gspdu.cpp:79
79 common/pdu/gspdu.cpp: No such file or directory.
in common/pdu/gspdu.cpp
使用命令 i locals,打印所有的變量的值。
(gdb) i locals
pPdu = (CBasePdu *) 0x2aaaec951650
pPduHeader = (CPduHeader *) 0x2aaaea1c4190
ulPduType = 50
到現在還沒有看出有什么明顯的異常,然后再把PDU的頭打印出來如下:
(gdb) p *pPduHeader
$1 = {m_ulHeadLen = 61, m_ulVersion = 2080000, m_ulPduType = 50, m_ulSrcSvrType = WEBEX_CONNECT_GS, m_strSrcSvrAddr = {
static npos = 18446744073709551615,
_M_dataplus = {<std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>},
_M_p = 0x2aaaeca52a68 "10.224.95.109:9900"}}, m_strSubject = {static npos = 18446744073709551615,
_M_dataplus = {<std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>},
_M_p = 0x2aaaec929b28 "qawin.qazone.GS"}}, m_ulSequence = 0}
從藍色的字的部分可以看出,這個PDU是從10.224.95.109這台server上發過來的。
當時QA測試的環境,都是10.224.122開頭的IP的server,怎么會有這個IP的PDU,於是,詢問QA,發現10.224.95.109這個server是其他DataCenter的Server,而且還是老的版本,由於當前測試環境的版本刪除了兩個PDU,同時又增加了四個PDU,導致了老的PDU發來的時候,新的版本的把它當作新的PUD解析,從而導致不能正確解析,最終導致了解析出來的長度不對。可以通過f 1命令進入第一級調用棧查看所有的局部變量。
(gdb) f 1
#1 0x000000000049da0d in CGSPduFactory::streamStringFrom (is=@0x7fff9b436360, strFrom=@0x2aaaec979760) at common/pdu/gspdu.cpp:422
422 in common/pdu/gspdu.cpp
(gdb) i locals
strTmp = 0x2aaaf1c00010 ""
iRet = 0
ulLen = 1179995975
可以看出解析出來的長度是一個很大的值1179995975,而線程21正式停止在分配內存之后,使用memset時,停止在那里。從Log中也可以看到,thread 21也一致阻塞在這里,而且沒有再繼續運行。
由於當時有兩台server crash,通過查看另外一台server的core file,發現另外一台server也是和本台server一樣的調用棧。在QA更新了10.224.95.109的版本后,crash沒有再出現。
通過這個實例,可以看出,當server出現crash的時候,雖然當前的調用棧可能沒有什么價值,但是,通過分析所有線程的調用棧,還是可能分析出蛛絲馬跡的,從而對於解決Crash的問題帶來幫助。
通過這個問題可以得到一個教訓,在修改Server之間的接口時,一定要考慮到和老版本的兼容問題,即使這個PDU可能永遠也不會使用,仍然需要保留,因為Production上,是先上GSB,然后再上Primary,肯定會存在兩個版本同時運行的情況。如果出現刪除或者改變PDU順序的情況,可能會導致整個系統不能工作。
希望本文章,對解決Crash問題和避免類似的Crash問題有一定的借鑒作用。