Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

[LUA][C++] gdb problem z silnikiem gry

Ostatnio zmodyfikowano wczoraj o godz. 15:30
Autor Wiadomość
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
P-179623
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-2

Jakbym 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.
P-179624
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 ?
P-179632
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:
  • ActionScript::internalAddPositionEx
  • Action::executeUse
  • Actions::UseItemEx
  • Game::playerUseItemEx

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.
P-179633
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.
P-179634
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:
C/C++
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 )
{
   
//onUse(uidplayer, item1,position1,item2,position2)
   
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 );
   
//std::cout << "posTo" <<  (Position)posTo << " stack" << (int32_t)posTo.stackpos <<std::endl;
   
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;
       
   
}
   
   
//not found
   
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 )
   
{
       
//Runes
       
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:
C/C++
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ą.

P-179647
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:
C/C++
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:
C/C++
std::lock_guard < std::mutex > guard( myMutex );
P-179649
pekfos
» 2022-09-24 15:30:34
Wygląda na to że synchronizacja już jest na poziomie klasy Game.
C/C++
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.

P-179651
« 1 »
  Strona 1 z 1