Повреждение кучи под Win32; как найти? - PullRequest
57 голосов
/ 04 августа 2008

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

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

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

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

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

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

И, нет: доставка со встроенным прибором Purify невозможна.

Коллега только что прошел мимо и спросил: «Переполнение стека? Мы сейчас получаем переполнение стека?!?»

А теперь вопрос: Как мне найти повреждителя кучи?


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

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


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

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

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

Понятно, что сейчас, опять же, мало помощи, пока что-то не пойдет не так. Я хочу поймать вандала в действии.

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

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

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

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


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

Ответы [ 15 ]

27 голосов
/ 04 августа 2008

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

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

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

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

Как выглядит стек вызовов и т. Д. Во время первого исключения?

11 голосов
/ 06 августа 2008

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

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

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

8 голосов
/ 16 сентября 2008

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

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

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

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

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

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

7 голосов
/ 22 августа 2008

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

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

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

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

4 голосов
/ 13 октября 2008

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

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

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

3 голосов
/ 02 сентября 2008

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

3 голосов
/ 25 августа 2008

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

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

1 голос
/ 17 сентября 2008

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

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

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

1 голос
/ 22 августа 2008

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

  • Неправильное использование кучи, то есть двойное освобождение, чтение после освобождения, запись после освобождения, установка флага HEAP_NO_SERIALIZE с помощью allocs и освобождения из нескольких потоков в одной куче
  • Недостаточно памяти
  • Неверный код (т. Е. Переполнение буфера, переполнение буфера и т. Д.)
  • «Сроки» выпуска

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

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

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

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

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

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

MSN

1 голос
/ 04 августа 2008

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...