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的时候,数据是不正确的。那么我调用接口,也就无效了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章