ycsm宕機、bug記錄

1、__stack_chk_fail宕機

  • 今天雲海服務器發生了宕機,堆棧如下:
    #0 0xf77a4440 in __kernel_vsyscall ()
    #1 0xf74af687 in raise () from /lib/i386-linux-gnu/libc.so.6
    #2 0xf74b2ab3 in abort () from /lib/i386-linux-gnu/libc.so.6
    #3 0xf74e9fd3 in ?? () from /lib/i386-linux-gnu/libc.so.6
    #4 0xf757f03b in __fortify_fail () from /lib/i386-linux-gnu/libc.so.6
    #5 0xf757efca in __stack_chk_fail () from /lib/i386-linux-gnu/libc.so.6
    #6 0x08212e51 in CGMActivitySys::OnRelevancePhone (this=0xe22f9e0, player=…, phoneNum=0x12c85ab4 “19907316909”, cdKey=0x1525e54c “446244”)
    at game_sys/gm_activity/gs_gm_activity.cpp:4572
    #7 0x0821275f in CGMActivitySys::OnC2S_RelevancePhone (this=0xe22f9e0, player=…, request=0xfff0bee8, errMsg=…)
    at game_sys/gm_activity/gs_gm_activity.cpp:4477
    #8 0x08356672 in MgrGameWorld::OnReceiveProtoEvent (this=0xce2e5f4, player=0x14fd9f10, eventID=CtmGame::C2SG_RELEVANCE_PHONE, request=0xfff0bee8,
    response=0xfff0bf14) at game_sys/base/game_world.cpp:222
    #9 0x085aa79f in PlayingGameProcessor::OnReceiveMsg (this=0x11954030, pClient=0x14eede78, eventID=2434, request=0xfff0bee8, response=0xfff0bf14)
    at login_sys/impl/login_processor/PlayingGameProcessor.cpp:198
    #10 0x0854aed2 in NetManagerListenForGate::OnReceiveMSG (this=0xb605638, pSession=0x14eede78, gameSysID=0 ‘\000’, packet=0x1195902c)
    at net_sys/gate/gate_server.cpp:532

查了一下__stack_chk_fail ()的說明,訪問鏈接:__stack_chk_fail ()
查看鏈接後,證實代碼確實有數組越界了,分析上下文,最終發現是一處拼接url的地方,寫數組越界。
源碼:

char url[128];
sprintf(url,gsCfg->PhoneURL,gsCfg->gameName,oid_s,phoneNum,act_s);

這裏拼接的字符串超出128字節了,原因是隨着項目的運行,增開的服務器越來越多,終於有一天,開到了sstx100,原先sstx99不會越界,但是gameName爲sstx100時,拼接後長度正好129,越界。

2、宕機在這裏插入圖片描述

  • 記一次外網驗收服務器的core文件分析,堆棧信息如下:
    Program terminated with signal SIGABRT, Aborted.
    #0 0xf76e7440 in __kernel_vsyscall ()
    (gdb) bt
    #0 0xf76e7440 in __kernel_vsyscall ()
    #1 0xf73f2687 in raise () from /lib/i386-linux-gnu/libc.so.6
    #2 0xf73f5ab3 in abort () from /lib/i386-linux-gnu/libc.so.6
    #3 0xf742cfd3 in ?? () from /lib/i386-linux-gnu/libc.so.6
    #4 0xf74374ca in ?? () from /lib/i386-linux-gnu/libc.so.6
    #5 0xf74382d9 in ?? () from /lib/i386-linux-gnu/libc.so.6
    #6 0x085f8d17 in operator delete (pointer=0xd9be110) at main.cpp:48
    #7 0x080aa545 in CFriendSystem::OnPlayerDestroy (this=0xee47370, player=0xd2b6ce0) at game_sys/friend/gs_friend.cpp:1043
    #8 0x088ff60a in EvEventBroadcastImp::ProcessEventArgPtr (this=0xbeef088, ev=0x97cc220 <_ZL17EV_PLAYER_DESTROY>, data=0xffafeb20) at ev_event_broadcast_imp.cpp:239
    #9 0x088ff7a8 in EvEventBroadcastImp::ProcessEventArgs (this=0xbeef088, ev=0x97cc220 <_ZL17EV_PLAYER_DESTROY>, iNumArgs=1) at ev_event_broadcast_imp.cpp:470
    #10 0x0892d107 in EvEventBroadcast::SendEvent (this=0xbeef088, ev=0x97cc220 <_ZL17EV_PLAYER_DESTROY>, a1=…) at ev_event.cpp:153
    #11 0x085b8d2b in CPlayer::OnBeforeOffline (this=0xd2b6ce0) at player_sys/impl/player_sys.cpp:775
    #12 0x085be0fe in CPlayerMgr::RemoveOnlinePlayer (this=0xc3188a8, player_oid=@0xffafecb0: 2314902248876802048) at player_sys/impl/player_sys.cpp:1842
    #13 0x085c0d30 in CMessageExtenedHandler::on_handle_msg<(unsigned short)207> (pClient=0xd465b80, pPacket=0xc07e2a4) at player_sys/impl/player_sys.cpp:2485
    #14 0x085a7e78 in NetManagerListenForGame::_Handle (this=0xa5f5298, conn=0xccb9fa0, ProtoID=207, packet=0xc07e2a4) at net_sys/game/game_server.h:17
    #15 0x080a413d in CCLNetSocketServer::_baseOnConnectionReceive (this=0xa5f5298, sess=0xccb9fa0, ProtoID=207, packet=0xc07e2a4)
    at …/common_engine/common/net/api/cl_net_socket_server.h:128
    #16 0x088798eb in CCLNetSocketServerBase::__lambda0::operator() (__closure=0xc926d70, conn=0xd465b80, packet=0xc07e2a4) at common/net/cl_net_socket_server.cpp:182
    #17 0x08879fc7 in std::_Function_handler<void(EV_NetClient*, EvMemPacket*), CCLNetSocketServerBase::OnReceive(EV_NetServer*, EV_NetClient*, EvMemPacket*)::__lambda0>::_M_invoke(const std::_Any_data &, EV_NetClient , EvMemPacket ) (__functor=…, __args#0=0xd465b80, __args#1=0xc07e2a4) at /usr/include/c++/4.8/functional:2071
    #18 0x0887e9e8 in std::function<void (EV_NetClient
    , EvMemPacket
    )>::operator()(EV_NetClient*, EvMemPacket*) const (this=0xffafef1c, __args#0=0xd465b80, __args#1=0xc07e2a4)
    at /usr/include/c++/4.8/functional:2471
    #19 0x0887e988 in cl_net_proxy::rcv(EV_NetClient*, EvMemPacket&, std::function<void (EV_NetClient*, EvMemPacket*)>) (this=0xdf19850, pClient=0xd465b80, pkt=…, rcv_cb=…)
    at common/net/net_packet/cl_net_proxy.cpp:73
    #20 0x08879a2b in CCLNetSocketServerBase::OnReceive (this=0xa5f5298, server=0xc07e248, conn=0xd465b80, packet=0xc07e2a4) at common/net/cl_net_socket_server.cpp:187

  • 1、堆棧第#6行,提示的是delete指針的時候,導致發生了coredown;這裏是項目重載了delete操作符,用來做內存統計的。

  • 2、看到報錯信息是:Program terminated with signal SIGABRT, Aborted.,猜測是delete了一個非法指針。一開始懷疑是double kill,delete了兩次。但是進入#7行,分析指針的數據看上去並沒有野掉,所以應該是第一次delete。

  • 3、所以再懷疑是內存被踩越界了,導致了非法指針。項目有幾十萬行代碼,最討厭的就是找內存越界,要老命啊!所幸運氣好,這裏再往上追蹤,是玩家離線的時候,拋出事件EV_PLAYER_DESTROY來銷燬每個系統掛載的玩家數據。那麼一般情況下,可以認爲是上一個遊戲系統非法操作,破壞了內存,導致下一個系統再使用內存的時候,掛逼了。然後再去分析遊戲log文件,找到上一個系統gs_yunshi,該系統新開發的玩法,並且和QA確認了下,宕機時間點,外網驗收正好有人在測試該玩法。嗯,好像找到一個重點嫌疑犯了。

  • 4、然後就是去review該系統的代碼,果然發現有這麼一段代碼:

auto iter = _info_map.find(act_id);
iter->second.act_id = act_id;
iter->second.act_time = nowtime;

這裏iter如果等於_info_map.end()的話,那麼2、3行的寫數據行爲,就極有可能破壞內存,把後面的內存寫壞了。這段代碼是在活動開啓的時候觸發的,接下來就是模擬操作,然後復現一下coredown文件;按操作成功復現了core文件。然後再修改這段代碼,再同樣模擬操作,沒宕。好了,可以收拾東西回家睡覺了。
這裏記一下查內存越界的思路:

  • 1、一般都是因爲上文非法操作了內存,導致下文的內存可能被寫壞了,所以可以先把上文列爲重點嫌疑對象。
  • 2、分析下core文件和日誌信息,定位到關鍵操作(玩家下線了、玩家參與了某個活動等等)或關鍵函數。
  • 3、有嫌疑對象、關鍵信息以後,還沒有查到結果,那就review代碼,分析的時候不要心急,因爲它宕了就一定會留下痕跡,所以總能找到蛛絲馬跡。
  • 4、定位問題以後,就模擬操作,重現宕機。
  • 5、改正代碼,再模擬操作,確認不會發生宕機。

3、記一次噁心BUG的查詢

  • 1、策劃把煉獄挑戰轉移到封魔錄中,所以需要在玩家登陸的時候,做一次老數據的轉換。將外網玩家的煉獄挑戰的進度映射到封魔錄中。
  • 2、假設封魔錄進度有6關,如果封魔錄沒有通過 6關,那麼在在主界面會顯示封魔錄的按鈕,如果6關都通關了,那麼主界面隱藏封魔錄按鈕。
  • 3、由於玩家已經完成煉獄挑戰的6關,那麼轉換到封魔錄,對應的進度,也應該是6關都通過了,那麼主界面應該隱藏封魔錄。

出現bug情況是:有個玩家通關了煉獄6關,但是在主界面出現了封魔錄的按鈕。當時不知道原因是什麼,怕玩家可以再打一次煉獄挑戰,拿6個關卡的獎勵,於是通宵查了一晚上,分析了所有代碼,最終還是一無所獲。困惑了一晚上,後來靈光一閃,用延遲工具設置了網絡延遲,終於復現了bug。

  • 最終的原因是:服務器的數據發包晚了,客戶端在沒有完全收到服務器數據的時候,就去初始化主鈕界面的封魔錄按鈕;並且服務器的數據包在隨後下發到客戶端時,由於客戶端界面已經初始化完成,未根據服務器數據再刷新一下界面,丟棄了本次服務器的數據,最終導致BUG。

4、外網玩家反饋釋放技能被拉回

  • 1、分析了外網log,發現是由於移動後,位置不同步。我們技能在釋放的時候,會上傳客戶端當前位置,服務器收到釋放技能的消息後,會校驗客戶端、服務器的位置差,如果發現大於5米的話,就會將客戶端位置強拉到服務器位置。
  • 2、去看了一下《鎮魔曲》,發現
    • a、斷網後,客戶端還可以在本地行走,那麼他們客戶端肯定也是用了移動預測,客戶端先模擬移動行爲的方案。
    • b、斷網後,客戶端移動一段距離,再恢復聯網。客戶端會被強拉到服務器位置。
    • c、斷網後,客戶端移動一段距離,再恢復聯網,並釋放技能。服務器會被強拉到客戶端位置。(這樣證明當客戶端釋放了技能,服務器居然是以客戶端位置爲準的。這樣確實可以保證玩家體驗,但是這樣比較容易作弊。“當然,猜測肯定是有一些限制條件的,在一定條件範圍內,服務器以客戶端的位置爲準",例如檢測到確實是有斷線重連才允許這樣,或者服務器、客戶端位置差在5米範圍內)
    • d、斷線重連的時間比較久,大概有5s左右纔會出現斷線重連界面。我們項目的話,基本一關閉網絡的時候,立馬就出現斷線重連界面了。

5、日曆系統踩坑

九州密藏是運營活動,運營會在後臺設置活動開啓的時間,例如每週二、四、六,這個活動有1小時的最大累積時間限制,在活動開啓的時候,每天會贈送20分鐘。那麼一週就會贈送3次,在週二、四、六時。

  • 實現的方式是mk給我提供了一個接口bool GetActiveIsOpen(UINT actId),可以判斷該活動是否開啓。我在dateChange()函數中,調用該接口,如果返回true,則給玩家贈送時間,否則不贈送。

外網玩家反饋活動時間累積的不對,於是去查看代碼。

  • 由於該活動是運營活動,先是由後臺通知到mgr,再由mgr廣播給game。這裏涉及到一個日曆系統的問題,日曆的觸發有個潛規則:1s內不能觸發2次。
  • 在後臺輸入gm指令,同步到mgr時,如果該tick內,正好有其它某處觸發了日曆系統,那麼此處在調用觸發,會發現時間並沒有被更新。導致mgr同步給game的時候,數據是不正確的。那麼我調用接口,也就無效了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章