1. 程式人生 > 其它 >【segmentation fault】 智慧指標異常崩潰

【segmentation fault】 智慧指標異常崩潰

 1 std::map<sio_t*, std::shared_ptr<Contextapc>> map_apc_context_;
 2 
 3 // 客戶端讀事件
 4 static void on_apc_recv(sio_t *io, void *buf, int readbytes)
 5 {
 6     // 獲取客戶端上下文
 7     std::shared_ptr<Context> context = Server::Instance()->ApcContextByIO(io);
 8     if (context != nullptr)
9 { 10 if (readbytes > 0) 11 { 12 // 接收客戶端資料 13 context->Receive(buf, readbytes); 14 } 15 else 16 { 17 // 客戶端異常 18 LOGW("[ on_apc_recv ] peer (%d) closed. len: %d", sio_fd(io), readbytes); 19 Server::Instance()->RemoveApcContext(io);
20 } 21 } 22 else 23 { 24 LOGW("[ on_apc_recv ] unknown context(%d). buf: %s, len: %d", sio_fd(io), (char *)buf, readbytes); 25 } 26 } 27 28 // 連線事件 29 static void on_apc_connected(sio_t *io) 30 { 31 // 設定讀事件 32 sio_setcb_read(io, on_apc_recv); 33 sio_read(io);
34 35 // 呼叫服務處理連線方法 36 Server::Instance()->onapcConnected(io); 37 } 38 39 // 獲取客戶端連線上下文 40 std::shared_ptr<Context> Server::ApcContextByIO(sio_t *io) 41 { 42 auto it = map_apc_context_.find(io); 43 44 if (it != map_apc_context_.end()) 45 { 46 return it->second; 47 } 48 else 49 { 50 return NULL; 51 } 52 } 53 54 // 連線處理方法 55 void Server::onapcConnected(sio_t *io) 56 { 57 // 建立智慧指標 58 std::shared_ptr<Contextapc> context = std::make_shared<Contextapc>(io); 59 // 物件儲存在 map 資料結構中 60 map_apc_context_[io] = context; 61 LOGI("[ onapcConnected ] fd: %d, size: %d", sio_fd(io), map_apc_context_.size()); 62 }

前幾天壓測服務程式碼,出現了崩潰,崩潰行數在13行

context->Receive(buf, readbytes);

1.因為是服務程式碼,訊號11都已經被捕獲了,只能看一些堆疊資訊,錯誤定位在這一行,我的第一反應是智慧指標 context 裡管理的物件是空的,

因此我註釋了訊號捕捉函式,得到coredump檔案,通過分析coredump檔案,context 管理的指標不是 NULL,我認為應該是 context 管理的物件被破壞了。--分析問題

2.於是我仔細閱讀了附近的程式碼,程式碼基本都是C++寫的,記憶體操作的地方不多,沒能看出問題。 --程式碼閱讀

3.codereview上無法看出問題,只能通過分析服務執行軌跡,看是否有思路。我在崩潰鏈路上加了很多日誌,發現 on_apc_connected --> onapcConnected --> on_apc_recv 直接就崩潰了,當時很疑惑,因為剛剛建立連結,收到第一個報文就崩潰了,任何業務程式碼都沒有執行,陷入僵局。   --業務分析

4.繼續復讀程式碼,猜測是不是多執行緒場景下,該例項先被從A執行緒 map_apc_context_ 刪除了,但是B執行緒剛好使用,因為我並沒有對 map_apc_context_ 加鎖保護,但是我90%確定這是一個單執行緒邏輯,不存在上述情況,但是我還是抱著僥倖心理,在使用 context 物件之前,判斷了一下 context 是否有效,結果仍是是崩潰。  --懷疑標準庫

5.因為本人沒有讀過智慧指標原始碼,我開始懷疑是智慧指標有BUG,因此我修改程式碼,不直接使用 context 呼叫 Receive 方法,而是取出 context 管理的例項來呼叫 Receive 方法,然後崩潰的斷點進入 Receive 函式內部一個使用成員屬性的地方,這個崩潰資訊很明確告訴我的確是 context 管理的物件出現問題了。   --確定崩潰的根本原因

6.我繼續復讀程式碼,確信接收報文這段業務邏輯是沒有問題的,那麼只剩下一個選項,肯定是某個地方記憶體越界了,影響了 map_apc_context_ 這個物件,此時閱讀程式碼的重點不再是連線的建立接收報文邏輯,而是轉向 context 實力內部業務處理邏輯,發現以下程式碼

if (msg->type & MSG_TYPE_TEST_BIT)
{
    char buff[128] = { 0 };
    sprintf(buff, "debug message;");
    memcpy(apc_msg_body(msg) + strlen(apc_msg_body(msg)), buff, strlen(buff));
}

這裡的msg本身的長度就是 apc_msg_body(msg) + strlen(apc_msg_body(msg)),這段程式碼居然還要在後面拼接上一段,很有可能記憶體洩漏了,但是我的訊息碼並不是MSG_TYPE_TEST_BIT,我認為不可能進入這段邏輯。我抱著試試看的心態將這段程式碼註釋了,準備再次進行測試(因為只有在壓測環境下才能復現問題,我自己並沒有相應的環境,所以改完程式碼到測試階段還有點時間)。就在我改完程式碼,我突然想到當時為了做記憶體管理,使用了jemalloc,是不是因為jemalloc做了記憶體管理,導致實際的記憶體洩漏被滯後了呢?於是我回滾程式碼,先將jemalloc註釋掉,然後再進行壓測,發現崩潰的地方就是上面分析的那塊程式碼,因為先前同事使用了 & 操作,並不完全等同於== ,而我新定義的訊息碼剛好就能進入這段邏輯,從而導致了崩潰。    --找到方案

 

總結:

    a. 出現 segmentation fault 肯定需要看附近的程式碼,如果附近的程式碼沒有問題,就要考慮是否是別的地方的記憶體越界影響了崩潰斷點

    b. 分析segmentation fault 絕對不能開 jemalloc

    c. 絕對不要懷疑第三方標準庫