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


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

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

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

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

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

14 150

14 ответов:

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

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

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

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

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


Если вы заинтересованы в более причины перегрузки этих функций/операторов распределения, взгляните на мой ответ на вопрос " есть ли причина для перегрузки глобального оператора 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 (Википедия также предлагает очистить и застраховать++) на Окна.

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

другим возможным средством отладки / инструментом может быть 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 heap corruption" и вы должны найти много статей на эту тему.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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