一個new失敗問題的查找過程

一個new失敗問題的查找過程


   在測試部發現一個問題,整個系統跑一陣後就有daemon程序崩潰,雖不是必現,但是一天還是可以出現好幾次,導致性能測試無法繼續下去,看core的信息是new失敗了,具體堆棧如下:

  (gdb) bt
  #0  0x2acd25c1 in kill () from /lib/libc.so.6
  #1  0x2adfc58d in pthread_kill () from /lib/libpthread.so.0
  #2  0x2adfc90b in raise () from /lib/libpthread.so.0
  #3  0x2acd2364 in raise () from /lib/libc.so.6
  #4  0x2acd389b in abort () from /lib/libc.so.6
  #5  0x2ac57b57 in __cxa_call_unexpected () from /usr/lib/libstdc++.so.5
  #6  0x2ac57ba4 in std::terminate() () from /usr/lib/libstdc++.so.5
  #7  0x2ac57d16 in __cxa_throw () from /usr/lib/libstdc++.so.5
  #8  0x2ac57f02 in operator new(unsigned) () from /usr/lib/libstdc++.so.5
  #9  0x2ac57fef in operator new[](unsigned) () from /usr/lib/libstdc++.so.5
  #10 0x2abbfe43 in NSlab::alloc_buf(unsigned*) (pSize=0x7ffff300) at Nslab.cpp:199

  儘管new失敗的情況是會有發生,但是在我們的整個系統裏面都是不處理這種情況的,我們大部分的內存都是定好的,什麼樣的平臺型號能夠支持多少併發連接,這些都是預算好的,是不會有new失敗的情況出現的。

  一開始懷疑該程序有內存泄漏,可是看整個core文件只有100多M,應該沒有發生內存泄漏的可能。另外一次發生可能還是偶然,在繼續跑的過程中發現其他程序也會崩潰,也是因爲new失敗了,不可能其他的程序都有可能內存泄漏,很多都是比較穩定的程序了,這個可能性不大。

  第二步開始懷疑係統是否真的沒有內存了?捕獲系統的SIGABRT信號,在信號處理函數裏面把系統的狀態全部打印了,包括:ps、top、free、/proc/meminfo,/proc/slabinfo等。然而崩潰的時候還是隻看到該進程只佔用了100多M的內存,而此時系統free的內存有2G之多,其餘的信息也可證明系統內存絕對充足。看來不是系統內存引起的原因,如果是的話,問題也就不用再繼續查找了,在有內存的時候也失敗這個就一定查下去了。

  第三步開始懷疑係統,new是標準庫提供的,而此時我們的內核版本從2.4.32遷移到2.4.35.4,是否有什麼東西不匹配造成的?download了一個stdc++庫,new的實現其實調用的就是C庫裏面的malloc,在GNU上下了一個匹配版本的C庫,在malloc裏面打印了一些日誌,想用DEBUG版本的c庫開查找下問題,C庫的編譯過程確實挺複雜麻煩,編完後就準備放到系統上,爲了不直接覆蓋之前的libc庫,我使用了mv把之前的libc改了個名字,誰知libc一修改後,所有的命令都無法使用,都是找不到libc庫,唯一可用的命令好像只剩下一個cd了,連dd居然也倚賴libc庫。無奈只好掛從盤,好不容易把libc拷貝過去了,好了,大鬆一口氣,重啓,然而換了libc後的系統就是無法啓動。折騰了一天,人被折騰到暈了,就是沒辦法讓它起來。

  第四步,既然換libc暫時行不通,那就換方向。malloc的實現是調用了操作系統的brk來實現的,難道這裏面有什麼貓膩?看了一下sys_brk的代碼,裏面會返回失敗的點還真多,反正修改內核代碼和換內核已經是輕車熟路,於是在每個返回點都打印了日誌,換內核。終於有收穫了,發現是在sys_brk裏面調用do_brk()的時候失敗了,再詳細跟蹤下去,發現do_brk()裏面如下語句導致的返回ENOMEM了:

     /* Check against address space limits *after* clearing old maps... */

      if ((mm->total_vm << PAGE_SHIFT) + len  > current->rlim[RLIMIT_AS].rlim_cur) 
          return -ENOMEM;

  這條語句表示該進程分配的內存已經超過了能夠分配的最大內存了,current->rlim[RLIMIT_AS].rlim_cur的值打印出來是134217728,也就是128MB,此時再回頭看看之前的core文件大小,果然不大不小,正好是134217728字節。這個值是可以通過setrlimit,取參數RLIMIT_AS來設置的,再看程序代碼,只有設置過RLIMIT_CORE的一些屬性,沒有設置過RLIMIT_AS的屬性。那又是誰設置的?到了這裏測試過可以再次serlimit把RLIMIT_AS屬性設置爲4G即可,但是如果問題的根源沒有找到,無法知道是否會有潛在問題。

  第五步,開始找是誰設置了RLIMIT_AS屬性?把整個系統的代碼搜索了一遍RLIMIT_AS和setrlimit,都沒有發現。難道是程序運行中被修改?爲了驗證,在程序啓動點和SIGABRT信號處理函數裏面都通過getrlimit取RLIMIT_AS的屬性並打印,手工運行了一下,發現在啓動的時候打印的值是4294967296,即4G,但是如果程序崩潰了,打印的值就是128MB,無語。爲了驗證是否有人在中途修改了此值,於是在sys_brk裏面任何分配內存,就把current的進程名和該進程的RLIMIT_AS值打印,想知道什麼時候RLIMIT_AS的值會被修改。幾次實驗下來,卻又發現與推論不符合的地方,這個值一直沒有被修改,只是程序啓動的時候有時候是4G,而有時候卻就是128M,碰到128M的時候一跑壓力就會new失敗了,再次陷入無語。

  第六階段,在看着上面所做的工作程序打印出來的日誌,陷入無聊。同時也在不停的重複着new失敗的現象,因爲我們發現修改一些系統參數後,new失敗的可能性提高到了50%以上。在無聊的盯着這些日誌看了好久之後,猛然靈光一閃,發現如果是從web操作頁面上點擊“啓用”來啓動程序的,RLIMIT_AS的值就是128M,然而如果是自己在shell控制檯裏面敲命令啓用程序的,RLIMIT_AS的值就是4G,無異於發現新大陸,莫非是CGI自己設置了RLIMIT_AS爲128M,然後調用execl啓用的程序也是128M,該屬性是從父進程繼承的。驗證一下,果然,CGI啓用程序一定會因new失敗而崩潰,並且CGI裏面RLIMIT_AS屬性值也是128M的。但是CGI代碼自己也沒有設置128M的限制,莫非又是boa(我們用的HTTP服務器是boa)的代碼裏面限制的?剛好我們的boa是從其他部門拿過來的,只有可執行文件,沒有源碼,所以也能夠解釋之前所有源碼搜索都搜索不到代碼的原因。問題就要水落石出了,等到拿到boa的源碼時,可惜一看,裏面也沒有設置這個值,不過重啓boa發現boa打印的RLIMIT_AS值確實是128M。見鬼了。

  第七階段,無聊時,突然聯想到,直接在shell裏面直接啓用boa是否也是4G?測試了一下,果然,直接敲入boa啓動,RLIMIT_AS的值是4G,而之前我們重啓boa都是通過他的腳本/etc/init.d/boa restart來啓用的,立即查看該腳本,在利用daemon啓用boa之前,赫然發現了裏面有這麼一句“ulimit -m 131072”,去掉,一切恢復正常。

  至此水落石出,從時間上來看,比起之前的跨N個模塊追查了3周才查到的一個BUG相比,此問題只查了3天的樣子,然而追查過程卻跌宕起伏,之前也不是由我來查這個問題,是項目組內的其他人,在查到有系統還有內存又new失敗的時候,他們就放棄不查了,肯定的說不是代碼問題,沒方法繼續查找下去的時候丟給了我。

  查BUG的經驗還是細心和開闊思路,細心當然就是關注到一些別人留意不到的地方,通常一個小的發現立即就可以解決問題,我看到有人在找了半天還沒有定位到問題的原因,而有些人對現場瞄一瞄,看看一些信息,立即就找到了思路,當然思路開闊的能力建立在你的知識系統之上的。同時注意在查BUG的時候,也要積累自己的知識。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章