Хочу начать серию статей, посвящённых отладке ваших .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На этом пожалуй закончу, так как статья получилась длинная из за всех листингов. Ждите продолжения. И более подробных объяснений назначения всех полей, а также в следующей статье мы поговорим о других коммандах.
