Как отладить повреждение в управляемой куче - PullRequest
27 голосов
/ 15 августа 2011

Моя программа выдает ошибку, которую она не может обработать с помощью блока 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 после того, как я включил paged heap :

 (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

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

Ответы [ 2 ]

55 голосов
/ 17 октября 2011

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

Отладка повреждения кучи в .NET 4:

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

Коротко:

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

  2. Изучение стека 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
    ...
    
  3. Стек CLR всегда показывает разные точки. Произошел ли сбой или показанный код явно не имеет значения, как метод StringBuilder, который, как показано, вызывает исключение.

Для получения дополнительной информации см. .NET Crash: Повреждение управляемой кучи, вызывающее неуправляемый код .

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

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

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

  1. Просмотрите код для unsafe, DllImport операторов.
  2. Загрузите .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).

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

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

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

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

    Для получения дополнительной информации см. Повреждение кучи, часть 2 .

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

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

TIP : для быстрого удаления Global Flags , Application Verifier иВ настройках вложений отладчика удалите следующий ключ в реестре: x64 - HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\*YourAppName*

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

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

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

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

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

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

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

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

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

<?xml version="1.0"?>
<configuration>
    <runtime>
        <gcConcurrent enabled="false" />
    </runtime>
</configuration>
8 голосов
/ 16 августа 2011

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

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

  1. Проверьте, есть ли у вас небезопасный код C #. Если у вас есть, дважды проверьте логику там.
  2. Включить выгружаемую кучу для вашего приложения. Запуск его с помощью выгружаемой кучи поможет выявить проблемы с неуправляемым кодом - в случае, если неуправляемый код повреждает управляемую кучу.
  3. Запустите !VerifyHeap в разных местах. Таким образом, вы сможете локализовать место в вашем коде, где происходит повреждение.
  4. Если для вашего приложения включен тип сбора мусора на сервере, временно измените его на сборку мусора на рабочей станции - таким образом вы получите более предсказуемое поведение.
  5. Прочтите сообщение в блоге Tesses .NET Crash: управляемое повреждение кучи, вызывающее неуправляемый код . Это демонстрирует некоторые примеры управляемого повреждения кучи.

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

...