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

165

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

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

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

  • Как мне их отладить?

Советы, инструменты, методы, просветления ... приветствуются.

Питер Мортенсен
источник

Ответы:

128

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

Стоит упомянуть Electric Fence (он же «efence»), dmalloc , valgrind и т. Д., Но большинство из них гораздо проще запустить под * nix, чем Windows. Valgrind смехотворно гибок: я отлаживал программное обеспечение для больших серверов со многими проблемами кучи, используя его.

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

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

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


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


Поскольку я продолжаю находить здесь свой собственный ответ при поиске значений alloc / free / fence, которые использует MS, вот еще один ответ, который охватывает значения заполнения dbgheap от Microsoft .

Leander
источник
3
Одна маленькая вещь, которую стоит отметить в Application Verifier: вы должны зарегистрировать символы Application Verifier перед символами сервера символов Microsoft в вашем пути поиска символов, если вы используете это ... Мне потребовался небольшой поиск, чтобы выяснить, почему! Avrf не был найти символы, которые ему нужны.
Леандер
Application Verifier оказал большую помощь, и в сочетании с некоторыми предположениями я смог решить проблему! Большое спасибо и всем остальным за то, что подняли полезные вопросы.
Нужно ли использовать Application Verifier с WinDbg или он должен работать с отладчиком Visual Studio? Я пытался использовать его, но он не вызывает каких-либо ошибок или, по-видимому, ничего делать, когда я отлаживаю в VS2012.
Натан Рид
@NathanReed: Я считаю, что это работает и с VS - см. Msdn.microsoft.com/en-us/library/ms220944(v=vs.90).aspx - хотя обратите внимание, что эта ссылка для VS2008, я не уверен в более поздних версиях. Память немного нечеткая, но я думаю, что когда у меня возникла проблема в ссылке «предыдущий вопрос», я просто запустил Application Verifier и сохранил параметры, запустил программу, а когда она потерпела крах, я выбрал VS для отладки. AV только что сделал это сбой / утверждать ранее. Насколько я знаю, команда! Avrf специфична для WinDbg. Надеюсь, другие могут предоставить больше информации!
Леандер
Спасибо. Я на самом деле решил свою первоначальную проблему, и оказалось, что это не куча повреждений, а что-то еще, так что, вероятно, это объясняет, почему App Verifier ничего не нашел. :)
Натан Рид
35

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

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

Теперь перезапустите ваш exe-файл и подключитесь к отладчику. С включенным Page Heap приложение будет работать в режиме отладки при любом повреждении кучи.

Canopus
источник
да, но как только я получаю этот вызов функции в моем дампе стека вызовов (после сбоя повреждения памяти): wow64! Wow64NotifyDebugger, что я могу сделать? Я до сих пор не знаю, что идет не так в моей заявке
Guillaume07
Только что попробовал gflags для отладки повреждения кучи здесь, ОЧЕНЬ полезный маленький инструмент, очень рекомендуется. Оказалось, что я получаю доступ к освобожденной памяти, которая, когда получает инструментальные средства от gflags, сразу же врывается в отладчик ... Удобно!
Дэйв Ф
Отличный инструмент! Просто нашел ошибку, на которую я охотился несколько дней, потому что Windows не говорит адрес повреждения, только то, что «что-то» не так, что не очень полезно.
Devolus
Немного опоздал на вечеринку, но я заметил значительное увеличение использования памяти моим приложением, которое я отлаживал, когда включил Page Heap. К сожалению, до того момента (32-битному) приложению не хватило памяти до того, как было обнаружено обнаружение повреждения кучи. Есть идеи, как решить эту проблему?
uceumern
13

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

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF );
Дэйв Ван Вагнер
источник
8

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

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

Как мне их отладить?

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

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

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

ChrisW
источник
1
Перестройка приложения с использованием среды отладки (/ MDd или / MTd flag) была бы моим первым шагом. Они выполняют дополнительные проверки в malloc и free и часто не эффективны для сужения местоположения ошибки (ов).
Занятый русский
HeapAgent от MicroQuill: об этом не так много написано или слышно, но в случае повреждения кучи он должен быть в вашем списке.
Самрат Патил
1
BoundsChecker отлично работает в качестве дымового теста, но даже не думайте о запуске программы под ним, пытаясь запустить эту программу также в производственном режиме. Замедление может быть от 60 до 300 раз, в зависимости от того, какие опции вы используете, и от того, используете ли вы функцию инструментария компилятора. Отказ от ответственности: я один из тех, кто поддерживает продукт для Micro Focus.
Рик Папо
8

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

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

#ifdef _DEBUG // detect the access to freed memory
#undef free
#define free(p) _free_dbg(p, _NORMAL_BLOCK); *(int*)&p = 0x666;
#endif
StackedCrooked
источник
5

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

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

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

Шинг Йип
источник
4

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

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

dreadpirateryan
источник
3

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

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

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

JaredPar
источник
Да: посмотрите на параметры компилятора / сборки приложения и убедитесь, что оно собрано для связи с «многопоточной» версией библиотеки времени выполнения C.
ChrisW
@ChrisW для API стилей HeapAlloc это другое. На самом деле это параметр, который можно изменить во время создания кучи, а не во время соединения.
JaredPar
Ой. Мне не пришло в голову, что ОП может говорить об этой куче, а не о куче в ЭЛТ.
ChrisW
@ChrisW, вопрос довольно расплывчатый, но я только что натолкнулся на проблему, которую подробно описал ~ 1 неделю назад, так что это свежо в моей памяти.
JaredPar
3

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

Владимир Обризан
источник
1

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

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

Джон Сондерс
источник
1

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

KindDragon
источник
0

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

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

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

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

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

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

Marty
источник