Как отладить ошибки повреждения кучи?

Я отлаживаю (собственное) многопоточное приложение C++ в Visual Studio 2008. В кажущихся случайными случаях я получаю " Windows вызвала точку останова..."ошибка с примечанием, что это может быть связано с повреждением в куче. Эти ошибки не всегда приведет к сбою приложения сразу, хотя это, вероятно, к сбою вскоре после.

большая проблема с этими ошибками заключается в том, что они появляются только после того, как коррупция фактически произошла, что делает их очень трудными для отслеживания и отладки, особенно в многопоточном приложении.

  • какие вещи могут вызвать эти ошибки?

  • как их отлаживать?

советы, инструменты, методы, enlightments... будем приветствовать.

14 ответов


Средство Проверки Приложений в сочетании с инструменты отладки для Windows удивительная настройка. Вы можете получить оба как часть Windows Driver Kit или более легкий Windows SDK. (Узнал о верификаторе приложений при исследовании ранее вопрос о проблеме коррупции кучи.) Я использовал BoundsChecker и Insure++ (упомянутые в других ответах) в прошлом, хотя я был удивлен, сколько функциональности было в приложении Верификатор.

электрический забор (он же "efence"), dmalloc, отчет, и так далее все стоит упомянуть, но большинство из них намного проще запустить под *nix, чем Windows. Valgrind смехотворно гибок: я отладил большое серверное программное обеспечение со многими проблемами кучи, используя его.

когда все остальное не удается, вы можете предоставить свой собственный глобальный оператор new / delete и перегрузки malloc/calloc/realloc-как это сделать, будет немного отличаться в зависимости от компилятор и платформа, и это будет немного инвестиций, но он может окупиться в долгосрочной перспективе. Желаемый список функций должен выглядеть знакомым из dmalloc и electricfence, а удивительно отличная книга Написание Твердого Кода:

  • значения sentry: позвольте немного больше Космоса перед и после каждым аллок, уважая максимальное требование к выравнивания; заполните с волшебными числами (помощь улавливает переполнения буфера и ундерфлов, и случайный" дикий " указатель)
  • alloc fill: заполните новые выделения значением magic non-0 -- Visual C++ уже сделает это для вас в отладочных сборках (помогает поймать использование неинициализированных vars)
  • бесплатно заполнить: заполните освобожденную память значением magic non-0, предназначенным для запуска segfault, если он разыменован в большинстве случаев (помогает поймать болтающиеся указатели)
  • отложенная бесплатно: не возвращайте освобожденную память куча на некоторое время, держите его свободным заполненным, но недоступным (помогает поймать больше болтающихся указателей, ловит близкие двойные освобождения)
  • отслеживание: возможность записывать, где было сделано выделение, иногда может быть полезно

обратите внимание, что в нашей локальной домашней системе (для встроенной цели) мы сохраняем отслеживание отдельно от большинства других вещей, потому что накладные расходы во время выполнения намного выше.


Если вас интересует больше причины перегрузки этих функций/операторов распределения, взгляните на мой ответ на "любой причине перегрузить глобальный оператор new и delete?"; бесстыдная самореклама в сторону, он перечисляет другие методы, которые полезны при отслеживании ошибок повреждения кучи, а также другие применимые инструменты.


вы можете обнаружить много проблем с повреждением кучи, включив кучу страниц для вашего приложения . Для этого нужно использовать gflags.exe, который входит в состав Инструменты Отладки Для Windows

Запустить Gflags.exe и в параметрах файла изображения для исполняемого файла установите флажок "Включить кучу страниц".

теперь перезапустите exe и присоединитесь к отладчику. При включенной куче страниц приложение будет разбиваться на отладчик при любом повреждении кучи происходит.



чтобы действительно замедлить работу и выполнить много проверки времени выполнения, попробуйте добавить следующее в верхней части вашего main() или эквивалент в Microsoft Visual Studio C++

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF );

какие вещи могут вызвать эти ошибки?

делать непослушные вещи с памятью, например, запись после конца буфера или запись в буфер после того, как он был освобожден обратно в кучу.

как их отлаживать?

используйте инструмент, который добавляет автоматическую проверку границ к исполняемому файлу: например, valgrind в Unix или такой инструмент, как BoundsChecker (Википедия также предлагает очистить и застраховать++) на Окна.

остерегайтесь, что это замедлит ваше приложение, поэтому они могут быть непригодны для использования, если ваше приложение soft-real-time.

другим возможным средством отладки/инструментом может быть HeapAgent MicroQuill.


один быстрый совет, который я получил от обнаружение доступа к освобожденной памяти это:

Если вы хотите найти ошибку быстро, не проверяя каждый оператор, который обращается к памяти блок, вы можете установить указатель памяти недопустимое значение освободив блок:

#ifdef _DEBUG // detect the access to freed memory
#undef free
#define free(p) _free_dbg(p, _NORMAL_BLOCK); *(int*)&p = 0x666;
#endif

лучший инструмент, который я нашел полезным и работал каждый раз, - это обзор кода (с хорошими рецензентами кода).

кроме обзора кода, я бы сначала попробовал Куча Странице. Куча страниц занимает несколько секунд, чтобы настроить и, если повезет, это может определить вашу проблему.

Если не повезло с кучей страниц, скачать инструменты отладки для Windows от Microsoft и научиться использовать WinDbg. Извините, не смог дать вам более конкретную помощь, но отладка многопоточной кучи коррупция-это больше искусство, чем наука. Google для "WinDbg куча коррупции", и вы должны найти много статей на эту тему.


вы также можете проверить, связываетесь ли вы с динамической или статической библиотекой времени выполнения C. Если ваши DLL-файлы связаны со статической библиотекой времени выполнения C, то DLL-файлы имеют отдельные кучи.

следовательно, если вы должны были создать объект в одной DLL и попытаться освободить его в другой DLL, вы получите то же сообщение, которое вы видите выше. Эта проблема упоминается в другом вопросе переполнения стека,освобождение памяти, выделенной в другом Dll файлы.


какой тип функций распределения вы используете? Недавно я столкнулся с аналогичной ошибкой, используя функции выделения стиля кучи*.

оказалось, что я ошибочно создавал кучу с . Это по существу делает функции кучи работать без потокобезопасности. Это улучшение производительности при правильном использовании, но никогда не должно использоваться, если вы используете HeapAlloc в многопоточной программе [1]. Я упоминаю об этом только потому, что в вашем посте упоминается, что у вас есть многопоточное приложение. Если вы используете HEAP_NO_SERIALIZE anywhere, удалите это, и это, вероятно, исправит вашу проблему.

[1] есть определенные ситуации, когда это законно, но для этого требуется сериализовать вызовы кучи* и, как правило, не относится к многопоточным программам.


Если эти ошибки происходят случайным образом, существует высокая вероятность того, что вы столкнулись с data-races. Пожалуйста, проверьте: вы изменяете указатели общей памяти из разных потоков? Intel Thread Checker может помочь обнаружить такие проблемы в многопоточной программе.


в дополнение к поиску инструментов, подумайте о поиске вероятного виновника. Есть ли какой-либо компонент, который вы используете, возможно, не написанный вами, который не был разработан и протестирован для работы в многопоточной среде? Или просто один, который вы не знаю работать в такой среде.

в последний раз это случилось со мной, это был родной пакет, который успешно использовался с пакетными заданиями в течение многих лет. Но это был первый раз в этой компании, что она был использован из веб-службы .NET (которая является многопоточным). Вот и все - они солгали о том, что код является потокобезопасным.


вы можете использовать макросы проверки кучи VC CRT для функции _crtsetdbgflag: _CRTDBG_CHECK_ALWAYS_DF или _CRTDBG_CHECK_EVERY_16_DF.._CRTDBG_CHECK_EVERY_1024_DF.


Я хотел бы добавить свой опыт. В последние несколько дней я решил экземпляр этой ошибки в своем приложении. В моем конкретном случае, ошибки в коде были:

  • удаление элементов из коллекции STL во время итерации по нему (я считаю, что в Visual Studio есть флаги отладки, чтобы поймать эти вещи; я поймал его во время обзора кода)
  • этот более сложный, я разделю его на этапы:
    • из собственного потока C++ перезвоните в управляемый код
    • в управляемой земле, звоните Control.Invoke и dispose управляемый объект, который обертывает собственный объект, к которому принадлежит обратный вызов.
    • поскольку объект все еще жив внутри собственного потока (он останется заблокированным в обратном вызове до Control.Invoke заканчивается). Я должен уточнить, что я использую boost::thread, поэтому я использую функцию-член в качестве функции потока.
    • решение используйте Control.BeginInvoke (мой GUI сделан с Winforms) вместо этого, чтобы собственный поток мог конец перед уничтожением объекта (цель обратного вызова-точно уведомить, что поток закончился и объект может быть уничтожен).

У меня была аналогичная проблема - и она появилась совершенно случайно. Возможно, что-то было повреждено в файлах сборки, но я в конечном итоге исправил его, сначала очистив проект, а затем восстановив.

Итак, в дополнение к другим ответам:

какие вещи могут вызвать эти ошибки? Что-то повреждено в файле сборки.

как их отлаживать? Очистка проекта и восстановление. Если это исправлено, это, вероятно, было проблема.