C++/msvc6 сбои приложения из-за повреждения "кучи", каких-либо подсказок?

О приложении

  • Это работает на Windows XP Professional SP2.
  • Это создается с Microsoft Visual C++ 6.0 с Пакетом обновления 6.
  • Это - базирующийся MFC.
  • Это использует несколько внешних dlls (например, Xerces, ZLib или ACE).
  • Это имеет высокопроизводительные требования.
  • Это делает много сетевого и жесткого диска ввод-вывод, но это - также интенсивный CPU.
  • Это имеет механизм обработки исключений, который генерирует мини-дамп, когда необработанное исключение происходит.
  • ОБНОВЛЕНИЕ: Это - очень многопоточное приложение, и мы используем взаимные исключения для защиты параллельного доступа (конечно, мы могли бы перестать работать в некотором месте...),

Факты о катастрофическом отказе

  • Это только происходит на многопроцессорных/многоядерных машинах и под большими нагрузками работы.
  • Это происходит наугад (ни мы, ни наш клиент еще не нашли шаблон) после немного несколько часов, работая.
  • Мы не можем воспроизвести катастрофический отказ на нашей лаборатории тестирования. Это только происходит в некоторых производственных системах (но всегда в многоядерных машинах)
  • Это всегда заканчивает тем, что отказало в той же точке, хотя полный стек является не всегда тем же. Позвольте мне добавить стопку отказывающего потока (полученное использование 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 соответствует вызову для стирания () станд.:: список.

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

  • Рассмотрение всего кода, связанного до такой степени, когда случай концов катастрофического отказа.
  • Попытка включить pageheap на нашей лаборатории тестирования, хотя ничто полезное не было найдено к настоящему времени.
  • Мы заменили станд.:: список для массива C и затем это отказывает в другой части кода (хотя он связан код, это не находится в коде, где старый список находился). По совпадению теперь это отказывает в другом стирании, хотя на этот раз станд.:: мультимножество. Позвольте мне скопировать стек, содержавшийся в дампе:
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 на производственных машинах, поскольку это влияет на производительность в большой степени.

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

6
задан ROMANIA_engineer 13 January 2018 в 08:52
поделиться

7 ответов

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

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

Высокая многопоточность

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

РЕДАКТИРОВАТЬ:

Для моих целей достаточно было просто установить флажок «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
ответ дан 17 December 2019 в 02:26
поделиться

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

1
ответ дан 17 December 2019 в 02:26
поделиться

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

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

1
ответ дан 17 December 2019 в 02:26
поделиться

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

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

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

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

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

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

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

1
ответ дан 17 December 2019 в 02:26
поделиться

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

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

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

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

1
ответ дан 17 December 2019 в 02:26
поделиться

Используйте профилировщик производительности, который может подключаться к событиям ЦП, например 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
ответ дан 17 December 2019 в 02:26
поделиться

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

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

0
ответ дан 17 December 2019 в 02:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: