Приложение C ++ / msvc6 вылетает из-за повреждения кучи, есть какие-то подсказки? - PullRequest
6 голосов
/ 07 апреля 2010

О приложении

  • Он работает в Windows XP Professional с пакетом обновления 2 (SP2).
  • Он построен с использованием Microsoft Visual C ++ 6.0 с пакетом обновления 6.
  • Он основан на MFC.
  • Он использует несколько внешних библиотек (например, Xerces, ZLib или ACE).
  • Он имеет требования к высокой производительности .
  • Он выполняет много операций ввода-вывода по сети и с жесткого диска, но также требует значительных ресурсов процессора.
  • Он имеет механизм обработки исключений, который генерирует мини-дамп при возникновении необработанного исключения.
  • UPDATE:Это очень многопоточное приложение, и мы используем мьютексы для защиты одновременного доступа (конечно, в некоторых местах может произойти сбой ...)

Факты о сбое

  • Это происходит только на многопроцессорных / многоядерных машинах и при большой нагрузке.
  • Это происходит случайным образом (ни мы, ни наш клиент еще не нашли шаблон) через несколько часов работы.
  • Мы не можем воспроизвестиавария в нашей тестовой лаборатории.Это происходит только в некоторых производственных системах (но всегда в многоядерных машинах)
  • Это всегда приводит к сбою в одной и той же точке , хотя полный стек не всегда одинаков.Позвольте мне добавить стек аварийного потока (полученный с помощью WinDbg, извините, у нас нет символов)
Exception code: c0000005 ACCESS_VIOLATION
Address        : 006a85b9
Access Type    : write
Access Address : 2e020fff
Fault address:  006a85b9 01:002a75b9 C:\MyDir\MyApplication.exe

ChildEBP RetAddr  Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
030af6c8 7c9206eb 77bfc3c9 01a80000 00224bc3 MyApplication+0x2a85b9
030af960 7c91e9c0 7c92901b 00000ab4 00000000 ntdll!RtlAllocateHeap+0xeac (FPO: [Non-Fpo])
030af98c 7c9205c8 00000001 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc (FPO: [3,0,0])
030af9c0 7c920551 01a80898 7c92056d 313adfb0 ntdll!RtlpFreeToHeapLookaside+0x22 (FPO: [2,0,4])
030afa8c 4ba3ae96 000307da 00130005 00040012 ntdll!RtlFreeHeap+0x1e9 (FPO: [Non-Fpo])
030afacc 77bfc2e3 0214e384 3087c8d8 02151030 0x4ba3ae96
030afb00 7c91e306 7c80bfc1 00000948 00000001 msvcrt!free+0xc8 (FPO: [Non-Fpo])
030afb20 0042965b 030afcc0 0214d780 02151218 ntdll!ZwReleaseSemaphore+0xc (FPO: [3,0,0])
030afb7c 7c9206eb 02e6c471 02ea0000 00000008 MyApplication+0x2965b
030afe60 7c9205c8 02151248 030aff38 7c920551 ntdll!RtlAllocateHeap+0xeac (FPO: [Non-Fpo])
030afe74 7c92056d 0210bfb8 02151250 02151250 ntdll!RtlpFreeToHeapLookaside+0x22 (FPO: [2,0,4])
030aff38 77bfc2de 01a80000 00000000 77bfc2e3 ntdll!RtlFreeHeap+0x647 (FPO: [Non-Fpo])
7c92056d c5ffffff ce7c94be ff7c94be 00ffffff msvcrt!free+0xc3 (FPO: [Non-Fpo])
7c920575 ff7c94be 00ffffff 12000000 907c94be 0xc5ffffff
7c920579 00ffffff 12000000 907c94be 90909090 0xff7c94be
*** WARNING: Unable to verify checksum for xerces-c_2_7.dll
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for xerces-c_2_7.dll - 
7c92057d 12000000 907c94be 90909090 8b55ff8b MyApplication+0xbfffff
7c920581 907c94be 90909090 8b55ff8b 08458bec xerces_c_2_7
7c920585 90909090 8b55ff8b 08458bec 04408b66 0x907c94be
7c920589 8b55ff8b 08458bec 04408b66 0004c25d 0x90909090
7c92058d 08458bec 04408b66 0004c25d 90909090 0x8b55ff8b
  • Адрес MyApplication + 0x2a85b9 соответствуетвызов erase () для std :: list.

Что я пробовал до сих пор

  • Просмотр всего кода, связанного сТочка, в которой происходит сбой.
  • Попытка включить pageheap в нашей тестовой лаборатории, хотя к настоящему времени ничего полезного не найдено.
  • Мы заменили std :: list на массив C, а затемон вылетает в другой части кода (хотя это связанный код, его нет в коде, где находился старый список).По совпадению, теперь это вылетает в другом стирании, хотя на этот раз std :: multiset.Позвольте мне скопировать стек, содержащийся в дампе:
ntdll.dll!_RtlpCoalesceFreeBlocks@16()  + 0x124e bytes  
ntdll.dll!_RtlFreeHeap@12()  + 0x91f bytes  
msvcrt.dll!_free()  + 0xc3 bytes    
MyApplication.exe!006a4fda()
[Frames below may be incorrect and/or missing, no symbols loaded for MyApplication.exe] 
MyApplication.exe!0069f305()
ntdll.dll!_NtFreeVirtualMemory@16()  + 0xc bytes    
ntdll.dll!_RtlpSecMemFreeVirtualMemory@16()  + 0x1b bytes   
ntdll.dll!_ZwWaitForSingleObject@12()  + 0xc bytes  
ntdll.dll!_RtlpFreeToHeapLookaside@8()  + 0x26 bytes    
ntdll.dll!_RtlFreeHeap@12()  + 0x114 bytes  
msvcrt.dll!_free()  + 0xc3 bytes    
c5ffffff()  
  • (12 апреля 2010 г.) Я попытался включить проверку без кучи (используя gflags)) но это сильно замедляет работу приложения ...

Возможные решения (о которых я знаю), которые нельзя применить

  • «Перенесите приложение на новый компилятор»: Мы работаем над этим, но на данный момент это не решение.
  • «Включить pageheap (обычный или полный)»: Мыне могу включить pageheap на рабочих машинах, так как это сильно влияет на производительность.

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

Ответы [ 7 ]

1 голос
/ 15 апреля 2010

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

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

Вам необходимо определить, какая часть приложения переполнена. Один из способов сделать это - посмотреть информацию, содержащуюся в переполненном блоке. Если у вас происходит сбой в RtlpCoalesceFreeBlocks, я думаю, что я помню, что один из регистров (@esi) указывает на начало поврежденного блока (я не нахожусь в системе Windows на момент написания и не могу это проверить). Или, если у вас есть дамп, с помощью команды windbg! Heap -a сбросит всю память и отобразит поврежденные блоки (лучше войти в файл, так как полный список кучи может быть длинным). Если поврежденные блоки известны, их содержимое может помочь идентифицировать код.

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

1 голос
/ 12 апреля 2010

Используйте профилировщик производительности, который может подключаться к событиям ЦП, таким как VTune. Установите его в режим выборки и скажите, чтобы он ожидал событий, связанных с совместным использованием строк кэша. Они идентифицированы событием HITM из фазы SNOOP.

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

Я не эксперт по архитектуре ЦП или чему-то в этом роде, но я понимаю, что когда ЦП собираются получить доступ к части данных, система проверит, обращаются ли другие ЦП к той же части данных, это сделано наблюдая за тем, как память извлекает и записывает данные, поступающие с каждого процессора, этот процесс называется отслеживанием. Snooping гарантирует, что если два или более ЦП имеют одинаковые данные в каждом из своих кэшей, дублированные копии данных удаляются при изменении одного из них. Событие HIT-Modified означает, что система обнаружила эту ситуацию и ей пришлось сбросить одну из строк кэша ЦП.

См. Этот документ для получения дополнительной информации об использовании VTune, как это

http://software.intel.com/en-us/articles/using-intel-vtune-performance-analyzer-events-ratios-optimizing-applications/

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

1 голос
/ 12 апреля 2010

Можете ли вы опубликовать, какие исключения вы получаете?

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

  1. Перемещение (или удаленный вход в систему) в производственную систему, установка Visual Studio, подготовка файлов .pdb и .map (а также символов Windows), присоединение отладчика к выпуску-сборке и ожидание сбоя. Хотя, если вы настроите его правильно, вы можете использовать файл мини-дамп на своем компьютере разработчика, где у вас уже есть настройки приложения и символов окна. Затем вы можете увидеть, какой бесплатный вызов генерирует вызов, и попытаться выяснить, какой объект освобождается, чтобы увидеть, не поврежден ли этот объект и нет ли поблизости объектов в памяти.
  2. Как-то найти способ воспроизвести ошибку в вашем офисе, можете ли вы создать достаточно большие тома, чтобы дублировать действия клиента?

Ваши опубликованные стеки вызовов не выглядят особенно осветительно.

Поскольку вы используете VS 6 с пакетом обновления 6 (SP6), его STL в порядке.

Можете ли вы сказать, если приложение в производственной системе протекает какие-либо ресурсы? Запуск perfmon может помочь с этим.

Другое дело, вы не звоните new / delete, как очень часто из разных тем, не так ли? Я обнаружил, что если вы сделаете это достаточно быстро, вы довольно быстро сломаете свое приложение (сделало это на XP). Мне пришлось заменить новые / удалить вызовы в моем приложении на VirtualAlloc (Windows Virtual Memory API), который отлично работал для меня. Конечно, STL также может выделяться из кучи.

1 голос
/ 12 апреля 2010

Ключевым моментом здесь является то, что это происходит только на многопроцессорных компьютерах (ядра совпадают с процессорами) Что происходит, когда многопоточная программа выполняется на одном процессоре, так это то, что два потока никогда не выполняются одновременно. ОС должна распределять по времени каждый процессор для имитации потоков. В многопроцессорной системе несколько потоков могут работать одновременно. Возможно, вы сейчас обращаетесь к общим ресурсам из разных потоков одновременно. Этими ресурсами могут быть соединения с внешними системами и даже глобальные переменные и структуры данных, даже классы Singleton. К сожалению, теперь у вас есть одна из самых сложных проблем. Если вы можете обнаружить, что память повреждена, вам нужно найти, кто еще использует ее в другом потоке, а затем синхронизировать память (Semaphore или CriticalSection). К сожалению, нет простого способа найти проблему.

Возможно, вы сможете временно установить привязку процессора к запуску только на одном процессоре, пока не найдете проблему. Смотрите ссылку http://msdn.microsoft.com/en-us/library/ms684251(VS.85).aspx Вот метод, чтобы установить сходство на Для Windows XP / Vista / 7 откройте Affinity, открыв диспетчер задач Windows (CTL + ALT + DEL или щелкните правой кнопкой мыши на панели задач), выберите вкладку «Процессы», щелкните правой кнопкой мыши процесс приложения, который вы хотите изолировать, затем выберите «Установить сходство». В диалоговом окне «Сходство с процессором» снимите флажок с процессора / ядер, которые вам не нужны. Это эффективно изолирует это приложение от выбранных процессоров / ядер, предотвращая перекрытие кэша и уменьшая переключение процессов, и упрощает вашу возможность контролировать распределение процессоров / ядер для нескольких программ.

1 голос
/ 08 апреля 2010

Используйте Application Verifier из средств отладки для Windows. Иногда это помогает.

Попробуйте настроить VS для загрузки символов отладки ОС и убедитесь, что OMIT FRAME POINTERS отключена в вашем приложении. Возможно, трассировка стека будет информативной.

Высоко многопоточный

Давным-давно я обнаружил, что существует ограничение на количество потоков на процесс в WinXP. Мой тестовый фрагмент может создать только несколько потоков. Проблема была решена с помощью пула потоков.

EDIT:

Для моих целей достаточно было просто установить флажок «Application Verifier» в gflags.exe. К сожалению, у меня нет опыта работы с другими вариантами. Что касается ограничения потока, тестовый фрагмент был прост:

unsigned __stdcall ThreadProc(LPVOID)
{
  _tprintf(_T("Thread started\n"));
  return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
  while (TRUE)
  {
    unsigned threadId = 0;
    _tprintf(_T("Start thread\n"));
    _beginthreadex( NULL, 0, &ThreadProc, NULL, 0, &threadId);
  }
  return 0;
}

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

1 голос
/ 07 апреля 2010

Вы можете попробовать добавить в свой код вызовы к процедурам проверки кучи отладки, чтобы узнать, сможете ли вы обнаружить повреждение ближе к источнику (вы используете CRT отладки для отслеживания этой проблемы, верно?):

0 голосов
/ 22 апреля 2010

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

Что именно это означает: «Мы не можем генерировать символы, потому что мы связываемся с библиотекой, которая не связывается, если мы их используем.»? Это кажется странным.

...