<2024年5月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

文章分类

导航

订阅

SDK安装程序为啥不动

今天又遇到SDK安装程序失败,停在Download状态长时间不动,倒一杯茶回来还是不动,开了一个小时的会回来也还是不动。安装程序出问题是常用的事,没有什么吸引力让我动调试器。但是杀掉再来一次,又是停在这个地方不动。是可忍孰不可忍,只好上调试器了。

sdksetup

时间关系,长话短说,是9号线程阻碍了“大军”的进程,加载了sos扩展,栈回溯如下:

0:009> !clrstack
OS Thread Id: 0x14d8 (9)
Child-SP         RetAddr          Call Site
000000001efbb560 000007ff002f4c2d SDKSetup.DownloadUtil.Dispose(Boolean)
000000001efbb590 000007fef950cb61 SDKSetup.DownloadThread.DownloadThreadProc()
000000001efbe5d0 000007fef916bc0c System.IO.__Error.WinIOError(Int32, System.String)
000000001efbe630 000007ff001b867e System.IO.FileInfo.get_Length()
000000001efbe660 000007ff001b82ac SDKSetup.DownloadUtil.DownloadFileAsyncUsingAsyncFileIO(System.String, System.String, Boolean)
000000001efbe6d0 000007ff001b7fe8 SDKSetup.DownloadUtil.DownloadFileAsync(System.Uri, System.Uri)
000000001efbe730 000007ff002f4a1d SDKSetup.DownloadUtil.DownloadFile(System.Uri, System.Uri)
000000001efbe770 000007fef8752bbb SDKSetup.DownloadThread.DownloadThreadProc()
000000001efbe830 000007fef87ea8dd System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
000000001efbe880 000007fef960d4a2 System.Threading.ThreadHelper.ThreadStart()

看来是负责下载文件的线程出了个异常,是调用取文件长度的方法get_Length时触发的异常,估计是文件根本不存在就去取长度,看一下当前线程的Last Error,果然如此:

0:009> !gle
LastErrorValue: (Win32) 0x3 (3) - The system cannot find the path specified.
LastStatusValue: (NTSTATUS) 0xc000003a - {Path Not Found}  The path %hs does not exist.

观察一下SDKSetup.DownloadThread对象的属性,可以看到当前下载任务的目标是:

C:\Users\...\AppData\Local\Temp\SDKSetup\WinSDK\WinSDKInterop_ia64\cab1.cab

下载的来源是:

\\<网络目录>\Win7SDK\GRMSDKX_EN_DVD_7_0_64bit\Setup\WinSDKInterop_ia64\cab1.cab

因为我是从一个网络共享目录安装,其实就是从网络目录下载到本机的临时目录。这么简单点事怎么出异常了呢?软件的根本在代码,看代码吧!

        IL_0059:  ldarg.0
        IL_005a:  ldfld      class [System]System.Uri SDKSetup.DownloadThread::m_SourceFile
        IL_005f:  ldarg.0
        IL_0060:  ldfld      class [System]System.Uri SDKSetup.DownloadThread::m_TargetFile
        IL_0065:  callvirt   instance void SDKSetup.DownloadUtil::DownloadFile(class [System]System.Uri,
                                                                               class [System]System.Uri)
        IL_006a:  ldloc.0
        IL_006b:  callvirt   instance float64 SDKSetup.DownloadUtil::get_DownloadSpeedBytesPerMilliSec()
        IL_0070:  pop
        IL_0071:  ldarg.0
        IL_0072:  ldfld      class [System]System.Uri SDKSetup.DownloadThread::m_TargetFile
        IL_0077:  callvirt   instance string [System]System.Uri::get_OriginalString()
        IL_007c:  newobj     instance void [mscorlib]System.IO.FileInfo::.ctor(string)
        IL_0081:  stloc.1
        IL_0082:  ldarg.0
        IL_0083:  ldloc.1
        IL_0084:  callvirt   instance int64 [mscorlib]System.IO.FileInfo::get_Length()
        IL_0089:  stfld      int64 SDKSetup.DownloadThread::m_FileSize

看惯了x86汇编后,看这个字节码(MSIL)多少会有些不习惯,但是大意还是很容易理解的。

先是加载参数,要下载的源和目标,然后调用SDKSetup.DownloadUtil实例的DownloadFile方法。看一下后者的代码,它又调用SDKSetup.DownloadUtil::DownloadFileAsync,开始异步下载并等待如下事件:

 SDKSetup.DownloadUtil::m_DownloadFileSynchronousCompletedEvent

0:009> !handle 7a8
Handle 7a8
  Type          Event

从上面的栈来看,还没有执行到等待的地方,因为DownloadFileAsync还没有返回,它继续调用了DownloadFileAsyncUsingAsyncFileIO,观察DownloadFileAsyncUsingAsyncFileIO的代码,它真的调用了get_Length方法:

        IL_0071:  ldarg.0
        IL_0072:  ldfld      class [System]System.Uri SDKSetup.DownloadThread::m_TargetFile
        IL_0077:  callvirt   instance string [System]System.Uri::get_OriginalString()
        IL_007c:  newobj     instance void [mscorlib]System.IO.FileInfo::.ctor(string)
        IL_0081:  stloc.1
        IL_0082:  ldarg.0
        IL_0083:  ldloc.1
        IL_0084:  callvirt   instance int64 [mscorlib]System.IO.FileInfo::get_Length()
        IL_0089:  stfld      int64 SDKSetup.DownloadThread::m_FileSize
        IL_008e:  ldnull

从上面的调用关系可以看到,下载线程检测出了安装源是文件共享,因此启用了异步文件IO下载方法,如果是网络那么会DownloadFileAsyncUsingWebClient。在文件下载方法DownloadFileAsyncUsingAsyncFileIO中,先是调用get_Length取要下载文件的长度,这很对啊,没有问题啊!

Dump一下System.IO.FileInfo对象的属性,当时的文件名的确也是对的:

\\<网络目录>\Win7SDK\GRMSDKX_EN_DVD_7_0_64bit\Setup\WinSDKInterop_ia64\cab1.cab

但是再多看一眼,还是有点不顺眼,我选择的是64为安装包,我的机器当然是x64,不是安腾64,但是这里的确是准备下载安腾64的文件包,不过多安装一点也是常用的事,但是查一下远程的安装目录,哦,真的没有这个文件夹。

哦,原来要下载的这个文件真的不存在!看来说安装程序不好,还有点冤枉它么!

但是话又说回来,文件不存在也不该挂死在这啊,弹出个错误对话框也好啊!为什么不弹出来呢?那就要看get_Length触发异常后,如何处理这个炸弹了!

反向追索,寻找异常处理器,首先DownloadFileAsyncUsingAsyncFileIO没有异常捕捉器,它的父函数DownloadFileAsync也没有,DownloadFileAsync的父函数DownloadFile也没有,直到DownloadThreadProc才有异常捕捉函数,而且有三层,起作用的应该是最内层的,即如下这个:

      finally
      {
        IL_00fe:  ldloc.0
        IL_00ff:  brfalse.s  IL_0109
        IL_0101:  ldloc.0
        IL_0102:  callvirt   instance void SDKSetup.DownloadUtil::Dispose()
        IL_0107:  ldnull
        IL_0108:  stloc.0
        IL_0109:  endfinally
      }  // end handler
      IL_010a:  leave.s    IL_0168、

没有catch,只有finally,上层有catch,因此逻辑是要先执行这个finally,这个finally中真的调用了SDKSetup.DownloadUtil::Dispose,析构SDKSetup.DownloadUtil对象,这也正是写这个finally的目的。在上面的栈回溯中,也是在执行这个Dispose方法。

看来Dispose出问题了,再看一下它的代码吧,整个方法并不长,因此完全复制过来:

.method family hidebysig instance void  Dispose(bool disposing) cil managed
{
  // Code size       57 (0x39)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  brtrue.s   IL_0004
  IL_0003:  ret
  IL_0004:  ldarg.0
  IL_0005:  call       instance void SDKSetup.DownloadUtil::CancelAsync()
  IL_000a:  br.s       IL_0016
  IL_000c:  ldc.i4     0x1f4
  IL_0011:  call       void [mscorlib]System.Threading.Thread::Sleep(int32)
  IL_0016:  ldarg.0
  IL_0017:  call       instance bool SDKSetup.DownloadUtil::get_IsBusy()
  IL_001c:  brtrue.s   IL_000c
  IL_001e:  ldarg.0
  IL_001f:  call       instance bool SDKSetup.DownloadUtil::get_CancelPending()
  IL_0024:  brtrue.s   IL_000c
  IL_0026:  ldarg.0
  IL_0027:  call       instance void SDKSetup.DownloadUtil::DisposeWebClient()
  IL_002c:  ldarg.0
  IL_002d:  call       instance void SDKSetup.DownloadUtil::DisposeAsyncFileIO()
  IL_0032:  ldarg.0
  IL_0033:  call       instance void SDKSetup.DownloadUtil::DisposeDownloadFileSynchronousCompletedEvent()
  IL_0038:  ret
} // end of method DownloadUtil::Dispose

首先,真的调用了Sleep,其次真的有一个循环,当SDKSetup.DownloadUtil::get_IsBusy返回真时,就一直循环,反复调用Sleep...、

观察DownloadUtil的m_isBusy成员,真的为1:

000007fef889afa0  40003f9       70         System.Int64  1 instance 1 m_isBusy

粗略想来,是在忙啊,不是在复制文件么,谁想到因为取文件长度而触发异常飞出来了呢?飞出来是没来得及改写Busy标志啊,没想到这个Busy标志不清,整个对象都析构不了,连整个安装程序都停止不动了!

 

posted on 2011年8月15日 21:48 由 Raymond

# re: SDK安装程序为啥不动 @ 2011年10月18日 12:10

用ED命令把Busy改为0,是否安装就能顺利进行?

starchenzhi

Powered by Community Server Powered by CnForums.Net