Сведения о вопросе

1234123213

16:03, 1st July, 2020

Теги

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

Просмотров: 756   Ответов: 15

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

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

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

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

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

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

И нет: доставка с встроенным прибором Purify-это не вариант.

Коллега только что прошел мимо и спросил "Stack Overflow? Are we getting stack overflows now?!?"

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


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

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


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

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

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

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

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

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

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

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


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



  Сведения об ответе

PHPH

18:03, 1st July, 2020

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

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

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

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

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


  Сведения об ответе

ITSME

18:03, 1st July, 2020

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

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

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


  Сведения об ответе

ASER

18:03, 1st July, 2020

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

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

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

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

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

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


  Сведения об ответе

qwerty101

18:03, 1st July, 2020

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

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

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

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


  Сведения об ответе

ITSME

18:03, 1st July, 2020

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

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

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


  Сведения об ответе

piter

18:03, 1st July, 2020

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

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


  Сведения об ответе

PHPH

18:03, 1st July, 2020

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


  Сведения об ответе

KOMP

18:03, 1st July, 2020

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

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

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


  Сведения об ответе

SEEYOU

18:03, 1st July, 2020

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

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

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

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

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


  Сведения об ответе

$DOLLAR

18:03, 1st July, 2020

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

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

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

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

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

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

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

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

MSN


  Сведения об ответе

qwerty101

18:03, 1st July, 2020

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

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

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


  Сведения об ответе

lesha

18:03, 1st July, 2020

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


  Сведения об ответе

PAGE

18:03, 1st July, 2020

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

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


  Сведения об ответе

KOMP

18:03, 1st July, 2020

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

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

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


  Сведения об ответе

SKY

18:03, 1st July, 2020

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться