Приложение в Qt, довольно большое, многопоточное (40+ разных нитей). Словили сегфолт, долго ловил, в итоге слёт удалось стабилизировать и локализовать.
В момент слёта оказался уничтожен стек вызовов, выглядел пример так:
Класс, функции которого вызывались, оказался также уничтожен, в отладчике его поля все показывались как <недоступно>, также поменялся адрес this.
Поиск момента, в который произошло разрушение стека и this'а дал следующее:
int MyClass::Foo(int arg)
{
...
return return_value;
}
void MyClass::Foo2()
{
...
Foo(i);
int v = 0;
}
В момент return return_value; в функции Foo() стек и this были ещё целы (точка останова на return'е), сразу после выхода из функции, в точке останова на int v = 0;, стек уже убит (как и this).
Выглядит так, будто где-то случилось переполнение стековых массивов и в итоге перетёрлись адреса возврата, плюс произошёл сдвиг адреса this. Попробую ещё вручную исправить адрес this'а, вычислив величину смещения, позже напишу, что получилось.
Проблема сложная. Посоветуйте, что можно пытаться искать? Что ковырять? Думал где-то переполнился стек, но это сомнительно — повышал размер до 128 мб (штатный стек у нас 8 мб), ошибка не ушла.
Сравнил адреса до и после разрушения, после разрушения адрес this либо вообще отсутствовал (в отладчике было пустое поле), либо становился равным 0x500000000000ace3.
Здравствуйте, Кузнец, Вы писали:
К>Приложение в Qt, довольно большое, многопоточное (40+ разных нитей). Словили сегфолт, долго ловил, в итоге слёт удалось стабилизировать и локализовать. К>В момент слёта оказался уничтожен стек вызовов, выглядел пример так: К>...
А не может быть того, что в одном потоке идёт вызов метода, а в другом в этот момент класс уничтожается? Класс точно спроектирован, как потокобезопасный?
Здравствуйте, Кузнец, Вы писали:
К>Приложение в Qt, довольно большое, многопоточное (40+ разных нитей). Словили сегфолт, долго ловил, в итоге слёт удалось стабилизировать и локализовать.
К>В момент слёта оказался уничтожен стек вызовов, выглядел пример так:
К>Выглядит так, будто где-то случилось переполнение стековых массивов и в итоге перетёрлись адреса возврата, плюс произошёл сдвиг адреса this. Попробую ещё вручную исправить адрес this'а, вычислив величину смещения, позже напишу, что получилось.
К>Проблема сложная. Посоветуйте, что можно пытаться искать? Что ковырять? Думал где-то переполнился стек, но это сомнительно — повышал размер до 128 мб (штатный стек у нас 8 мб), ошибка не ушла.
А Address / Memory / Thread Sanitizer никаких ошибок не находят?
Здравствуйте, Кузнец, Вы писали:
К>Проблема сложная. Посоветуйте, что можно пытаться искать? Что ковырять? Думал где-то переполнился стек, но это сомнительно — повышал размер до 128 мб (штатный стек у нас 8 мб), ошибка не ушла.
Если сразу после return все ломается, значит вы внутри функции "проехались" по стеку и возврат происходит в случайное место. this может быть в регистре и также быть не правильно восстановлен из стека. Стеки у потоков разнесены, по-этому, с большой вероятностью, проблема где-то рядом с тем местом, где return. Падение, также, может быть связано с многопоточностью и синхронизацией, но тогда, обычно, проявляется в разных местах, а не в одном и том же, как у вас.
К>В момент return return_value; в функции Foo() стек и this были ещё целы (точка останова на return'е), сразу после выхода из функции, в точке останова на int v = 0;, стек уже убит (как и this).
Похоже, при выполнении деструкторов локальных переменных MyClass::Foo кто-то расстрелял память, повредив стек ниже себя.
В результате поврежден указатель на точку вызова функции (куда возвращаться по return).
Есть ли там объекты с нетривиальными деструкторами?
Объекты стандартной библиотеки (std::string, std::vector) тоже могут расстрелять память в деструкторе, если находятся в несогласованном состоянии (т.е. кто-то предварительно расстрелял их самих).
Если падение воспроизводится в отладчике, то находясь в точке останова на return, в деструкторы локальных объектов можно зайти командой "Step into (F11)", или поставить точки останова на места объявления локальных переменных...
Если в отладчике не воспроизводится, проверить гипотизу можно добавив буфер:
int MyClass::Foo(int arg)
{
char bufer[1024];
...
return return_value;
}
Расстрел памяти, с высокой вероятностью, придется на него, а не на системную информацию на стеке.
Здравствуйте, Кузнец, Вы писали:
К>Проблема сложная. Посоветуйте, что можно пытаться искать? Что ковырять? Думал где-то переполнился стек, но это сомнительно — повышал размер до 128 мб (штатный стек у нас 8 мб), ошибка не ушла.
Версии:
1. memcpy куда-то не туда или не того размера. Посмотри на работу с массивами и на выравнивание данных в структурах.
2. Неинициализированные переменные (или наоборот — указатель инициализирован неправильным значением).
3. использование функций printf, sprintf и т.п., но спецификаторы не соответствуют данным, из-за этого портится стек.
Если проблема повторяется — скорее всего портится стек в этом потоке. Иначе — ошибка может быть в любом другом потоке и проявится в другом месте.
Здравствуйте, Кузнец, Вы писали:
К>Проблема сложная. Посоветуйте, что можно пытаться искать? Что ковырять? Думал где-то переполнился стек, но это сомнительно — повышал размер до 128 мб (штатный стек у нас 8 мб), ошибка не ушла.
Недавно ловил похожее. Причина: мой код звал либу, эта либа делала memcpy() для клонирования одной структуры. Беда была в том, что мой код юзал старые хидеры либы, поэтому аллоцировал меньший размер. А либа в тот день была сбилжена новая, при копировании ориентировалась на свои свежие хидеры, с которыми собрана. В результате скопировала с частичным затиранием дальнейшей части стека (благо у меня там лежал индекс для обращения к массиву, и при затирании туда залилось здоровое число, которое практически тут же ассёртнул эррэй-гвард).
Здравствуйте, Кузнец, Вы писали:
К>Проблема сложная. Посоветуйте, что можно пытаться искать? Что ковырять? Думал где-то переполнился стек, но это сомнительно — повышал размер до 128 мб (штатный стек у нас 8 мб), ошибка не ушла.
Надо понимать, что то, что программа где-то падает в каком-то конкретном месте, не обязательно означает, что это место программы как-то связано с причиной этого падения.
Прежде всего, нужно собрать debug info, чтобы было куда глянуть при случае.
Далее, если ты знаешь, как проблему воспроизвести, собери отладочную версию (без оптимизации), и попробуй воспроизвести там.
Если воспроизведётся -- будет гораздо легче.
Спасибо за полезные ответы. Почти всё было применено, в итоге проблему нашли. Всё оказалось просто до безобразия — в одном месте, вообще другом, залезли вне диапазона двумерного массива, который был размещён на стеке, чаще всего это портило лишь немного данных, но при определённых условиях это разрушало стек, в итоге программа начинала работать (почти) непредсказуемо, и падала вообще в другом месте.
Причина того, что залезли вне диапазона также было в совершенно другом месте, задолго до этого обращения
Итог: программа падала в некотором месте, потому что был убит стек во втором месте (совершенно далёком от первого), а стек был убит по причине ошибки в третьем месте (ещё более далёком).
Нашли это безобразие аккуратным анализом хода выполнения программы (пока анализировал простыни чужого кода, не раз проклял break/continue, применяемые везде, где только возможно, ибо шагание по точкам останова в таком коде так себе удовольствие). Что забавно, фикс ошибки — изменение одной буквы во всём коде проекта
А ошибка была при копипасте (которую, к слову, делал не я), в последней строке поменяли dR на dB во всех местах, кроме одного.
Мораль — если копипастите, внимательно проверяйте результат, и особенно последнюю сточку.
PS. Valgrind запустить можно было, но он бы искал проблему 2-3 дня минимум, и не факт что нашёл бы, так как приложение очень тяжёлое по ресурсам, а падение было далеко не при запуске, в итоге пока valgrind дочапает до проблемного места, пройдёт немало времени, а то и вообще комп не выдержал бы... Поэтому искал без него.
К>PS. Valgrind запустить можно было, но он бы искал проблему 2-3 дня минимум, и не факт что нашёл бы, так как приложение очень тяжёлое по ресурсам, а падение было далеко не при запуске, в итоге пока valgrind дочапает до проблемного места, пройдёт немало времени, а то и вообще комп не выдержал бы... Поэтому искал без него.
Так поэтому и нужно sanitizer использовать, он на порядок быстрее за счет того,
что выполняется не на аналоге виртуальной машины (valgrind), а напрямую на процессоре,
не даром ребята из google эти santizer сделали.
Здравствуйте, Zhendos, Вы писали:
Z>Так поэтому и нужно sanitizer использовать, он на порядок быстрее за счет того, Z>что выполняется не на аналоге виртуальной машины (valgrind), а напрямую на процессоре, Z>не даром ребята из google эти santizer сделали.
Спасибо, попробую разобраться с ними, если они и правда шустрее и также эффективны, то лучше работать с ними.
Здравствуйте, Кузнец, Вы писали:
К>Здравствуйте, Zhendos, Вы писали:
Z>>Так поэтому и нужно sanitizer использовать, он на порядок быстрее за счет того, Z>>что выполняется не на аналоге виртуальной машины (valgrind), а напрямую на процессоре, Z>>не даром ребята из google эти santizer сделали.
К>Спасибо, попробую разобраться с ними, если они и правда шустрее и также эффективны, то лучше работать с ними.
К>На них есть русскоязычная документация? А то я пока только английскую нашёл (https://github.com/google/sanitizers/wiki).
Не знаю насчет русской документации, но к ним особая документация
и не нужна добавляете например -fsanitize=address в опции gcc/clang
пересобираете свое приложение и запускаете, если что не так
приложение упадет и в стандартный stdout/stderr напишет что и почему,
так же другие sanitizer подключаются. Возможно man gcc или man clang
есть на русском.
Здравствуйте, Zhendos, Вы писали:
Z>Не знаю насчет русской документации, но к ним особая документация Z>и не нужна добавляете например -fsanitize=address в опции gcc/clang Z>пересобираете свое приложение и запускаете, если что не так Z>приложение упадет и в стандартный stdout/stderr напишет что и почему, Z>так же другие sanitizer подключаются. Возможно man gcc или man clang Z>есть на русском.
К>Собрался в релизе — вывод от санитайзера был, в дебуге ни в какую не хочет, где я мог накосячить?
В дебуге тоже удалось получить вывод, но почему-то он не отличается от вывода в релизе, видимо санитайзер не умеет показывать место в коде, где произошла ошибка, только функцию. Но всё равно интересная штука.