Как отладить ошибки повреждения кучи?
Я отлаживаю (собственное) многопоточное приложение C++ в Visual Studio 2008. На, казалось бы, случайных случаев, я получаю "окна, вызвал брейк-пойнт..."ошибка с примечанием, что это может быть связано с повреждением в куче. Эти ошибки не всегда приведут к сбою приложения сразу,хотя это, вероятно, произойдет вскоре после этого.
большая проблема с этими ошибками заключается в том, что они появляются только после того, как коррупция действительно произошла, что делает их очень трудными для отслеживания и отладки, особенно в многопоточных приложениях.
какие вещи могут вызвать эти ошибки?
как их отлаживать?
советы, инструменты, методы, просвещение... будем приветствовать.
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 и присоедините к отладчику. При включенной куче страниц приложение будет разбиваться на отладчик при любом повреждении кучи происходит.
очень актуальная статья отладка повреждения кучи с помощью Application Verifier и Debugdiag.
чтобы действительно замедлить работу и выполнить много проверок во время выполнения, попробуйте добавить следующее в верхней части вашего
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) вместо того, чтобы родной поток может завершите до того, как объект будет уничтожен (цель обратного вызова точно уведомляет, что поток закончился и объект может быть уничтожен).
У меня была аналогичная проблема - и она выскочила совершенно случайно. Возможно, что-то было повреждено в файлах сборки, но я в конечном итоге исправил его, сначала очистив проект, а затем перестроив.
таким образом, в дополнение к другим ответам:
какие вещи могут вызвать эти ошибки? Что-то повреждено в файле сборки.
как их отлаживать? Очистка проекта и перестройка. Если это исправлено, это, вероятно, было проблема.