HarpyWar wrote:По моему, нужно делать дампы на Windows...
... если на windows эти ошибки вообще всплывут. У всех же вроде работает все?
HarpyWar wrote:А утечка памяти там возможно из-за дубликата строки "newkey = xstrdup(key);" (она уже удалена в последних коммитах)?
Да, действительно, убрал дубль, утечек памяти нет.
HarpyWar wrote:Не особенно понимаю как работает valgrid, и как с помощью него можно отследить место утечки памяти.
Погуглил про valgrind и про вообще, возник вопрос (может, туплю, или что-то тут очень сильно не так?)
valgrind пишет:
==44725== Invalid write of size 4
==44725== at 0x4DC6DE: pvpgn::bnetd::userlog_filename(char const*, bool) (userlog.cpp:262)
Лезу в этот файл, нахожу:
filepath = buildpath(filepath, lusername.c_str());
[b] std::strcat(filepath, ".log");[/b] // 262-я строка
А вот он - buildpath:
extern char * buildpath(char const *root, const char *suffix)
{
char *result;
result = (char*)xmalloc(std::strlen(root) + 1 + std::strlen(suffix) + 1); // выделяется памяти ровно "root/suffix\0"
std::strcpy(result, root); std::strcat(result, "/"); std::strcat(result, suffix);
return result;
}
Честно, strcat никогда в жизни не пользовался, но читаю доку: en.cppreference.com/w/cpp/string/byte/strcat
The destination byte string must have enough space for the contents of both dest and src, plus the terminating null character.
И пример:
#include <cstring>
#include <cstdio>
int main()
{
char str[50] = "Hello ";
char str2[50] = "World!";
std::strcat(str, str2);
std::strcat(str, " Goodbye World!");
std::puts(str);
}
Т.е. создать массив подлиннее, чтобы туда все влезло.
Или я совсем туплю, или в нашем коде ".log" пишется заведомо за пределами выделенного массива, при этом ГАРАНТИРОВАННО разрушая данные в памяти? О чем и оповещает valgrind?
Это я все о последней актуальной версии с github, если что
Добавлено: 12.12.2014 00:07
Значит, дальше буду ковырять master с github:
Userlog я переписал - коммит вышлю.
Следующее, что мне удалось увидеть:
При входе на сервер, при попытке 1-й раз набрать /con - получил простыню из ошибок про строку 3323 и 3314:
==52522== Invalid read of size 1
==52522== at 0x2107950: strnlen (in /lib/libc.so.7)
==52522== by 0x21011B8: ??? (in /lib/libc.so.7)
==52522== by 0x20FC5BC: snprintf (in /lib/libc.so.7)
==52522== by 0x44993F: pvpgn::bnetd::_handle_connections_command(pvpgn::bnetd::connection*, char const*) (command.cpp:3323)
==52522== by 0x438986: pvpgn::bnetd::handle_command(pvpgn::bnetd::connection*, char const*) (command.cpp:603)
==52522== by 0x47A7DB: pvpgn::bnetd::_client_message(pvpgn::bnetd::connection*, pvpgn::t_packet const*) (handle_bnet.cpp:3591)
==52522== by 0x46F318: pvpgn::bnetd::handle(pvpgn::bnetd::t_htable_row const*, int, pvpgn::bnetd::connection*, pvpgn::t_packet const*) (handle_bnet.cpp:344)
==52522== by 0x46F17D: pvpgn::bnetd::handle_bnet_packet(pvpgn::bnetd::connection*, pvpgn::t_packet const*) (handle_bnet.cpp:314)
==52522== by 0x4BCFC0: pvpgn::bnetd::sd_tcpinput(pvpgn::bnetd::connection*) (server.cpp:681)
==52522== by 0x4BD786: pvpgn::bnetd::handle_tcp(void*, pvpgn::t_fdwatch_type) (server.cpp:906)
==52522== by 0x4E52D5: pvpgn::FDWKqueueBackend::handle() (fdwatch_kqueue.cpp:226)
==52522== by 0x4E3BE5: pvpgn::fdwatch_handle() (fdwatch.cpp:198)
==52522== Address 0x69a91b8 is 24 bytes inside a block of size 29 free'd
==52522== at 0x120A698: operator delete(void*) (in /usr/local/lib/valgrind/vgpreload_memcheck-amd64-freebsd.so)
==52522== by 0x195F0EF: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() (basic_string.h:535)
==52522== by 0x4498AC: pvpgn::bnetd::_handle_connections_command(pvpgn::bnetd::connection*, char const*) (command.cpp:3314)
==52522== by 0x438986: pvpgn::bnetd::handle_command(pvpgn::bnetd::connection*, char const*) (command.cpp:603)
==52522== by 0x47A7DB: pvpgn::bnetd::_client_message(pvpgn::bnetd::connection*, pvpgn::t_packet const*) (handle_bnet.cpp:3591)
==52522== by 0x46F318: pvpgn::bnetd::handle(pvpgn::bnetd::t_htable_row const*, int, pvpgn::bnetd::connection*, pvpgn::t_packet const*) (handle_bnet.cpp:344)
==52522== by 0x46F17D: pvpgn::bnetd::handle_bnet_packet(pvpgn::bnetd::connection*, pvpgn::t_packet const*) (handle_bnet.cpp:314)
==52522== by 0x4BCFC0: pvpgn::bnetd::sd_tcpinput(pvpgn::bnetd::connection*) (server.cpp:681)
==52522== by 0x4BD786: pvpgn::bnetd::handle_tcp(void*, pvpgn::t_fdwatch_type) (server.cpp:906)
==52522== by 0x4E52D5: pvpgn::FDWKqueueBackend::handle() (fdwatch_kqueue.cpp:226)
==52522== by 0x4E3BE5: pvpgn::fdwatch_handle() (fdwatch.cpp:198)
==52522== by 0x4BF190: pvpgn::bnetd::_server_mainloop(pvpgn::list*) (server.cpp:1526)
Это дублируется 3 раза. Надо исправлять, займусь в ближайшее время, хотя в причину ошибки вникнуть не удалось...
Дальше вообще супер - нашел багу:
пустил привилегированную команду /set - он что-то ругнулся на отсутствующий хелп, valgrind выплюнул 1 ошибку, и пвпгн умер:
==52522== Invalid read of size 8
==52522== at 0x20F412D: ??? (in /lib/libc.so.7)
==52522== by 0x20F404B: rewind (in /lib/libc.so.7)
==52522== by 0x4928FB: pvpgn::bnetd::describe_command(pvpgn::bnetd::connection*, char const*) (helpfile.cpp:204)
==52522== by 0x451871: pvpgn::bnetd::_handle_set_command(pvpgn::bnetd::connection*, char const*) (command.cpp:4623)
==52522== by 0x438986: pvpgn::bnetd::handle_command(pvpgn::bnetd::connection*, char const*) (command.cpp:603)
==52522== by 0x47A7DB: pvpgn::bnetd::_client_message(pvpgn::bnetd::connection*, pvpgn::t_packet const*) (handle_bnet.cpp:3591)
==52522== by 0x46F318: pvpgn::bnetd::handle(pvpgn::bnetd::t_htable_row const*, int, pvpgn::bnetd::connection*, pvpgn::t_packet const*) (handle_bnet.cpp:344)
==52522== by 0x46F17D: pvpgn::bnetd::handle_bnet_packet(pvpgn::bnetd::connection*, pvpgn::t_packet const*) (handle_bnet.cpp:314)
==52522== by 0x4BCFC0: pvpgn::bnetd::sd_tcpinput(pvpgn::bnetd::connection*) (server.cpp:681)
==52522== by 0x4BD786: pvpgn::bnetd::handle_tcp(void*, pvpgn::t_fdwatch_type) (server.cpp:906)
==52522== by 0x4E52D5: pvpgn::FDWKqueueBackend::handle() (fdwatch_kqueue.cpp:226)
==52522== by 0x4E3BE5: pvpgn::fdwatch_handle() (fdwatch.cpp:198)
==52522== Address 0x48 is not stack'd, malloc'd or (recently) free'd
==52522==
==52522==
==52522== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==52522== Access not within mapped region at address 0x48
==52522== at 0x20F412D: ??? (in /lib/libc.so.7)
==52522== by 0x20F404B: rewind (in /lib/libc.so.7)
==52522== by 0x4928FB: pvpgn::bnetd::describe_command(pvpgn::bnetd::connection*, char const*) (helpfile.cpp:204)
==52522== by 0x451871: pvpgn::bnetd::_handle_set_command(pvpgn::bnetd::connection*, char const*) (command.cpp:4623)
==52522== by 0x438986: pvpgn::bnetd::handle_command(pvpgn::bnetd::connection*, char const*) (command.cpp:603)
==52522== by 0x47A7DB: pvpgn::bnetd::_client_message(pvpgn::bnetd::connection*, pvpgn::t_packet const*) (handle_bnet.cpp:3591)
==52522== by 0x46F318: pvpgn::bnetd::handle(pvpgn::bnetd::t_htable_row const*, int, pvpgn::bnetd::connection*, pvpgn::t_packet const*) (handle_bnet.cpp:344)
==52522== by 0x46F17D: pvpgn::bnetd::handle_bnet_packet(pvpgn::bnetd::connection*, pvpgn::t_packet const*) (handle_bnet.cpp:314)
==52522== by 0x4BCFC0: pvpgn::bnetd::sd_tcpinput(pvpgn::bnetd::connection*) (server.cpp:681)
==52522== by 0x4BD786: pvpgn::bnetd::handle_tcp(void*, pvpgn::t_fdwatch_type) (server.cpp:906)
==52522== by 0x4E52D5: pvpgn::FDWKqueueBackend::handle() (fdwatch_kqueue.cpp:226)
==52522== by 0x4E3BE5: pvpgn::fdwatch_handle() (fdwatch.cpp:198)
==52522== If you believe this happened as a result of a stack
==52522== overflow in your program's main thread (unlikely but
==52522== possible), you can try to increase the size of the
==52522== main thread stack using the --main-stacksize= flag.
==52522== The main thread stack size used in this run was 16777216.
==52522==
==52522== HEAP SUMMARY:
==52522== in use at exit: 3,284,020 bytes in 26,278 blocks
==52522== total heap usage: 1,971,664 allocs, 1,945,386 frees, 397,742,765 bytes allocated
==52522==
==52522== LEAK SUMMARY:
==52522== definitely lost: 18,172 bytes in 4,116 blocks
==52522== indirectly lost: 0 bytes in 0 blocks
==52522== possibly lost: 89,632 bytes in 828 blocks
==52522== still reachable: 3,176,216 bytes in 21,334 blocks
==52522== suppressed: 0 bytes in 0 blocks
==52522== Rerun with --leak-check=full to see details of leaked memory
==52522==
==52522== For counts of detected and suppressed errors, rerun with: -v
==52522== ERROR SUMMARY: 36 errors from 4 contexts (suppressed: 51 from 7)
Segmentation fault
Добавлено: 12.12.2014 00:50
Собственно, на отсутствующий хелп он ругнулся в лог при входе юзера на сервер:
Dec 12 00:40:27 [error] conn_send_welcome: could not open MOTD file "/usr/local/pvpgn-1.9.9/etc/bnmotd-enUS.txt" for reading (std::fopen: No such file or directory)
В функции describe_command в./bnetd/helpfile.cpp отсутствует проверка после вызова:
std::FILE* hfd = get_hfd(c);
В функции get_hfd, похоже, эта проверка тоже отсутствует.
Интересно, что вернет "return hfd_list[languages[0]];"?
По функции helpfile_init у меня сомнения:
если внутри цикла какой-нибудь из языков не нашелся, она не пытаясь загрузить другие языки возвращает ошибку. После этого работа программы продолжается. При этом уже загруженные ранее языки могут быть нормально использованы. Вряд ли это так и задумывалось, и return -1; - там лишний.
Также, нормальное продолжение работы после того, как ни один язык не был загружен, приводит к падению сервера.
Также очень не понравилось отсутствие проверок значения lang в функции conn_get_gamelang_localized.
Оно присваивается, потом по возможности переприсваивается, и потом без всяких проверок валидности возвращается наружу. При этом минимум одна из опций - взять значение из настроек аккаунта, т.е. из базы, в которой может оказаться все, что угодно.
После чего, с этим lang в остальном коде делается все что угодно, причем в разных местах, разбросанных по коду.
Вообще вся эта локализация выглядит стремно и сыро, и требует не просто добавления множественных проверок, а переработки самой логики, чтобы эти проверки были естественной ее частью, ИМХО.
Подумалось: может быть, мой сервер падал, когда когда просто кто-нибудь подключался клиентом с хитрым языком (типа какого-нибудь испанского/португальского), в результате чего нужный файл motd или help не мог открыться, и сервер падал, причем не сразу, а при попытке обращения к этому файлу?
Добавлено: 24.12.2014 10:52
HarpyWar опять пропал, и опять как-то проект пока подвис, у меня тоже как-то ни времени нет, ни сил, заниматься...
Наверно займусь в ближайшие дни допиливанием:
Во-первых, огромное спасибо за такую замечательную обертку для *alloc-free функций!
Есть план дополнить эту обертку:
1. вести связный список выделенных участков памяти.
Добавляя элементы в x*alloc, удаляя в free. При этом сохраняя имена функций/строки, где происходило выделение памяти. Это позволит отслеживать неосвобожденную память при завершении программы или по требованию.
2. выделять памяти не указанный size, а на несколько байт (килобайт, если большой кусок памяти) больше. Остаток дополнять контрольными данными, которые можно проверять по требованию.
Это позволит отслеживать переполнения, а также защищать данные от разрушения в случае, если переполнение все же произойдет.
3. добавить /команду для форсирования сброса в логи текущего состояния.
Добавлено: 25.12.2014 19:31
Значит, написал класс работы с памятью - сделал все по своему плану, кроме /команды для дампа. Пока дамп делается только при выходе. Пожалуй, единственное возможное нарушение - самописный вариант не привязывает передаваемую программе память к границам параграфа (или к чему там она может быть привязана). Очень сомневаюсь, что для данного проекта это имеет значение.
Издеваюсь над последней версией с github:
Получил массу интересных результатов:
1. до загрузки main, уже используется xalloc - загружается bigint и что-то еще, уже не помню...
Пришлось свой класс инициализировать тоже не в main, а раньше.
При завершении программы (в режиме - запустил сервер, подождал окончания загрузки, остановил) не выгружаются из памяти:
bigint.cpp, 140, 50
common/util.cpp, 582
bnetd/attr.h, 42, 45, 46
common/list.cpp, 39
bnetd/icons.cpp, 922, 986, 987, 754, 755 и т.д.
common/list.cpp, 39, 104
bnetd/anongame_infos.cpp, 420
bnetd/sql_common.cpp, 342
2. malloc чаще всего выделяет память, забитую нулями, но не всегда. Попробовал инициализировать ее не нулями - сразу при загрузке получил ошибку чтения конфига с не \0-терм именем.
Жестко инициализировал память нулями.
3. realloc. Обычно просто расширяет существующий диапазон, добавляя места к концу уже заполненного массива данных. Но не всегда. Попробовал реализовать realloc, всегда выделяющий память в другом месте и с пустыми данными. Программа не запустилась, выдав ошибку в /bnetd/ladder.cpp про "I found 0 for a level XP".
Добавил в realloc всегда копирование исходного буфера - все запустилось.
4. Выхода за границы выделенного мной диапазона (с перезаписью) пока программу вывести не удалось.
Пока все.
Идеи:
- запускаю программу на тестовом стенде в максимально жестком режиме, вылавливаю и исправляю все ошибки, которые удастся.
- добавляю вывод в логи ошибок про каждое невозможное в соответствии с логикой работы событие. Чтобы хотя бы оно писало, что указатель был нулевой, прежде чем упасть.
- гоняю программу под valgrind, до полного прояснения и исправления всех ошибок.
- запускаю программу в продакшен в максимально мягком режиме работы с памятью (инициализируя нулями, копируя realloc, выделяя больше памяти, чем нужно и т.п.) и запускаю народ. Жду падений и/или ошибок в логах.
Как-то так...
Добавлено: 29.12.2014 15:33
Да, проблема...
Решил использовать свой класс как сборщик мусора - чтобы он автоматом подчищал все, выделенное с помощью xmalloc - не тут-то было!
Оказалось, по всему проекту раскиданы всякие глобальные static и прочие объекты, которые инициализируются в непойми каком порядке, и соответственно, вызывающие xmalloc. Значит, надо проинициализироваться до них всех, а потом освободить память после их завершения.
А то получается, сначала я очищаю все выделенное программой, а потом valgrind находит еще 300+ неудаленных кусков памяти в 2-3 разных классах... А инициализация/деинициализация static-объектов как известно, происходит в порядке, на усмотрение компилятора: forum.sources.ru/index.php?showt … 8&st=0
И вот как мне загрузить свой менеджер памяти до них всех, соответственно, чтобы он выгрузился последним - это вопрос, и я понятия не имею, что с этим делать...