Хочу начать серию статей, посвящённых отладке ваших .NET приложений на стороне заказчика, а также оптимизации вашего кода. В связи с этим понадобиться немного подготовить вашу систему. В этой статье мы ознакомимся с различными инструментами для отладки приложений, немного углубимся в описание CLR, где это будет необходимо.
Мне удобно, когда это всё установлено на отдельную виртуальную машину, и не засоряет основную ОС, которую я использую только для разработки приложений на .NET и развлечения.
При разработке приложений разработчики обычно работают на машинах, над которыми у них есть полный контроль, и они могут использовать различные утилиты. Для отладки у них есть среды разработки, с возможностью пошагового выполнения кода, изменения его и компиляции. Без всяких проблем можно в любое время приостановить приложение, запустить его снова. Однако на серверах заказчика, приостанавливать приложение для пошаговой отладки практически невозможно без того, что пользователи системы будут затронуты. Вообще, отладка приложений на сервере заказчика означает обычно, что проблема должна быть решена в кратчайшие сроки. При простое сервиса заказчик теряет деньги, уходят клиенты, идёт простой производственного цикла, возможны и другие проблемы. При возникновении проблемы, крайне желательно предпринять меры для её временного решения. Это может быть перезагрузка приложения, ограничение функциональности и другие решения по ситуации.
Может случиться несколько проблем, это: зависание приложения, взаимные блокировки, необрабатываемые или фатальные исключения, потеря данных, проблемы с производительностью, утечки ресурсов и памяти, некорректная функциональность. Все эти ситуации могут затрагивать как всех пользователей системы, так и конкретных пользователей.
Основные причины, которые приводят неполадкам в приложении, это различие окружения (аппаратного, программного, различие в настройке), а также взаимодействие с другими системами.
Чем так сложна отладка приложений на стороне заказчика? Нельзя использовать стандартные средства, сложно или невозможно получить доступ к серверу, проблема может появляться очень редко или только на стороне заказчика.
В зависимости от типа вашего приложения, вы можете выполнить следующие шаги перед тем, как приложение будет установлено у заказчика.
Серверное приложение:
Клиентское приложение:
Чтобы свести риски к минимуму, лучше находить и исправлять ошибки до того, как они появятся у заказчика, отрабатывать различные ситуации, делать автоматизированные тестирования, как стандартные в Visual Studio, так и сторонние — NUnit, NCover.
При возникновении проблемы важно собрать, как можно больше информации, которая будет полезна, для изучения и исправления ошибки.
Я не буду сильно вдаваться в подробности. Они легкодоступны в различных вариантах, я лишь немного остановлюсь на том, что мы дальше будем использовать, и отмечу дополнительные источники информации.
Исполняемый файл для .NET начинается с обычных заголовков для MS-DOS и COFF-заголовка, стандартных в отношении всех Windows приложений.
В первой секции данных находятся CLR-заголовок и данные.
Отметим, что первая секция имеет атрибуты CNT_CODE, MEM_EXECUTE, MEM_READ, указывающие загрузчику на то, что в секции содержится код, который будет выполняться средой выполнения. В таблице импорта можно увидеть вызов “_CorExeMain” в случае, если у вас выполняемое приложение. Она реализована в mscoree.dll – в основном модуле среды выполнения.
Дополнительную информацию вы можете получить из статьи Unmanaged API Reference.
За этими сегментами следует сам IL код вашего приложения. Результатом компиляции является код на промежуточном языке (MSIL). Это процессорно независимый язык – язык более высокого уровня, чем native код. Спецификации вы можете найти здесь.
За IL кодом идётут заголовки метаданных, сами метаданные и дополнительные сегменты, содержащие ресурсы приложения, native код приложения, если приложение было разработано на управляемом c++.
Информацию о структуре исполняемого файла можно получить с помощью стандартной утилиты ildasm.exe.
На этом этапе я надеюсь вы уже установили все нужные приложения и настроили их (добавьте ещё директорию Debugging Tools в переменную окружения PATH). Я буду делать дальнейшее исследование на этой тестовой программе:
Вы можете (и даже нужно) написать своё приложение, которое будет выполнять какую-нибудь чушь. Что важного в этой программе (о__О, меня от таких мыслей перекосило), а важного в ней то, что мы создаём несколько объектов в хипе, вызываем несколько методов, и в конце случайно генерируем исключение. Создадим debug версию приложения с символами.
Запустим наше приложение. И введём такую команду в директории с приложением
adplus -quiet -crash -fullonfirst -pn ApplicationForWinDbg.exe
ApplicationForWinDbg.exe — ваш скомпилённый exe.
В консольном приложении нажмём любую клавишу. Если вы всё сделали правильно, то в директории, куда были установлены Debugging Tools появится папка Crash_Mode__Date_[DATE]__Time_[TIME]. Где [DATE] и [TIME] – дата и время создания дампа.
Мы создали дамп, попробуем его исследовать, а заодно разобраться во внутренностях CLR. Детальных объяснений в данной статье я не буду давать, для нас главное научиться использовать WinDbg + SOS. Более детально в других статьях.
Открываем дамп в WinDbg. У вас должно получиться что-то на подобие этого:
Загружаем расширение WinDbg – SOS, выполнив команду:
0:000>.loadby sos mscorwks
Чтобы проверить, как прошла загрузка, выполним следующую команду:
0:000> !eeversion
Мы убедились, что расширение загружено, нажимаем ctrl+s и добавляем путь до нашего приложения, чтобы WinDbg подгрузил символы. Теперь можем приступить к исследованию. Посмотрим на потоки, которые есть у нас в приложении:
0:000> ~*
Посмотрим на управляемые потоки:
0:000> !threads
Из этого мы можем заметить, что у нас в приложении 2 управляемых потока – основной управляемый поток, в котором выполняется наше приложение, и поток Finalizer. И ещё есть один неуправляемый поток mscorwks!DebuggerRCThread::ThreadProcStatic – это поток Debugger'а. Спецификации общения закрыты, предоставляет отладочную информацию, возможности пошаговой отладки и др. Этот поток существует вне зависимости от того, создаёте ли вы Release версию вашего приложения или Debug версию.
Посмотрим на AppDomain'ы нашего приложения:
0:000> !dumpdomain
Можно увидеть, что кроме домена, в котором выполняется наш код, у нас есть ещё System Domain, Shared Domain. Первый отвечает за загрузку Shared Domain, а также за загрузку и выгрузку всех пользовательских доменов. Второй выступает как хранилище для доменно-нейтральных ассемблерных сборок. Код не выполняется в Shared Domain, потому что код может быть только выполнен в пользовательских доменах.
В пользовательском домене мы видим модули – выполняемые файлы dll, exe и другие. Ассемблерная сборка может содержать несколько модулей, это не только выполняемые файлы, но и файлы ресурсов. Возьмём из предыдущего примера адрес нашей ассемблерной сборки 0051ca70, и получим дополнительную информацию:
0:000> !dumpassembly 0x0051ca70
Посмотрим на информацию о модуле 00202c5c:
0:000> !dumpmodule 0x00202c5c
Если мы используем параметр –mt, то мы также получим информацию о типах, в нашем модуле:
0:000> !dumpmodule -mt 0x00202c5c
Мы обнаружили наши типы, определённые в коде, это ApplicationForWinDbg.Class1 и ApplicationForWinDbg.Program. Информация об этих классах хранится во внутренних структурах EEClass. Эта стуктура содержит информацию о количестве интерфейсов, методов, полей, а также их структуры и многое другое.
Посмотрим, что у нас твориться в хипе:
0:000> !dumpheap
Попробуйте ответить, сколько строк, находится сейчас в хипе?
Получим информацию о нашем классе ApplicationForWinDbg.Class1 используя указатель на метаданные:
0:000> !dumpmt 0x00203080
Теперь нам доступен указатель на его EEClass, с его описанием, смотрим:
0:000> !dumpclass 0x0020135c
Посмотрим на список всех методов, у нашего класса, по его указателю на таблицу методов 00203080:
0:000> !dumpmt -md 0x00203080
И для примера посмотрим на информацию о ApplicationForWinDbg.Class1.Method1 и его скомпилированный код:
0:000> !dumpmd 0x00203060
0:000> !u 0x004b0188
На этом пожалуй закончу, так как статья получилась длинная из за всех листингов. Ждите продолжения. И более подробных объяснений назначения всех полей, а также в следующей статье мы поговорим о других коммандах.
Утилиты
- WinDbg + SOS, adplus.vbs – скачать можно с официальной страницы Debugging Tools for Windows — Overview. Как настроить WinDbg на сервер символов, можно прочитать в статье Use the Microsoft Symbol Server to obtain debug symbol files, шпаргалку по командам можно взять здесь, а описание параметров adplus.vbs в статье How to use ADPlus to troubleshoot «hangs» and «crashes»;
- Debug Diagnostic Tool v1.1;
- Reflector;
- CLR Profiler for the .NET Framework 2.0, или более удобный, но платный JetBrains dotTrace (ознакомительный период 10 дней).
Мне удобно, когда это всё установлено на отдельную виртуальную машину, и не засоряет основную ОС, которую я использую только для разработки приложений на .NET и развлечения.
Отладка приложений .NET
При разработке приложений разработчики обычно работают на машинах, над которыми у них есть полный контроль, и они могут использовать различные утилиты. Для отладки у них есть среды разработки, с возможностью пошагового выполнения кода, изменения его и компиляции. Без всяких проблем можно в любое время приостановить приложение, запустить его снова. Однако на серверах заказчика, приостанавливать приложение для пошаговой отладки практически невозможно без того, что пользователи системы будут затронуты. Вообще, отладка приложений на сервере заказчика означает обычно, что проблема должна быть решена в кратчайшие сроки. При простое сервиса заказчик теряет деньги, уходят клиенты, идёт простой производственного цикла, возможны и другие проблемы. При возникновении проблемы, крайне желательно предпринять меры для её временного решения. Это может быть перезагрузка приложения, ограничение функциональности и другие решения по ситуации.
Может случиться несколько проблем, это: зависание приложения, взаимные блокировки, необрабатываемые или фатальные исключения, потеря данных, проблемы с производительностью, утечки ресурсов и памяти, некорректная функциональность. Все эти ситуации могут затрагивать как всех пользователей системы, так и конкретных пользователей.
Основные причины, которые приводят неполадкам в приложении, это различие окружения (аппаратного, программного, различие в настройке), а также взаимодействие с другими системами.
Чем так сложна отладка приложений на стороне заказчика? Нельзя использовать стандартные средства, сложно или невозможно получить доступ к серверу, проблема может появляться очень редко или только на стороне заказчика.
В зависимости от типа вашего приложения, вы можете выполнить следующие шаги перед тем, как приложение будет установлено у заказчика.
Серверное приложение:
- Установить пакет отладки на сервер заказчика;
- Установить сервер символов на вашей стороне;
- Добавить логирование и средства мониторинга.
Клиентское приложение:
- Установить сервер символов на вашей стороне;
- Добавить логирование и средства мониторинга.
Чтобы свести риски к минимуму, лучше находить и исправлять ошибки до того, как они появятся у заказчика, отрабатывать различные ситуации, делать автоматизированные тестирования, как стандартные в Visual Studio, так и сторонние — NUnit, NCover.
При возникновении проблемы важно собрать, как можно больше информации, которая будет полезна, для изучения и исправления ошибки.
Структура управляемой ассемблерной сборки
Я не буду сильно вдаваться в подробности. Они легкодоступны в различных вариантах, я лишь немного остановлюсь на том, что мы дальше будем использовать, и отмечу дополнительные источники информации.
Исполняемый файл для .NET начинается с обычных заголовков для MS-DOS и COFF-заголовка, стандартных в отношении всех Windows приложений.
В первой секции данных находятся CLR-заголовок и данные.
Отметим, что первая секция имеет атрибуты CNT_CODE, MEM_EXECUTE, MEM_READ, указывающие загрузчику на то, что в секции содержится код, который будет выполняться средой выполнения. В таблице импорта можно увидеть вызов “_CorExeMain” в случае, если у вас выполняемое приложение. Она реализована в mscoree.dll – в основном модуле среды выполнения.
Дополнительную информацию вы можете получить из статьи Unmanaged API Reference.
За этими сегментами следует сам IL код вашего приложения. Результатом компиляции является код на промежуточном языке (MSIL). Это процессорно независимый язык – язык более высокого уровня, чем native код. Спецификации вы можете найти здесь.
За IL кодом идётут заголовки метаданных, сами метаданные и дополнительные сегменты, содержащие ресурсы приложения, native код приложения, если приложение было разработано на управляемом c++.
Информацию о структуре исполняемого файла можно получить с помощью стандартной утилиты ildasm.exe.
Изучаем возможности WinDbg+SOS, adplus.vbs
На этом этапе я надеюсь вы уже установили все нужные приложения и настроили их (добавьте ещё директорию Debugging Tools в переменную окружения PATH). Я буду делать дальнейшее исследование на этой тестовой программе:
Вы можете (и даже нужно) написать своё приложение, которое будет выполнять какую-нибудь чушь. Что важного в этой программе (о__О, меня от таких мыслей перекосило), а важного в ней то, что мы создаём несколько объектов в хипе, вызываем несколько методов, и в конце случайно генерируем исключение. Создадим debug версию приложения с символами.
Запустим наше приложение. И введём такую команду в директории с приложением
adplus -quiet -crash -fullonfirst -pn ApplicationForWinDbg.exe
ApplicationForWinDbg.exe — ваш скомпилённый exe.
В консольном приложении нажмём любую клавишу. Если вы всё сделали правильно, то в директории, куда были установлены Debugging Tools появится папка Crash_Mode__Date_[DATE]__Time_[TIME]. Где [DATE] и [TIME] – дата и время создания дампа.
Мы создали дамп, попробуем его исследовать, а заодно разобраться во внутренностях CLR. Детальных объяснений в данной статье я не буду давать, для нас главное научиться использовать WinDbg + SOS. Более детально в других статьях.
Открываем дамп в WinDbg. У вас должно получиться что-то на подобие этого:
Загружаем расширение WinDbg – SOS, выполнив команду:
0:000>.loadby sos mscorwks
Чтобы проверить, как прошла загрузка, выполним следующую команду:
0:000> !eeversion
2.0.50727.3053 retail Workstation mode SOS Version: 2.0.50727.3053 retail build
Мы убедились, что расширение загружено, нажимаем ctrl+s и добавляем путь до нашего приложения, чтобы WinDbg подгрузил символы. Теперь можем приступить к исследованию. Посмотрим на потоки, которые есть у нас в приложении:
0:000> ~*
. 0 Id: e8c.11bc Suspend: 1 Teb: 7ffde000 Unfrozen Start: ApplicationForWinDbg!COM+_Entry_Point <PERF> (ApplicationForWinDbg+0x284e) (0011284e) Priority: 0 Priority class: 32 Affinity: 3 1 Id: e8c.13f0 Suspend: 1 Teb: 7ffdd000 Unfrozen Start: mscorwks!DebuggerRCThread::ThreadProcStatic (7243237f) Priority: 0 Priority class: 32 Affinity: 3 2 Id: e8c.130c Suspend: 1 Teb: 7ffdc000 Unfrozen Start: mscorwks!Thread::intermediateThreadProc (724c1fcf) Priority: 2 Priority class: 32 Affinity: 3
Посмотрим на управляемые потоки:
0:000> !threads
ThreadCount: 2 UnstartedThread: 0 BackgroundThread: 1 PendingThread: 0 DeadThread: 0 Hosted Runtime: no PreEmptive GC Alloc Lock ID OSID ThreadOBJ State GC Context Domain Count APT Exception 0 1 11bc 00529188 a020 Disabled 01c903cc:01c91fe8 00524c40 0 MTA 2 2 130c 00537020 b220 Enabled 00000000:00000000 00524c40 0 MTA (Finalizer)
Из этого мы можем заметить, что у нас в приложении 2 управляемых потока – основной управляемый поток, в котором выполняется наше приложение, и поток Finalizer. И ещё есть один неуправляемый поток mscorwks!DebuggerRCThread::ThreadProcStatic – это поток Debugger'а. Спецификации общения закрыты, предоставляет отладочную информацию, возможности пошаговой отладки и др. Этот поток существует вне зависимости от того, создаёте ли вы Release версию вашего приложения или Debug версию.
Посмотрим на AppDomain'ы нашего приложения:
0:000> !dumpdomain
-------------------------------------- System Domain: 728ed058 LowFrequencyHeap: 728ed07c HighFrequencyHeap: 728ed0c8 StubHeap: 728ed114 Stage: OPEN Name: None -------------------------------------- Shared Domain: 728ec9a8 LowFrequencyHeap: 728ec9cc HighFrequencyHeap: 728eca18 StubHeap: 728eca64 Stage: OPEN Name: None Assembly: 0051c920 -------------------------------------- Domain 1: 00524c40 LowFrequencyHeap: 00524c64 HighFrequencyHeap: 00524cb0 StubHeap: 00524cfc Stage: OPEN SecurityDescriptor: 00526198 Name: ApplicationForWinDbg.exe Assembly: 0051c920 [C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll] ClassLoader: 0051c990 SecurityDescriptor: 005384a8 Module Name 70da1000 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll Assembly: 0051ca70 [D:\#Projects\#Active\TestApplicationNet\ApplicationForWinDbg\bin\Debug\ApplicationForWinDbg.exe] ClassLoader: 0051cae0 SecurityDescriptor: 0053d088 Module Name 00202c5c D:\#Projects\#Active\TestApplicationNet\ApplicationForWinDbg\bin\Debug\ApplicationForWinDbg.exe
Можно увидеть, что кроме домена, в котором выполняется наш код, у нас есть ещё System Domain, Shared Domain. Первый отвечает за загрузку Shared Domain, а также за загрузку и выгрузку всех пользовательских доменов. Второй выступает как хранилище для доменно-нейтральных ассемблерных сборок. Код не выполняется в Shared Domain, потому что код может быть только выполнен в пользовательских доменах.
В пользовательском домене мы видим модули – выполняемые файлы dll, exe и другие. Ассемблерная сборка может содержать несколько модулей, это не только выполняемые файлы, но и файлы ресурсов. Возьмём из предыдущего примера адрес нашей ассемблерной сборки 0051ca70, и получим дополнительную информацию:
0:000> !dumpassembly 0x0051ca70
Parent Domain: 00524c40 Name: D:\#Projects\#Active\TestApplicationNet\ApplicationForWinDbg\bin\Debug\ApplicationForWinDbg.exe ClassLoader: 0051cae0 SecurityDescriptor: 002bed20 Module Name 00202c5c D:\#Projects\#Active\TestApplicationNet\ApplicationForWinDbg\bin\Debug\ApplicationForWinDbg.exe
Посмотрим на информацию о модуле 00202c5c:
0:000> !dumpmodule 0x00202c5c
Name: D:\#Projects\#Active\TestApplicationNet\ApplicationForWinDbg\bin\Debug\ApplicationForWinDbg.exe Attributes: PEFile Assembly: 0051ca70 LoaderHeap: 00000000 TypeDefToMethodTableMap: 00200038 TypeRefToMethodTableMap: 00200048 MethodDefToDescMap: 002000a0 FieldDefToDescMap: 002000b8 MemberRefToDescMap: 002000bc FileReferencesMap: 00200118 AssemblyReferencesMap: 0020011c MetaData start address: 001120e4 (1668 bytes)
Если мы используем параметр –mt, то мы также получим информацию о типах, в нашем модуле:
0:000> !dumpmodule -mt 0x00202c5c
Name: D:\#Projects\#Active\TestApplicationNet\ApplicationForWinDbg\bin\Debug\ApplicationForWinDbg.exe Attributes: PEFile Assembly:thodTableMap: 00200038 TypeRefToMethodTableMap: 00200048 MethodDefToDescMap: 002000a0 FieldDefToDescMap: 002000b8 MemberRefToDescMap: 002000bc FileReferencesMap: 00200118 AssemblyReferencesMap: 0020011c MetaData start address: 001120e4 (1668 bytes) Types defined in this module MT TypeDef Name ------------------------------------------------------------------------------ 00203080 0x02000002 ApplicationForWinDbg.Class1 0020300c 0x02000003 ApplicationForWinDbg.Program Types referenced in this module MT TypeRef Name ------------------------------------------------------------------------------ 71010508 0x01000001 System.Object 710108ec 0x01000012 System.String 71014258 0x01000013 System.Console 71012b38 0x01000015 System.Int32 0051ca70 LoaderHeap: 00000000 TypeDefToMe
Мы обнаружили наши типы, определённые в коде, это ApplicationForWinDbg.Class1 и ApplicationForWinDbg.Program. Информация об этих классах хранится во внутренних структурах EEClass. Эта стуктура содержит информацию о количестве интерфейсов, методов, полей, а также их структуры и многое другое.
Посмотрим, что у нас твориться в хипе:
0:000> !dumpheap
Address MT Size 01c71000 0052ccc8 12 Free 01c7100c 0052ccc8 12 Free 01c71018 0052ccc8 12 Free 01c71024 71010b10 72 01c7106c 71010ba0 72 01c710b4 71010c30 72 01c710fc 71010cc0 72 01c71144 71010cc0 72 01c7118c 71010508 12 01c71198 710108ec 20 01c711ac 71010fb8 28 01c711c8 710108ec 160 01c71268 710108ec 216 01c71340 710110cc 100 01c713a4 710113d8 44 01c713d0 70fe40bc 80 01c71420 710108ec 28 01c7143c 710108ec 32 01c7145c 710108ec 20 01c71470 710108ec 52 01c714a4 710108ec 40 ………………………………………… 01c90398 7101151c 24 01c903b0 710108ec 28 02c71000 0052ccc8 16 Free 02c71010 70fe40bc 4096 02c72010 0052ccc8 16 Free 02c72020 70fe40bc 528 02c72230 0052ccc8 16 Free 02c72240 70fe40bc 4096 02c73240 0052ccc8 16 Free total 5361 objects Statistics: MT Count TotalSize Class Name 71013dc0 1 12 System.Text.DecoderExceptionFallback 71013d7c 1 12 System.Text.EncoderExceptionFallback 71013ae4 1 16 System.Text.DecoderReplacementFallback 71013a94 1 16 System.Text.EncoderReplacementFallback 71014808 1 20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle 710147b0 1 20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle 71014724 1 20 System.Text.InternalEncoderBestFitFallback 71012e50 1 20 System.Security.PermissionToken 7100e8b8 1 20 Microsoft.Win32.SafeHandles.SafeFileHandle 71014770 1 24 System.Text.InternalDecoderBestFitFallback 710143c0 1 24 System.IO.TextWriter+SyncTextWriter 710127cc 2 24 System.Security.Permissions.SecurityPermission 71012510 1 24 System.OperatingSystem 71014290 1 28 Microsoft.Win32.Win32Native+InputRecord 71013f24 1 28 System.Text.EncoderNLS 71013c34 1 28 System.IO.Stream+NullStream 71013990 1 28 System.Text.UTF8Encoding 71010fb8 1 28 System.SharedStatics 71013ea4 1 32 System.Text.UTF8Encoding+UTF8Encoder 71012efc 1 32 System.Security.PermissionTokenFactory 710122cc 1 32 Microsoft.Win32.Win32Native+OSVERSIONINFO 710142e4 1 36 System.IO.__ConsoleStream 71012eb0 1 36 System.Security.Util.TokenBasedSet 71012d18 1 36 System.Security.Permissions.FileIOPermission 710120c8 1 36 System.Int64[] 710123d4 1 40 Microsoft.Win32.Win32Native+OSVERSIONINFOEX 7101278c 1 44 System.Security.FrameSecurityDescriptor 710113d8 1 44 System.AppDomainSetup 71012874 3 48 System.Security.Permissions.FileIOAccess 71012494 2 48 System.Version 71011e38 2 48 System.Reflection.Assembly 7100e6f4 4 48 System.UInt16 71012c14 2 56 System.Collections.ArrayList+ArrayListEnumeratorSimple 71010ec0 1 56 System.Threading.Thread 71013714 1 68 System.Globalization.CultureTable 710128bc 3 72 System.Security.Util.StringExpressionSet 710125b0 2 72 System.Security.PermissionSet 71010c30 1 72 System.ExecutionEngineException 71010ba0 1 72 System.StackOverflowException 71010b10 1 72 System.OutOfMemoryException 71014608 1 76 System.Text.SBCSCodePageEncoding 710137a8 5 100 System.Globalization.CultureTableItem 710110cc 1 100 System.AppDomain 0052ccc8 7 100 Free 71012b38 9 108 System.Int32 710140d4 2 112 System.IO.StreamWriter 71010a28 6 120 System.Text.StringBuilder 71010508 10 120 System.Object 710136c4 3 144 System.Globalization.CultureTableRecord 7101291c 6 144 System.Collections.ArrayList 71010cc0 2 144 System.Threading.ThreadAbortException 71011a6c 8 160 System.RuntimeType 71011fe4 3 192 System.IO.UnmanagedMemoryStream 710134e8 3 204 System.Globalization.CultureInfo 71013850 2 256 System.Globalization.NumberFormatInfo 71012f40 7 392 System.Collections.Hashtable 7101335c 3 684 System.Byte[] 7101303c 7 1008 System.Collections.Hashtable+bucket[] 71012a88 14 1536 System.Int32[] 70fe40bc 28 9564 System.Object[] 00203080 1000 12000 ApplicationForWinDbg.Class1 7101151c 1022 25376 System.Char[] 710108ec 3160 82600 System.String Total 5361 objects
Попробуйте ответить, сколько строк, находится сейчас в хипе?
Получим информацию о нашем классе ApplicationForWinDbg.Class1 используя указатель на метаданные:
0:000> !dumpmt 0x00203080
EEClass: 0020135c Module: 00202c5c Name: ApplicationForWinDbg.Class1 mdToken: 02000002 (D:\#Projects\#Active\TestApplicationNet\ApplicationForWinDbg\bin\Debug\ApplicationForWinDbg.exe) BaseSize: 0xc ComponentSize: 0x0 Number of IFaces in IFaceMap: 0 Slots in VTable: 7
Теперь нам доступен указатель на его EEClass, с его описанием, смотрим:
0:000> !dumpclass 0x0020135c
Class Name: ApplicationForWinDbg.Class1 mdToken: 02000002 (D:\#Projects\#Active\TestApplicationNet\ApplicationForWinDbg\bin\Debug\ApplicationForWinDbg.exe) Parent Class: 70da3ef0 Module: 00202c5c Method Table: 00203080 Vtable Slots: 4 Total Method Slots: 5 Class Attributes: 100000 NumInstanceFields: 0 NumStaticFields: 0
Посмотрим на список всех методов, у нашего класса, по его указателю на таблицу методов 00203080:
0:000> !dumpmt -md 0x00203080
EEClass: 0020135c Module: 00202c5c Name: ApplicationForWinDbg.Class1 mdToken: 02000002 (D:\#Projects\#Active\TestApplicationNet\ApplicationForWinDbg\bin\Debug\ApplicationForWinDbg.exe) BaseSize: 0xc ComponentSize: 0x0 Number of IFaces in IFaceMap: 0 Slots in VTable: 7 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 70f66a70 70de4934 PreJIT System.Object.ToString() 70f66a90 70de493c PreJIT System.Object.Equals(System.Object) 70f66b00 70de496c PreJIT System.Object.GetHashCode() 70fd72f0 70de4990 PreJIT System.Object.Finalize() 004b0150 00203078 JIT ApplicationForWinDbg.Class1..ctor() 004b0188 00203060 JIT ApplicationForWinDbg.Class1.Method1(System.String) 004b01d8 0020306c JIT ApplicationForWinDbg.Class1.Method2(Int32)
И для примера посмотрим на информацию о ApplicationForWinDbg.Class1.Method1 и его скомпилированный код:
0:000> !dumpmd 0x00203060
Method Name: ApplicationForWinDbg.Class1.Method1(System.String) Class: 0020135c MethodTable: 00203080 mdToken: 06000001 Module: 00202c5c IsJitted: yes CodeAddr: 004b0188
0:000> !u 0x004b0188
Normal JIT generated code ApplicationForWinDbg.Class1.Method1(System.String) Begin 004b0188, size 3a >>> 004b0188 55 push ebp 004b0189 8bec mov ebp,esp 004b018b 83ec0c sub esp,0Ch 004b018e 894dfc mov dword ptr [ebp-4],ecx 004b0191 8955f8 mov dword ptr [ebp-8],edx 004b0194 833d142e200000 cmp dword ptr ds:[202E14h],0 004b019b 7405 je 004b01a2 004b019d e8dfa21472 call mscorwks!JIT_DbgIsJustMyCode (725fa481) 004b01a2 90 nop 004b01a3 8b153020c702 mov edx,dword ptr ds:[2C72030h] ("$") 004b01a9 8b4df8 mov ecx,dword ptr [ebp-8] 004b01ac e86feaaa70 call mscorlib_ni+0x1bec20 (70f5ec20) (System.String.Concat(System.String, System.String), mdToken: 060001c9) 004b01b1 8945f4 mov dword ptr [ebp-0Ch],eax 004b01b4 8b4df4 mov ecx,dword ptr [ebp-0Ch] 004b01b7 e81c36fc70 call mscorlib_ni+0x6d37d8 (714737d8) (System.Console.WriteLine(System.String), mdToken: 060007c8) 004b01bc 90 nop 004b01bd 90 nop 004b01be 8be5 mov esp,ebp 004b01c0 5d pop ebp 004b01c1 c3 ret
На этом пожалуй закончу, так как статья получилась длинная из за всех листингов. Ждите продолжения. И более подробных объяснений назначения всех полей, а также в следующей статье мы поговорим о других коммандах.