Перейти к содержимому

Debugger is found on this machine что это

  • автор:

Debugger is found on this machine что это

Post by ljhowell » Sun Nov 13, 2011 4:28 pm

A client installed our software and when he launched the program the following message appear immediately.

“Debugger is found on this machine!” All he can do is click Ok and the program ended.

I check the protect configuration and did not find any message containing this message.

I check his task manager and could not find any programs that looked like a debugger.

Windows 7 with AVG free anti-virus installed.

Any help on this would be great!

ljhowell Posts: 9 Joined: Mon Dec 27, 2010 11:06 pm

Re: Debugger is found on this machine!

Post by ljhowell » Sun Nov 13, 2011 5:32 pm

After Google research I found the issue and I will post here for your information.

If you right-click on your program, under the compatibility tab, make sure you uncheck «Run this program in compatibility mode» and uncheck «Run as adminsistrator»

That fixed the problem.

Screen shot attached of what caused the error message.

Attachments Debugger_Issue.jpg (55.88 KiB) Viewed 19613 times
Enigma Site Admin Posts: 2902 Joined: Wed Aug 20, 2008 2:24 pm

Re: Debugger is found on this machine!

Post by Enigma » Mon Nov 14, 2011 10:14 am

Hi Les, it is probably due to some plugin enabled.

Check the list of plugins you are using in Miscellaneous — Plugins panel.

My advice — do not use plugins until you 100% test them.

3 posts • Page 1 of 1

  • The Enigma Protector
  • ↳ Basic
  • ↳ Problems
  • ↳ Registration
  • The Enigma Protector x64
  • ↳ Enigma Protector x64
  • Enigma Protector — Online Solutions
  • ↳ Enigma Protector — Online Solutions
  • ↳ Online Activation Panel
  • Enigma Virtual Box FREEWARE
  • ↳ Enigma Virtual Box x86/x64
  • Add-on’s
  • ↳ Plugins
  • ↳ Tutorials
  • Board index
  • All times are UTC
  • Delete cookies

Powered by phpBB® Forum Software © phpBB Limited

Copyright © 2004-2023, The Enigma Protector Developers Team. All rights reserved.

Translation of «debugging» in Russian

Often, this can be quicker than debugging, and you can compare printed outputs against other runs of the app.

Часто это может быть быстрее, чем отладка, и вы можете сравнивать печатные результаты с другими прогонами приложения.

As for the cost of improvements, it is known only that the debugging engine will cost 19,900 euros.
Что касается стоимости доработок, то известно лишь, что доводка двигателя обойдется в 19900 евро.
After debugging and testing, the machine began to be produced serially from the 86 year.
После доводки и испытаний машина начала выпускаться серийно с 86-го года.
IBM should have made debugging compiler free and charge money only for optimizing complier.

IBM должна была бы сделать отладочный компилятор бесплатным и брать деньги только за оптимизирующий компилятор.

Packages providing debugging information for executables and shared libraries.

Пакеты, предоставляющие отладочную информацию для исполняемых файлов и динамически подключаемых библиотек.

Programmers commonly use stack tracing during interactive and post-mortem debugging.
Программисты обычно используют трассировку стека во время интерактивной а также посмертной отладки.
Dbg are debugging functions such as a software breakpoint.
Dbg — вспомогательные функции отладки, такие как программная точка останова.
Use this tab to configure packet-level logging for debugging purposes.
Используйте эту вкладку для настройки ведения журнала на уровне пакетов в целях отладки.
A particularly effective way to ease debugging is to design for transparency and discoverability.

Особенно эффективный способ простой отладки заключается в проектировании с учетом прозрачности (transparency) и воспринимаемости (discoverability).

Design is the activity that links requirements to coding and debugging.
Проектирование — это тот процесс, который связывает выработку требований с кодированием и отладкой.

Features include access control, memory management, debugging, chaining and protocol-oriented programming.

Функции языка включают в себя контроль доступа, управление памятью, отладку, цепочку вызовов и протокол-ориентированное программирование.

However, testing and debugging R-1 continued.
Тем не менее, испытания и доводку Р-1 продолжили.

Depending on the class of debugger used, various simulators can support high-level symbolic debugging of programs.

В зависимости от класса используемого отладчика некоторые модели симуляторов поддерживают высокоуровневую символьную отладку программ.

Despite a lengthy debugging and all the efforts of developers, the list of detected and unresolved problems consists of 276 points.

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

After a little debugging, the first video game was ready for its debut.
После небольшой отладки первая видеоигра была готова к своему дебюту.

After passing the test period and debugging all the processes, the authors intend to adapt the project for other countries.

После прохождения тестового периода и отладки всех процессов его авторы намерены адаптировать проект для других стран.

For example, you might want to turn on internal debugging flags, or even to make emergency repairs.

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

A problem-solving software may require countless hours of programming, debugging and testing before it is ready for launch.

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

Over the past couple of years I have spent a lot of time debugging other engineers’ test code.
Последние пару лет я потратил немало времени на отладку тестового кода других инженеров.
Possibly inappropriate content

Examples are used only to help you translate the word or expression searched in various contexts. They are not selected or validated by us and can contain inappropriate terms or ideas. Please report examples to be edited or not to be displayed. Rude or colloquial translations are usually marked in red or orange.

Расширенное устранение неполадок при возникновении ошибок при остановке или синем экране

Если вы не являетесь агентом службы поддержки или ИТ-специалистом, дополнительные сведения об стоп-ошибках («синий экран») см. в статье Устранение ошибок синего экрана.

Применимо к: Поддерживаемые версии Windows Server и клиента Windows

Что вызывает стоп-ошибки?

Когда Windows сталкивается с условием, которое скомпрометирует безопасную работу системы, система останавливается. Примеры включают сбой, который может поставить под угрозу безопасность или привести к повреждению операционной системы (ОС) и (или) пользовательских данных. Когда компьютер останавливается, чтобы предотвратить переход операционной системы в этих условиях, он называется ошибкой проверка (или bugcheck). Его также часто называют сбоем системы, ошибкой ядра, синим экраном, синим экраном смерти (BSOD) или стоп-ошибкой. В предварительных версиях Windows цвет экрана может быть зеленым, что приводит к зеленому экрану смерти (GSOD).

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

  • 70 % — это сторонний код драйвера.
  • 10 % из-за проблем с оборудованием.
  • 5 % вызваны кодом Майкрософт.
  • 15 % имеют неизвестные причины, так как память слишком повреждена для анализа.

Первопричиной стоп-ошибок редко является процесс в пользовательском режиме. Хотя процесс в пользовательском режиме (например, Блокнот или Slack) может вызвать стоп-ошибку, он обычно приводит к возникновению базовой проблемы в драйвере, оборудовании или операционной системе.

Общие действия по устранению неполадок

Чтобы устранить неполадки с сообщениями о стоп-ошибках, выполните следующие общие действия.

  1. Просмотрите код стоп-ошибки, который вы найдете в журналах событий. Выполните поиск в Интернете конкретных кодов стоп-ошибок, чтобы узнать, есть ли известные проблемы, способы их устранения или обходные пути.
  2. Убедитесь, что установлены последние обновления Windows, накопительные обновления и накопительные обновления. Чтобы проверить состояние обновления, обратитесь к соответствующему журналу обновлений для системы. Например:
    • Windows 10 версии 21H2
    • Windows 10 версии 21H1
    • Windows 10 версии 20H2
  3. Убедитесь, что BIOS и встроенное ПО обновлены.
  4. Выполните все соответствующие тесты оборудования и памяти.
  5. Запустите средство проверки безопасности (Майкрософт ) или любую другую программу обнаружения вирусов, включающую проверку MBR на наличие инфекций.
  6. Убедитесь, что на жестком диске достаточно свободного места. Точные требования различаются, но мы рекомендуем 10–15 процентов свободного места на диске.
  7. Обратитесь к соответствующему поставщику оборудования или программного обеспечения, чтобы обновить драйверы и приложения в следующих сценариях:
    • Сообщение об ошибке указывает, что проблема вызывается определенным драйвером.
    • Вы видите сообщение о том, что служба запускается или останавливается до сбоя. В этой ситуации определите, согласовано ли поведение службы во всех случаях сбоя.
    • Вы внесли любые изменения в программное или аппаратное обеспечение.

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

Вы можете отключить драйвер, выполнив действия, описанные в статье Как временно отключить драйвер фильтра в режиме ядра в Windows.

Вы также можете рассмотреть возможность отката изменений или возврата к последнему известному рабочему состоянию. Дополнительные сведения см. в разделе Откат драйвера устройства до предыдущей версии.

Сбор дампов памяти

Чтобы настроить систему для файлов дампа памяти, выполните следующие действия.

  1. Выберите поле поиска на панели задач, введите Дополнительные параметры системы и нажмите клавишу ВВОД.
  2. На вкладке Дополнительно в поле Свойства системы нажмите кнопку Параметры , которая отображается в разделе Запуск и восстановление.
  3. В новом окне выберите раскрывающийся список под параметром Запись сведений об отладке.
  4. Выберите Автоматический дамп памяти.
  5. Нажмите кнопку ОК.
  6. Перезагрузите компьютер, чтобы параметр вступил в силу.
  7. Если сервер виртуализирован, отключите автоматическую перезагрузку после создания файла дампа памяти. Это отключение позволяет snapshot сервера в состоянии, а также в случае повторения проблемы.

Файл дампа памяти сохраняется в следующих местах:

Тип файла дампа Расположение
(нет) %SystemRoot%\MEMORY. DMP (неактивный или серый)
Файл небольшого дампа памяти (256 КБ) %SystemRoot%\Minidump
Файл дампа памяти ядра %SystemRoot%\MEMORY. DMP
Полный файл дампа памяти %SystemRoot%\MEMORY. DMP
Файл автоматического дампа памяти %SystemRoot%\MEMORY. DMP
Файл дампа активной памяти %SystemRoot%\MEMORY. DMP

Вы можете использовать средство проверки файлов аварийного дампа (Майкрософт) (DumpChk), чтобы убедиться, что файлы дампа памяти не повреждены или недопустимы. Дополнительные сведения см. в следующем видео:

Дополнительные сведения об использовании Dumpchk.exe для проверка файлов дампа см. в следующих статьях:

  • Использование DumpChk
  • Скачать DumpChk

Параметры файла подкачки

Дополнительные сведения о параметрах файла подкачки см. в следующих статьях:

  • Общие сведения о файлах страниц
  • Определение подходящего размера файла подкачки для 64-разрядных версий Windows
  • Создание ядра или полный аварийный дамп

Анализ дампа памяти

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

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

Для диагностики журналов дампа можно использовать такие средства, как пакет SDK для Windows и символы. В следующем разделе рассматривается использование этого средства.

Дополнительные действия по устранению неполадок

Расширенное устранение неполадок аварийных дампов может оказаться очень сложной задачей, если у вас нет опыта программирования и внутренних механизмов Windows. Мы попытались дать краткое представление о некоторых используемых методах, включая некоторые примеры. Тем не менее, чтобы эффективно устранять неполадки аварийного дампа, следует потратить время на знакомство с расширенными методами отладки. Общие сведения о видео см. в статье Отладка режима ядра завершается сбоем и зависанием. Также см. дополнительные ссылки, перечисленные ниже.

Справочники по расширенной отладке

  • Расширенная отладка Windows, книга первого выпуска
  • Средства отладки для Windows (WinDbg, KD, CDB, NTSD)

Шаги отладки

  1. Убедитесь, что компьютер настроен для создания полного файла дампа памяти при сбое. Дополнительные сведения см. в разделе Метод 1. Дамп памяти.
  2. Найдите файл memory.dmp в каталоге Windows на компьютере, на который происходит сбой, и скопируйте его на другой компьютер.
  3. На другом компьютере скачайте пакет SDK для Windows 10.
  4. Запустите установку и выберите Средства отладки для Windows. Установлено средство WinDbg.
  5. Перейдите в меню Файл и выберите Путь к файлу символа , чтобы открыть средство WinDbg и задать путь к символу.
    1. Если компьютер подключен к Интернету, введите сервер https://msdl.microsoft.com/download/symbols общедоступных символов Майкрософт и нажмите кнопку ОК. Рекомендуется использовать этот метод.
    2. Если компьютер не подключен к Интернету, укажите путь к локальному символу.
  6. Выберите Открыть аварийный дамп, а затем откройте скопированный файл memory.dmp . Снимок экрана: пример выходных данных в WinDbg при открытии файла аварийного дампа.
  7. В разделе Анализ проверки ошибок выберите !analyze -v . Команда !analyze -v вводится в командной строке в нижней части страницы.
  8. Появится подробный анализ ошибок проверка. Снимок экрана: подробный пример проверка анализа ошибок.
  9. Прокрутите вниз до раздела STACK_TEXT . Будут строки чисел с каждой строкой, за которой следует двоеточие и некоторый текст. В этом тексте должно быть показано, какая библиотека DLL вызывает сбой. Если применимо, в нем также указывается, какая служба завершает работу библиотеки DLL.
  10. Дополнительные сведения о том, как интерпретировать выходные данные STACK_TEXT, см. в разделе Использование расширения !analyze.

Существует много возможных причин ошибки проверка, и каждый случай является уникальным. В приведенном выше примере важные строки, которые можно определить по STACK_TEXT: 20, 21 и 22:

Здесь удаляются данные HEX, а строки нумеруются для ясности.

1 : nt!KeBugCheckEx 2 : nt!PspCatchCriticalBreak+0xff 3 : nt!PspTerminateAllThreads+0x1134cf 4 : nt!PspTerminateProcess+0xe0 5 : nt!NtTerminateProcess+0xa9 6 : nt!KiSystemServiceCopyEnd+0x13 7 : nt!KiServiceLinkage 8 : nt!KiDispatchException+0x1107fe 9 : nt!KiFastFailDispatch+0xe4 10 : nt!KiRaiseSecurityCheckFailure+0x3d3 11 : ntdll!RtlpHpFreeWithExceptionProtection$filt$0+0x44 12 : ntdll!_C_specific_handler+0x96 13 : ntdll!RtlpExecuteHandlerForException+0xd 14 : ntdll!RtlDispatchException+0x358 15 : ntdll!KiUserExceptionDispatch+0x2e 16 : ntdll!RtlpHpVsContextFree+0x11e 17 : ntdll!RtlpHpFreeHeap+0x48c 18 : ntdll!RtlpHpFreeWithExceptionProtection+0xda 19 : ntdll!RtlFreeHeap+0x24a 20 : FWPolicyIOMgr!FwBinariesFree+0xa7c2 21 : mpssvc!FwMoneisDiagEdpPolicyUpdate+0x1584f 22 : mpssvc!FwEdpMonUpdate+0x6c 23 : ntdll!RtlpWnfWalkUserSubscriptionList+0x29b 24 : ntdll!RtlpWnfProcessCurrentDescriptor+0x105 25 : ntdll!RtlpWnfNotificationThread+0x80 26 : ntdll!TppExecuteWaitCallback+0xe1 27 : ntdll!TppWorkerThread+0x8d0 28 : KERNEL32!BaseThreadInitThunk+0x14 29 : ntdll!RtlUserThreadStart+0x21 

Эта проблема связана с службой mpssvc, которая является компонентом брандмауэра Windows. Проблема была устранена путем временного отключения брандмауэра, а затем сброса политик брандмауэра.

Дополнительные примеры см. в разделе Примеры отладки.

Видеоресурс

В следующих видео иллюстрируются различные методы устранения неполадок для анализа файлов дампа.

  • Анализ файла дампа
  • Установка средства отладки для Windows (x64 и x86)
  • Отладка дампов аварийной памяти в режиме ядра
  • Специальный пул

Расширенное устранение неполадок с помощью средства проверки драйверов

По нашим оценкам, около 75 процентов всех стоп-ошибок вызваны неисправными драйверами. Средство проверки драйверов предоставляет несколько методов для устранения неполадок. К ним относятся запуск драйверов в изолированном пуле памяти (без совместного использования памяти с другими компонентами), создание чрезвычайного объема памяти и проверка параметров. Если средство обнаруживает ошибки при выполнении кода драйвера, оно заранее создает исключение. Затем он может дополнительно изучить ту часть кода.

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

Не пытайтесь проверить все драйверы одновременно. Это действие может снизить производительность и сделать систему непригодной для использования. Это также ограничивает эффективность средства.

При использовании средства проверки драйверов используйте следующие рекомендации.

  • Протестируйте все «подозрительные» драйверы. Например, драйверы, которые недавно были обновлены или которые, как известно, являются проблемными.
  • Если у вас по-прежнему возникают неизменяемые сбои, попробуйте включить проверку для всех сторонних и неподписанных драйверов.
  • Включите параллельную проверку для групп из 10–20 драйверов.
  • Кроме того, если компьютер не может загрузиться на рабочем столе из-за средства проверки драйверов, вы можете отключить средство, запустив в безопасном режиме. Это решение связано с тем, что средство не может работать в безопасном режиме.

Дополнительные сведения см. в разделе Средство проверки драйверов.

Распространенные стоп-ошибки Windows

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

В следующих разделах перечислены общие процедуры устранения неполадок для распространенных кодов стоп-ошибок.

VIDEO_ENGINE_TIMEOUT_DETECTED или VIDEO_TDR_TIMEOUT_DETECTED

Stop error code 0x00000141 или 0x00000117

Обратитесь к поставщику указанного драйвера дисплея, чтобы получить соответствующее обновление для этого драйвера.

DRIVER_IRQL_NOT_LESS_OR_EQUAL

0x0000000D1 кода stop-ошибки

Примените последние обновления для драйвера, применив последние накопительные обновления для системы на веб-сайте каталога Центра обновления Майкрософт. Обновление устаревшего сетевого драйвера. Виртуализированные системы VMware часто используют сетевое подключение Intel(R) PRO/1000 MT (e1g6032e.sys). Этот драйвер можно скачать на веб-сайте Intel Download Drivers & Software. Обратитесь к поставщику оборудования, чтобы обновить сетевой драйвер для разрешения. Для систем VMware используйте интегрированный сетевой драйвер VMware вместо e1g6032e.sys Intel. Например, используйте типы VMware VMXNET, VMXNET2 или VMXNET3.

PAGE_FAULT_IN_NONPAGED_AREA

0x000000050 кода ошибки остановки

Если драйвер указан в сообщении о стоп-ошибке, обратитесь к производителю за обновлением. Если обновления недоступны, отключите драйвер и отслеживайте стабильность системы. Выполните команду chkdsk /f /r для обнаружения и исправления ошибок диска. Перезапустите систему до начала сканирования диска в системном разделе. Обратитесь к производителю за любыми средствами диагностики, которые он может предоставить для подсистемы жесткого диска. Попробуйте переустановить приложение или службу, которые были недавно установлены или обновлены. Возможно, что сбой был вызван, когда система запускала приложения и считывала в реестре параметры предпочтения. Переустановка приложения может исправить поврежденные разделы реестра. Если проблема не устранена и вы выполнили недавнее резервное копирование состояния системы, попробуйте восстановить кусты реестра из резервной копии.

SYSTEM_SERVICE_EXCEPTION

Код ошибки остановки c000021a Системный процесс подсистемы Windows неожиданно завершился с состоянием 0xc0000005. Выполнено завершение работы системы.

Используйте средство проверки системных файлов, чтобы исправить отсутствующие или поврежденные системные файлы. Средство проверки системных файлов позволяет пользователям проверять наличие повреждений в системных файлах Windows и восстанавливать поврежденные файлы. Дополнительные сведения см. в статье Использование средства проверки системных файлов.

NTFS_FILE_SYSTEM

Код ошибки stop 0x000000024

Эта стоп-ошибка обычно возникает из-за повреждения файловой системы NTFS или поврежденных блоков (секторов) на жестком диске. Поврежденные драйверы для жестких дисков (SATA или IDE) также могут негативно повлиять на способность системы читать и записывать данные на диск. Запустите любые аппаратные диагностика, предоставляемые производителем подсистемы хранилища. Используйте средство проверки диска, чтобы убедиться в отсутствии ошибок файловой системы. Чтобы выполнить это действие, щелкните правой кнопкой мыши диск, который требуется проверить, выберите Свойства, Выберите Сервис, а затем нажмите кнопку Проверить сейчас. Обновите драйвер файловой системы NTFS (Ntfs.sys). Примените последние накопительные обновления для текущей операционной системы, в которой возникла проблема.

KMODE_EXCEPTION_NOT_HANDLED

Код ошибки остановки 0x0000001E

Если драйвер указан в сообщении о стоп-ошибке, отключите или удалите его. Отключите или удалите драйверы или службы, которые были недавно добавлены.

Если ошибка возникает во время последовательности запуска и системный раздел отформатирован с помощью файловой системы NTFS, возможно, вы сможете использовать безопасный режим, чтобы отключить драйвер в диспетчер устройств. Чтобы отключить драйвер, выполните следующие действия.

  1. Перейдите > в раздел ПараметрыОбновление & восстановления безопасности>.
  2. В разделе Расширенный запуск выберите Перезапустить сейчас.
  3. После перезагрузки компьютера на экране Выбор параметра выберите Устранение неполадок>с дополнительными параметрами>>Параметры запускаПерезапуск.
  4. После перезагрузки компьютера отобразится список параметров. Нажмите клавишу 4 или F4, чтобы запустить компьютер в безопасном режиме. Если вы планируете использовать Интернет в безопасном режиме, нажмите клавишу 5 или F5 для параметра Безопасный режим с сетью .

DPC_WATCHDOG_VIOLATION

Код stop error 0x00000133

Этот код стоп-ошибки вызван неисправным драйвером, который не завершает свою работу в течение отведенного времени в определенных условиях. Чтобы устранить эту ошибку, соберите файл дампа памяти из системы, а затем используйте отладчик Windows для поиска неисправного драйвера. Если драйвер указан в сообщении о стоп-ошибке, отключите драйвер, чтобы изолировать проблему. Обратитесь к производителю за обновлениями драйверов. Проверьте системный вход в Просмотр событий на наличие других сообщений об ошибках, которые могут помочь определить устройство или драйвер, вызывающие stop-ошибку 0x133. Убедитесь, что любое установленное новое оборудование совместимо с установленной версией Windows. Например, сведения о требуемом оборудовании можно получить в разделе Спецификации Windows 10. Если отладчик Windows установлен и у вас есть доступ к общедоступным символам, можно загрузить файл c:\windows\memory.dmp в отладчик. Затем ознакомьтесь с разделом Определение источника ошибок проверки ошибок 0x133 (DPC_WATCHDOG_VIOLATION) в Windows Server 2012 , чтобы найти проблемный драйвер из дампа памяти.

USER_MODE_HEALTH_MONITOR

0x0000009E кода stop-ошибки

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

Эта стоп-ошибка обычно возникает в кластерной среде, и указанный неисправный драйвер RHS.exe. Проверьте журналы событий на наличие сбоев хранилища, чтобы определить сбой процесса. Попробуйте обновить компонент или процесс, указанные в журналах событий. Должно быть записано следующее событие:

  • Идентификатор события: 4870
  • Источник: Microsoft-Windows-FailoverClustering
  • Описание. Мониторинг работоспособности в пользовательском режиме обнаружил, что система не реагирует. Виртуальный адаптер отказоустойчивого кластера потерял связь с процессом сервера кластера с идентификатором процесса «%1» в течение «%2» секунд. Выполняется действие восстановления. Просмотрите журналы кластера, чтобы определить процесс и выяснить, какие элементы могут привести к зависаю процесса.

Примеры отладки

Пример 1

Эта ошибка проверка вызвана зависанием драйвера во время обновления, что приводит к ошибке проверка D1 в NDIS.sys, который является драйвером Майкрософт. В IMAGE_NAME сообщается, что неисправный драйвер, но так как этот драйвер является драйвером Майкрософт, его нельзя заменить или удалить. Метод разрешения заключается в отключении сетевого устройства в диспетчере устройств и попытке обновления еще раз.

2: kd> !analyze -v ******************************************************************************* * * * Bugcheck Analysis * * * ******************************************************************************* DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1) An attempt was made to access a pageable (or completely invalid) address at an interrupt request level (IRQL) that is too high. This is usually caused by drivers using improper addresses. If kernel debugger is available get stack backtrace. Arguments: Arg1: 000000000011092a, memory referenced Arg2: 0000000000000002, IRQL Arg3: 0000000000000001, value 0 = read operation, 1 = write operation Arg4: fffff807aa74f4c4, address which referenced memory Debugging Details: ------------------ KEY_VALUES_STRING: 1 STACKHASH_ANALYSIS: 1 TIMELINE_ANALYSIS: 1 DUMP_CLASS: 1 DUMP_QUALIFIER: 400 SIMULTANEOUS_TELSVC_INSTANCES: 0 SIMULTANEOUS_TELWP_INSTANCES: 0 BUILD_VERSION_STRING: 16299.15.amd64fre.rs3_release.170928-1534 SYSTEM_MANUFACTURER: Alienware SYSTEM_PRODUCT_NAME: Alienware 15 R2 SYSTEM_SKU: Alienware 15 R2 SYSTEM_VERSION: 1.2.8 BIOS_VENDOR: Alienware BIOS_VERSION: 1.2.8 BIOS_DATE: 01/29/2016 BASEBOARD_MANUFACTURER: Alienware BASEBOARD_PRODUCT: Alienware 15 R2 BASEBOARD_VERSION: A00 DUMP_TYPE: 2 BUGCHECK_P1: 11092a BUGCHECK_P2: 2 BUGCHECK_P3: 1 BUGCHECK_P4: fffff807aa74f4c4 WRITE_ADDRESS: fffff80060602380: Unable to get MiVisibleState Unable to get NonPagedPoolStart Unable to get NonPagedPoolEnd Unable to get PagedPoolStart Unable to get PagedPoolEnd 000000000011092a CURRENT_IRQL: 2 FAULTING_IP: NDIS!NdisQueueIoWorkItem+4 [minio\ndis\sys\miniport.c @ 9708] fffff807`aa74f4c4 48895120 mov qword ptr [rcx+20h],rdx CPU_COUNT: 8 CPU_MHZ: a20 CPU_VENDOR: GenuineIntel CPU_FAMILY: 6 CPU_MODEL: 5e CPU_STEPPING: 3 CPU_MICROCODE: 6,5e,3,0 (F,M,S,R) SIG: BA'00000000 (cache) BA'00000000 (init) BLACKBOXPNP: 1 (!blackboxpnp) DEFAULT_BUCKET_ID: WIN8_DRIVER_FAULT BUGCHECK_STR: AV PROCESS_NAME: System ANALYSIS_SESSION_HOST: SHENDRIX-DEV0 ANALYSIS_SESSION_TIME: 01-17-2019 11:06:05.0653 ANALYSIS_VERSION: 10.0.18248.1001 amd64fre TRAP_FRAME: ffffa884c0c3f6b0 -- (.trap 0xffffa884c0c3f6b0) NOTE: The trap frame doesn't contain all registers. Some register values may be zeroed or incorrect. rax=fffff807ad018bf0 rbx=0000000000000000 rcx=000000000011090a rdx=fffff807ad018c10 rsi=0000000000000000 rdi=0000000000000000 rip=fffff807aa74f4c4 rsp=ffffa884c0c3f840 rbp=000000002408fd00 r8=ffffb30e0e99ea30 r9=0000000001d371c1 r10=0000000020000080 r11=0000000000000000 r12=0000000000000000 r13=0000000000000000 r14=0000000000000000 r15=0000000000000000 iopl=0 nv up ei ng nz na pe nc NDIS!NdisQueueIoWorkItem+0x4: fffff807`aa74f4c4 48895120 mov qword ptr [rcx+20h],rdx ds:00000000`0011092a=. Resetting default scope LAST_CONTROL_TRANSFER: from fffff800603799e9 to fffff8006036e0e0 STACK_TEXT: ffffa884`c0c3f568 fffff800`603799e9 : 00000000`0000000a 00000000`0011092a 00000000`00000002 00000000`00000001 : nt!KeBugCheckEx [minkernel\ntos\ke\amd64\procstat.asm @ 134] ffffa884`c0c3f570 fffff800`60377d7d : fffff78a`4000a150 ffffb30e`03fba001 ffff8180`f0b5d180 00000000`000000ff : nt!KiBugCheckDispatch+0x69 [minkernel\ntos\ke\amd64\trap.asm @ 2998] ffffa884`c0c3f6b0 fffff807`aa74f4c4 : 00000000`00000002 ffff8180`f0754180 00000000`00269fb1 ffff8180`f0754180 : nt!KiPageFault+0x23d [minkernel\ntos\ke\amd64\trap.asm @ 1248] ffffa884`c0c3f840 fffff800`60256b63 : ffffb30e`0e18f710 ffff8180`f0754180 ffffa884`c0c3fa18 00000000`00000002 : NDIS!NdisQueueIoWorkItem+0x4 [minio\ndis\sys\miniport.c @ 9708] ffffa884`c0c3f870 fffff800`60257bfd : 00000000`00000008 00000000`00000000 00000000`00269fb1 ffff8180`f0754180 : nt!KiProcessExpiredTimerList+0x153 [minkernel\ntos\ke\dpcsup.c @ 2078] ffffa884`c0c3f960 fffff800`6037123a : 00000000`00000000 ffff8180`f0754180 00000000`00000000 ffff8180`f0760cc0 : nt!KiRetireDpcList+0x43d [minkernel\ntos\ke\dpcsup.c @ 1512] ffffa884`c0c3fb60 00000000`00000000 : ffffa884`c0c40000 ffffa884`c0c39000 00000000`00000000 00000000`00000000 : nt!KiIdleLoop+0x5a [minkernel\ntos\ke\amd64\idle.asm @ 166] RETRACER_ANALYSIS_TAG_STATUS: Failed in getting KPCR for core 2 THREAD_SHA1_HASH_MOD_FUNC: 5b59a784f22d4b5cbd5a8452fe39914b8fd7961d THREAD_SHA1_HASH_MOD_FUNC_OFFSET: 5643383f9cae3ca39073f7721b53f0c633bfb948 THREAD_SHA1_HASH_MOD: 20edda059578820e64b723e466deea47f59bd675 FOLLOWUP_IP: NDIS!NdisQueueIoWorkItem+4 [minio\ndis\sys\miniport.c @ 9708] fffff807`aa74f4c4 48895120 mov qword ptr [rcx+20h],rdx FAULT_INSTR_CODE: 20518948 FAULTING_SOURCE_LINE: minio\ndis\sys\miniport.c FAULTING_SOURCE_FILE: minio\ndis\sys\miniport.c FAULTING_SOURCE_LINE_NUMBER: 9708 FAULTING_SOURCE_CODE: 9704: _In_ _Points_to_data_ PVOID WorkItemContext 9705: ) 9706: < 9707: >9708: ((PNDIS_IO_WORK_ITEM)NdisIoWorkItemHandle)->Routine = Routine; 9709: ((PNDIS_IO_WORK_ITEM)NdisIoWorkItemHandle)->WorkItemContext = WorkItemContext; 9710: 9711: IoQueueWorkItem(((PNDIS_IO_WORK_ITEM)NdisIoWorkItemHandle)->IoWorkItem, 9712: ndisDispatchIoWorkItem, 9713: CriticalWorkQueue, SYMBOL_STACK_INDEX: 3 SYMBOL_NAME: NDIS!NdisQueueIoWorkItem+4 FOLLOWUP_NAME: ndiscore MODULE_NAME: NDIS IMAGE_NAME: NDIS.SYS DEBUG_FLR_IMAGE_TIMESTAMP: 0 IMAGE_VERSION: 10.0.16299.99 DXGANALYZE_ANALYSIS_TAG_PORT_GLOBAL_INFO_STR: Hybrid_FALSE DXGANALYZE_ANALYSIS_TAG_ADAPTER_INFO_STR: GPU0_VenId0x1414_DevId0x8d_WDDM1.3_Active; STACK_COMMAND: .thread ; .cxr ; kb BUCKET_ID_FUNC_OFFSET: 4 FAILURE_BUCKET_ID: AV_NDIS!NdisQueueIoWorkItem BUCKET_ID: AV_NDIS!NdisQueueIoWorkItem PRIMARY_PROBLEM_CLASS: AV_NDIS!NdisQueueIoWorkItem TARGET_TIME: 2017-12-10T14:16:08.000Z OSBUILD: 16299 OSSERVICEPACK: 98 SERVICEPACK_NUMBER: 0 OS_REVISION: 0 SUITE_MASK: 784 PRODUCT_TYPE: 1 OSPLATFORM_TYPE: x64 OSNAME: Windows 10 OSEDITION: Windows 10 WinNt TerminalServer SingleUserTS Personal OS_LOCALE: USER_LCID: 0 OSBUILD_TIMESTAMP: 2017-11-26 03:49:20 BUILDDATESTAMP_STR: 170928-1534 BUILDLAB_STR: rs3_release BUILDOSVER_STR: 10.0.16299.15.amd64fre.rs3_release.170928-1534 ANALYSIS_SESSION_ELAPSED_TIME: 8377 ANALYSIS_SOURCE: KM FAILURE_ID_HASH_STRING: km:av_ndis!ndisqueueioworkitem FAILURE_ID_HASH: FAILURE_ID_REPORT_LINK: https://go.microsoft.com/fwlink/?LinkID=397724&FailureHash=10686423-afa1-4852-ad1b-9324ac44ac96 Followup: ndiscore --------- 

Пример 2

В этом примере драйвер, отличный от Майкрософт, вызвал ошибку страницы, поэтому у нас нет символов для этого драйвера. Однако при просмотре IMAGE_NAME и или MODULE_NAME указывается, что проблема вызвана WwanUsbMP.sys . Отключение устройства и повторная попытка обновления — это возможное решение.

1: kd> !analyze -v ******************************************************************************* * * * Bugcheck Analysis * * * ******************************************************************************* PAGE_FAULT_IN_NONPAGED_AREA (50) Invalid system memory was referenced. This can't be protected by try-except. Typically the address is just plain bad or it is pointing at freed memory. Arguments: Arg1: 8ba10000, memory referenced. Arg2: 00000000, value 0 = read operation, 1 = write operation. Arg3: 82154573, If non-zero, the instruction address which referenced the bad memory address. Arg4: 00000000, (reserved) Debugging Details: ------------------ *** WARNING: Unable to verify timestamp for WwanUsbMp.sys *** ERROR: Module load completed but symbols could not be loaded for WwanUsbMp.sys KEY_VALUES_STRING: 1 STACKHASH_ANALYSIS: 1 TIMELINE_ANALYSIS: 1 DUMP_CLASS: 1 DUMP_QUALIFIER: 400 BUILD_VERSION_STRING: 16299.15.x86fre.rs3_release.170928-1534 MARKER_MODULE_NAME: IBM_ibmpmdrv SYSTEM_MANUFACTURER: LENOVO SYSTEM_PRODUCT_NAME: 20AWS07H00 SYSTEM_SKU: LENOVO_MT_20AW_BU_Think_FM_ThinkPad T440p SYSTEM_VERSION: ThinkPad T440p BIOS_VENDOR: LENOVO BIOS_VERSION: GLET85WW (2.39 ) BIOS_DATE: 09/29/2016 BASEBOARD_MANUFACTURER: LENOVO BASEBOARD_PRODUCT: 20AWS07H00 BASEBOARD_VERSION: Not Defined DUMP_TYPE: 2 BUGCHECK_P1: ffffffff8ba10000 BUGCHECK_P2: 0 BUGCHECK_P3: ffffffff82154573 BUGCHECK_P4: 0 READ_ADDRESS: 822821d0: Unable to get MiVisibleState 8ba10000 FAULTING_IP: nt!memcpy+33 [minkernel\crts\crtw32\string\i386\memcpy.asm @ 213 82154573 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] MM_INTERNAL_CODE: 0 CPU_COUNT: 4 CPU_MHZ: 95a CPU_VENDOR: GenuineIntel CPU_FAMILY: 6 CPU_MODEL: 3c CPU_STEPPING: 3 CPU_MICROCODE: 6,3c,3,0 (F,M,S,R) SIG: 21'00000000 (cache) 21'00000000 (init) BLACKBOXBSD: 1 (!blackboxbsd) BLACKBOXPNP: 1 (!blackboxpnp) DEFAULT_BUCKET_ID: WIN8_DRIVER_FAULT BUGCHECK_STR: AV PROCESS_NAME: System CURRENT_IRQL: 2 ANALYSIS_SESSION_HOST: SHENDRIX-DEV0 ANALYSIS_SESSION_TIME: 01-17-2019 10:54:53.0780 ANALYSIS_VERSION: 10.0.18248.1001 amd64fre TRAP_FRAME: 8ba0efa8 -- (.trap 0xffffffff8ba0efa8) ErrCode = 00000000 eax=8ba1759e ebx=a2bfd314 ecx=00001d67 edx=00000002 esi=8ba10000 edi=a2bfe280 eip=82154573 esp=8ba0f01c ebp=8ba0f024 iopl=0 nv up ei pl nz ac pe nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010216 nt!memcpy+0x33: 82154573 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] Resetting default scope LOCK_ADDRESS: 8226c6e0 -- (!locks 8226c6e0) Cannot get _ERESOURCE type Resource @ nt!PiEngineLock (0x8226c6e0) Available 1 total locks PNP_TRIAGE_DATA: Lock address : 0x8226c6e0 Thread Count : 0 Thread address: 0x00000000 Thread wait : 0x0 LAST_CONTROL_TRANSFER: from 82076708 to 821507e8 STACK_TEXT: 8ba0ede4 82076708 00000050 8ba10000 00000000 nt!KeBugCheckEx [minkernel\ntos\ke\i386\procstat.asm @ 114] 8ba0ee40 8207771e 8ba0efa8 8ba10000 8ba0eea0 nt!MiSystemFault+0x13c8 [minkernel\ntos\mm\mmfault.c @ 4755] 8ba0ef08 821652ac 00000000 8ba10000 00000000 nt!MmAccessFault+0x83e [minkernel\ntos\mm\mmfault.c @ 6868] 8ba0ef08 82154573 00000000 8ba10000 00000000 nt!_KiTrap0E+0xec [minkernel\ntos\ke\i386\trap.asm @ 5153] 8ba0f024 86692866 a2bfd314 8ba0f094 0000850a nt!memcpy+0x33 [minkernel\crts\crtw32\string\i386\memcpy.asm @ 213] 8ba0f040 866961bc 8ba0f19c a2bfd0e8 00000000 NDIS!ndisMSetPowerManagementCapabilities+0x8a [minio\ndis\sys\miniport.c @ 7969] 8ba0f060 866e1f66 866e1caf adfb9000 00000000 NDIS!ndisMSetGeneralAttributes+0x23d [minio\ndis\sys\miniport.c @ 8198] 8ba0f078 ac50c15f a2bfd0e8 0000009f 00000001 NDIS!NdisMSetMiniportAttributes+0x2b7 [minio\ndis\sys\miniport.c @ 7184] WARNING: Stack unwind information not available. Following frames may be wrong. 8ba0f270 ac526f96 adfb9000 a2bfd0e8 8269b9b0 WwanUsbMp+0x1c15f 8ba0f3cc 866e368a a2bfd0e8 00000000 8ba0f4c0 WwanUsbMp+0x36f96 8ba0f410 867004b0 a2bfd0e8 a2bfd0e8 a2be2a70 NDIS!ndisMInvokeInitialize+0x60 [minio\ndis\sys\miniport.c @ 13834] 8ba0f7ac 866dbc8e a2acf730 866b807c 00000000 NDIS!ndisMInitializeAdapter+0xa23 [minio\ndis\sys\miniport.c @ 601] 8ba0f7d8 866e687d a2bfd0e8 00000000 00000000 NDIS!ndisInitializeAdapter+0x4c [minio\ndis\sys\initpnp.c @ 931] 8ba0f800 866e90bb adfb64d8 00000000 a2bfd0e8 NDIS!ndisPnPStartDevice+0x118 [minio\ndis\sys\configm.c @ 4235] 8ba0f820 866e8a58 adfb64d8 a2bfd0e8 00000000 NDIS!ndisStartDeviceSynchronous+0xbd [minio\ndis\sys\ndispnp.c @ 3096] 8ba0f838 866e81df adfb64d8 8ba0f85e 8ba0f85f NDIS!ndisPnPIrpStartDevice+0xb4 [minio\ndis\sys\ndispnp.c @ 1067] 8ba0f860 820a7e98 a2bfd030 adfb64d8 8ba0f910 NDIS!ndisPnPDispatch+0x108 [minio\ndis\sys\ndispnp.c @ 2429] 8ba0f878 8231f07e 8ba0f8ec adf5d4c8 872e2eb8 nt!IofCallDriver+0x48 [minkernel\ntos\io\iomgr\iosubs.c @ 3149] 8ba0f898 820b8569 820c92b8 872e2eb8 8ba0f910 nt!PnpAsynchronousCall+0x9e [minkernel\ntos\io\pnpmgr\irp.c @ 3005] 8ba0f8cc 820c9a76 00000000 820c92b8 872e2eb8 nt!PnpSendIrp+0x67 [minkernel\ntos\io\pnpmgr\irp.h @ 286] 8ba0f914 8234577b 872e2eb8 adf638b0 adf638b0 nt!PnpStartDevice+0x60 [minkernel\ntos\io\pnpmgr\irp.c @ 3187] 8ba0f94c 82346cc7 872e2eb8 adf638b0 adf638b0 nt!PnpStartDeviceNode+0xc3 [minkernel\ntos\io\pnpmgr\start.c @ 1712] 8ba0f96c 82343c68 00000000 a2bdb3d8 adf638b0 nt!PipProcessStartPhase1+0x4d [minkernel\ntos\io\pnpmgr\start.c @ 114] 8ba0fb5c 824db885 8ba0fb80 00000000 00000000 nt!PipProcessDevNodeTree+0x386 [minkernel\ntos\io\pnpmgr\enum.c @ 6129] 8ba0fb88 8219571b 85852520 8c601040 8226ba90 nt!PiRestartDevice+0x91 [minkernel\ntos\io\pnpmgr\enum.c @ 4743] 8ba0fbe8 820804af 00000000 00000000 8c601040 nt!PnpDeviceActionWorker+0xdb4b7 [minkernel\ntos\io\pnpmgr\action.c @ 674] 8ba0fc38 8211485c 85852520 421de295 00000000 nt!ExpWorkerThread+0xcf [minkernel\ntos\ex\worker.c @ 4270] 8ba0fc70 82166785 820803e0 85852520 00000000 nt!PspSystemThreadStartup+0x4a [minkernel\ntos\ps\psexec.c @ 7756] 8ba0fc88 82051e07 85943940 8ba0fcd8 82051bb9 nt!KiThreadStartup+0x15 [minkernel\ntos\ke\i386\threadbg.asm @ 82] 8ba0fc94 82051bb9 8b9cc600 8ba10000 8ba0d000 nt!KiProcessDeferredReadyList+0x17 [minkernel\ntos\ke\thredsup.c @ 5309] 8ba0fcd8 00000000 00000000 00000000 00000000 nt!KeSetPriorityThread+0x249 [minkernel\ntos\ke\thredobj.c @ 3881] RETRACER_ANALYSIS_TAG_STATUS: Failed in getting KPCR for core 1 THREAD_SHA1_HASH_MOD_FUNC: e029276c66aea80ba36903e89947127118d31128 THREAD_SHA1_HASH_MOD_FUNC_OFFSET: 012389f065d31c8eedd6204846a560146a38099b THREAD_SHA1_HASH_MOD: 44dc639eb162a28d47eaeeae4afe6f9eeccced3d FOLLOWUP_IP: WwanUsbMp+1c15f ac50c15f 8bf0 mov esi,eax FAULT_INSTR_CODE: f33bf08b SYMBOL_STACK_INDEX: 8 SYMBOL_NAME: WwanUsbMp+1c15f FOLLOWUP_NAME: MachineOwner MODULE_NAME: WwanUsbMp IMAGE_NAME: WwanUsbMp.sys DEBUG_FLR_IMAGE_TIMESTAMP: 5211bb0c DXGANALYZE_ANALYSIS_TAG_PORT_GLOBAL_INFO_STR: Hybrid_FALSE DXGANALYZE_ANALYSIS_TAG_ADAPTER_INFO_STR: GPU0_VenId0x1414_DevId0x8d_WDDM1.3_NotActive;GPU1_VenId0x8086_DevId0x416_WDDM1.3_Active_Post; STACK_COMMAND: .thread ; .cxr ; kb BUCKET_ID_FUNC_OFFSET: 1c15f FAILURE_BUCKET_ID: AV_R_INVALID_WwanUsbMp!unknown_function BUCKET_ID: AV_R_INVALID_WwanUsbMp!unknown_function PRIMARY_PROBLEM_CLASS: AV_R_INVALID_WwanUsbMp!unknown_function TARGET_TIME: 2018-02-12T11:33:51.000Z OSBUILD: 16299 OSSERVICEPACK: 15 SERVICEPACK_NUMBER: 0 OS_REVISION: 0 SUITE_MASK: 272 PRODUCT_TYPE: 1 OSPLATFORM_TYPE: x86 OSNAME: Windows 10 OSEDITION: Windows 10 WinNt TerminalServer SingleUserTS OS_LOCALE: USER_LCID: 0 OSBUILD_TIMESTAMP: 2017-09-28 18:32:28 BUILDDATESTAMP_STR: 170928-1534 BUILDLAB_STR: rs3_release BUILDOSVER_STR: 10.0.16299.15.x86fre.rs3_release.170928-1534 ANALYSIS_SESSION_ELAPSED_TIME: 162bd ANALYSIS_SOURCE: KM FAILURE_ID_HASH_STRING: km:av_r_invalid_wwanusbmp!unknown_function FAILURE_ID_HASH: FAILURE_ID_REPORT_LINK: https://go.microsoft.com/fwlink/?LinkID=397724&FailureHash=31e4d053-0758-e43a-06a7-55f69b072cb3 Followup: MachineOwner --------- ReadVirtual: 812d1248 not properly sign extended 

.NET: Лечение зависимостей

Кто не сталкивался с проблемами из-за assembly redirect? Скорее всего все, кто разрабатывал относительно большое приложение, рано или поздно с этой проблемой столкнется.

Сейчас я работаю в компании JetBrains, в проекте JetBrains Rider, и занимаюсь задачей миграции Rider на .NET Core. Ранее занимался общей инфраструктурой в Контуре, облачной платформой хостинга приложений.

Под катом — расшифровка моего доклада с конференции DotNext 2019 Moscow, где я рассказал о трудностях при работе со сборками в .NET и на практических примерах показал, что бывает и как с этим бороться.

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

Структура поста:

  1. Проблемы с зависимостями
  2. Strict assembly loading
    • Binding redirects
    • Strong naming

  3. .NET Core
    • Shared frameworks, .runtimeconfig.json
    • Dependency manifest (.deps.json)
    • Хаки для запуска JetBrains Rider на Core

  4. Отладка загрузки сборок
    • Fusion logs
    • Runtime events

Какие вообще бывают проблемы с зависимостями?

Когда начинали разрабатывать .NET Framework в начале 2000-х, уже была известна проблема Dependency hell, когда во всех библиотеках разработчики допускают ломающие изменения (breaking changes), и эти библиотеки становятся несовместимыми для использования с уже скомпилированным кодом. Как такую проблему решать? Первое решение очевидно. Всегда сохранять обратную совместимость. Конечно, это не очень реалистично, потому что breaking change посадить в код очень легко. Например:

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

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

Version hell — это невозможность использовать зависимость, которая бинарно совместима, но при этом имеет версию, которая не подошла рантайму или другому компоненту, который эти версии проверяет. В .NET, типичное проявление version hell — FileLoadException, хотя файл на диске лежит, но он почему-то не грузится рантаймом.

В .NET у сборок есть много различных версий — различным образом пытались починить version hell-ы, и смотрите, что получилось. У нас есть пакет System.Collections.Immutable . Многие его знают. У него последняя версия NuGet-пакета 1.6.0. В нём лежит библиотека, сборка с версией 1.2.4.0. Вы получили эксепшн, что у вас нет библиотеки сборки версии 1.2.4.0. Как понять, что она лежит в NuGet-пакете 1.6.0? Это будет нелегко. Кроме Assembly Version, у этой библиотеки есть ещё несколько версий. Например, Assembly File Version, Assembly Information Version. В этом NuGet-пакете на самом деле лежат три разные сборки с одинаковыми версиями (для различных версий .NET Standard).

По тому, как работать со сборками в .NET, написано очень много документации. Есть .NET Guide по разработке современных приложений на .NET с учётом .NET Framework, .NET Standard, .NET Core, опенсорса и всего, что только может быть. Загрузке сборок в нём посвящено примерно 30 % всего документа. Разберем конкретные проблемы и примеры, которые могут возникнуть.

Для чего вообще всё это нужно? Во-первых, чтобы избежать наступания на грабли. Во-вторых, вы сможете сделать жизнь пользователей ваших библиотек проще, потому что с вашей библиотекой они не будут иметь тех проблем с зависимостями, к которым они привыкли. Также это поможет справиться вам с миграцией сложных приложений на .NET Core. И в довершение всего вы сможете стать SRE, это Senior (Binding) Redirect инженер, к которому все в команде приходят и спрашивают, как написать очередной редирект.

Strict assembly Loading

Strict assembly loading — это основная проблема, с которой сталкиваются разработчики на .NET Framework. Она выражается в FileLoadException . Перед тем, как перейти к самому Strict assembly loading, напомню несколько базовых вещей.

Когда вы собираете .NET-приложение, у вас на выходе получается некоторый артефакт, который обычно находится в Bin/Debug или в Bin/Release, и содержит в себе некоторый набор assembly-сборок и конфигурационных файлов. Сборки референсят друг друга по именам, Assembly name. Здесь важно понять то, что ссылки на сборку находятся непосредственно в сборке, которая на эту сборку ссылается, нет волшебных конфигурационных файлов, где прописаны assembly-референсы. Даже несмотря на то, что вам может показаться, что такие файлы есть. Референсы находятся в самих сборках в бинарном виде.

В .NET существует процесс assembly resolving — это когда определение сборки превращается уже в настоящую сборку, которая лежит на диске или загружена куда-то в память. Аssembly resolving выполняется дважды: на этапе билда, когда у вас есть референсы в *.csproj, и на этапе рантайма, когда у вас есть референсы внутри в сборках, и они по каким-то правилам превращаются уже в сборки, которые можно загрузить.

// Simple name
MyAssembly, Version=6.0.0.0,
Culture=neutral, PublicKeyToken=null

// Strong name
Newtonsoft.Json, Version=6.0.0.0,
Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed // PublicKey

Переходим к проблеме. Assembly name существуют двух основных видов. Первый вид assembly name — это Simple name. Их легко опознать по тому, что у них PublicKeyToken=null. Есть Strong name, их легко опознать по тому, что у них PublicKeyToken не null, а какое-то значение.

Разберем пример. У нас есть программа, которая зависит от библиотеки с утилитами MyUtils, и версия MyUtils — 9.0.0.0. У этой же программы есть ссылка на другую библиотеку. Эта библиотека тоже хочет использовать MyUtils, но версии 6.0.0.0. MyUtils версии 9.0.0.0, и версии 6.0.0.0 имеют PublicKeyToken=null, то есть у них Simple name. Какая версия попадёт в бинарной артефакт, 6.0.0.0 или 9.0.0.0? 9-я версия. Сможет MyLibrary использовать MyUtils версии 9.0.0.0, которая попала в бинарный артефакт?

На самом деле сможет, потому что MyUtils имеет Simple name и соответственно для неё Strict assembly loading-а не существует.

Другой пример. Вместо MyUtils у нас полноценная библиотека из NuGet, которая имеет Strong name. Большинство библиотек в NuGet имеют Strong name.

На этапе билда в BIN скопируется версия 9.0.0.0, а вот в рантайме мы получим знаменитый FileLoadException . Чтобы MyLibrary, которая хочет версию 6.0.0.0 у Newtonsoft.Json , смогла использовать версию 9.0.0.0, надо пойти и написать Binding redirect в App.config .

Binding redirects

В нём указано, что сборку с таким-то именем и таким-то publicKeyToken-ом нужно с такого-то диапазона версий перенаправлять на такой диапазон версий. Вроде бы очень простая запись, но тем не менее здесь она находится в App.config , а могла находиться в других файлах. Есть файл machine.config внутри .NET Framework, внутри рантайма, в котором определен какой-то стандартный набор редиректов, который может от версии к версии .NET Framework отличаться. Может получиться так, что на 4.7.1 у вас ничего не работает, а на 4.7.2 уже работает, или наоборот. Нужно иметь в виду то, что редиректы могут прийти не только из вашего .App.config , и при отладке это стоит учитывать.

Упрощаем написание редиректов

Никто не хочет писать Binding redirect-ы руками. Давайте отдадим эту задачу MSBuild!

Несколько советов по тому, как можно упростить работу с Binding redirect. Совет первый: включите автогенерацию Binding redirect в MSBuild. Включается свойством в *.csproj . При сборке проекта в бинарный артефакт будет попадать App.config , в котором указаны редиректы на версии библиотек, которые находятся в этом же артефакте. Это работает только для запускаемых приложений, console application, WinExe. Для библиотек это не работает, потому что для библиотек App.config чаще всего просто не актуален, потому что он актуален для приложения, которое запускается и само загружает сборки. Если вы сделали конфиг для библиотеки, то в приложении некоторые зависимости могут тоже отличаться от тех, которые были при сборке библиотеки, и получится то, что конфиг для библиотеки смысла особого не имеет. Тем не менее иногда для библиотек конфиги всё-таки имеют смысл.

Ситуация, когда мы пишем тесты. Тесты обычно находятся в ClassLibrary и в них тоже нужны редиректы. Тестовые фреймворки умеют распознавать, что у библиотеки с тестами есть dll-конфиг, и променять редиректы, которые в них находятся, для кода из тестов. Можно сгенерить эти редиректы автоматически. Если у нас старый формат *.csproj , не SDK-style, то можно пойти простым путем, поменять OutputType на Exe и добавить пустой entry point, это заставит MSBuild сгенерить редиректы. Можно пойти другим путем и использовать хак. Можно дописать ещё одну property в *.csproj , которая заставит MSBuild считать, что для этого OutputType всё равно нужно генерить Binding redirect-ы. Этот способ, хоть и выглядит хаком, позволит генерить редиректы для библиотек, которые нельзя переделать в Exe, и для других типов проектов (кроме тестов).

Для нового формата *.csproj редиректы будут генерироваться сами, если вы используете современный Microsoft.NET.Test.Sdk.

Третий совет: не используйте генерации Binding redirect средствами NuGet. В NuGet есть возможность сгенерить Binding redirect для библиотек, которые проходят из пакетов на последние версии, но это не самый лучший вариант. Все эти редиректы придётся добавить в App.config и закоммитить, а если вы генерите редиректы средствами MSBuild, то редиректы генерятся при билде. Если вы их закоммитили, у вас могут быть merge конфликты. Вы можете сами просто забывать обновить Binding redirect в файле, а если они генерятся при билде, вы не забудете.

Домашнее задание для тех, кто хочет лучше разобраться с тем, как работает генерация Binding redirect-ов: узнайте, как она работает, посмотрите это в коде. Пойдите в директорию .NET, грепните везде имя property, которая используется для включения генерации. Это вообще такой распространённый подход, если есть какая-то странная property для MSBuild, можно пойти и грепнуть её использование. К счастью, используются property в XML-конфигах обычно, и вы легко найдёте их использование.

Если вы изучите, что в этих XML-таргетах находится, вы увидите, что эта property вызывает срабатывание двух MSBuild task-ов. Первый task называется ResolveAssemblyReferences , и он генерирует набор редиректов, которые запишутся в файлы. Второй task GenerateBindingRedirects записывает результаты работы первого task в App.config . Там есть XML-логика, которая немного исправляет работу первого task и убирает некоторые лишние редиректы, либо добавляет новые.

Альтернатива XML-конфигам

Не всегда удобно держать редиректы в XML-конфиге. У нас может быть такая ситуация, что приложение загружает плагин, а этот плагин использует другие библиотеки, для которых нужны редиректы. В этом случае нам может быть неизвестен набор редиректов, которые нам нужны, или мы можем не захотеть генерировать XML. В такой ситуации мы можем создать AppDomain и при его создании всё-таки передать ему, где находится XML с нужными редиректами. Ещё мы можем обрабатывать ошибки загрузки сборок прямо в рантайме. Рантайм .NET такую возможность дает.

AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) => < var name = eventArgs.Name; var requestingAssembly = eventArgs.RequestingAssembly; return Assembly.LoadFrom(. ); // PublicKeyToken should be equal >; 

В нём есть event, он называется CurrentDomain.AssemblyResolve . Подписавшись на этот event, мы будем получать ошибки обо всех неудачных загрузках сборок. Мы получаем имя сборки, которая не загрузилась, и получаем assembly-сборку, которая запросила загрузку первой сборки. Здесь мы можем вручную загрузить сборку из правильного места, например, отбросив версии, просто взяв её из файла, и вернуть из обработчика этого event. Либо вернуть null, если нам нечего возвращать, если мы не можем загрузить сборку. PublicKeyToken-ы должны быть одинаковыми, сборки с разными PublicKeyToken никак в рантайме между собой не дружат.

Этот event применяется только к одному application domain. Если у нас плагин создает AppDomain внутри себя, то в них этот редирект в рантайме не сработает. Нужно каким-то образом во всех AppDomain, которые плагин создал, тоже подписаться на этот event. Мы можем сделать это, используя AppDomainManager.

AppDomainManager — это отдельная сборка, в которой находится класс, реализующий определенный интерфейс, и один из методов этого интерфейса будет позволять инициализировать любой новый AppDomain, который создается в приложении. Как только создается AppDomain, этот метод будет вызываться. В нём вы можете подписаться на этот event.

Strict assembly loading & .NET Core

В .NET Core нет проблемы под названием «Strict assembly loading», которая связана с тем, что для подписанных сборок требуется ровно та версия, которая была затребована. Есть другое требование. Для всех сборок независимо от того, подписаны они Strong name-ом или нет, проверяется, что версия, которая загрузилась в рантайме, больше либо равна предыдущей. Если мы находимся в ситуации приложения с плагинами, у нас может возникнуть такая ситуация, что плагин собрали, например, с новой версии SDK, а приложение, в которое его загружают, использует до сих пор старую версию SDK, и вместо того, чтобы развалиться, мы можем тоже подписаться на этот event, но уже в .NET Core, и так же загружать сборку, которая у нас есть. Можем написать такой код:

AppDomain.CurrentDomain.AssemblyResolve += (s, eventArgs) => < CheckForRecursion(); var name = eventArgs.Name; var requestingAssembly = eventArgs.RequestingAssembly; name.Version = new Version(0, 0); return Assembly.Load(name); >; 

У нас есть имя сборки, которая не загрузилась, мы в обнуляем версию и вызываем Assembly.Load от этой же версии. Рекурсии здесь не будет, потому что рекурсию я уже проверил.

Надо было загрузить MyUtils версии 0.0.2.0. В BIN у нас лежит MyUtils версии 0.0.1.0. Мы сделали редирект с версии 0.0.2.0 на версию 0.0. Версия 0.0.1.0 у нас не загрузится. У нас вылетит эксепшн, что не удалось загрузить сборку с версией 0.0.2 16–1 . 2 16–1 .

new Version(0, 0) == new Version(0, 0, -1, -1) class Version < readonly int _Build; readonly int _Revision; readonly int _Major; readonly int _Minor; >(ushort) -1 == 65535 

В классе Version не все компоненты обязательные, и вместо необязательных хранятся –1, а где-то внутри происходит переполнение, и получаются те самые 2 16–1 . Если интересно, можете попробовать найти, где именно происходит переполнение.

Если вы работаете со сборками reflection-ом и хотите получить все типы, то может получиться так, что у вас метод GetTypes не все типы сможет загрузить. В сборке есть класс, который унаследован от другого класса, который находится в сборке, которая не грузится.

static IEnumerable GetTypesSafe(this Assembly assembly) < try < return assembly.GetTypes(); >catch (ReflectionTypeLoadException e) < return e.Types.Where(x =>x != null); > > 

В таком случае, проблема будет в том, что вылетит исключение ReflectionTypeLoadException. Внутри ReflectionTypeLoadException есть свойство, в котором находятся те типы, которые всё-таки удалось загрузить. Эту вещь учитывают не все популярные библиотеки. AutoMapper, по крайней мере какая-то из его версий, если сталкивалась с ReflectionTypeLoadException-ом просто падала, вместо того чтобы пойти и забрать типы изнутри исключения.

Strong naming

Поговорим о том, что приводит к возникновению Strict assembly loading, это Strong name.
Strong Name — это подпись сборки некоторым приватным ключом с помощью асимметричного шифрования. PublicKeyToken — это хеш публичного ключа этой сборки.

Strong Naming позволяет отличать разные сборки, которые имеют одинаковые имена. Например, MyUtils — это не какое-то уникальное имя, может быть несколько сборок с таким названием, но при этом если подписать Strong name-ом, у них будут разные PublicKeyToken и мы их сможем таким образом отличить. Strong name необходимы для некоторых сценариев загрузки сборок.

Например, для того чтобы установить сборку в Global Assembly Cache или чтобы загрузить сразу несколько версий side-by-side. Самое важное, что strong named сборки могут ссылаться только на другие strong named сборки. Так как некоторые пользователи хотят подписать свои сборки Strong name-ом, разработчики библиотек подписывают и свои библиотеки тоже, чтобы пользователям было их легче установить, чтобы пользователям не надо было эти библиотеки дополнительно переподписывать.

Strong name: легаси?

Microsoft явно говорит на MSDN, что Strong name для целей security использовать не стоит, что они предоставляют только для того, чтобы отличать разные сборки с одинаковыми именами. Ключ сборки никак нельзя поменять, если вы его поменяли, то вы сломаете всем вашим пользователям редиректы. Если у вас приватная часть ключа для Strong name утекла в публичный доступ, то вы никак эту подпись отозвать не сможете. Формат файла SNK, в котором находится Strong name, никак не предоставляет такой возможности, а другие форматы для хранения ключей хотя бы содержат ссылку на CRL Certificate Revocation List, по которому можно понять, что сертификат этот уже не валиден. В SNK ничего такого нет.

В Open-source гайде есть следующие рекомендации. Во-первых, дополнительно для целей security использовать другие технологии. Во-вторых, если у вас опенсорсная библиотека, то вообще предлагается закоммитить приватную часть ключа в репозиторий, чтобы людям было легче форкнуть вашу библиотеку, пересобрать её и подложить уже готовому приложению. В-третьих, никогда не менять Strong name. Слишком разрушительное действие. Несмотря на то, что оно слишком разрушительное и о нём написано в Open-source гайде, у Microsoft иногда бывают проблемы со своими собственными библиотеками.

Есть библиотека под названием System.Reactive. Раньше это были несколько NuGet-пакетов, один из них Rx-Linq. Это просто пример, для остальных пакетов то же самое. Во второй версии он был подписан ключом Microsoft. В третьей версии он переехал в репозиторий в проекте github.com/dotnet и стал иметь подпись .NET Foundation. У библиотеки, по сути, поменялся Strong name. Переименовался NuGet-пакет, но сборка называется внутри точно так же, как и раньше. Как сделать редирект со второй версии на третью? Этот редирект никак не сделать.

Strong name validation

Ещё один аргумент за то, что Strong name это уже что-то, что уходит в прошлое, и осталось чисто формальным, это то, что они не валидируются. У нас есть подписанная сборка и мы хотим исправить в ней какой-то баг, а доступа к исходникам у нас нет. Мы можем просто взять dnSpy — это утилита, которая позволяет декомпилировать и исправлять уже скомпилированные сборки. У нас всё будет работать. Потому что по дефолту включен Strong name validation bypass, то есть проверяется только то, что PublicKeyToken-ы равны, а целостность самой подписи не проверяется. Могут быть энвайронменты, в которых подпись всё-таки проверяется, и здесь яркий пример — это IIS. На IIS проверяется целостность подписи (Strong name validation bypass выключен по умолчанию), и у нас всё сломается, если мы отредактируем подписанную сборку.

Дополнение: Можно отключить проверку подписи для сборки, используя public sign. При нём для подписи используется только публичный ключ, что обеспечивает сохранность имени сборки. Используемые Microsoft публичные ключи выложены здесь.
В Rider public sign можно включить в свойствах проекта.

Open-source гайд также предлагает некоторую Versioning policy, цель которой — это сократить количество необходимых Binding redirect-ов и изменений в них для пользователей на NET Framework. Эта Versioning policy заключается в том, что мы не должны менять Assembly Version постоянно. Это, конечно, может привести к проблемам с установкой в GAC, с тем, что установленный нативный образ может не соответствовать сборке и придётся выполнять JIT компиляцию заново, но, на мой взгляд, это меньшее зло, чем проблемы с версионированием. В случае с CrossGen нативные сборки не устанавливаются глобально — никаких проблем не будет.

Например, NuGet-пакет Newtonsoft.Json, у него есть несколько версий: 12.0.1, 12.0.2 и так далее — во всех этих пакетах лежит сборка с версией 12.0.0.0. Рекомендация заключается в том, что Assembly Version надо обновлять при изменении мажорной версии NuGet-пакета.

Выводы

Следуйте советам для .NET Framework: генерируйте редиректы вручную и старайтесь использовать одинаковые версии зависимостей во всех проектах в своем solution. Это должно значительно минимизировать количество редиректов. Strong naming вам нужен, только если у вас есть конкретный сценарий загрузки сборок, где это необходимо, либо вы разрабатываете библиотеку и хотите упростить жизнь пользователям, которым Strong naming действительно нужен. Не меняйте Strong name.

.NET Standard

Переходим к .NET Standard. Он довольно тесно связан с Version hell в .NET Framework. .NET Standard — это средство для написания библиотек, которые совместимы с различными реализациями платформы .NET. Под реализациями имеются в виду .NET Framework, .NET Core, Mono, Unity и Xamarin.

Это таблица поддержки .NET Standard различных версий различными версиями рантаймов. И вот здесь мы можем увидеть, что .NET Framework ни в каком виде не поддерживает .NET Standard версии 2.1. Релиза .NET Framework, который будет поддерживать .NET Standard 2.1 и дальнейшие версии, пока что не запланировано. Если вы разрабатываете библиотеку и хотите, чтобы она работала у пользователей на .NET Framework, вам придется иметь таргет на .NET Standard 2.0. Кроме того что .NET Framework не поддерживает последнюю версию .NET Standard, давайте обратим внимание на звёздочку. .NET Framework 4.6.1 поддерживает .NET Standard 2.0, но со звёздочкой. Такая сноска есть напрямую в документации, откуда я эту таблицу взял.

Рассмотрим пример проекта. Приложение на .NET Framework, которое имеет одну зависимость, таргетящую .NET Standard. Примерно так: ConsoleApp и ClassLibrary. Библиотека таргетит .NET Standard. Когда мы этот проект соберем, в нашем BIN будет вот так.

У нас там будет сотня DLL, из них имеющих отношение к приложению только одна, всё остальное пришло для того, чтобы поддержать .NET Standard. Дело в том, что .NET Standard 2.0 появился позже, чем .NET Framework 4.6.1, но при этом они оказались совместимыми по API, и разработчики решили добавить поддержку Standard 2.0 в .NET 4.6.1. Сделали её не нативно (включением netstandard.dll в сам рантайм), а так, что в BIN кладется напрямую .NET Standard *.dll и все другие assembly-фасады.

Если посмотрим на зависимости версии .NET Framework, которые мы таргетим, и количество библиотек, которые попали в BIN, то увидим, что в 4.7.1 их уже не так много, а начиная с 4.7.2 дополнительных библиотек вообще не приезжает и .NET Standard там поддержан нативно.

Это твит одного из разработчиков .NET, в котором, описывается эта проблема и дается рекомендация использовать .NET Framework версии 4.7.2, если у нас есть .NET Standard-библиотеки. Даже не с версии 2.0 здесь, а с версии 1.5.

Выводы

По возможности поднимите Target Framework в своем проекте хотя бы до 4.7.1, лучше до 4.7.2. Если вы разрабатываете библиотеку, чтобы сделать жизнь пользователей библиотеки проще, сделайте отдельный Target для .NET Framework, он позволит избежать огромного количества dll, которые могут с чем-либо законфликтовать.

.NET Core

Начнем с общей теории. Обсудим, как мы запускали JetBrains Rider на .NET Core, и зачем вообще нужно об этом говорить. Rider — это очень большой проект, у него огромный энтерпрайзный solution с большим количеством различных проектов, сложной системой зависимостей, его нельзя просто так взять и за один раз смигрировать на другой рантайм. Для этого нам приходится использовать некоторые хаки, которые тоже разберем.

.NET Core приложение

Как выглядит типичное .NET Core приложение? Зависит от того, как именно оно деплоится, во что оно в итоге собирается. У нас может быть несколько сценариев. Первый — это Framework-dependent deployment. Это то же самое, что было в .NET Framework, когда приложение использует рантайм, заранее установленный на компьютере. Может быть Self-contained deployment, это когда приложение несет рантайм за собой. И может быть ещё Single-file deployment, это когда у нас получается один exe-файл, но в случае с .NET Core внутри этого exe-файла находится артефакт Self-contained приложения, это самораспаковывающийся архив.

Мы будем рассматривать только Framework-dependent deployment. У нас есть dll с приложением, есть два конфигурационных файла, первый из которых обязательный, это runtimeconfig.json и deps.json . Начиная с .NET Core 3.0 генерируется exe-файл, который нужен для того, чтобы приложение было удобнее запустить, чтобы не надо было вводить команду .NET, если мы на Windows. В этот артефакт попадают зависимости, начиная с .NET Core 3.0, в .NET Core 2.1 нужно сделать для этого publish или использовать другую property в *.csproj .

Shared frameworks, .runtimeconfig.json

.runtimeconfig.json содержит в себе настройки рантайма, которые нужны для его запуска. Там указано, под каким Shared Framework приложение будет запускаться, и выглядит это вот так. У нас указывается, что приложение будет запускаться под “Microsoft.NETCore.App” версии 3.0.0, могут быть и другие Shared Framework. Также здесь могут находиться другие настройки. Например, можно включить серверный Garbage collector.

.runtimeconfig.json генерируется при сборке проекта. А если мы хотим включить серверный GC, то нам нужно этот файл каким-то образом модифицировать заранее, ещё до того, как мы проект соберем, либо руками дописывать. Свои настройки сюда можно добавить так. Мы можем либо включить property в *.csproj , если такая property предусмотрена разработчиками .NET, либо если property не предусмотрена, мы можем создать файл под названием runtimeconfig.template.json и прописать нужные настройки сюда. При сборке в этот template будут добавлены другие нужные настройки, например, тот же Shared Framework.

Shared Framework — это набор, состоящий из рантайма и из библиотек. По сути то же самое, что рантайм .NET Framework, который раньше просто вот устанавливался один раз на машину и для всех был одной версией. Shared Framework-и, в отличие от единого рантайма .NET Framework, могут версионироваться, разные приложения могут использовать разные версии, установленных рантаймов. Также Shared Framework-и могут наследоваться. Сами Shared Framework-и можно посмотреть вот в таких локациях на диске, какие вообще установлены в системе.

Есть несколько стандартных Shared Framework, например, Microsoft.NETCore.App, на котором запускаются обычные консольные приложения, AspNetCore.App — для веб-приложений, и WindowsDesktop.App — это новый Shared Framework в .NET Core третьем, на котором запускаются десктопные приложения на Windows Forms и WPF. Два последних Shared Framework по сути дополняют собой первый, необходимый для консольных приложений, то есть они не несут за собой целиком новый рантайм, а просто дополняют уже имеющийся нужными библиотеками. Это наследование выглядит так, что в директориях Shared Framework тоже есть runtimeconfig.json , в которых указан базовый Shared Framework.

Dependency manifest ( .deps.json )

Второй конфигурационный файл — это .deps.json . Этот файл содержит описание всех зависимостей приложения или Shared Framework, или библиотеки, у библиотек .deps.json тоже есть. Там содержатся все зависимости, в том числе и транзитивные. И поведение рантайма .NET Core отличается в зависимости от того, есть .deps.json у приложения или нет. Если .deps.json нет, то приложение сможет загружать все сборки, которые есть в его Shared Framework или в его BIN директории. Если же .deps.json есть, то включается валидация. Если какой-то из сборок, которая указана в .deps.json , нет, то приложение просто не запустится. Вы увидите ошибку, котора представлена выше. Если приложение попробует в рантайме загрузить какую-то сборку, которой в .deps.json нет, например, методами Assembly load или в процессе resolve сборок, то вы увидите ошибку очень похожую на Strict assembly loading.

JetBrains Rider

Rider — это .NET IDE. Не все знают то, что Rider — это IDE, состоящие из фронтенда на базе IntelliJ IDEA и написанном на Java и Kotlin, и бэкенда. Бэкенд — это по сути R#, который умеет общаться с IntelliJ IDEA. Этот бэкенд — это кроссплатформенное .NET приложение уже сейчас.
Где же оно запускается? На Windows используется .NET Framework, который установлен на компьютере пользователя. На других информационных системах, на Linux и Mac используется Mono.

Это не идеальное решение, когда везде разные рантаймы, и хочется прийти к следующему состоянию, чтобы Rider запускался на .NET Core. Для того, чтобы производительность стала лучше, потому что в .NET Core все самые последние фичи с этим связанные. Чтобы уменьшить потребление памяти. Сейчас есть проблема, связанная с тем, как Mono работает с памятью.

Переход на .NET Core позволит отказаться от legacy, от неподдерживаемых технологий и позволят контрибьютить какие-то фиксы для проблем, которые в рантайме нашлись. Переход на .NET Core позволит контролировать версию рантайма, то есть Rider будет запускаться уже не на .NET Framework, который установлен на компьютере пользователя, а на конкретной версии .NET Core, которую можно забандлить, в виде self-contained deployment. Переход на .NET Core в итоге позволит использовать новые API, которые завозятся именно в Core.

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

Особенности, из-за которых перевести Rider на .NET Core сложно

Visual Studio, даже если в неё не установлен R#, падает с Out Of Memory на больших solution-ах, внутри которых есть проекты с SDK-style *.csproj. SDK-style *.csproj — это одно из основных условий полноценного переезда .NET Core.

Это проблема, потому что Rider основан на R#, они живут в одном репозитории, разработчики R# хотят использовать Visual Studio, чтобы разрабатывать именно свой продукт в своем продукте, чтобы догфудить его. В R# есть ссылки специфичные библиотеки под фреймворк, с которыми надо что-то делать. На Windows мы можем использовать Framework для десктоп-приложений, а на Linux и на Mac уже сейчас используются Mock-и для Windows-библиотек с минимальной функциональностью.

Решение

Мы решили пока оставаться на старых *.csproj , собираться под полный Framework, но так как сборки у Framework и Core бинарно совместимы, запускать их на Core. Мы не используем несовместимые апишки, добавляем все нужные конфигурационные файлы вручную и загружаем специальные версии зависимостей под .NET Core, если они есть.

На какие хаки пришлось пойти?

Хак один: мы хотим вызывать метод, который есть только во Framework, например, этот метод нужен в R#, а на Core не нужен. Проблема в том, что если метода нет, то тот метод, который его вызывает при JIT-компиляции ещё раньше упадет с MissingMethodException . То есть метод, которого нет, испортил метод, который его вызывает.

static void Method() < if (NetFramework) CallNETFrameworkOnlyMethod(); . >[MethodImpl(MethodImplOptions.NoInlining)] static void CallNETFrameworkOnlyMethod()

Решение здесь: мы выносим вызовы несовместимых методов в отдельные методы. Есть ещё одна проблема: такой метод может заинлайниться, поэтому помечаем атрибутом NoInlining .

Хак номер два: нам нужно уметь загружать сборки по относительным путям. У нас есть одна сборка для Framework, есть специальная версия для .NET Core. Как нам загрузить на .NET Core версию для .NET Core?

Нам помогут .deps.json . Посмотрим на .deps.json для библиотеки System.Diagnostics.PerformanceCounter. Такая библиотека примечательная в плане её .deps.json . В нём есть секция runtime, в которой указана одна версия библиотеки с её относительным путем. Вот эта библиотека, сборка будет загружаться на всех рантаймах, и она просто кидает эксепшны. Если, например, она загружается на Linux, на Linux PerformanceCounter не работают by design, и оттуда вылетает PlatformNotSupportedException. Также в этом .deps.json есть секция runtimeTargets и вот здесь уже указана версия этой сборки конкретно для Windows, где PerformanceCounter должны работать.

Если мы возьмем секцию runtime и пропишем в ней относительный путь до той библиотеки, которую хотим загрузить, нам это ничем не поможет. В секции runtime на самом деле прописан относительный путь внутри NuGet-пакета, а не относительно BIN. Если мы ищем эту сборку в BIN, будет использоваться оттуда только имя файла. В секции runtimeTargets прописан уже честный относительный путь, честный путь относительно BIN. Будем прописывать для наших сборок относительный путь в секции runtimeTargets. Вместо runtime identifier, который здесь «win», мы можем взять другой, который нам понравится. Например, пропишем runtime identifier «any», и будет эта сборка загружаться вообще на всех платформах. Либо пропишем «unix», и будет загружаться и на Linux, и на Mac, и так далее.

Следующий хак: мы хотим загрузить на Linux и на Mac Mock для сборки WindowsBase. Проблема в том, что сборка с именем WindowsBase уже присутствует в Shared Framework Microsoft.NETCore.App , даже если мы находимся не на Windows. На Windows Shared Framework Microsoft.WindowsDesktop.App своим WindowsBase переопределяет ту версию, которая находится в NETCore.App . Посмотрим на .deps.json этих Framework, точнее на те секции, которые описывают WindowsBase.

Если какая-то библиотека конфликтует и присутствует в нескольких .deps.json , то выбирается максимальная из них по паре, состоящей из assemblyVersion и fileVersion . В .NET гайде написано, что fileVersion нужен только для того, чтобы показывать его в Windows Explorer, но это не так, он попадает в .deps.json . Это единственный случай, о котором мне известно, когда версия, прописанная в .deps.json , assemblyVersion и fileVersion , действительно используются. Во всех других случаях я видел поведение, что какие бы версии в .deps.json не были написаны, всё равно сборка будет продолжать загружаться.

Четвертый хак. Задача: у нас появился .deps.json-файл для предыдущих двух хаков, и он нам нужен только для конкретных зависимостей. Так как .deps.json генерируются в полуручном режиме, у нас есть скрипт, который по некоторому описанию того, что туда должно попасть, его генерирует при билде, нам хочется сохранять этот .deps.json минимально возможным, чтобы нам было понятно, что вообще в нём находится. Мы хотим отключить валидацию и разрешить загрузку сборок, которые лежат в BIN, но не описаны в .deps.json .

Решение: включаем специальную настройку в runtimeconfig. Эта настройка на самом деле нужна для обратной совместимости .NET Core 1.0.

Выводы

Итак, .runtime.json и .deps.json на .NET Core — это в своем роде аналоги App.config . App.config позволяют делать те же вещи, например, загружать сборки по относительным путям. Используя .deps.json , переписывая его вручную, вы можете кастомизировать загрузку сборок на .NET Core, если у вас очень сложный сценарий.

Отладка загрузки сборок

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

Fusion logs

Механизм загрузки сборок в .NET Framework называется Fusion, и он умеет выдавать на диск логи о том, что он вообще делал. Чтобы включить логирование, нужно добавить специальные настройки в реестр. Это не очень удобно, поэтому имеет смысл использовать утилиты, а именно Fusion Log Viewer и Fusion++. Fusion Log Viewer — это стандартная утилита, которая входит в Visual Studio, её можно запустить из командной строки Visual Studio, Visual Studio Developer Command Prompt. Fusion++ — это опенсорсный аналог этого инструмента с более приятным интерфейсом.

Fusion Log Viewer выглядит вот так. Это похлеще, чем WinDbg, потому что это окошко даже не растягивается. Тем не менее можно тут протыкать галочки, хотя и не всегда очевидно, какой набор галочек правильный.

Во Fusion++ есть одна кнопка «Начать логирование», и потом появляется кнопка «Остановить логирование». В нём можно посмотреть все записи о загрузке сборок, прочитать логи о том, что именно происходило. Эти логи выглядят примерно вот так в кратком виде.

Это эксепшн от Strict assembly loading. Если мы будем смотреть логи Fusion, мы увидим, что нам требовалось загрузить версию 9.0.0.0, после того как мы обработали все конфиги. Мы нашли файл, в котором подозревается, что есть нужная нам сборка. Мы посмотрели, что в этом файле лежит версия 6.0.0.0. У нас warning, что мы сравнили полные имена сборок, и они отличаются по мажорной версии. И дальше случилась ошибка — version mismatch.

Runtime events

На Mono можно включить логирование с помощью переменных окружения, и логи будут в итоге писаться в stdout и stderr . Не так удобно, но решение рабочее.

В .NET Core также есть специальная переменная окружения COREHOST_TRACE , включающая запись логов в stderr . С .NET Core 3.0 можно писать логи в файл, указав путь до него в переменной COREHOST_TRACEFILE .

Есть событие, которое срабатывает при неудачной загрузке сборок. Это event AssembleResolve . Есть второй полезный event, это FirstChanceException . Можно на него подписаться и получить ошибку о загрузке сборок, даже если кто-то написал try..catch и подропал все эксепшны в том месте, где FileLoadException произошел. Если приложение уже скомпилировано, можно запустить perfview , и он умеет мониторить .NET эксепшны, и там отыскать те, которые относились к фейлам загрузки.

Выводы

Перекладывайте работу на инструменты, на средства разработки, на IDE, на MSBuild, который позволяет генерировать редиректы. Вы можете перейти на .NET Core, тогда вы забудете о том, что такое Strict Assembly Loading, и сможете использовать новый API точно так же, как мы этого хотим достичь в Rider. Если подключаете библиотеке .NET Standard, то поднимите таргет-версию .NET Framework хотя бы до 4.7.1. Если кажется, что попали в безвыходную ситуацию, то ищите хаки, применяйте их, или придумывайте свои хаки для безвыходных ситуаций. И вооружайтесь средствами отладки.

Настоятельно рекомендую ознакомиться со следующими ссылками:

  • .NET Guide;
  • статья про примитивы конфигов в .NET Core.

Этим летом я снова буду выступать с докладом на DotNext 2020 Piter онлайн. И если у вас уже есть билет, его можно проапгрейдить до абонемента на 8 летних конференций JUG Ru Group.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *