dunno Temat założony przez niniejszego użytkownika |
[LUA][C++] gdb problem z silnikiem gry » 2022-08-30 05:02:31 Kilka razy byliście w stanie nakierować mnie na rozwiązanie problemu, w związku z tym i tym razem zwracam się o pomoc. Posiadam skompilowany na Debian 10 silnik gry mmorpg, lecz losowo sypie mi błędami crashując cały serwer gry. #0 __GI_abort () at abort.c:107 act = {__sigaction_handler = {sa_handler = 0x0, sa_sigaction = 0x0}, sa_mask = {__val = {4294967295 <repeats 32 times>}}, sa_flags = 0, sa_restorer = 0x0} sigs = {__val = {32, 0 <repeats 31 times>}} #1 0xf78f3d2c in __libc_message (action=do_abort, fmt=<optimized out>) at ../sysdeps/posix/libc_fatal.c:181 ap = <optimized out> fd = 2 list = <optimized out> nlist = <optimized out> cp = <optimized out> written = <optimized out> #2 0xf78faaed in malloc_printerr (str=str@entry=0xf7a02902 "corrupted double-linked list") at malloc.c:5341 No locals. #3 0xf78fc27b in _int_free (av=<optimized out>, p=<optimized out>, have_lock=<optimized out>) at malloc.c:4325 size = <optimized out> fb = <optimized out> nextchunk = <optimized out> nextsize = <optimized out> nextinuse = <optimized out> prevsize = <optimized out> bck = <optimized out> fwd = <optimized out> __PRETTY_FUNCTION__ = "_int_free" #4 0xf7d8cb9f in luaM_realloc () from /lib/liblua50.so.5.0 No symbol table info available. #5 0xf7d90a31 in luaH_free () from /lib/liblua50.so.5.0 No symbol table info available. #6 0xf7d8a99d in ?? () from /lib/liblua50.so.5.0 No symbol table info available. #7 0xf7d8b495 in luaC_collectgarbage () from /lib/liblua50.so.5.0 No symbol table info available. #8 0xf7d85d86 in lua_pushlstring () from /lib/liblua50.so.5.0 No symbol table info available. #9 0xf7d85def in lua_pushstring () from /lib/liblua50.so.5.0 No symbol table info available. #10 0x5664aa26 in LuaScript::setField (L=0x58058310, index=0x566c36e4 "y", val=132) at luascript.cpp:604 No locals. #11 0x56568a9d in ActionScript::internalAddPositionEx (L=0x58058310, pos=...) at actions.cpp:1132 No locals. #12 0x56565388 in Action::executeUse (this=0x58057180, player=0xf4019c10, item=0xce899f60, posFrom=..., posTo=...) at actions.cpp:503 thingId2 = 1 playerpos = {<Position> = {x = 154, y = 132, z = 7}, stackpos = 0} cid = 1 itemid1 = 2 luaState = 0x58058310 thing = 0xf4019c18 ret = false #13 0x56564c61 in Actions::UseItemEx (this=0x5674fe40 <actions>, player=0xf4019c10, from_pos=..., from_stack=26 '\032', to_pos=..., to_stack=1 '\001', itemid=2314) at actions.cpp:422 itempos = {x = 154, y = 132, z = 7} posFromEx = {<Position> = {x = 65535, y = 70, z = 26}, stackpos = 26} posToEx = {<Position> = {x = 154, y = 132, z = 7}, stackpos = 1} item = 0xce899f60 action = 0x58057180 #14 0x565cb038 in Game::playerUseItemEx (this=0x5674fba0 <g_game>, player=0xf4019c10, posFrom=..., stack_from=26 '\032', posTo=..., stack_to=1 '\001', itemid=2314) at game.cpp:6492 sit = {first = 64, second = 0x0} lockClass = {mutex = 0x5674fc54 <g_game+180>} ret = false thingpos = {x = 154, y = 132, z = 7} item = 0xce899f60 #15 0x56691cb9 in Protocol76::parseUseItemEx (this=0xe4604cb0, msg=...) at protocol76.cpp:1311 pos_from = {x = 65535, y = 70, z = 26} itemid = 2314 from_stackpos = 26 '\032' pos_to = {x = 154, y = 132, z = 7} to_stackpos = 1 '\001' #16 0x5668eb6b in Protocol76::parsePacket (this=0xe4604cb0, msg=...) at protocol76.cpp:304 recvbyte = 131 '\203' #17 0x5668e322 in Protocol76::ReceiveLoop (this=0xe4604cb0) at protocol76.cpp:97 msg = {_vptr.NetworkMessage = 0x5674cde8 <vtable for NetworkMessage+8>, m_MsgSize = 19, m_ReadPos = 19, m_MsgBuf = "\021\000\203\377\377F\000\032\201\f\032\232\000\204\000\ac\000\001\060\060\060\060tal8\v\000\377\363\001\000\377\363\001\000\377\363\001\070\v\000\377\363\001\000\377\363\001\235\a\314\a\000\377\363\001\261\005\063\n\000\377\243\021\367\022\000\377\243\021\235\a\372\022\000\377\243\021\261\005o\n\246\n\000\377\346\001'\t\212\n\000\377\346\001\257\005(\n\000\377\346\001\065\002\304\005>\002\000\377\363\001a\000\000\000\000\000w)\004\020\004\000Hayad\001\201sNN\000\000\000\312\003\000\000\070\v8\v\000\377\363\001>\a\000\377\066\002\000\377\363\001\000\377\363\001\304\005\255\n\000\377\363\001\277\005\000\377\363\001\257\005\253\n\000\377\363\001\262\005\206\n\000\377\243\021\367\022\000"...} #18 0x56671b5b in ConnectionHandler (dat=0x5971a9b0) at otserv.cpp:654 stat = 0x5684bcc0 timeNow = 1661808295 protocol = 0xe4604cb0 placeInQueue = 2 ACCESS_ENTER = 1 now = 1661808295 timeinfo = 0xf7a605c0 <_tmbuf> start = 1638223200 isLocked = false player = 0xf4019c10 playerexist = false acc_pass = "tajne" fightTicks = 0 clientos = 0 '\000' version = 760 unknown = 1 '\001' accnumber = 111111 name = "Gracz" password = "tajne" protId = 522 s = 0 msg = {_vptr.NetworkMessage = 0x5674cde8 <vtable for NetworkMessage+8>, m_MsgSize = 34, m_ReadPos = 34, m_MsgBuf = " \000\n\002\000\370\002\001\377\232\001\000\b\000Gracz\n\000tajne", '\000' <repeats 16733 times>} #19 0xf7dbafd2 in start_thread (arg=<optimized out>) at pthread_create.c:486 ret = <optimized out> start = <optimized out> pd = <optimized out> now = <optimized out> unwind_buf = {cancel_jmp_buf = {{jmp_buf = {-136503296, -872416448, -136503296, -872418776, -168519392, -1119268520}, mask_was_saved = 0}}, priv = {pad = {0x0, 0x0, 0x0, 0x0}, data = {prev = 0x0, cleanup = 0x0, canceltype = 0}}} not_first_call = 0 #20 0xf797d6d6 in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:108 No locals. A debugging session is active.
Inferior 1 [process 26140] will be killed.
Quit anyway? (y or n) [answered Y; input not from terminal] Python Exception <class 'AttributeError'> 'gdb.ExitedEvent' object has no attribute 'exit_code': Jedynie widzę że jest problem z LUA mam wersje 5.0, kiedyś aktualizowałem do wersji 5.1 i był ten sam problem. W praktyce wygląda to tak, że są skrypty lua w których są zapisywane różnego rodzaju akcje i gracz je wywołuje. Skrypty te są niezmieniane od lat. Problem natomiast pojawia się często przy większej ilości graczy na serwerze gry. Natomiast Natomiast nie bardzo wiem co konkretnie może powodować ten błąd. Miałem podobny problem gdzie było wpisane "unsorted double link corrupted" https://cpp0x.pl/forum/temat/?id=28621&p=1 I przyczyna była jak się okazało prosta - (zapomniana) ustawiona w cronie automatyczna kopia bazy danych w nocy - bez wyłączania serwera gry powodowała taką sytuację. a w dzisiejszym logu akurat jest coś podobnego "corrupted double-linked list" Jakieś rady? wskazówki? może znowu naprowadzicie mnie na rozwiązanie problemu?:D |
|
pekfos |
» 2022-08-30 18:28:21 I przyczyna była jak się okazało prosta - (zapomniana) ustawiona w cronie automatyczna kopia bazy danych w nocy - bez wyłączania serwera gry powodowała taką sytuację. Może zwiększała szansę na problem, ale na pewno nie była to przyczyna. Odpowiedź jest ta sama co wcześniej Spróbuj narzędzi typu valgrind, efence, heaptrack, etc. Sam backtrace jest tu bezużyteczny, trzeba by analizować core dump pod kątem uszkodzenia pamięci. Jeśli nie czujesz się na siłach, użyj któregoś z wymienionych narzędzi by może znaleźć problem bliżej źródła. Masz tu pewnie buffer overflow, albo use after free. Możesz spróbować _FORTIFY_SOURCE=2, wyłapuje tylko niektóre możliwe przyczyny, ale wysiłek jest nieduży więc czemu nie. https://stackoverflow.com/questions/13517526/difference-between-gcc-d-fortify-source-1-and-d-fortify-source-2Jakbym to ja miał zdiagnozować, to bym w pierwszej kolejności zobaczył na czym potknęło się _int_free. Może tam być coś oczywistego, jak choćby tekst w miejscu wskaźników. Numer linii jest podany, chociaż patrząc po tych wszystkich "optimized out", może być potrzebne czytanie kodu w asmie. |
|
dunno Temat założony przez niniejszego użytkownika |
» 2022-09-04 11:07:46 Problem występuje zazwyczaj gdy jest dużo graczy np. 70 osób - gdy jest ich mniej np. 40 to problem praktycznie się nie zdarza. Valgrind uruchomiony na tym silniku praktycznie nie pozwala grać, pamiętam, ze kiedyś szukałem z jego pomocą tego błędu - bezskutecznie.
A czy jesteś w stanie polecić coś do statycznej analizy tego kodu źródłowego? Jest coś takiego? Bo wszystko w zasadzie rozbija się o "spells". Próbowałem samodzielnie weryfikować co krok po kroku idzie, ale niestety bez skutku. Chyba, że logować wszystkie wywołania executeUse oraz internalAddPositionEx wraz z zmiennymi ? |
|
DejaVu |
» 2022-09-04 11:16:14 Może masz za mało RAM-u na 70 osób (choć wtedy komunikat byłby inny). Raczej błąd sugeruje, że źle się obchodzisz ze wskaźnikami. Czytaj: - gdzieś brakuje Ci locka przy pracy ze wskaźnikami - gdzieś kopiujesz wskaźniki, a jak zwalniasz/realokujesz pamięć to nie zagwarantowałeś, aby nikt inny w tym samym czasie nie pracował z tym wskaźnikiem ORAZ aby przypadkiem nie skorzystał ze wskaźnika po jego zwolnieniu. Na Twoim miejscu rozpocząłbym analizę kodu od metody Game::playerUseItemEx i weryfikował czy aby na pewno są locki w miejscach, w których próbujesz odczytywać lub zapisywać dane. W praktyce obszar do przeanalizowania to:
Kolejną rzeczą, którą bym poddał analizie to czy LuaScript jest bezpieczny do używania go wielowątkowo. Być może nie jest, a Ty być może nie masz locków w każdym możliwym miejscu, gdy zaczynasz pracować z tym obiektem, co prowadzi do takiego błędu. /edit: luaC_collectgarbage - skoro jakiś 'garbage collector' się wywołał, to na 90% bym obstawiał, że nie masz locków, gdy pracujesz z LuaScript. Doszło do sytuacji, w której w tym samym czasie 'zwolniłeś pamięć starego obiektu' oraz 'rozdałeś natychmiast tą pamięć innemu obiektowi', co doprowadziło do sytuacji, że w 'garbage collectorze' zawisł Ci wskaźnik, który 'komuś' już rozdałeś i jak doszło do momentu jego 'sprzątania' to po prostu Ci się wysypuje kod. |
|
pekfos |
» 2022-09-04 13:45:20 Valgrind uruchomiony na tym silniku praktycznie nie pozwala grać, pamiętam, ze kiedyś szukałem z jego pomocą tego błędu - bezskutecznie. A efence? A czy jesteś w stanie polecić coś do statycznej analizy tego kodu źródłowego? Jest coś takiego? Jest, np cppcheck. |
|
dunno Temat założony przez niniejszego użytkownika |
» 2022-09-23 20:14:53 Doszło do sytuacji, w której w tym samym czasie 'zwolniłeś pamięć starego obiektu' oraz 'rozdałeś natychmiast tą pamięć innemu obiektowi', co doprowadziło do sytuacji, że w 'garbage collectorze' zawisł Ci wskaźnik, który 'komuś' już rozdałeś i jak doszło do momentu jego 'sprzątania' to po prostu Ci się wysypuje kod
Myślę, że trafiłeś i możesz mieć racje. Na Twoim miejscu rozpocząłbym analizę kodu od metody Game::playerUseItemEx i weryfikował czy aby na pewno są locki w miejscach, w których próbujesz odczytywać lub zapisywać dane.
W praktyce obszar do przeanalizowania to:
ActionScript::internalAddPositionEx Action::executeUse Actions::UseItemEx Game::playerUseItemEx
Załączę te funkcje tutaj: void ActionScript::internalAddPositionEx( lua_State * L, const PositionEx & pos ) { lua_newtable( L ); setField( L, "z", pos.z ); setField( L, "y", pos.y ); setField( L, "x", pos.x ); setField( L, "stackpos", pos.stackpos ); }
bool Action::executeUse( Player * player, Item * item, PositionEx & posFrom, PositionEx & posTo ) { script->ClearMap(); script->_player = player; PositionEx playerpos = player->pos; uint32_t cid = script->AddThingToMap(( Thing * ) player, playerpos ); uint32_t itemid1 = script->AddThingToMap( item, posFrom ); lua_State * luaState = script->getLuaState(); lua_pushstring( luaState, "onUse" ); lua_gettable( luaState, LUA_GLOBALSINDEX ); lua_pushnumber( luaState, cid ); script->internalAddThing( luaState, item, itemid1 ); script->internalAddPositionEx( luaState, posFrom ); Thing * thing = script->game->getThing(( Position ) posTo, posTo.stackpos, player ); if( thing && posFrom != posTo ) { int32_t thingId2 = script->AddThingToMap( thing, posTo ); script->internalAddThing( luaState, thing, thingId2 ); script->internalAddPositionEx( luaState, posTo ); } else { script->internalAddThing( luaState, NULL, 0 ); PositionEx posEx; script->internalAddPositionEx( luaState, posEx ); } lua_pcall( luaState, 5, 1, 0 ); bool ret =( script->internalGetNumber( luaState ) != 0 ); return ret; }
bool Actions::UseItemEx( Player * player, const Position & from_pos, const unsigned char from_stack, const Position & to_pos, const unsigned char to_stack, const uint16_t itemid ) { if( canUse( player, from_pos ) == TOO_FAR ) { player->sendCancel( "Too far away." ); return false; } Item * item = dynamic_cast < Item * >( game->getThing( from_pos, from_stack, player ) ); if( !item ) return false; if( item->getID() != itemid ) return false; if( !item->isUseable() ) return false; Action * action = getAction( item ); if( action ) { if( action->allowFarUse() == false ) { if( canUse( player, to_pos ) == TOO_FAR ) { player->sendCancel( "Too far away." ); return false; } } else if( canUseFar( player, to_pos, action->blockWalls() ) == TOO_FAR ) { player->sendCancel( "Too far away." ); return false; } else if( canUseFar( player, to_pos, action->blockWalls() ) == CAN_NOT_THTOW ) { player->sendCancel( "You cannot throw there." ); return false; } Position itempos = game->getThingMapPos( player, from_pos ); game->autoCloseTrade( item ); PositionEx posFromEx( from_pos, from_stack ); PositionEx posToEx( to_pos, to_stack ); if( action->executeUse( player, item, posFromEx, posToEx ) ) return true; } player->sendCancel( "You can not use this object." ); return false; }
bool Game::playerUseItemEx( Player * player, const Position & posFrom, const unsigned char stack_from, const Position & posTo, const unsigned char stack_to, const uint16_t itemid ) { OTSYS_THREAD_LOCK_CLASS lockClass( gameLock, "Game::playerUseItemEx()" ); if( player->isRemoved ) return false; bool ret = false; Position thingpos = getThingMapPos( player, posFrom ); Item * item = dynamic_cast < Item * >( getThing( posFrom, stack_from, player ) ); if( item ) { std::map < uint16_t, Spell * >::iterator sit = spells.getAllRuneSpells()->find( item->getID() ); if( sit != spells.getAllRuneSpells()->end() ) { if(( abs( thingpos.x - player->pos.x ) > 1 ) ||( abs( thingpos.y - player->pos.y ) > 1 ) ) { player->sendCancel( "To far away..." ); ret = false; } else { std::string var = std::string( "" ); if( player->access >= g_config.ACCESS_PROTECT || sit->second->getMagLv() <= player->maglevel ) { bool success = sit->second->getSpellScript()->castSpell( player, posTo, var ); ret = success; if( success ) { autoCloseTrade( item ); item->setItemCharge( std::max(( int32_t ) item->getItemCharge() - 1, 0 ) ); if( item->getItemCharge() == 0 ) { if( removeThing( player, posFrom, item ) ) { FreeThing( item ); } } } } else { player->sendCancel( "You don't have the required magic level to use that rune." ); } } } else { actions.UseItemEx( player, posFrom, stack_from, posTo, stack_to, itemid ); ret = true; } } return ret; } - gdzieś brakuje Ci locka przy pracy ze wskaźnikami - gdzieś kopiujesz wskaźniki, a jak zwalniasz/realokujesz pamięć to nie zagwarantowałeś, aby nikt inny w tym samym czasie nie pracował z tym wskaźnikiem ORAZ aby przypadkiem nie skorzystał ze wskaźnika po jego zwolnieniu.
Tylko, że próbowałem dodać te "locki" ale nie bardzo wiem jak. To ma być coś w stylu dodać na początku std::mutex myMutex; a potem: void ActionScript::internalAddPositionEx( lua_State * L, const PositionEx & pos ) { std::lock_guard < std::mutex > guard( myMutex ); lua_newtable( L ); setField( L, "z", pos.z ); setField( L, "y", pos.y ); setField( L, "x", pos.x ); setField( L, "stackpos", pos.stackpos ); } i tak w kazdej z tych funkcji? i to wystarczy? czy ewentualnie jakiś przykład możecie podać? W zasadzie silnik, którego używam w 90% bazuje na tym: https://github.com/divinity76/YurOTS/tree/master/ots/source. różni się elementami rozgrywki natomiast te funkcje w zasadzie niewiele się różnią. |
|
DejaVu |
» 2022-09-24 11:31:39 Jeżeli ActionScript to jest jeden obiekt, który zarządza całym LuaScriptem, to tak. Tworzysz std::mutex jako pole klasy ActionScript (zapewne to Twój myMutex). Następnie zakładasz w każdej metodzie locki (nawet w tych metodach, które odczytują dane). Jeżeli masz const-owe metody, to zadeklaruj myMutex tak: mutable std::mutex myMutex;
Natomiast co do dopisywania locków w każdej metodzie to zrób dokładnie tak jak w przykładowej metodzie, którą wkleiłeś tj. na początku każdej metody napisz: std::lock_guard < std::mutex > guard( myMutex );
|
|
pekfos |
» 2022-09-24 15:30:34 Wygląda na to że synchronizacja już jest na poziomie klasy Game. OTSYS_THREAD_LOCK_CLASS lockClass( gameLock, "Game::playerUseItemEx()" );
Jeśli gdzieś jej brakuje, to trzeba to naprawić właśnie w tej klasie. Najlepiej weź wszystkie publiczne metody z Game które nie mają locka w środku i po kolei sprawdź czy są używane w bezpiecznych kontekstach. gameLock jest używane też poza metodami Game, więc weź to też pod uwagę przy ocenianiu kontekstu. Patrząc po tworzonych wątkach, ciekawie wygląda klasa PoolManager. Metoda dumpStats operuje na mapie bez blokowania poolLock, więc jest szansa że będzie crash w tej metodzie. Miałbyś to w stack trace, więc jest to niezwiązane. |
|
« 1 » 2 3 |