Как отлаживать ошибки коррупции кучи?

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

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

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

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

Подсказки, инструменты, методы, просветы... приветствуются.

Ответ 1

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

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

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

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

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


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

Ответ 2

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

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

Теперь перезапустите exe и присоедините его к отладчику. С включенной функцией "Куча страниц" приложение распадается на отладчик всякий раз, когда происходит куча кучи.

Ответ 4

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

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF );

Ответ 5

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

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

Как отладить их?

Используйте инструмент, который добавляет автоматическую проверку границ вашему исполняемому файлу: например, valgrind в Unix или инструмент, такой как BoundsChecker (Wikipedia предлагает также Purify and Insure ++) в Windows.

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

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

Ответ 6

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

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

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

Ответ 7

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

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

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

Ответ 8

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

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

Ответ 9

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

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

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

Ответ 10

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

Ответ 11

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

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

Ответ 12

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

Ответ 13

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

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

Ответ 14

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

Так что в дополнение к другим приведенным ответам:

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

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