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

Как отладить коррупцию в управляемой куче

Моя программа выдает ошибку, которую она не может обрабатывать блоком catch(Exception e), а затем сбой:

Нарушение нарушенного нарушения прав доступа.

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

Код работает в фоновом потоке и время от времени падает, что невозможно легко воспроизвести. Поэтому я приложил WinDbg к этому процессу и имел следующий стек исключения:

000000001dabd8c8 000007feea129a1d [HelperMethodFrame: 000000001dabd8c8]
000000001dabda00 000007fee90cfce8 System.Text.StringBuilder.ExpandByABlock(Int32)
000000001dabda40 000007fee90cfba4 System.Text.StringBuilder.Append(Char*, Int32)
000000001dabdaa0 000007fee9102955 System.Text.StringBuilder.Append(System.String, Int32, Int32)
000000001dabdaf0 000007ff00bf5ce3 MineUtils.Common.Strings.Strings.Replace(System.String, System.String, System.String, Boolean, Boolean)
000000001dabdb90 000007ff00bf5a59 MineUtils.Common.Strings.Strings.RemoveSubstrings(System.String, System.String, System.String, Boolean) [D:\Programs\Visual Studio 2005 Projects\MineUtils.Common\Strings\Strings.Common-Main.cs @ 1481

WinDbg показывает это исключение:

EXCEPTION_RECORD:  ffffffffffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 000007feea129a1d (clr!WKS::gc_heap::find_first_object+0x0000000000000092)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 0000000000000000
   Parameter[1]: 0000000000003d80
Attempt to read from address 0000000000003d80

Я прочитал такие исключения, которые можно обрабатывать с помощью атрибута метода [HandleProcessCorruptedStateExceptions], но почему это исключение когда-либо происходит, если я использую только StringBuilder?

Это предыдущий анализ WinDbg (StringBuilder.ToString() вызывает исключение):

*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************

FAULTING_IP:
clr!WKS::gc_heap::find_first_object+92
000007fe`ea129a1d f70100000080    test    dword ptr [rcx],80000000h

EXCEPTION_RECORD:  ffffffffffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 000007feea129a1d (clr!WKS::gc_heap::find_first_object+0x0000000000000092)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000001
NumberParameters: 2
   Parameter[0]: 0000000000000000
   Parameter[1]: 0000000000001c98
Attempt to read from address 0000000000001c98

ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.

EXCEPTION_PARAMETER1:  0000000000000000

EXCEPTION_PARAMETER2:  0000000000001c98

READ_ADDRESS:  0000000000001c98

FOLLOWUP_IP:
clr!WKS::gc_heap::find_first_object+92
000007fe`ea129a1d f70100000080    test    dword ptr [rcx],80000000h

MOD_LIST: <ANALYSIS/>

NTGLOBALFLAG:  0

APPLICATION_VERIFIER_FLAGS:  0

MANAGED_STACK:
(TransitionMU)
000000001AB7DFC0 000007FEE90CFE07 mscorlib_ni!System.Text.StringBuilder.ToString()+0x27
000000001AB7E010 000007FF00C750A9 SgmlReaderDll!Sgml.Entity.ScanToken(System.Text.StringBuilder, System.String, Boolean)+0x169
000000001AB7E080 000007FF00C760E6 SgmlReaderDll!Sgml.SgmlDtd.ParseParameterEntity(System.String)+0xc6
000000001AB7E0F0 000007FF00C76FD8 SgmlReaderDll!Sgml.SgmlDtd.ParseModel(Char, Sgml.ContentModel)+0x298
000000001AB7E160 000007FF00C7701C SgmlReaderDll!Sgml.SgmlDtd.ParseModel(Char, Sgml.ContentModel)+0x2dc
000000001AB7E1D0 000007FF00C7701C SgmlReaderDll!Sgml.SgmlDtd.ParseModel(Char, Sgml.ContentModel)+0x2dc
000000001AB7E240 000007FF00C76BA5 SgmlReaderDll!Sgml.SgmlDtd.ParseContentModel(Char)+0x65
000000001AB7E290 000007FF00C763D7 SgmlReaderDll!Sgml.SgmlDtd.ParseElementDecl()+0xe7
000000001AB7E320 000007FF00C747A1 SgmlReaderDll!Sgml.SgmlDtd.Parse()+0xc1
000000001AB7E370 000007FF00C73EF5 SgmlReaderDll!Sgml.SgmlDtd.Parse(System.Uri, System.String, System.IO.TextReader, System.String, System.String, System.Xml.XmlNameTable)+0x175
000000001AB7E410 000007FF00C73B33 SgmlReaderDll!Sgml.SgmlReader.LazyLoadDtd(System.Uri)+0x163
000000001AB7E480 000007FF00C737B9 SgmlReaderDll!Sgml.SgmlReader.OpenInput()+0x19
000000001AB7E4E0 000007FF00C7334C SgmlReaderDll!Sgml.SgmlReader.Read()+0x1c
000000001AB7E530 000007FEE5983C4C System_Xml_ni!System.Xml.XmlLoader.Load(System.Xml.XmlDocument, System.Xml.XmlReader, Boolean)+0xac
000000001AB7E590 000007FEE5983730 System_Xml_ni!System.Xml.XmlDocument.Load(System.Xml.XmlReader)+0x90
...
000000001AB7F0A0 000007FEE97ED792 mscorlib_ni!System.Threading.Tasks.Task.Execute()+0x82
000000001AB7F100 000007FEE90A181C mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+0xdc
000000001AB7F160 000007FEE97E7F95 mscorlib_ni!System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef)+0x1b5
000000001AB7F1E0 000007FEE97E7D90 mscorlib_ni!System.Threading.Tasks.Task.ExecuteEntry(Boolean)+0xb0
000000001AB7F220 000007FEE90EBA83 mscorlib_ni!System.Threading.ThreadPoolWorkQueue.Dispatch()+0x193
000000001AB7F2C0 000007FEE90EB8D5 mscorlib_ni!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()+0x35
(TransitionUM)

EXCEPTION_OBJECT: !pe 2a61228
Exception object: 0000000002a61228
Exception type:   System.ExecutionEngineException
Message:          <none>
InnerException:   <none>
StackTrace (generated):
<none>
StackTraceString: <none>
HResult: 80131506

MANAGED_OBJECT_NAME:  System.ExecutionEngineException

MANAGED_STACK_COMMAND:  _EFN_StackTrace

LAST_CONTROL_TRANSFER:  from 000007feea12bce4 to 000007feea129a1d

ADDITIONAL_DEBUG_TEXT:  Followup set based on attribute [Is_ChosenCrashFollowupThread] from Frame:[0] on thread:[PSEUDO_THREAD]

FAULTING_THREAD:  ffffffffffffffff

DEFAULT_BUCKET_ID:  INVALID_POINTER_READ_CALL

PRIMARY_PROBLEM_CLASS:  INVALID_POINTER_READ_CALL

BUGCHECK_STR:  APPLICATION_FAULT_INVALID_POINTER_READ_WRONG_SYMBOLS_CALL__SYSTEM.EXECUTIONENGINEEXCEPTION

ОБНОВЛЕНО СНОВА

Вот стоп WinDbg исключения после того, как я включил выгружаемую кучу:

 (1480.e84): Access violation - code c0000005 (first chance)
ntdll!ZwTerminateProcess+0xa:
00000000`77c415da c3              ret
0:023> !clrstack
OS Thread Id: 0xe84 (23)
Child SP         IP               Call Site
0000000037ded848 0000000077c415da [HelperMethodFrame: 0000000037ded848]
0000000037dedab0 000007fee9effd17 System.Text.StringBuilder.ToString()*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_64\mscorlib\8f7f691aa155c11216387cf3420d9d1b\mscorlib.ni.dll

0000000037dedb00 000007ff00cceae9 Sgml.Entity.ScanToken(System.Text.StringBuilder, System.String, Boolean)

0000000037dedb70 000007ff00cd19b2 Sgml.SgmlDtd.ParseAttDefault(Char, Sgml.AttDef)
0000000037dedbc0 000007ff00cd120b Sgml.SgmlDtd.ParseAttDef(Char)
0000000037dedc00 000007ff00cd1057 Sgml.SgmlDtd.ParseAttList(System.Collections.Generic.Dictionary`2<System.String,Sgml.AttDef>, Char)
0000000037dedc50 000007ff00cd10cd Sgml.SgmlDtd.ParseAttList(System.Collections.Generic.Dictionary`2<System.String,Sgml.AttDef>, Char)
0000000037dedca0 000007ff00cd0e9a Sgml.SgmlDtd.ParseAttList()
0000000037dedd10 000007ff00cce1f1 Sgml.SgmlDtd.Parse()
0000000037dedd60 000007ff00ccd945 Sgml.SgmlDtd.Parse(System.Uri, System.String, System.IO.TextReader, System.String, System.String, System.Xml.XmlNameTable)
0000000037dede00 000007ff00ccd582 Sgml.SgmlReader.LazyLoadDtd(System.Uri)
0000000037dede70 000007ff00ccd1f9 Sgml.SgmlReader.OpenInput()
0000000037deded0 000007ff00cccd8c Sgml.SgmlReader.Read()
0000000037dedf20 000007fee67b3bfc System.Xml.XmlLoader.Load(System.Xml.XmlDocument, System.Xml.XmlReader, Boolean)*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_64\System.Xml\8e4323f5bfb90be4621456033d8b404b\System.Xml.ni.dll
*** ERROR: Module load completed but symbols could not be loaded for C:\Windows\assembly\NativeImages_v4.0.30319_64\System.Xml\8e4323f5bfb90be4621456033d8b404b\System.Xml.ni.dll

0000000037dedf80 000007fee67b36e0 System.Xml.XmlDocument.Load(System.Xml.XmlReader)
[deleted]
0000000037deea90 000007feea61d432 System.Threading.Tasks.Task.Execute()
0000000037deeaf0 000007fee9ed17ec System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
0000000037deeb50 000007feea617c35 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef)
0000000037deebd0 000007feea617a30 System.Threading.Tasks.Task.ExecuteEntry(Boolean)
0000000037deec10 000007fee9f1b953 System.Threading.ThreadPoolWorkQueue.Dispatch()
0000000037deecb0 000007fee9f1b7a5 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
0000000037def310 000007feeae4dc54 [DebuggerU2MCatchHandlerFrame: 0000000037def310]
0:023> !verifyheap
-verify will only produce output if there are errors in the heap
The garbage collector data structures are not in a valid state for traversal.
It is either in the "plan phase," where objects are being moved around, or
we are at the initialization or shutdown of the gc heap. Commands related to
displaying, finding or traversing objects as well as gc heap segments may not
work properly. !dumpheap and !verifyheap may incorrectly complain of heap
consistency errors.
object 000000000e34caf8: bad member 000000001024b9a0 at 000000000e34cb08
curr_object:      000000000e34caf8
Last good object: 000000000e34cab0
----------------
0:023> !analyze
Last event: 1480.e84: Exit process 0:1480, code 80131506
  debugger time: Sun Sep 18 14:22:42.592 2011 (UTC + 1:00)
0:023> !analyze -v
Last event: 1480.e84: Exit process 0:1480, code 80131506
  debugger time: Sun Sep 18 14:22:42.592 2011 (UTC + 1:00)
0:023> .do e34cab0
          ^ Syntax error in '.do e34cab0'
0:023> !do e34cab0
Name:        System.String
MethodTable: 000007feea026870
EEClass:     000007fee9baed58
Size:        72(0x48) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      appliedFiltersContainer
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007feea02c758  4000103        8         System.Int32  1 instance               23 m_stringLength
000007feea02b298  4000104        c          System.Char  1 instance               61 m_firstChar
000007feea026870  4000105       10        System.String  0   shared           static Empty
                                 >> Domain:Value  00000000021343a0:000000000db21420 <<
0:023> !do e34caf8
<Note: this object has an invalid CLASS field>
Name:        System.Reflection.RuntimeAssembly
MethodTable: 000007feea02a128
EEClass:     000007fee9baf968
Size:        48(0x30) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007feea9ef7f0  4000e14        8 ...solveEventHandler  0 instance 0000000000000000 _ModuleResolve
000007feea036338  4000e15       10 ...che.InternalCache  0 instance 000000001024b9a0 m_cachedData
000007feea0259c8  4000e16       18        System.Object  0 instance 000000000e3abd18 m_syncRoot
000007feea033450  4000e17       20        System.IntPtr  1 instance         37a95f10 m_assembly

Что это может быть?

4b9b3361

Ответ 1

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

Отладка кучи в .NET 4:

Как подозревать повреждение кучи?

Коротко:

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

  • Рассмотрение стека CLR на дампах сбоя приложения показывает сборщик мусора в верхней части стека:

    000000001dabd8c8 000007feea129a1d [**HelperMethodFrame**: 000000001dabd8c8]
    000000001dabda00 000007fee90cfce8 System.Text.StringBuilder.ExpandByABlock(Int32)
    000000001dabda40 000007fee90cfba4 System.Text.StringBuilder.Append(Char*, Int32)
    ...
    
    EXCEPTION_RECORD:  ffffffffffffffff -- (.exr 0xffffffffffffffff)
    ExceptionAddress: 000007feea129a1d (**clr!WKS::gc_heap**::find_first_object+0x0000000000000092)
       ExceptionCode: c0000005 (Access violation)
      ExceptionFlags: 00000000
    NumberParameters: 2
       Parameter[0]: 0000000000000000
       Parameter[1]: 0000000000003d80
    ...
    
  • В стеке CLR всегда отображаются разные точки. Был ли произошел сбой или отображается код, который явно не имеет значения, например метод StringBuilder, который, как показано, вызывает исключение.

Подробнее см. .NET Crash: Управляемая куча Коррупция, вызывающая неуправляемый код.

Переход шаг за шагом. Каждый следующий шаг используется, если предыдущий не помогает.

Шаг 1. Проверьте код.

Проверьте код на использование небезопасных или собственных кодов:

  • Просмотрите код для операторов unsafe, DllImport.
  • Загрузите .NET Reflector и используйте его для анализа сборок приложений для PInvoke. Аналогичным образом проанализируйте сторонние сборки, которые используются приложением.

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

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

Чтобы обрабатывать такие исключения, нужно украсить метод, который содержит оператор catch(Exception) с атрибутом [HandleProcessCorruptedStateExceptions] или применить в файле app.config следующее:

<configuration>
    <runtime>
        <legacyCorruptedStateExceptionsPolicy enabled="true" />
    </runtime>
</configuration>

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

Исключенные ошибки кучи не могут быть обработаны вообще: HandleProcessCorruptedStateExceptions не работает.

Дополнительная информация об исключениях поврежденных состояний см. в Все о поврежденных исключениях в .NET4.

Шаг 3. Живая отладка.

На этом этапе мы отлаживаем аварийное приложение в рабочей среде (или где мы можем воспроизвести аварий).

Загрузите инструменты отладки для Windows из Microsoft Windows SDK для Windows 7 и .NET Framework 4 (будет загружен веб-установщик, который позволит выбор необходимых компонентов для установки - отметьте все компоненты). Он установит как 32-разрядные, так и 64-разрядные (если ваша система x64) версии необходимых средств отладки.

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

Включить помощники отладки:

  • Запустите Application Verifier (C:\Program Files\Application Verifier - используйте требуемую версию, x86 или x64, в зависимости от вашего исполняемого режима компиляции), добавьте исполняемый файл на левую панель и в правой панели проверьте node "Основы/кучи". Сохраните изменения.

  • Запустите помощник глобальных флагов (C:\Program Files\Debugging Tools for Windows\gflags.exe - снова выберите правильную версию, x86 или x64). После запуска глобальных флагов перейдите на вкладку "Файл изображения" и в верхнем текстовом поле введите имя исполняемого файла без каких-либо путей (например, "MyProgram.exe" ). Затем нажмите клавишу Tab и установите следующие поля:

    • Включить проверку хвоста кучи
    • Включить проверку свободной памяти
    • Включить проверку параметров кучи
    • Включить проверку кучи при вызове
    • Отключить кучу coalesce на бесплатном
    • Включить кучу страниц
    • Включить теги кучи
    • Включить верификатор приложения
    • Отладчик (введите путь к установленному WinDbg в текстовом поле справа, например, C:\Program Files\Debugging Tools for Windows (x64)\windbg.exe -g).

    Подробнее см. "Куча кучи" , часть 2.

  • Перейдите в раздел "Панель управления/Система и безопасность/Система" (или щелкните правой кнопкой мыши "Компьютер" в меню "Пуск" и выберите "Свойства" ). В появившемся диалоговом окне нажмите "Дополнительные системные настройки" на вкладку "Дополнительно" и нажмите кнопку "Переменные среды". В появившемся диалоговом окне добавьте новую Системную переменную (если вы являетесь системным администратором - переменная пользователя в противном случае - в этом случае вам необходимо выйти/войти в систему). требуемая переменная - "COMPLUS_HeapVerify" со значением "1". Более подробную информацию можно найти в вопросе о переполнении стека .NET/С#: как установить переменную среды отладки COMPLUS_HeapVerify?.

Теперь мы готовы начать отладку. Запустите приложение. WinDbg должен автоматически запускаться для него. Оставьте приложение запущенным до сбоя в WinDgb, а затем просмотрите дамп.

СОВЕТ. Чтобы быстро удалить глобальные флаги, приложение Verifier и настройки вложения отладчика, удалите следующий ключ в реестре: x64 - HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\*YourAppName*

Шаг 4. Включите MDA.

Попробуйте использовать помощников по администрированию отладки. Подробности в вопросе Какие MDA полезны для отслеживания кучи?.

MDA должны использоваться вместе с WinDbg. Я использовал их даже вместе с Global Flags и Application Verifier.

Шаг 5. Включите GCStress.

Использование GCStress - экстремальный вариант, потому что приложение становится практически непригодным, но это все равно способ. Подробнее см. В GCStress: как включить в Windows 7?.

Шаг 6. Компиляция для x86.

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

Шаг 7. Отключите параллельный GC - это то, что сработало для меня

Сообщается известная проблема в .NET 4, представленная в потоке Нарушение прав доступа в .NET 4 Runtime в gc_heap:: garbage_collect без неуправляемых модулей. Проблема может быть решена путем отключения параллельного GC в файле app.config:

<?xml version="1.0"?>
<configuration>
    <runtime>
        <gcConcurrent enabled="false" />
    </runtime>
</configuration>

Ответ 2

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

Я бы сделал следующее:

  • Проверьте, есть ли у вас какой-либо небезопасный код С#. Если у вас есть, дважды проверьте там логику.
  • Включить выгружаемую кучу для вашего приложения. Запуск его с помощью выгружаемой кучи поможет выявить проблемы с неуправляемым кодом - в случае, если неуправляемый код искажает управляемую кучу.
  • Запустите !VerifyHeap в разных местах. Таким образом, вы сможете локализовать место в своем коде, где происходит коррупция.
  • Если у вас включен тип сортировки мусора для вашего приложения, временно измените его на сборку мусора рабочей станции таким образом, вы получите более предсказуемое поведение.
  • Прочитайте сообщение в блоге Tesses .NET Crash: Управляемая куча Коррупция, вызывающая неуправляемый код. Он демонстрирует некоторые примеры управляемой коррупции кучи.

Обратите внимание, что когда вы будете запускать свой код под WinDbg, вы столкнетесь с случайным первым шансом AV. Безопасно входить в это, просто наберите sxd av, как только вы присоедините WinDbg к процессу, и исследуйте только второй шанс AV.