Подтвердить что ты не робот

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

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

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

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

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

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

  • Получите безумно хруповую коробку (чтобы заменить текущий dev dev: 2Gb RAM в E6550 Core2 Duo); это позволит воспроизвести крах, вызывающий неправильное поведение при работе в мощной среде отладки; или
  • Перепишите операторы 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, отмените выбор, пока не найдете проекты, у которых нет правильных настроек (все они имеют правильные настройки).
4b9b3361

Ответ 1

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

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

Также проверяются проверки работоспособности, такие как: проверить соответствие ваших библиотек времени выполнения (выпуск по сравнению с debug, multi-threaded vs. single-threaded, dll vs. static lib), искать плохие удаления (например, delete where delete [] должны были использоваться), убедитесь, что вы не смешиваете и не сопоставляете свои выделения.

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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

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

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

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

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

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

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

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

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

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

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

MSN

Ответ 9

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

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

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

Ответ 10

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

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

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

Ответ 11

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

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

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

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

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

Ответ 12

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

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

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

Ответ 13

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

Ответ 14

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

Ответ 15

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

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