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

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

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

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

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

Мои текущие лучшие предположения относительно того, куда идти дальше:

  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диалоговом окне, снимите флажок , пока не найдете проекты, которые не имеют правильных настроек (у всех были правильные настройки).

4.08.2008 07:30:01
Как именно выглядит сбой? Вы говорите «включая сбой выделения памяти в куче» - может ли это означать, что у вас просто не хватает памяти? (Я не
svec 5.08.2008 21:03:11
@svec C ++ говорит, что из-за нехватки памяти выбрасывается std :: bad_alloc. То, что я вижу, это исключения из памяти («эй, ты не можешь читать (или, возможно, писать) там!»)
Josh 6.08.2008 12:36:16
> Сборка с 2008 года была бы такой безумной чушью ... может быть, даже MSVC6, но я не уверен. MSVC6 не поймет этого, но Линт будет. Хорошим местом для начала может быть доработка вашего кода. Это всего $ 250 (ничто по сравнению с количеством времени, сэкономленного на отладке). Совет для начинающих пользователей lint: все выключите и медленно включите. Я начал с ненужных заголовков и дошел до 20 пунктов. Когда я впервые запустил его на нашем продукте, в нем было больше ошибок, чем строк кода!
graham.reeds 6.08.2008 12:49:02
Было бы интересно узнать, есть ли у вас реальное решение здесь ...
Roddy 15.03.2010 15:49:00
Я не думаю, что вы получите std :: bad_alloc в VC6, я думаю, что он возвращает ноль?
paulm 2.01.2013 11:01:24
15 ОТВЕТОВ
РЕШЕНИЕ

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

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

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

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

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

28
1.09.2015 17:32:32

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

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

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

11
7.02.2013 12:30:58

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

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

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

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

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

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

8
7.02.2013 12:33:55

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

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

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

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

7
7.02.2013 12:32:29
«всегда используйте strncpy вместо strcpy» - в Microsoft CRT есть еще лучшая альтернатива strcpy_s.
Constantin 12.10.2008 20:37:15
не забудьте прочитать полные спецификации MSDN с такими функциями! странные вещи могут случиться, если вы не прочитаете их полностью!
alcor 10.04.2013 19:49:33

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

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

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

4
7.02.2013 12:32:50

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

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

3
25.08.2008 19:55:09

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

3
7.02.2013 12:34:24
Это было очень полезно! Я не знал, почему мой новый неожиданно возвращал NULL после включения Application Verifier!
Vinz 8.06.2015 20:36:16

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

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

Если вы сможете узнать, что именно МОЖЕТ вызвать эту проблему, через Google и документацию об исключениях, которые вы получаете, возможно, это даст дополнительное понимание того, что искать в коде.

1
4.08.2008 07:48:51

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

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

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

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

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

1
4.08.2008 08:26:55

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

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

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

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

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

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

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

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

MSN

1
22.08.2008 16:51:06

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

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

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

1
17.09.2008 13:40:35

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

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

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

0
2.09.2008 04:23:34

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

0
19.12.2008 11:52:50

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

0
30.07.2009 13:48:40

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

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

0
3.10.2009 16:48:57