欢迎光临 Enjoy IT (ITECN.NET) 登录 | 注册 | 帮助

Windows Vista打开对话框延时问题的排错实例

原文 作者 Mark Russinovich
翻译 作者 盆盆 [ITECN站长]
ITECN博客
http://blogs.itecn.net/blogs/
盆盆 导读 本文由Mark Russinovich(微软院士)所著,由盆盆所翻译。Mark Russinovich是《Windows Internals》一书的合作者,Sysinternals Suite的开发者。这篇文章主要描述:对于加入域的计算机,在处于离线状态时,如果试图调出"打开"对话框,就会出现515秒的延时故障现象。作者从底层分析了该故障现象的深层次原因。为了确保原汁原味,翻译文章保留第一人称,文中的""就是指Mark Russinovich
这篇文章可以看出Mark对待技术的严谨,原文中多次出现类似"close to the end of my investigation"等字样,表明分析过程即将结束,但是只要发现一点点小的纰漏,Mark就会返回探索其深层次的原因,这种"绝知此事要躬行"的态度让我非常敬佩。Mark的成就,我等固然难以企及,然这种治学态度,却可轻松效仿。


2
周前,我在巴塞罗那参加微软TechEd/IT Forum大会,在那儿主讲了一些课程,其中两个课程,《Advanced Malware Cleaning》和《Windows Vista Kernel Changes》,还分别获得该星期最佳课程的第一和第二名,大家可以在这里看到我在大会上的相关访谈。大会极其成功,Windows Vista也表现得非常出色。然而,在上课前做彩排演示时,我注意到“打开”对话框经常要花515秒钟才能弹出来。

因为在演讲前我没有太多时间进行分析,所以后来在上《Windows Vista Kernel Changes》这堂课时,这种延时现象让我大吃一惊。整个故障现象和我以前写过的文章《The Case of the Process Startup Delays》一样不可思议。在那个案例中,当进程启动时,Windows DefenderRPC通信机制会尝试联系域控制器,这就导致当系统不在域中时,会出现进程启动延时现象。在课堂上我只能从Windows Vista的角度竭力进行开脱,通过后面的演示来分散听众的注意力。

直到飞回家,我才有时间进行深入研究。我用和Windows Defender延时那个案例类似的步骤进行分析。首先用在Windbg工具窗口里启动记事本程序,按Ctrl+O组合键调出"打开"对话框,当出现延时故障时,查看记事本主线程的堆栈,如下图所示。

所谓堆栈,就是线程最近所调用的一组函数。从下往上查看该堆栈的内容,可以发现记事本先加载Browseui.Dll,并调用该dll文件中的CAddressBand::SetNavigationState函数、该函数随之调用CBreadcrumbBar::SetNavigationState、然后依次调用CBreadcrumbBar::SetIDList…等等。

从堆栈里的函数名中马上就可以发现:第一次调出"打开"对话框时,总是试图定位到"文档"文件夹。在Windows Vista中,笔者的"文档"文件夹是C:\Users\Markruss\Documents,但是为了让工具栏显示得更加人性化(Bread Crumb Bar),系统会在"打开"对话框上显示为"Mark Russinovich\Documents",所以系统会调用GetUserNameEx 函数,尝试到活动目录的用户对象里搜索我的用户全名。我认为这个想法是对的,因为可以确认传递给GetUserNameEx的第一个参数SHGetUserDisplayName,也就是EXTENDED_NAME_FORMAT枚举中的3: NameDisplay

我在这个函数调用的返回中设置一个断点,并在延时故障结束时到达这个断点。GetUserNameEx返回ERROR_NO_SUCH_DOMAIN 的错误代码,逐步执行SHGetUserDisplayName时还发现该函数转而开始调用GetUserNameGetUserName函数并不会查找用户的全名,而是会从进程的访问令牌(定义进程所有者的内核数据结构)里获取帐户的SID,然后调用LookupAccountName函数把SID转化成用户帐户名称,本例就是"markruss"。因此"打开"对话框如下图所示。

与此相反,当我回到办公室并且连接到企业的域环境,"打开"对话框如下图所示。

现在这个问题已经解释清楚了,但是我还是非常好奇,想知道这个延时故障到底发生在什么地方,所以继续分析Secure32!CallSPM函数调用(位于堆栈顶部)的另一端,到底发生了什么问题。我知道LSASS进程负责身份验证,包括和域控制器进行联系、以及帐户名称的转化,所以我把Windbg连接到LSASS进程(注意在用"qd"命令退出时,必须先把调试程序从LSASS进程中断开,否则LSASS进程会中止,系统就会进入一个倒计时30分钟的关机状态)。我猜测Secur32.Dll兼具客户端和服务端的功能,并确认该dll文件会加载到LSASS进程中,但是我需要确定和Secur32!SecpGetUserName有关的服务端函数。所以我就用最"笨"的办法:把所有Secur32.Dll 能实现的函数全部转储出来,然后在里面搜索包含"name"的函数,如下图所示。

我在其中一些函数里设置了断点,当重现延时故障时,到达SecpGetUserName中的断点。逐步执行该函数,最终看到以下的堆栈信息,如下图所示。

文档显示,DsGetDcName 的作用是返回特定域中的域控制器的名称。而SecpTranslateName函数,则显然是要找到一个域控制器,以便发送查询帐户全名的请求。遂作进一步跟踪,结果发现LSASS会把查询结果缓存45秒钟,这就是为什么在延时故障后立即打开另一个应用程序,并调出"打开"对话框,却不会看到延时故障的重现。而当Netapi32!DsrGetDcNameEx2执行一个RPC请求时,却暂时陷于一个死胡同。

所以又猜测Netapi32兼有客户端和服务端的功能,我转储了它的symbol,并在名称中包含"dc"的函数中设置断点。让LSASS继续执行,令人惊讶的是,还是到达同一个函数,Netapi32!DsrGetDcNameEx2。于是对该函数调用作持续深入追踪,直到线程的调用深入到内核为止(Ntdll!KiFastSystemCallRet),如下图所示。

现在离最终的研究结果很近了。我还有最后一个疑问,Netlogon到底调用哪个设备驱动程序,以便发送浏览器数据包?通过查看它传递给NlBrowserDeviceIoControl的第一个参数,我知道了问题的答案,我猜想这个参数是某个文件对象的句柄。于是我就以Local Kernel Debugging mode(本地内核调试模式)打开Windbg(注意在Windows Vista上,我们必须引导系统到调试模式,才能进行操作),这样我们就可以查看真正的内核数据结构,并可以转储该句柄的信息。结果显示打开的是一个设备对象,也就是Bowser.sys (NT Lan Manager Datagram Receiver Driver) ,如下图所示。

到此为止,这次的研究分析该结束了,然而当我尝试重现延时故障时,却失败了。于是我再回头重新进行逐步追踪,结果发现LsapGetUserNameForLogonSession 会把用户全名缓存30分钟。此外,用户全名会和缓存凭据一起被缓存,所以在登录或者断开网络后的最初30分钟内,用户不会遇到这种延时故障现象。为了确认这个事实,我等待了30分钟,结果真的重现延时故障现象。

我的调查研究就算结束了。我已经确定当"打开"对话框要显示"文档"文件夹时,为了更加人性化(Breadcrumb工具栏) Windows Vista通过Browser.sys驱动发送浏览器数据包,来定位域控制器,以便查找用户的全名。对于加入域环境的用户系统,如果从域中断开,这些用户就会遇到对话框延时的情况,我知道目前暂时没有什么解决方案—至少要等到Windows Vista Service Pack 1发布了。

已发表 2007年1月21日 13:36 作者 ahpeng

评论通知

如果您想在帖子更新时接到邮件通知,请先登录。这里

订阅帖子评论使用 RSS

评论

# re: Windows Vista打开对话框延时问题的排错实例

该博客原文链接:

http://blogs.technet.com/markrussinovich/archive/2006/11/27/532465.aspx

欢迎大家挑出其中的翻译问题,我核实后立即改正,以免其中的谬误影响读者。

2007年1月21日 14:07 by ahpeng

# re: Windows Vista打开对话框延时问题的排错实例

Mark都混到院士了呀…………越來越崇拜他鳥~~

很不錯的文章,主要看點在他的邏輯判斷~~盆子翻譯得也很好,看不出哪裏錯誤哦~~

2007年2月5日 2:23 by USCONAN

说说您的看法?

(必填) 
必填 
(必填)