Прежде всего, я понимаю, что это не идеальный вопрос в стиле вопросов и ответов с абсолютным ответом, но я не могу придумать какую-либо формулировку, чтобы она работала лучше. Я не думаю, что есть абсолютное решение для этого, и это одна из причин, почему я публикую это здесь вместо переполнения стека.
За последний месяц я переписывал довольно старый кусок серверного кода (mmorpg), чтобы он был более современным и его было легче расширять / мод. Я начал с сетевой части и внедрил стороннюю библиотеку (libevent), чтобы обрабатывать вещи для меня. Со всеми ре-факторингом и изменениями кода я где-то привел к повреждению памяти, и я изо всех сил пытался выяснить, где это происходит.
Кажется, я не могу надежно воспроизвести его в моей среде dev / test, даже при реализации примитивных ботов для имитации некоторой нагрузки я больше не получаю сбои (я исправил проблему libevent, которая вызывала некоторые вещи)
Я пытался до сих пор:
Valgrinding черт из этого - нет недействительных записей, пока вещь не выходит из строя (что может занять 1+ дня в производстве ... или просто час), что действительно сбивает меня с толку, конечно, в какой-то момент он получит доступ к недействительной памяти и не перезапишет материал шанс? (Есть ли способ «разложить» диапазон адресов?)
Инструменты для анализа кода, а именно coverity и cppcheck. В то время как они указали на некоторые ... гадости и крайние случаи в коде, не было ничего серьезного.
Запись процесса, пока он не завершится с помощью gdb (через undodb), а затем работа в обратном направлении. Это / звучит / как будто это должно быть выполнимо, но я либо заканчиваю тем, что ломаю gdb, используя функцию автозаполнения, либо попадаю в какую-то внутреннюю структуру libevent, где я теряюсь, так как слишком много возможных ветвей (одно повреждение вызывает другое, и так на). Я думаю, было бы неплохо, если бы я мог видеть, к чему изначально принадлежит указатель / где он был размещен, что позволило бы устранить большинство проблем ветвления. Я не могу запустить valgrind с помощью undodb, и нормальная запись GDB слишком медленная (если это работает даже в сочетании с valgrind).
Обзор кода! Я сам (тщательно) и несколько друзей просматриваем мой код, хотя я сомневаюсь, что он был достаточно тщательным. Я думал о том, чтобы, возможно, нанять разработчика, чтобы сделать со мной обзор / отладку кода, но я не могу позволить себе вкладывать в это слишком много денег, и я не знаю, где искать кого-то, кто хотел бы работать за небольшую - к-нет денег, если он не найдет проблему или кто-то квалифицируется вообще.
Я должен также отметить: я обычно получаю последовательные следы. Есть несколько мест, где происходит сбой, в основном связанный с тем, что класс сокета как-то повреждается. Будь то недопустимый указатель, указывающий на что-то, что не является сокетом, или сам класс сокета перезаписывается (частично?) Бредом. Хотя я подозреваю, что там больше всего происходит сбой, так как это одна из наиболее часто используемых частей, поэтому используется первая испорченная память.
В целом, этот вопрос занимал меня почти 2 месяца (время от времени, больше хобби), и это действительно расстраивает меня до такой степени, что я становлюсь раздражительным IRL и думаю о том, чтобы просто сдаться. Я просто не могу думать о том, что еще я должен сделать, чтобы найти проблему.
Есть ли полезные методы, которые я пропустил? Как ты с этим справляешься? (Это не может быть настолько распространенным, так как об этом не так много информации ... или я просто слепой?)
Редактировать:
Некоторые спецификации на случай, если это имеет значение:
Использование c ++ (11) через gcc 4.7 (версия предоставлена debian wheezy)
Кодовая база составляет около 150 тыс. Строк
Редактировать в ответ на сообщение david.pfx: (извините за медленный ответ)
Ведете ли вы тщательный учет аварий, чтобы искать закономерности?
Да, у меня все еще есть свалки недавних аварий, лежащих вокруг
Несколько мест действительно похожи? В каком смысле?
Ну, в самой последней версии (кажется, они меняются всякий раз, когда я добавляю / удаляю код или изменяю связанные структуры), он всегда попадет в метод таймера предмета. В основном у элемента есть определенное время, после которого он истекает, и он отправляет обновленную информацию клиенту. Недопустимый указатель сокета будет в (все еще допустимом, насколько я могу судить) классе Player, в основном связанном с этим. Я также испытываю множество сбоев на этапе очистки после нормального завершения работы, когда он уничтожает все статические классы, которые не были явно уничтожены ( __run_exit_handlers
в обратном следе). В основном это std::map
один класс, предполагающий, что это только первое, что приходит на ум.
Как выглядят поврежденные данные? Нули? Ascii? Узоры?
Я не нашел никаких паттернов, мне кажется, что-то случайное. Трудно сказать, так как я не знаю, где началась коррупция.
Это связано с кучей?
Это полностью связано с кучей (я включил защиту стека gcc, и это ничего не перехватило).
Коррупция случается после
free()
?
Тебе придется немного углубиться в это. Вы имеете в виду наличие указателей на уже свободные объекты? Я устанавливаю каждую ссылку на ноль, как только объект разрушается, поэтому, если я что-то не пропустил, нет. Это должно появиться в Valgrind, хотя это не так.
Есть ли что-то особенное в сетевом трафике (размер буфера, цикл восстановления)?
Сетевой трафик состоит из необработанных данных. Таким образом, массивы char (u) intX_t или pack (для удаления заполнения) создают более сложные вещи, каждый пакет имеет заголовок, состоящий из идентификатора и самого размера пакета, который проверяется на соответствие ожидаемому размеру. Они имеют размер около 10-60 байт, при этом размер самого большого (внутреннего загрузочного пакета, запускаемого один раз при запуске) составляет несколько мегабайт.
Много-много продукции утверждает. Сбой рано и предсказуемо, прежде чем ущерб распространяется.
Однажды у меня был сбой, связанный с std::map
коррупцией, у каждой сущности есть карта своего «вида», каждая сущность, которая может ее видеть, и наоборот, находится в этом. Я добавил 200-байтовый буфер впереди и после, заполнил его 0x33 и проверял его перед каждым доступом. Коррупция волшебным образом исчезла, я должен был что-то переделать, что сделало ее чем-то другим.
Стратегическое ведение журнала, чтобы вы точно знали, что происходило прямо перед этим. Добавьте в журнал, как вы получите ближе к ответу.
Это работает .. до некоторой степени.
В отчаянии, вы можете сохранить состояние и автоматический перезапуск? Я могу вспомнить несколько программных продуктов, которые это делают.
Я немного делаю это. Программное обеспечение состоит из основного процесса «кеширования» и некоторых других рабочих, которые все получают доступ к кешу для получения и сохранения содержимого. Так что в случае аварии я не теряю большого прогресса, он по-прежнему отключает всех пользователей и так далее, это определенно не решение.
Параллельность: многопоточность, условия гонки и т. Д.
Существует поток mysql для выполнения «асинхронных» запросов, но все это остается нетронутым и передает информацию только классу базы данных через функции со всеми блокировками.
Прерывания
Есть таймер прерывания для предотвращения его блокировки, который просто прерывается, если он не завершил цикл в течение 30 секунд, хотя этот код должен быть безопасным:
if (!tics) {
abort();
} else
tics = 0;
тики, volatile int tics = 0;
которые увеличиваются каждый раз, когда цикл завершен. Старый код тоже.
события / обратные вызовы / исключения: состояние повреждения или стек непредсказуемо
Используется множество обратных вызовов (асинхронный сетевой ввод / вывод, таймеры), но они не должны делать ничего плохого.
Необычные данные: необычные входные данные / время / состояние
У меня было несколько крайних случаев, связанных с этим. Отключение сокета во время обработки пакетов привело к доступу к nullptr и тому подобное, но их было легко обнаружить до сих пор, поскольку каждая ссылка очищается сразу после сообщения самому классу, что это сделано. (Само разрушение обрабатывается циклом, удаляющим все разрушенные объекты каждый цикл)
Зависимость от асинхронного внешнего процесса.
Хотите разработать? Это в некоторой степени, процесс кэширования, упомянутый выше. Единственное, что я мог себе представить, - это то, что он недостаточно быстро завершает работу и использует мусорные данные, но это не тот случай, поскольку он также использует сеть. Та же модель пакета.
/analyze
) и Apple Malloc и Scribble. Вам также следует использовать как можно больше компиляторов, используя как можно больше стандартов, потому что предупреждения компилятора являются диагностическими и со временем становятся лучше. Здесь нет серебряной пули, и один размер подходит не всем. Чем больше инструментов и компиляторов вы используете, тем более полный охват, потому что у каждого инструмента есть свои сильные и слабые стороны.