Предлагаем вашему вниманию прошлогоднюю статью Марка Руссиновича, которая, к сожалению, раньше осталась незамеченной. В данной статье Руссинович говорит о причине задержки открытия диалога Open в Windows Explorer и иных приложениях, поэтому она может оказаться поистине полезной.
Несколько недель назад я был на конференции TechEd/ITForum в Барселоне, где выступал с несколькими докладами. Конференция прошла очень хорошо и Windows Vista, которую я решил испробовать впервые, работала отлично. Однако, когда я просматривал демо перед одной из своих лекций, я заметил, что диалог открытия файлов, который является общим для всех Windows-приложений, появляется с интервалом 5-15 секунд после нажатия.
У меня не было времени разобраться с причиной до доклада, поэтому подобные задержки во время моего доклада «Изменения в ядре Windows Vista» (Windows Vista Kernel Changes) застали меня врасплох. Такое поведение ОС оставляло такое же странное впечатление, как и в том случае, который я описал в заметке «Причина задержки автоматически загружаемых процессов» (The Case of the Process Startup Delays). В том случае Windows Defender Remote Procedure Call (RPC) пытался соединиться с контроллером домена, что приводило к зависаниям, в случае если компьютер к домену не подключён. Я бормотал извинения от имени Windows Vista и пытался отвлечь аудиторию, переходя к следующим демонстрациям.
До тех пор, пока я не прилетел домой, у меня не было возможности взглянуть на эту проблему еще раз. Я совершил шаги, похожие на те, которые я делал, когда исследовал зависания Windows Defender. Я запустил инструмент Windbg из Debugging Tools for Windows, а из него блокнот, нажал Ctrl O для открытия диалога открытия файлов и, когда система начала тормозить, я посмотрел в стек основного потока в блокноте.
Если вы раньше не видели стек – то это список всех последних команд, вызванных потоком. Вы читаете их снизу вверх, и таким образом, можете прочитать, что блокнот загрузил Browseui.Dll, и вызвал её функцию CAddressBand::SetNavigationState. Эта функция вызвала CBreadcrumbBar::SetNavigationState, которая, в свою очередь, вызвала CBreadcrumbBar::SetIDList, и так далее.
Взгляд на названия функций в стеке сразу мне сказал, что же произошло: когда вы первый раз в приложении запускаете диалог открытия файлов, то он открывает вашу папку документов. В Windows Vista моя папка с документами это C:\Users\Markruss\Documents, но оболочка хочет сделать путь в новой панели «хлебных крошек» (от англ. «bread crumb») красивым, отображая его как «Mark Russinovich\Documents», и поэтому, вызывает [url= http://msdn2.microsoft.com/en-gb/library/ms724435.aspx]функцию GetUserNameEx[/url], чтобы посмотреть, как отображается имя моей учётной записи в объекте Пользователь в Active Directory. Я подтвердил свою теорию, проверив, что первый параметр SHGetUserDisplayName передает параметры команде GetUserNameEx, которая интерпретируется как перечень EXTENDED_NAME_FORMAT, и есть 3: NameDisplay.
Я установил контрольную точку на возвращении вызова и обратился к ней, когда зависание закончилось. Команда GetUserNameEx вернула код ошибки ERROR_NO_SUCH_DOMAIN, и прошла через SHGetUserDisplayName, показав, что это она снижает скорость передачи данных к команде GetUserName. Вместо того, чтобы искать отображаемое имя пользователя, эта функция просто получает идентификатор безопасности (SID) пользователя от маркера процесса (структура данных ядра, которая определяет владельца процесса), и вызывает LookupAccountName, чтобы перевести SID в имя учётной записи, которое, в моём случае, просто markruss. Таким образом, появившийся диалог выглядел таким образом.
В противоположность вот, что появилось, когда я пришёл на работу, и подключился к .
Я разобрался с проблемой, но мне стало интересно, где именно происходила задержка, и поэтому я продолжил исследование того, что же именно происходило по ту сторону вызова Secure32!CallSPM, который находится в начале листинга стека. Я знал, что процесс Local Security Authority (LSASS) ответственен за , включая взаимодействие с контроллерами домена и трансляторами имени учётной записи, так что, я прикрепил Windbg к процессу Lsass.exe (убедитесь, что вы открепили дебаггер от LSASS, иначе при его выключении с помощью команды qd отключится и процесс LSASS, и система перезагрузится через 30 секунд). Я определил, что Secur32.Dll связывается и с клиентом и с сервером, и подтвердил, что она загружается в процесс LSASS, но мне необходимо было определить определённую серверную функцию, которая отвечает за Secur32!SecpGetUserName. Я её определил брут-форсом (то есть перебором): я выгружал функции, осуществляемые Secur32.Dll, и искал какую-либо со словом «name» в ней.
Я установил контрольные точки на некоторых из них и, когда я заново вызвал подвисание, я запустил одну на SecpGetUserName, и переступил через неё, чтобы добраться до этого стека.
Функция DsGetDcName документирована как возвращающая имя контроллера домена в определённом домене. Очевидно, функции SecpTranslateName необходимо найти контроллер домена, которому она пошлёт запрос на отображаемое имя пользователя. Я проследовал далее и обнаружил, что LSASS кэширует результат поиска на 45 секунд, что объясняет, почему у меня не было задержки, если я запускал другое приложение и диалог открытия файла в нём сразу после этого зависания. После этого я попал в тупик, когда Netapi32!DsrGetDcNameEx2 выполняла RPC запрос.
Узнав, что Netapi32 работает и с клиентом и с сервером, я убрал её символы и установил точки на командах, содержащих «dc». Я позволил LSASS запускать процессы и к своему удивлению наткнулся на ту же самую функцию Netapi32!DsrGetDcNameEx2. Я проследил за запросом всё глубже и глубже, пока поток меня, наконец, не привёл в ядро (Ntdll!KiFastSystemCallRet).
Я был близок к окончанию своего расследования. Последний вопрос, который у меня был – что это за драйвер устройства Netlogon, делающий вызов на посылание дейтаграммы ? Я ответил на этот вопрос, посмотрев на первый параметр, который он передал NlBrowserDeviceIoControl, который, как я предположил, был обработчиком объекта файла. Потом я открыл Windbg в режиме Local Kernel Debugging (заметьте, что в Windows Vista вы должны загрузиться в режиме отладки, чтобы сделать это, что позволит вам посмотреть на живую структуру данных ядра) и убрал информацию обработчика. Это показало мне объект устройства, который подсказал мне, что драйвер Bowser.sys — это NT драйвер-получатель дейтаграммы менеджера локальных подключений (NT Lan Manager Datagram Receiver Driver).
Я думал, что моё исследование подошло к концу, но когда позже я пытался вызвать снова подвисание, у меня ничего не вышло. Я повторил свои шаги и узнал, что LsapGetUserNameForLogonSession кэширует отображаемое имя на 30 минут. Далее отображаемое имя кэшируется с кэшируемыми мандатами, так что вы не будете сталкиваться с задержками в течение 30 минут после подключения или отключения от корпоративной сети. Я подтвердил свои предположения, подождав 30 минут и вызвав зависание.
Итак, диалог открытия файлов пытается отобразить имя пользователя для панели «хлебных крошек» при отображении папки документов и в процессе пытается определить местоположение контроллера домена, отсылая дейтаграмму менеджера локальных подключений через драйвер устройства Bowser.sys. Я также узнал, что нет способов решения проблемы задержек, и что любой, у кого компьютер подключён к домену, но в данный момент отключённый от него, тоже будет сталкиваться с такими задержками, по крайней мере, до выхода Vista SP1.
Нет ли изменений после установки недавно выпущенного Vista SP1?
Источник: http://blogs.technet.com/markrussinovich
Tags: domain, Windows Vista