当你对ASP.NET 4.0网站执行代码覆盖率测试时,可能会遇到下面这个异常:
System.Security.VerificationException: Operation could destabilize the runtime.
一般来说,VerificiationException都是关于代码访问安全(CAS)的问题,.NET在执行托管代码之前,首先会验证这个托管程序的强签名,通过确定其来源来判定该托管程序应该拥有的权限。比如说,如果托管程序是微软公司的签名,我们当然是无比的信任它—除非你不用Windows操作系统。机器管理员也可以依据托管程序的来源来决定该托管程序应该拥有的权限,例如读磁盘文件、操作注册表这些操作都是可以配置的权限。比方说,管理员可以在机器上设置,由360以及QQ发行的托管程序,只能访问显卡(绘制界面)以及读写网络,而不能执行访问磁盘文件等操作。
执行代码安全的前提是,整个程序完全是由托管代码写成的,如果托管代码里混合非托管代码,无论是通过托管C++写成的,还是里面执行了平台调用(P/Invoke)。就破坏规矩了,因为非托管代码可以使用指针操作,以及调用其它非托管代码的方式,甚至—启动一个进程,然后将原来的托管程序杀掉。因此,进行代码访问安全的前提条件是,整个应用程序都是使用托管代码写成的,为了保证这个前提条件,CLR在执行托管程序之前,首先会验证程序是否满足—整个程序都是用托管代码编写这一条件。如果不满足这个条件,那么CLR会扔出VerificationException,中断托管程序的执行,在.NET SDK里,你可以通过PEVerify.exe这个程序验证一个程序是否可以通过这个验证。
当然,你可能会说,很多本机运行的托管程序,即使调用了非托管代码,例如执行了一个平台调用,还是可以照常执行啊。那是因为,当你的程序使用了/unsafe编译选项时,C#编译器会自动在托管程序添加上SuppressUnmanagedCodeSecurityAttribute这个属性,这个属性告诉CLR。如果程序是在本机执行,那么就跳过验证是否调用了非托管代码这一环节。有兴趣的读者可以把调用非托管代码的托管程序放在一个网络路径上,从那里执行,应该就可以看到SecurityException了。
回到ASP.NET网站的情形,当你将ASP.NET网站的装配件(Assembly)使用 vsinstr.exe创建一个可以收集代码覆盖率的装配件(Assembly)时,vsinstr.exe实际上在装配件(Assembly)的IL代码里,添加了很多收集代码覆盖率的代码。这个过程可以参考我以前的文章:。这些代码里,有一个很重要的步骤,就是联系本机运行的VSPerfMon.exe,VSPerfMon.exe是用来收集整个机器上代码覆盖率的,不知道是什么愿意,联系本机运行的VSPerfMon.exe过程是通过非托管的API实现的,这就意味着vsinstr.exe生成的 装配件(Assembly)就必须调用到非托管代码,因此也就通过CLR的验证过程。
在2.0的时候,你可能还不会碰到这个问题,但是在4.0里面,你就很有可能碰到这个问题—特别是在网站运行在IIS下的话,经常发生。
既然道理已经明白了,修复起来也很容易:
- 将网站,以及所有需要做代码覆盖率的装配件(Assembly)—也就是被vsinstr.exe处理过的装配件(Assembly),在它们的源代码(既然你要收集代码覆盖率,那肯定是由源代码的)里的AssemblyInfo.cs里,加上下面这一行
[assembly: SecurityRules(SecurityRuleSet.Level1, SkipVerificationInFullTrust=true)]
- 然后在网站的web.config里,做如下修改(这个选项也可以通过IISAdmin.exe修改):
<system.web>
<trust level="Medium" />
…
</system.web>
说明,SecurityRules这个属性,告诉CLR,这个装配件(Assembly)有自定义的安全机制,SecurityRuleSet.Level1说明采用.NET 2.0下面的安全验证策略,SkipVerificationInFullTrust告诉CLR,只有网站运行在FullTrust模式下,才可以跳过安全验证。最后第二步就是将网站的运行模式改在FullTrust模式下。