Данная статья создавалась как попытка продолжить изучение не достаточно хорошо знакомой для меня темы восстановления работоспособности [приложений] .Net Framework в системах Windows, а так же поиска проблем ошибок в Net Framework приложениях. Очевидно, что все тут перечисленное представляет собой достаточно упрощенный подход к изучению структур .Net-приложений, тем не менее, в некоторых случаях достаточный для выявления причин неисправностей.
Основная концепция создания .Net Framework заключалась в обеспечении свободы разработки, обусловленной возможностью создавать приложения с использованием множества языков программирования, способных исполняться на широком спектре устройств, работающих под разнообразными операционных системах [мультиязычность и кроссплатформенность]. Программа для платформы .NET Framework, в начале исполнения переводится компилятором в единый для .NET промежуточный байт-код "высокоуровневого ассемблера" виртуальной машины .NET (Common Intermediate Language, CIL -- ранее известного как Microsoft Intermediate Language, MSIL), называемый в контексте .NET сборкой (assembly). Далее получившийся код либо исполняется виртуальной машиной Common Language Runtime (общеязыковая среда выполнения, CLR), либо транслируется утилитой NGen.exe в исполняемый код для определенного целевого процессора. И на финальном этапе, встроенный в виртуальную машину CLR компилятор "на лету" (в режиме реального времени) преобразует промежуточный байт-код в машинные коды целевого процессора [для непосредственного исполнения кода ядром].
Поскольку мы упомянули некий "высокоуровневый ассемблер" виртуальной машины .NET, становится очевидным что в .Net Framework мы имеем дело с так называемым "управляемым" кодом.
В приложениях с управляемым кодом, бинарный код, получающийся на выходе компилятора, получается в формате промежуточного языка (Microsoft Intermediate Language, MSIL), который является платформонезависимым. Когда управляемый код исполняется, среда выполнения преобразует его в обычный машинный код конкретной процессорной архитектуры (х86, х64 или IA64).
Процесс генерации машинного кода из кода MSIL называется компиляцией на лету (just-in-time (JIT) compiling). После того, как JIT-компилятор (jitter) скомпилировал MSIL для определенного метода, машинный код этого метода остается в памяти. Если когда-либо еще данный метод будет вызван, машинный код просто выполняется (поскольку он уже размещен в памяти) и JIT-компилятор может вообще в этом случае не вовлекаться в процесс.
Исключения в .NET
Поскольку популярность .Net платформы с каждым годом набирает обороты, в валовом отношении растет и количество ошибок .Net Framework, возникающих в коде. С другой стороны, неоспоримым плюсом является то, что по сравнению с неуправляемыми (классическими) приложениями, управляемые приложения меньше подвержены появлению в них [определенных видов] ошибок. Тем не менее, как и любой другой тип приложений, .NET-приложения в процессе своего функционирования сталкиваются с ошибками времени выполнения кода, иными словами - с некоторыми видами исключений.
В момент возникновения исключения, CLR начинает поиск блока Catch (тип которого соответствует типу исключения) в стеке вызовов. В случае, когда ни один из блоков Catch не отвечает типу исключения (обработчик не найден), исключение считается необработанным (unhandled exception). Необработанное исключение указывает на ситуацию, не предусмотренную разработчиком приложения, и обычно считается признаком серьезной ошибки. Системный механизм WER производит запись в Журнале событий (раздел Приложение) и на экран выдается информационное окно (внешний вид может варьироваться в зависимости от версии):
Фактически на этом [моменте] работа сбойного приложения прекращается. Самая информативная для технического инженера часть - это сигнатуры проблемы, которая в Журнале событий (раздел Приложение) и в поле Подробности проблемы информационного окна дает нам некоторое представления о деталях сбоя:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Сигнатура проблемы: Имя события проблемы: CLR20r3 Сигнатура проблемы 01: mmc.exe Сигнатура проблемы 02: 6.1.7601.23892 Сигнатура проблемы 03: 5990c6ab Сигнатура проблемы 04: mscorlib Сигнатура проблемы 05: 2.0.0.0 Сигнатура проблемы 06: 5c9c4d2b Сигнатура проблемы 07: 2d59 Сигнатура проблемы 08: 79 Сигнатура проблемы 09: System.Security.Security Версия ОС: 6.1.7601.2.1.0.256.48 Код языка: 1049 |
В случае, описанном выше, имеет место падение оснастки Просмотр Событий (eventvwr.exe), которая работает через консоль управления (mmc.exe). Далее приведем описание полей сигнатуры:
Сигнатура | Описание |
---|---|
сигнатура_01 | Имя процесса программы (исполняемого образа/файла), в контексте выполнения которого возникло исключение ( <= 32 знака). |
Сигнатура_02 | Версия сборки [исполняемого образа]. |
Сигнатура_03 | Штамп времени [исполняемого образа]. |
Сигнатура_04 | Имя библиотеки (из состава приложения), сборки или иной файл из состава .Net Framework, при исполнении функции [из] которой возникла ошибка ( <= 64 знака). |
Сигнатура_05 | Версия (аварийной) библиотеки/сборки приложения/платформы .Net Framework, в которой произошло исключение. |
Сигнатура_06 | Штамп времени проблемной (аварийной) сборки. |
Сигнатура_07 |
Определение (маркер) метода (в таблице MethodDef ) и типа, в котором произошло исключение (с "обрезанным" старшим байтом 0x06, типом маркера). Идентифицируют запись в соответствующей таблице метаданных.
|
Сигнатура_08 | Смещение инструкции (команды) в рамках метода в коде на промежуточном языке (IL), при выполнении которой произошло исключение. Взяв величину смещения, при помощи любого .Net-рефлектора можно найти некорректный код. |
Сигнатура_09 | Тип вброшенного исключения (название класса или пространства имен .Net). |
Для формирования полной картины сбоя, потребуется взять во внимание совокупность всех сигнатур проблемы, в комплексе могущих дать понимание о природе сбоя (особое внимание обратить на имя класса-источника). Ну и для полноты картины можно привести открытые свойства типа System.Exception, которые можно найти в отчете отладчика/аварийном дампе памяти после прекращения работы приложения:
Свойство | Тип | Описание |
---|---|---|
message | String | Содержит осмысленный (иногда) текст, описывающий причину исключения. Сообщение содержит технические подробности, которые могут оказаться полезными для технических специалистов/разработчиков. |
data | IDictionary | Ссылка на список пар в формате параметр-значение. Непосредственно перед вбросом исключения, код добавляет запись в этот набор. |
stacktrace | String | Имена/сигнатуры методов, вызов которых привел к возникновению исключения. Помогает обнаружить объект, являющийся источником исключения. |
source | String | Имя сборки, вбросившей исключение. |
innerexception | Exception | Указатель на "предыдущее" исключение (в случае, если текущее исключение было вброшено в ходе обработки другого). Часто содержит значение null. Тип Exception содержит также открытый метод GetBaseException, анализирующий список внутренних исключений и возвращающий самое первое. |
helpURL | String | URL документации с информацией об исключении. Обычно содержит null, поскольку с точки зрения безопасности сведения о необработанных исключениях не должны быть доступны. |
TargetSite | MethodBase | Имя метода, ставшего источником исключения. |
Возникает резонный вопрос: есть ли возможность, исключительно на основании представленной выше информации, сделать однозначный вывод о причине исключительной ситуации? Я думаю, исключать подобную возможность не стоит, поскольку все зависит от глубины теоретических и практических знаний, и если у Вас уже большая наработка данных об исключениях, то я думаю сможете сделать довольно точное предположение относительно причины сбоя. Для всех остальных разработан специализированный инструментарий.
Метод 1: отладка дампа .Net-приложения
Далеко не всегда у нас имеется возможность "живой отладки" сбойного .Net-приложения в реальном времени, непосредственно на системе пользователя, значительно чаще приходится довольствоваться созданным аварийным дампом. Стоит напомнить, что в случае приложений, написанных для платформы .Net Framework, мы имеем дело с управляемым кодом, в противоположность типовому неуправляемому коду классических приложений. Но так просто до управляемого кода с помощью отладчика не добраться.
Когда .Net-приложение "падает" (завершается аварийно) или "подвисает", специалист тут же получает большую проблему, поскольку очень сложно продраться сквозь нативный ассемблерный код, исследовать стеки вызовов, найти исходные тексты и строки структур .NET-приложения. Чтобы помочь исследователям увидеть при анализе дампа или живой отладке разнообразные структуры .Net-приложения, легче баражировать через комбинации управляемого и неуправляемого кода, специалисты Microsoft разработали расширение отладчика SOS, весь функционал которого сосредоточен в библиотеке sos.dll (являющейся частью .NET Framework).
Проблемы в .Net-приложениях [с управляемым кодом] могут диагностироваться из без расширения SOS, он этот подход требует превосходного знания разнообразных внутренних структур .Net. С расширением SOS все существенно упрощается, позволяя исследователям/разработчикам сфокусироваться на поиске источника проблемы. Поэтому расширение SOS предоставляет все необходимое для отладки приложений, в которых скомбинированы управляемый и неуправляемый код.
Создание дампа приложения
- В данном сценарии подразумевается, что мы уже имеем на руках дамп сбойного приложения (полученный от пользователя). Если дамп приложения необходимо создать, то можно посоветовать использовать один из следующих способов: с использованием CDB, при помощи ProcDump, с использованием встроенного механизма WER. Способ с ProcDump один из самых простых, поэтому скачиваем ProcDump.
- Запускаем сбойное приложение через ProcDump с использованием команды:
procdump.exe -accepteula -e -w <имя_сбойного_приложения.exe> c:\temp\
где параметр c:\temp - любая временная директория по вашему выбору.
- Дожидаемся когда в приложении возникнет исключение;
- После падения приложения в каталоге C:\TEMP получаем дамп приложения (файл с расширением .dmp);
Изучение дампа приложения
Наиболее часто встречающийся сценарий подразумевает, что вы используете отладчик для изучения дампа [приложения с управляемым кодом], созданного на другом компьютере. Общий алгоритм отладки, в этом случае, следующий:
- Устанавливаем пакет Debugging Tools for Windows.
- Запускаем отладчик WinDbg. По предварительной настройке отладчика можете почитать эту статью.
- Открываем дамп приложения через меню File - Open Crash Dump.... Мы работаем с дампом процесса, поэтому отладчик должен автоматически загрузить версию DAC (компонент доступа к данным, используемый SOS для связи с CLR), соответствующую версии CLR, используемую .Net-приложением на другой станции (на которой создавался дамп). Разрядность (32/64-бит) так же имеет значение. DAC в данном случае это библиотека mscordacwks.dll, которая входит в состав пакета .NET Framework.
- Если у Вас на компьютере нет подходящей версии .Net Framework, то можно загрузить требуемый DAC с публичного сервера символов Microsoft. Для этого, на всякий случай, если предварительная настройка на получение символов у вас не проведена, мы может настроиться вручную и выполнить серию команд:
.sympath+ srv\*
!sym noisy - Важно отметить, что при отладке с использованием расширения SOS необходимо использовать правильную версию SOS. Для загрузки требуемой отлаживаемому приложению версии DAC, присоединяем отладчик к .Net-приложению, после чего расширение SOS (sos.dll) загружается автоматически. Для этого выполним следующую команду:
.cordll -ve -u -l
если по каким-либо причинам этого не произошло, можно выполнить ручную загрузку SOS:
12.loadby sos clr ;для 4.0 версии CLR.loadby sos mscorwks ;для 1.0 и 2.0 версий CLR -
Проверяем работоспособность расширения путем выполнения команды !sos.help:
1234567890:006> !sos.help-------------------------------------------------------------------------------SOS is a debugger extension DLL designed to aid in the debugging of managedprograms. Functions are listed by category, then roughly in order ofimportance. Shortcut names for popular functions are listed in parenthesis.Type "!help <functionname>" for detailed info on that function.. . .. . .. . .
Выполняем команду !sos.pe:
1234567891011121314150:006> !sos.peException object: 0000000004ff75f8Exception type: System.Reflection.TargetInvocationExceptionMessage: Адресат вызова создал исключение.InnerException: System.Security.SecurityException, use !PrintException 0000000004ff7188 to see moreStackTrace (generated):SP IP Function0000000003BF6390 000007FEE81C2501 Microsoft_ManagementConsole_ni!Microsoft.ManagementConsole.Internal.SnapInMessagePumpProxy.OnThreadException(System.Object, System.Threading.ThreadExceptionEventArgs)+0x310000000003BF63D0 000007FED415E910 System_Windows_Forms_ni!System.Windows.Forms.Application+ThreadContext.OnThreadException(System.Exception)+0xa00000000003BF6440 000007FED416BD0C System_Windows_Forms_ni!System.Windows.Forms.Control.WndProcException(System.Exception)+0x1c0000000003BF6470 000007FED46C38AC System_Windows_Forms_ni!System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)+0xbafd9cStackTraceString: <none>HResult: 80131604There are nested exceptions on this thread. Run with -nested for detailsВ принципе, мы могли сразу получить развернутый вывод по всем имеющимся вложенным объектам исключений командой !sos.pe -nested. Но можем использовать и другую стратегию: двигаться вниз по цепочке вложенных исключений. Выведем информацию по вложенному исключению командой (или щелчком по ссылке) !PrintException 0000000004ff7188 или !sos.pe 0000000004ff7188 (где аргумент команды - адрес объекта исключения):
1234567891011121314151617181920210:006> !PrintException 0000000004ff7188Exception object: 0000000004ff7188Exception type: System.Security.SecurityExceptionMessage: Запрошенный доступ к реестру запрещен.InnerException: <none>StackTrace (generated):SP IP Function0000000003BF83C0 000007FED6824B78 mscorlib_ni!Microsoft.Win32.RegistryKey.OpenSubKey(System.String, Boolean)+0x9f6cc80000000003BF8430 000007FED436CC57 System_Windows_Forms_ni!System.Windows.Forms.LinkUtilities.GetIELinkBehavior()+0x770000000003BF84A0 000007FED436D3F5 System_Windows_Forms_ni!System.Windows.Forms.LinkUtilities.EnsureLinkFonts(System.Drawing.Font, System.Windows.Forms.LinkBehavior, System.Drawing.Font ByRef, System.Drawing.Font ByRef)+0x350000000003BF8500 000007FED439D6A5 System_Windows_Forms_ni!System.Windows.Forms.LinkLabel.OnPaint(System.Windows.Forms.PaintEventArgs)+0x4350000000003BF8980 000007FED3B2B719 System_Windows_Forms_ni!System.Windows.Forms.Control.PaintWithErrorHandling(System.Windows.Forms.PaintEventArgs, Int16, Boolean)+0xa90000000003BF89F0 000007FED3B30A55 System_Windows_Forms_ni!System.Windows.Forms.Control.WmPaint(System.Windows.Forms.Message ByRef)+0x5f50000000003BF8D80 000007FED3B142F7 System_Windows_Forms_ni!System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)+0x4e70000000003BF8F30 000007FED3B09AF1 System_Windows_Forms_ni!System.Windows.Forms.Label.WndProc(System.Windows.Forms.Message ByRef)+0x410000000003BF8FE0 000007FED3B13D27 System_Windows_Forms_ni!System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)+0x470000000003BF9030 000007FED3B13BC6 System_Windows_Forms_ni!System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)+0xb6StackTraceString: <none>HResult: 8013150aThere are nested exceptions on this thread. Run with -nested for detailsтут у нас видно сразу и свойство Message, содержащее осмысленное описание ошибки и на вершине стека непосредственно функцию OpenSubKey, вызвавшую исключение. Теперь можно посмотреть CLR-стек для активного потока процесса и найти там интересующую нас функцию OpenSubKey (вывод сокращен до данных интересующей нас функции):
1234567891011121314150:006> !clrstack -p -lOS Thread Id: 0xd5c (6). . .. . .0000000003bf83c0 000007fed436cc56 Microsoft.Win32.RegistryKey.OpenSubKey(System.String, Boolean)PARAMETERS:this = <no data>name = <no data>writable = <no data>LOCALS:<no data><no data><no data>. . .. . .в данном случае почему то не видно параметров и переменных, возможно это объясняется тем, что вызываемый метод является функцией WinAPI и входные параметры в неё передаются как-то иначе. В случае же наличия параметров, можно выполнить команду !sos.do XxXXXXXXXX - с указанием адреса, который будет виден в выводе команды clrstack для данного метода. Таким образом мы надеемся получить подробную информацию об объекте, попытка доступа к которому завершилась возбуждением исключения.
Но поскольку в нашем случае параметров не видно, попробуем подойти к вопросу с другой стороны. Запрашиваем список всех объектов, находящихся в настоящее время в стеке текущего потока (вывод сокращен):123456789101112131415161718192021222324252627282930310:006> !dumpstackobjectsOS Thread Id: 0xd5c (6)RSP/REG Object Name. . .0000000003bf74f8 0000000004ff70e0 System.Runtime.CompilerServices.RuntimeHelpers+CleanupCode0000000003bf7500 0000000004ff7070 System.Environment+ResourceHelper+GetResourceStringUserData0000000003bf7748 0000000004ff70e0 System.Runtime.CompilerServices.RuntimeHelpers+CleanupCode0000000003bf7778 0000000004ff70a0 System.Runtime.CompilerServices.RuntimeHelpers+TryCode0000000003bf7780 0000000004ff7070 System.Environment+ResourceHelper+GetResourceStringUserData0000000003bf7788 0000000004ff6dd0 System.String0000000003bf7da8 0000000004ff6dd0 System.String0000000003bf7df0 0000000004a79cb8 System.Threading.Thread0000000003bf7e38 0000000004a79cb8 System.Threading.Thread0000000003bf81f0 0000000004ff5e58 System.String0000000003bf81f8 0000000004ff7188 System.Security.SecurityException0000000003bf8200 0000000004abeca8 Microsoft.Win32.RegistryKey0000000003bf8210 0000000004ff7188 System.Security.SecurityException0000000003bf8230 0000000004ff7188 System.Security.SecurityException0000000003bf8268 0000000004ff61a0 Microsoft.Win32.SafeHandles.SafeRegistryHandle0000000003bf82e8 0000000004abeca8 Microsoft.Win32.RegistryKey0000000003bf8300 0000000004ff7188 System.Security.SecurityException0000000003bf8308 0000000004abeca8 Microsoft.Win32.RegistryKey0000000003bf8310 0000000004ff7188 System.Security.SecurityException0000000003bf8320 0000000004ff5e58 System.String0000000003bf8398 0000000004ff5e58 System.String0000000003bf83a8 0000000004ff7120 System.String0000000003bf83b0 0000000004ff7188 System.Security.SecurityException0000000003bf83f0 0000000004ff61a0 Microsoft.Win32.SafeHandles.SafeRegistryHandle0000000003bf8420 0000000004ff5ce0 System.Security.Permissions.RegistryPermission0000000003bf8458 0000000004ff5d20 System.Security.FrameSecurityDescriptor. . .Так как команда !dumpstackobjects проходит по стеку вверх, в выводе её мы можем заметить повторение некоторых элементов по несколько раз, поскольку они передаются в качестве параметров ко многим функциям. В стеке можно наблюдать несколько объектов
System.Security.SecurityException
, но если вы обратите внимание на значение, то заметите, что все они ссылаются на один и тот же экземпляр объекта 0000000004ff7188. Давайте посмотрим на содержимое данного объекта:12345678910111213141516171819202122232425262728293031323334353637380:006> !dumpobj 0000000004ff7188Name: System.Security.SecurityExceptionMethodTable: 000007fed5f7ec50EEClass: 000007fed5c14110Size: 240(0xf0) bytes(C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)Fields:MT Field Offset Type VT Attr Value Name000007fed5f69d58 40000b5 8 System.String 0 instance 0000000000000000 _className000007fed5f68100 40000b6 10 ...ection.MethodBase 0 instance 0000000000000000 _exceptionMethod000007fed5f69d58 40000b7 18 System.String 0 instance 0000000000000000 _exceptionMethodString000007fed5f69d58 40000b8 20 System.String 0 instance 0000000004ff7120 _message000007fed5f61378 40000b9 28 ...tions.IDictionary 0 instance 0000000000000000 _data000007fed5f6a020 40000ba 30 System.Exception 0 instance 0000000000000000 _innerException000007fed5f69d58 40000bb 38 System.String 0 instance 0000000000000000 _helpURL000007fed5f69648 40000bc 40 System.Object 0 instance 0000000004ff7410 _stackTrace000007fed5f69d58 40000bd 48 System.String 0 instance 0000000000000000 _stackTraceString000007fed5f69d58 40000be 50 System.String 0 instance 0000000000000000 _remoteStackTraceString000007fed5f70fc8 40000bf 70 System.Int32 1 instance 0 _remoteStackIndex000007fed5f69648 40000c0 58 System.Object 0 instance 0000000000000000 _dynamicMethods000007fed5f70fc8 40000c1 74 System.Int32 1 instance -2146233078 _HResult000007fed5f69d58 40000c2 60 System.String 0 instance 0000000000000000 _source000007fed5f6c760 40000c3 68 System.IntPtr 1 instance 0 _xptrs000007fed5f70fc8 40000c4 78 System.Int32 1 instance -532459699 _xcode000007fed5f69d58 4001f2a 80 System.String 0 instance 0000000000000000 m_debugString000007fed5f5de20 4001f2b 7c System.Int32 1 instance 0 m_action000007fed5f6ba30 4001f2c 88 System.Type 0 instance 0000000000000000 m_typeOfPermissionThatFailed000007fed5f69d58 4001f2d 90 System.String 0 instance 0000000000000000 m_permissionThatFailed000007fed5f69d58 4001f2e 98 System.String 0 instance 0000000000000000 m_demanded000007fed5f69d58 4001f2f a0 System.String 0 instance 0000000000000000 m_granted000007fed5f69d58 4001f30 a8 System.String 0 instance 0000000000000000 m_refused000007fed5f69d58 4001f31 b0 System.String 0 instance 0000000000000000 m_denied000007fed5f69d58 4001f32 b8 System.String 0 instance 0000000000000000 m_permitOnly000007fed5f74180 4001f33 c0 ...tion.AssemblyName 0 instance 0000000000000000 m_assemblyName000007fed5f71d98 4001f34 c8 System.Byte[] 0 instance 0000000000000000 m_serializedMethodInfo000007fed5f69d58 4001f35 d0 System.String 0 instance 0000000000000000 m_strMethodInfo000007fed5f9feb8 4001f36 e0 System.Int32 1 instance 0 m_zone000007fed5f69d58 4001f37 d8 System.String 0 instance 0000000000000000 m_urlдля нас важным является свойство Message. Давайте выведем значение поля _message, поскольку именно в нем Message хранит текстовую строку. Шестнадцатеричное число в столбце Value является экземпляром объекта:
123456789101112131415160:006> !dumpobj 0000000004ff7120Name: System.StringMethodTable: 000007fed5f69d58EEClass: 000007fed5b6e650Size: 102(0x66) bytes(C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)String: Запрошенный доступ к реестру запрещен.Fields:MT Field Offset Type VT Attr Value Name000007fed5f70fc8 4000096 8 System.Int32 1 instance 39 m_arrayLength000007fed5f70fc8 4000097 c System.Int32 1 instance 38 m_stringLength000007fed5f6b7a0 4000098 10 System.Char 1 instance 417 m_firstChar000007fed5f69d58 4000099 20 System.String 0 shared static Empty>> Domain:Value 0000000000176120:0000000004a41308 00000000046de6e0:0000000004a41308 <<000007fed5f6b650 400009a 28 System.Char[] 0 shared static WhitespaceChars>> Domain:Value 0000000000176120:0000000004a41a38 00000000046de6e0:0000000004a7eb78 <<ну да, очень похоже на то, что мы видели выше в выводе команды !PrintException. Но это всего-лишь сообщение об ошибке, а как нам добраться до конкретного ключа, попытка доступа к которому у нас закончилась неудачей? Попробуем посмотреть объект с именем Microsoft.Win32.RegistryKey:
12345678910111213141516171819202122232425262728293031320:006> !dumpobj 0000000004abeca8Name: Microsoft.Win32.RegistryKeyMethodTable: 000007fed5f62ec0EEClass: 000007fed5b6ba20Size: 56(0x38) bytes(C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)Fields:MT Field Offset Type VT Attr Value Name000007fed5f69648 400018a 8 System.Object 0 instance 0000000000000000 __identity000007fed5f63240 400177a 10 ...afeRegistryHandle 0 instance 0000000004abec88 hkey000007fed5f70fc8 400177b 20 System.Int32 1 instance 6 state000007fed5f69d58 400177c 18 System.String 0 instance 0000000004a65d38 keyName000007fed5f68f28 400177d 28 System.Boolean 1 instance 0 remoteKey000007fed5f62630 400177e 24 System.Int32 1 instance 0 checkMode000007fed5f6c760 4001772 b80 System.IntPtr 1 shared static HKEY_CLASSES_ROOT>> Domain:Value 0000000000176120:ffffffff80000000 00000000046de6e0:ffffffff80000000 <<000007fed5f6c760 4001773 b88 System.IntPtr 1 shared static HKEY_CURRENT_USER>> Domain:Value 0000000000176120:ffffffff80000001 00000000046de6e0:ffffffff80000001 <<000007fed5f6c760 4001774 b90 System.IntPtr 1 shared static HKEY_LOCAL_MACHINE>> Domain:Value 0000000000176120:ffffffff80000002 00000000046de6e0:ffffffff80000002 <<000007fed5f6c760 4001775 b98 System.IntPtr 1 shared static HKEY_USERS>> Domain:Value 0000000000176120:ffffffff80000003 00000000046de6e0:ffffffff80000003 <<000007fed5f6c760 4001776 ba0 System.IntPtr 1 shared static HKEY_PERFORMANCE_DATA>> Domain:Value 0000000000176120:ffffffff80000004 00000000046de6e0:ffffffff80000004 <<000007fed5f6c760 4001777 ba8 System.IntPtr 1 shared static HKEY_CURRENT_CONFIG>> Domain:Value 0000000000176120:ffffffff80000005 00000000046de6e0:ffffffff80000005 <<000007fed5f6c760 4001778 bb0 System.IntPtr 1 shared static HKEY_DYN_DATA>> Domain:Value 0000000000176120:ffffffff80000006 00000000046de6e0:ffffffff80000006 <<000007fed5f57b48 4001779 9d8 System.Object[] 0 shared static hkeyNames>> Domain:Value 0000000000176120:0000000004a65e68 00000000046de6e0:0000000004abec30 <<000007fed5f70fc8 400177f bb8 System.Int32 1 shared static _SystemDefaultCharSize>> Domain:Value 0000000000176120:2 00000000046de6e0:2 <<А затем значение поля keyName:
123456789101112131415160:006> !dumpobj 0000000004a65d38Name: System.StringMethodTable: 000007fed5f69d58EEClass: 000007fed5b6e650Size: 60(0x3c) bytes(C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)String: HKEY_CURRENT_USERFields:MT Field Offset Type VT Attr Value Name000007fed5f70fc8 4000096 8 System.Int32 1 instance 18 m_arrayLength000007fed5f70fc8 4000097 c System.Int32 1 instance 17 m_stringLength000007fed5f6b7a0 4000098 10 System.Char 1 instance 48 m_firstChar000007fed5f69d58 4000099 20 System.String 0 shared static Empty>> Domain:Value 0000000000176120:0000000004a41308 00000000046de6e0:0000000004a41308 <<000007fed5f6b650 400009a 28 System.Char[] 0 shared static WhitespaceChars>> Domain:Value 0000000000176120:0000000004a41a38 00000000046de6e0:0000000004a7eb78 <<опять же, добрались до куста HKEY_CURRENT_USER, можно конечно попробовать поиграться с разрешениями на целый куст, но это не очень хорошая затея и комфортнее было бы определить полный путь. Как нам это сделать? Вспомним, что выделенная стеку область памяти используется [в том числе] для передачи параметров в методы и хранения
определенных в пределах методов переменных. Поэтому непосредственно до вызова самого метода (снизу вверх) в стеке должны храниться параметры. Давайте вернемся к списку объектов (виденному нами выше) в стеке, среди прочего у нас есть объекты System.String:12345. . .0000000003bf8320 0000000004ff5e58 System.String0000000003bf8398 0000000004ff5e58 System.String0000000003bf83a8 0000000004ff7120 System.String. . .Последний в списке (0000000004ff7120) нами уже проверен выше. Два остальных объекта у нас идентичны, поскольку показывают один и тот же адрес, поэтому стоит просмотреть содержимое:
123456789101112131415160:006> !dumpobj 0000000004ff5e58Name: System.StringMethodTable: 000007fed5f69d58EEClass: 000007fed5b6e650Size: 154(0x9a) bytes(C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)String: Software\Microsoft\Internet Explorer\MainFields:MT Field Offset Type VT Attr Value Name000007fed5f70fc8 4000096 8 System.Int32 1 instance 65 m_arrayLength000007fed5f70fc8 4000097 c System.Int32 1 instance 41 m_stringLength000007fed5f6b7a0 4000098 10 System.Char 1 instance 53 m_firstChar000007fed5f69d58 4000099 20 System.String 0 shared static Empty>> Domain:Value 0000000000176120:0000000004a41308 00000000046de6e0:0000000004a41308 <<000007fed5f6b650 400009a 28 System.Char[] 0 shared static WhitespaceChars>> Domain:Value 0000000000176120:0000000004a41a38 00000000046de6e0:0000000004a7eb78 <<И что же мы тут видим? Объект содержит строку, являющуюся полным путем к искомому разделу реестра. В итоге, мы смогли воспроизвести полный путь раздела, при попытке доступа к которому возникло исключение: это HKCU\Software\Microsoft\Internet Explorer\Main. Смотрим через regedit.exe на разрешения к данному ключу, и выясняется, что для текущего пользователя кто-то их убрал (очередное криво-установившееся обновление?). Не важно, посмотрим разрешения на аналогичной рабочей операционной системе, они там присутствуют, значит надо выставлять по аналогии.
Метод 2: определение источника при помощи ProcMon
Отладчик является довольно мощным средством для поиска причин ошибок Net Framework. Но встречаются случаи, когда мы (по абсолютно разным причинам) не можем найти нужных структур в файле дампа приложения, либо у нас не хватает уровня знаний для того, чтобы до этих данных дотянуться. В подобной ситуации хорошо бы иметь под рукой еще несколько методов анализа боя. И в этом разделе мы рассмотрим другой метод поиска причин ошибок Net Framework, который предполагает работу уже с объектами файловой системы, сбор возникающих во время работы сбойного приложения событий при помощи средства под названием Procmon.
Общий алгоритм решения:
- Скачиваем утилиту Procmon. Более подробно о данной программе можно почитать в этой статье.
- Запускаем Procmon из-под учетной записи с правами локального Администратора (с повышением привилегий). Стартует процесс сбора системных событий.
- В параллель запускаем вызывающее ошибку приложение. Дожидаемся возникновения ошибки.
- Переключаемся в окно Process Monitor, нажатием на значок лупы прекращаем запись событий (дабы не раздувать список событий и не увеличивать понапрасну нагрузку на систему).
- В получившемся списке собранных событий ставим курсор (маркируем) на самое первое событие, открываем окно поиска комбинацией клавиш Ctrl+F и сперва ищем словосочетание access denied.
- Каждое найденное таким образом событие сверяем по столбцу Process Name, дабы имя соответствовало нашей проблемной (падающей, сбойной) программе (для случая выше это eventvwr.exe). Выглядеть это будет подобным образом:
Как мы видим, проблема заключается в отсутствии доступа к разделу реестра HKCU\Software\Microsoft\Internet Explorer\Main
- Запускаем Regedit.exe. Разворачиваем путь до проблемного раздела реестра, в меню открываем Разрешения объекта. Для начала меняем владельца объекта на собственную учетную запись, затем переоткрываем разрешения и устанавливаем необходимые разрешения безопасности для нашей учетной записи (из-под которой планируется запускать наше сбойное приложение).
Метод 3: дизассемблер IL (IL DASM)
Все исполняемые файлы, содержащие в себе управляемый и неуправляемый код, размещаются в файловой системе в виде типовых исполняемых .exe-файлов в формате PE. Дизассемблер, входящий в состав пакета Windows SDK, предназначен для декомпиляции (рефлексии) исполняемых файлов, содержащих в своем составе код на языке IL.
- Скачиваем и устанавливаем Архив Windows SDK под нужную нам систему;
- Запускаем ildasm.exe. Обычно располагается по пути: C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.2 Tools"\ - *соответственно не забудьте сделать корректировку версий в составе пути, у вас они могут отличаться.
- Открываем файл, фигурирующий у нас в параметре Сигнатура 04 подробностей исключения.
- Открываем окно с метаданными указанной сборки: пункт меню Вид - Метаданные - Показать!.
- В открывшемся окне метаданных (MetaInfo) выполняем поиск по определению метода, которое содержится в параметре исключения Сигнатура 07 деталей сбоя, при этом добавляя префикс 0x06 (таблица определений методов). В итоге, для примера выше, получается значение
06002d59
. - В найденной записи таблиц метаданных смотрим соответствующее имя метода в поле MethodName: <имя_метода> (06002d59).
Таким образом мы узнаем метод, в котором возникло (необработанное) исключение, и имея под рукой его исходный код, уже гораздо проще найти место возникновения ошибки. Преимущество дизассемблера IL Dasm состоит в том, что он самостоятельно анализирует таблицы метаданных и группирует всю необходимую информацию в удобочитаемом виде, поэтому специалисту не приходится заниматься самостоятельным синтаксическим разбором "сырых" табличных данных.