Статьи

Модульная архитектура - анализ зависимостей

8 мая 2023 г.

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

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

Так-же, в случае если Common библиотеки собираются в отдельном решении и используются в большом количестве других решений, то может быть полезно узнать, а какие публичные объекты из Common библиотек используются в других проектах? Или часть из этих публичных объектов уже не используется и их можно спокойно удалять или менять уровень видимости?

Отрисовка графиков всех зависимостей

В этот момент я подумал что неплохо-бы нарисовать полный график всех зависимых сборок чтобы понимать, от каких сборок зависят конкретные части логики, а какие сборки, по неизвестным причинам, уже давно не существуют на серверах. С использованием моего компонента PEReader эту идею удалось реализовать не только для CLI сборок, включая неуправляемые библиотеки, которые используются в качестве зависимостей через DllImportAttribute (Они хранятся в таблице метаданных ModuleRef), но и для неуправляемых приложений, которые используют PE Import и Export директории.
Поиск зависимостей используется не только в текущей папке, но и в шаренных папках (GAC, %winDir% и т.п.). Как и для управляемых сборок, так и для неуправляемых PE файлов. Для управляемых сборок используется Fusion для поиска сборок, а для неуправляемых библиотек используется функция SearchPath.
Увы, для .NET Core и .NET найти универсальный алгоритм поиска зависимостей в шаренных папках мне не удалось, поэтому если Вы знаете как это сделать, то буду благодарен за подсказку.

Итак, для примера рассмотрим пример на приложении из 8 исполняемых файлов Flatbed.MDI:

Подсветка используемая в примерах (Меняется в настройках):

А теперь добавим все зависимости из GAC для этого приложения:

А так будет выглядеть все ссылки на native библиотеки, без сборок из GAC:

Ну и напоследок, посмотрим какие ещё лежат в папке библиотеки, но которые не использует ни один из исполняемых файлов в текущей папке:

В данном случае видно, что библиотеки AspNet.Security.OAuth.Apple.dll и Plugin.FileDomainPluginProvider.dll не используются ни одной из сборок напрямую. Более того, библиотека AspNet.Security.OAuth.Apple.dll зависит от большого количества других библиотек, но ни одна из них не найдена. Из этого можно сделать вывод, что ни одна из этих библиотек не испольуется, однако библиотека Plugin.FileDomainPluginProvider.dll использует общий компонент SAL.Flatbed.dll, так что можно предположить, что данная библиотека используется не напрямую, а косвенно через рефлексию.
Так что видимость что библиотека не используется напрямую, ещё не гарантирует что данная библиотека абсолютно бесполезна и в данном случае придётся разбираться вручную.

Отображение всех используемых публичных объектов

После того как удалось понять как исполняемые файлы зависят между собой, интересно узнать, какие именно публичные функции, методы, свойства, события или поля используются из родительского исполняемого файла. Для этого, выбираем стрелку с зависимостью и по нажатию правой кнопкой мыши на стрелку - выбираем пункт меню Analyze:

Так же можно открыть интересующий файл напрямую из мень хоста, дабы миновать процесс построения диаграммы. Проанализировав файл - мы увидим следующую картину с отфильтрованными объектами или со всеми объектами. Где цветом выделено то, что используется, а что не используется:

Все публичные объекты с подсветкой неиспользованных Список только используемых объектов, без списка неиспользованных

Если набросать сборок из других проектов, которые тоже ссылаются на корневую сборку SAL.Flatbed, то список используемых публичных объектов значительно расширится, но всё равно - часть объектов явно нигде не используется:

Однако, для примера при просмотре зависимостей из kernel32.dll, лучше смотреть только используемые функции, а не все подряд:

Но стоит учитывать, что константы в управляемых приложениях - переносятся в зависимые сборки в момент компиляции, так что их не удастся увидеть в зависимостях, по причине отсутствия ссылки на объект.

Заключение

В итоге, полученное решение позволяет анализировать все зависимые управляемые и неуправляемые PE файлы, а так-же позволяет просматривать какие именно публичные объекты используются во внешних сборках или библиотеках и решать что с ними делать: удалять неиспользуемые объекты или уменьшать область видимости.

Внешние ресурсы