TL; DR: как автоматически добавить часы в gdb при вызове функции, чтобы я мог отладить некоторые повреждения памяти?
В настоящее время я имею дело с некоторым повреждением памяти в C ++
Я в основном наблюдаю 4-5 типов восстанавливающих сбоев — все они не имеют особого смысла, поэтому я предполагаю, что это должно быть связано с повреждением памяти.
Эти сбои происходят только на рабочем сервере, примерно каждые 2-5 часов.
Большинство из них состоят из доступа или передачи нулевого указателя, где он, возможно, вообще не существовал.
Одним из таких мест является лямбда-захват этого. (увидеть ниже)
Очевидно, посмотрел на дампы ядра и даже подключил GDB во время сбоя
Вальгринд: Я часами смотрел на несколько экземпляров Вальгринда, но безуспешно.
Включена защита стека gccs (-fstack-protector-all)
Я попытался просмотреть код & изменения, но мне было невозможно что-либо найти (всего 100 тыс. строк кода: «На главном компьютере изменилось 10 437 файлов, и было сделано 3 352 600 добавлений и 85 495 удалений». с момента последнего выпуска на производственном сервере). Возможно, я просто что-то пропустил или не посмотрел в правильные места — не могу сказать.
Использовал cppcheck, чтобы увидеть, было ли что-то явно не так с кодом
Если есть более простой / более прямой метод определения места коррупции, не стесняйтесь предлагать это тоже.
Давайте посмотрим на упрощенный код.
У меня есть класс, Socket, который управляет клиентским подключением.
Он построен примерно так
Listener::OnAccept(fd){
Socket* s = new Socket();
if (s->Setup(fd)){
// push into a vector and do some other things
}
}
Socket :: Setup вызывает (виртуальный) OnConnect класса Socket, который затем создает событие ping, используя лямбду:
Socket::OnConnect(){
m_pingEvent = new Event([this](Event* e){
if (!this->GotPong()){
// close connection
}else{
this->Ping();
}
}, 30 /*seconds*/, true /* loop */);
}
Событие принимает функцию std :: function в качестве обратного вызова
m_pingEvent удаляется в деструкторе (если он установлен), который отменяет событие, если оно запущено.
Что случается (редко), так это то, что лямбда вызывает Ping для nullptr, который вызывает m_pingPacket-> Send () для this = 0x1f8, что приводит к segfault.
Мой вопрос — или, скорее, мое предлагаемое решение — будет наблюдать за записанным этим указателем для записи, что определенно не должно произойти.
Есть только одна маленькая проблема с этим ..
Как бы я вообще наблюдал за таким большим количеством указателей, не добавляя каждый из них вручную? (около 400 одновременных соединений с большим количеством соединений)
Что касается захваченных данных, я обнаружил, что это в объекте __closure:
(gdb) frame 2
#2 0x081b9d63 in operator() (e=0x9b2a748, __closure=0xb5a8318)
at net/socket/Client.cpp:151
151 net/socket/Client.cpp: No such file or directory.
(gdb) ptype __closure
type = const struct {
net::socket::Client * const __this;
} * const
Что я могу получить при создании лямбды, просто переместив лямбду в «auto callback =», который будет иметь тип:
(gdb) info locals
callback = {__this = 0xb4dd0948}
(gdb) ptype callback
type = struct {
net::socket::Client * const __this;
}
(gdb) print callback
$1 = {__this = 0xb4dd0948}
(Это gcc версии 4.7.2 (Debian 4.7.2-5) для справки, может отличаться от других компиляторов / версий)
Незадолго до публикации я понял, что структура, вероятно, изменит адрес после перемещения в std :: function (это правильно?)
Я копался в «функциональном» заголовке gnu, но я пока не смог ничего найти, я буду продолжать искать (и обновлять это)
Еще одно примечание: я публикую это полное описание со всеми подробностями, включенными в случае, если у кого-то есть более простое решение для меня. (Проблема XY)
Редактировать:
(gdb) print *(void**)m_pingEvent->m_callback._M_functor._M_unused._M_object
$8 = (void *) 0xb4dd56d8
(gdb) print this
$4 = (net::socket::Client * const) 0xb4dd56d8
Нашел это 🙂
Edit2:
break net/socket/Client.cpp:158
commands
silent
watch -l m_pingEvent->m_callback._M_functor._M_unused._M_object
continue
end
Это имеет два недостатка: вы можете смотреть только 4 адреса одновременно & нет способа удалить часы, как только объект будет освобожден.
Су это непригодно.
Изменить 3:
Я разобрался, как сделать наблюдение, используя этот скрипт на python, который я написал (связывая его внешне, так как он довольно длинный): https://gist.github.com/imermcmaps/4a6d8a1577118645acf3
Следующая проблема имеет смысл вывода ..
Added watch 7 -> 0x10eb2200
Hardware watchpoint 7: -location m_pingEvent->m_callback._M_functor._M_unused._M_obj
Old value = (void *) 0x10eba4b0
New value = (void *) 0x10eba400
net::Packet::Packet (this=0x10eb1088) at ../shared/net/Packet.cpp:13
Как будто он говорит, что он изменился со старого значения, которое даже не должно быть исходным, так как я проверяю, совпадают ли указатель this и значение указателя, что они и делают.
Изменить 4 (yay):
Оказывается, часы не работают так, как я хочу.
Ручной захват адреса, а затем просмотр этого адреса, кажется, работает
Как автоматически добавить часы в GDB, когда функция вызывается так
Я могу отладить некоторые повреждения памяти?
Повреждение памяти часто обнаруживается после того, как некоторые модули загружены в ваш процесс. Поэтому ручная отладка может быть не очень полезна для реальных сложных проектов. Потому что любые сторонние модули / библиотеки, загруженные в ваш процесс, также могут привести к этой проблеме. Из вашего поста видно, что эта проблема не всегда воспроизводима, что указывает на то, что это может быть связано с проблемой многопоточности / синхронизации, которая приводит к некоторому повреждению памяти. Исходя из моего опыта, я настоятельно рекомендую вам сосредоточиться на воспроизведении проблемы в динамических инструментах (Valgrind/Helgrind).
Однако, как вы упомянули в своем вопросе, вы можете прикрепить свою программу с помощью Valgrind. Поэтому вы можете прикрепить свою программу (a.out) на тот случай, если вы этого не сделали.
$ valgrind --tool=memcheck --db-attach=yes ./a.out
Таким образом, Вальгринд автоматически прикрепите вашу программу в отладчике при обнаружении первой ошибки памяти, чтобы вы могли выполнить оперативную отладку (GDB). Похоже, что это лучший способ выяснить причину вашей проблемы.
Однако я думаю, что может быть какой-то сценарий гонки данных, который приводит к повреждению памяти. Так что вы можете использовать Helgrind проверить / найти проблему скачек / потоков данных, которая может привести к этой проблеме.
Для получения дополнительной информации об этом, вы можете обратиться к следующему сообщению: