Повреждение кучи под Win32; как найти?


я работаю над многопоточный C++ приложение, которое развращает кучу. Обычные инструменты для обнаружения этого повреждения кажутся неприменимыми. Старые сборки (18 месяцев) исходного кода демонстрируют то же поведение, что и самый последний релиз, поэтому это было вокруг в течение длительного времени и просто не было замечено; с другой стороны, исходные дельты не могут быть использованы для идентификации, когда ошибка была введена - есть большое изменений кода в хранилище.

запрос на сбой behaviuor должен генерировать пропускную способность в этом системном сокете передачи данных, которые munged во внутреннее представление. У меня есть набор тестовых данных, которые будут периодически вызывать исключение приложения (различные места, различные причины - в том числе сбой выделения кучи, таким образом: повреждение кучи).

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

теперь вот в чем загвоздка:
Когда он запускается в облегченной среде отладки (скажем Visual Studio 98 / AKA MSVC6) повреждение кучи достаточно легко воспроизвести - десять или пятнадцать минут проходят, прежде чем что-то не удается ужасно и исключения, как alloc; при работе в сложной среде отладки (Rational Purify,VS2008/MSVC9 или даже Microsoft Application Verifier) система становится memory-speed bound и не падает (Memory-bound: CPU не становится выше 50%, свет диска не горит, программа идет так быстро, как может, коробка потребляет 1.3G 2G ОЗУ). Итак,у меня есть выбор между возможностью воспроизвести проблему (но не идентифицировать причину) или возможностью идентифицировать причину или проблему, которую я не могу воспроизвести.

мои текущие лучшие догадки о том, где дальше это:

  1. получим окно безумно грунты (чтобы заменить текущее окно Дэв: 2 ГБ оперативной памяти в E6550 Core2 Duo); это позволит повторить сбой, вызывающий неправильное поведение при работе в мощной среде отладки; или
  2. операторы переписать new и delete использовать VirtualAlloc и VirtualProtect чтобы пометить память как доступную только для чтения, как только это будет сделано. Беги под MSVC6 и пусть ОС поймает плохого парня, который пишет в свободную память. Да, это признак того, что отчаяние: кто, черт возьми, переписывает new и delete?! Интересно, будет ли это так же медленно, как под Purify et al.

и, нет: грузить с очищает встроенное инструментирование нет варианта.

коллега просто прошел мимо и спросил "переполнение стека? Мы получаем переполнение стека сейчас?!?"

а теперь вопрос:как мне найти корруптор кучи?


обновление: балансировка new[] и delete[] похоже, долгий путь к решению проблемы. Вместо 15 минут, приложение теперь идет около двух часов до аварии. Пока еще нет. Есть еще предложения? Повреждение кучи сохраняется.

обновление: сборка выпуска под Visual Studio 2008 кажется значительно лучше; текущее подозрение основывается на STL реализация, которая поставляется с VS98.


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

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

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

получил, что происходит в данный момент, снова: не так много помощи, пока что-то не пойдет не так. Я хочу поймать вандала с поличным.

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

я не очень надеюсь, но отчаянные времена требуют...

и вы уверены, что все компоненты проекта имеют правильные настройки среды выполнения библиотек (C/C++ tab, категория генерации кода в настройках проекта VS 6.0)?

Нет, я не буду, и я проведу пару часов завтра проходим через рабочее пространство (58 проектов в нем) и проверяем, что все они компилируются и связываются с соответствующими флагами.


Обновление: это заняло 30 секунд. Выберите все проекты Settings диалог, снимите флажок, пока не найдете проект(ы), которые не имеют правильных настроек (все они имели правильные настройки).
15 55

15 ответов:

моим первым выбором будет выделенный инструмент кучи, такой как pageheap.exe.

перезапись new и delete может быть полезна, но это не улавливает выделения, зафиксированные кодом более низкого уровня. Если это то, что вы хотите, лучше объехать low-level alloc APIс помощью Microsoft объезды.

также проверки здравомыслия, такие как: проверьте соответствие библиотек времени выполнения (выпуск против отладки, многопоточный против однопоточного, dll против статического lib), ищите плохие удаления (например, удалить где delete [] должен был использоваться), убедитесь, что вы не смешиваете и не сопоставляете свои распределения.

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

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

у меня же проблемы в моей работе (мы также используем VC6 иногда). И для этого нет простого решения. У меня есть только некоторые подсказки:

  • попробуйте с автоматическими аварийными дампами на производственной машине (см. Process Dumper). Мой опыт говорит, что доктор Ватсон не подходит для демпинга.
  • удалить все поймать(...) из кода. Они часто скрывают серьезные исключения памяти.
  • Регистрация Дополнительные Окна Отладка - есть много отличных советов для таких проблем, как у вас. Я рекомендую это всем своим сердцем.
  • если вы используете STL попробовать STLPort и проверка сборки. Недопустимый итератор-это ад.

удачи. Такие проблемы, как Ваша, мы решаем месяцами. Будьте готовы к этому...

запустите исходное приложение с помощью ADplus -crash -pn appnename.exe Когда проблема с памятью всплывает, вы получите хороший большой дамп.

вы можете проанализировать дамп, чтобы выяснить, какая ячейка памяти была повреждена. Если Вам ПОВЕЗЕТ, перезапись памяти-это уникальная строка, вы можете выяснить, откуда она взялась. Если вам не повезет, вам нужно будет копаться в win32 куча и выяснить, что было исходные характеристики памяти. (куча -X может помочь)

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

надеюсь, это ускорит мониторинг достаточно, чтобы поймать виновника.

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

P. S: Вы можете использовать DebugDiag для анализа дампов. Это может указывать элемент DLL владение поврежденной кучей и предоставление вам других полезных сведений.

нам очень повезло, написав наши собственные malloc и бесплатные функции. В производстве они просто называют стандартный malloc и free, но в debug они могут делать все, что вы хотите. У нас также есть простой базовый класс, который ничего не делает, кроме переопределения операторов new и delete для использования этих функций, тогда любой класс, который вы пишете, может просто наследовать от этого класса. Если у вас есть тонна кода, это может быть большая работа, чтобы заменить вызовы malloc и free в новый malloc и free (не забудьте realloc!), но в долгосрочной перспективе это очень полезно.

в книге Стива Магуайра Написание Твердого Кода (настоятельно рекомендуется), есть примеры отладки вещи, которые вы можете сделать в эти процедуры, как:

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

еще одна хорошая идея -никогда использовать такие вещи, как strcpy,strcat или sprintf -- всегда использовать strncpy,strncat и snprintf. Мы также написали наши собственные версии этих, чтобы убедиться, что мы не списываем конец буфера, и они тоже поймали много проблем.

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

для статического анализа рассмотрим компиляцию с PREfast (cl.exe /analyze). Он обнаруживает несоответствие delete и delete[], переполнение буфера и множество других проблем. Будьте готовы, однако, пробираться через многие килобайты предупреждения L6, особенно если ваш проект все еще имеет L4 не зафиксировано.

PREfast доступен с Visual Studio Team System и,видимо, как часть Windows пакет SDK.

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

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

это в условиях низкой памяти? Если это так, то может быть, что новый возвращается NULL вместо того, чтобы бросать std:: bad_alloc. Старше VC++ компиляторы не реализовали это должным образом. Есть статья о устаревшие ошибки выделения памяти авария STL приложения, созданные с VC6.

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

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

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

мое первое действие будет следующим:

  1. создайте двоичные файлы в версии "Release", но создав файл отладочной информации (вы найдете эту возможность в настройках проекта).
  2. используйте Dr Watson в качестве отладчика defualt (DrWtsn32-I) на машине, на которой вы хотите воспроизвести проблему.
  3. Repdroduce проблема. Доктор Ватсон создаст дамп, который может быть полезен в дальнейшем анализе.

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

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

и вы уверены, что все компоненты проекта имеют правильные настройки библиотеки времени выполнения (вкладка C/C++, категория генерации кода в настройках проекта VS 6.0)?

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

  • плохое использование кучи, т. е. двойное освобождение, чтение после свободного, запись после свободного, установка флага HEAP_NO_SERIALIZE с выделениями и освобождает от нескольких потоков в одной куче
  • из памяти
  • плохой код (т. е. переполнение буфера, переполнение буфера и т. д.)
  • "время" вопросов

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

что, скорее всего, означает, что это связано с тем, как код обращается к общей памяти. К сожалению, отслеживание этого будет довольно болезненным. Несинхронизированный доступ к общей памяти часто проявляется как странные "временные" проблемы. Такие вещи, как не использование семантики acquire/release для синхронизации доступа к общей памяти с флагом, не использование блокировок соответствующим образом и т. д.

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

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

когда вы тестировали с VS2008, вы работали с HeapVerifier с сохранением памяти, установленной в Да? Что это может снизить производительность распределителя кучи. (Кроме того, вы должны запустить с ним Debug->Start with Application Verifier, но вы уже можете это знать.)

вы также можете попробовать отладку с Windbg и различными видами использования!команда кучи.

MSN

Если вы решите переписать new / delete, я сделал это и имею простой исходный код по адресу:

http://gandolf.homelinux.org / ~smhanov / blog/? id=10

Это ловит утечки памяти, а также вставляет данные защиты до и после блока памяти для захвата повреждения кучи. Вы можете просто интегрировать с ним, поставив #include " debug.h " в верхней части каждого файла CPP и определение DEBUG и DEBUG_MEM.

предложение Грэма о пользовательском malloc / free-хорошая идея. Смотрите, если вы можете охарактеризовать некоторые модели о коррупции, чтобы дать вам ручку для рычагов.

например, если он всегда находится в блоке одинакового размера (скажем, 64 байта), то измените свою пару malloc/free, чтобы всегда выделять 64-байтовые куски на своей собственной странице. Когда вы освобождаете 64-байтовый кусок, затем установите биты защиты памяти на этой странице, чтобы предотвратить чтение и wites (используя VirtualQuery). Тогда кто-нибудь пытается доступ к этой памяти приведет к созданию исключения, а не к повреждению кучи.

Это предполагает, что количество выдающихся 64-байтовых блоков является только умеренным или у вас есть много памяти, чтобы записать в поле!

мало времени у меня было, чтобы решить подобную проблему. Если проблема все еще существует, я предлагаю вам сделать это : Мониторинг всех вызовов new / delete и malloc/calloc/realloc / free. Я делаю один DLL экспорт функции для регистрации всех вызовов. Эта функция получает параметр для идентификации источника кода, указатель на выделенную область и тип вызова, сохраняя эту информацию в таблице. Вся выделенная/освобожденная пара исключается. В конце или после вам нужно сделать вызов другой функции для создайте отчет для левых данных. С помощью этого вы можете определить неправильные вызовы (новый/бесплатный или malloc/delete) или отсутствует. Если в вашем коде есть какой-либо случай перезаписи буфера, сохраненная информация может быть неправильной, но каждый тест может обнаруживать/обнаруживать/включать решение выявленного сбоя. Многие запуски помогают выявить ошибки. Удача.

Как вы думаете, это состояние гонки? Несколько потоков совместно используют одну кучу? Вы можете дать каждому потоку частную кучу с HeapCreate, то они могут работать быстро с HEAP_NO_SERIALIZE. В противном случае куча должна быть потокобезопасной, если вы используете многопоточную версию системных библиотек.

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

во-вторых-для переключателя / analyze-он действительно генерирует обильные предупреждения. Чтобы использовать этот параметр в моем собственном проекте, я создал новый файл заголовка, который использовал #pragma warning Для отключения всех дополнительных предупреждений, генерируемых /analyze. Затем далее в файле я включаю только те предупреждения, которые меня волнуют. Затем используйте параметр компилятора /FI, чтобы принудительно включить этот файл заголовка сначала во все ваши единицы компиляции. Это должно позволить вам использовать переключатель /analyze при управлении выходом