понедельник, 8 февраля 2010 г.

Диагностика проблем загрузки сборок

Практически каждый разработчик сталкивался с неприятной ситуацией, когда во время загрузки приложения, разработанного с использованием .NET Framework, возникают какие-то ошибки, связанные с поиском или загрузкой сборок и запуск приложения завершается предложением отправить отчет в Майкрософт. Кроме того, практически каждый, кто читал замечательную книгу Джеффри Рихтера, ужаснулся тому многообразию вариантов, откуда может быть загружена сборка, а также богатым возможностям администрирования .Net приложений (probing, dependend assemblies, codebase, Publisher Policy и др.) [1], [2]. Помимо проблем с поиском нужной сборки подливают масла в огонь вероятные ошибки загрузки сборок, связанные с вопросами безопасности (в результате чего генерируется SecurityException), а также форматом сборки (исключение BadImageFormat).

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

За загрузку сборок в CLR отвечает специальный загрузчик, получивший кодовое имя Fusion. Если в процессе загрузки сборок возникают проблемы, то для упрощения диагностики существует возможность включить логгирование этого процесса. Для этого необходимо задать следующие параметры в реесте: установить параметр HKLM\Software\Microsoft\Fusion\ForceLog в 1, а в значении параметра HKLM\Software\Microsoft\Fusion\LogPath указать путь хранения лог-файла (по умолчанию, этих параметров в реестре нет, соответственно, диагностика загрузки сборок не производится).

Для проверки ошибок загрузки сборок я создал простое решение (solution) с двумя проектами: консольным приложением TestFusion и библиотекой классов TestFusionLib. Я добавил в библиотеку класс TestClass, а в TestFusion добавил использование этого класса. После компиляции обоих проектов, я удалил из папки bin файл TestFustionLib и запустил TestFustion.exe.

D:\Sources\VS2008\TestFusion\TestFusion\bin\Debug>TestFusion.exe

Необработанное исключение: System.IO.FileNotFoundException: Невозможно загрузить файл или сборку "TestFusionLib, Version

=1.0.0.0, Culture=neutral, PublicKeyToken=null" или один из зависимых от них компонентов. Не удается найти указанный файл.

Имя файла: "TestFusionLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"

   в TestFusion.Program.Main(String[] args)

Диспетчер сборки загружен с:  C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll

Выполняется в контексте исполняемого файла  D:\Sources\VS2008\TestFusion\TestFusion\bin\Debug\TestFusion.exe

--- Подробный журнал ошибок.

=== Информация о состоянии предварительной привязки ===

Журнал: User = HOME\Sergey

Журнал: DisplayName = TestFusionLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

 (Fully-specified)

Журнал: Appbase = file:///D:/Sources/VS2008/TestFusion/TestFusion/bin/Debug/

Журнал: Initial PrivatePath = NULL

Вызов сборки: TestFusion, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.

===

Журнал: данная привязка начинается в контексте загрузки default.

Журнал: файл конфигурации приложения не найден.

Журнал: используется файл конфигурации компьютера из C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config

.

Журнал: политика в данный момент не применяется к ссылке (личная, пользовательская, частичная привязка сборки или привязка по местоположению).

Журнал: попытка загрузки нового URL file:///D:/Sources/VS2008/TestFusion/TestFusion/bin/Debug/TestFusionLib.DLL.

Журнал: попытка загрузки нового URL file:///D:/Sources/VS2008/TestFusion/TestFusion/bin/Debug/TestFusionLib/TestFusionLi

b.DLL.

Журнал: попытка загрузки нового URL file:///D:/Sources/VS2008/TestFusion/TestFusion/bin/Debug/TestFusionLib.EXE.

Журнал: попытка загрузки нового URL file:///D:/Sources/VS2008/TestFusion/TestFusion/bin/Debug/TestFusionLib/TestFusionLib.EXE.

Помимо просмотра и конфигурирования процесса логирования загрузки сборок вручную, в составе .NET Framework SDK поставляется полезная утилита с названием Fuslogvw.exe (Fusion Log Viewer) [3], которая в значительной степени упрощает подобный процесс диагностики.

Внимание! Утилиту Fuslogvw.exe необходимо запускать с правами Администратора, в противном случае вы не сможете изменить ни какие параметры.

Примечание. Путь к Fuslogvw.exe: %ProgramFiles%\MicrosoftSDKs\Windows\v6.0A\bin\Fuslogvw.exe

Если после неудачного запуска приложения (в нашем случае TestFusion.exe) запустить Fuslogvw.exe (или нажать кнопку Refresh, если эта утилита уже была запущена), то мы увидим следующую картину:

Fuslogvw

Нас интересует вторая строка. Если на ней нажать View Log, получим следующие данные:

*** Запись журнала привязки сборки  (06.02.2010 @ 17:47:02) ***

Операция выполнена со сбоем.

Результат привязки: hr = 0x80070002. Не удается найти указанный файл.

Диспетчер сборки загружен с:  C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll

Выполняется в контексте исполняемого файла  D:\Sources\VS2008\TestFusion\TestFusion\bin\Debug\TestFusion.exe

--- Подробный журнал ошибок.

=== Информация о состоянии предварительной привязки ===

Журнал: User = HOME\Sergey

Журнал: DisplayName = TestFusionLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

 (Fully-specified)

Журнал: Appbase = file:///D:/Sources/VS2008/TestFusion/TestFusion/bin/Debug/

Журнал: Initial PrivatePath = NULL

Журнал: Dynamic Base = NULL

Журнал: Cache Base = NULL

Журнал: AppName = NULL

Вызов сборки: TestFusion, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.

===

Журнал: данная привязка начинается в контексте загрузки default.

Журнал: файл конфигурации приложения не найден.

Журнал: используется файл конфигурации компьютера из C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.

Журнал: политика в данный момент не применяется к ссылке (личная, пользовательская, частичная привязка сборки или привязка по местоположению).

Журнал: попытка загрузки нового URL file:///D:/Sources/VS2008/TestFusion/TestFusion/bin/Debug/TestFusionLib.DLL.

Журнал: попытка загрузки нового URL file:///D:/Sources/VS2008/TestFusion/TestFusion/bin/Debug/TestFusionLib/TestFusionLib.DLL.

Журнал: попытка загрузки нового URL file:///D:/Sources/VS2008/TestFusion/TestFusion/bin/Debug/TestFusionLib.EXE.

Журнал: попытка загрузки нового URL file:///D:/Sources/VS2008/TestFusion/TestFusion/bin/Debug/TestFusionLib/TestFusionLib.EXE.

Журнал: все попытки проверки URL закончились неудачно.

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

Отступление от темы. Диагностика проблем загрузки неуправляемых библиотек

Обсуждая вопрос диагностики загрузки управляемых библиотек, нельзя оставить без внимания вопросы загрузки неуправляемых библиотек (native dll). На различных форумах очень часто задают вопросы подобного рода: «Мое приложение при переносе с моей машины на какую-то другую, перестает запускаться. В чем может быть проблема?». Подобная проблема очень часто связана с тем, что при компиляции неуправляемого кода (например, mixed сборок, разработанных с помощью C++/CLI) помимо стандартных управляемых библиотек приложение использует C Runtime Library в виде отдельной dll. Поскольку на машине разработчика эта библиотека существует в папке System32 с тех самых пор, как на этот компьютер установлена среда разработки, это не вызывает никаких проблем у разработчика, но этой библиотеки очень часто не бывает на машине пользователя. Многие разработчики справляются с этой проблемой путем включения в свой инсталляционный пакет Visual С++ Redistributable Package  

В отладке подобных проблем первое, что нужно сделать, это скачать замечательную утилиту под названием Dependency Walker [5] и попытаться открыть ваш exe-файл с помощью этой утилиты на машине пользователя (или на машине с идентичной конфигурацией). Dependency Walker рекурсивно проходится по всем неуправляемым библиотекам и сразу же покажет вам, какой именно неуправляемой библиотеки не хватает.

Дополнительные ссылки

1.     Джеффри Рихтер, CLR via C#, Глава 2, раздел “Простое средство администрирования (конфигурационный файл)”.

2.     Джеффри Рихтер, CLR via C#, Глава 3, раздел “Дополнительные конфигурационные средства (конфигурационные файлы)”.

3.     Assembly Binding Log Viewer (Fuslogvw.exe) in MSDN documentation

4.     Debugging Assembly Loading Failures by Suzanne Cook (http://blogs.msdn.com/suzcook/archive/2003/05/29/57120.aspx)

5.     Утилита Dependency Walker

3 комментария:

  1. Эх, как бы все было хорошо, будь у меня %ProgramFiles%\MicrosoftSDKs\Windows\v6.0A\bin\Fuslogvw.exe... Но ее в этой папке почему-то нет - не подскажете, почему и где взять? Установлен Microsoft Visual C# 2008, экспресс-выпуск. Сама вышеупомянутая папка полна других утилит, а нужной нет :(

    ОтветитьУдалить
  2. Fuslogvw идет в составе .NET Framework SDK и не поставляется в комплекте с Visual Studio.

    ОтветитьУдалить
  3. А еще могу сказать насчет 64-битных native dll- надо в студии снять галочку- Prefer 32-bit.... Час убил на это.

    ОтветитьУдалить