Windows Phone App的dump文件实例分析-System.ExecutionEngineException
这篇文章我们接着分析上一篇的crash,在上一篇的文章中我们已经学会了如何在高度优化的release版本中分辨出正确的上下文地址,接下来我们继续查找程序奔溃的原因。首先打开dump文件,按照上一篇的步骤恢复到”第一案发现场“。
定位异常上下文
1. 通过了上一篇文章我们找到了真正的异常上下文,先看来看一下这个调用堆栈。
2. 让我们来看看FontFileReference::ReleaseFragment到底干了些什么,使用命令”uf 模块名字!函数名字“来反编译这个函数。
3. 再次查看这个函数对应的栈帧里面的内容来辅助我们理解上面的汇编代码,”.frame + frame index”
4. 使用命令”dt 模块名字!类名字”来查看FontFileReference的内存布局
分析和推测
1. 从调用堆栈可以看到,下面的语句触发了异常,这里的指令正在执行加载寄存器的操作,load r3寄存器里面存的地址,但是r3=0,我们不禁要问,r3为什么会是0,而这个r3里面又应该是什么呢?
2. 重点分析寄存器,r3。这里给大家分享个看汇编指令的心得,其实我们并不用每次都一行一行的来分析,简单的方法就是找到关键的指令!在这个case里面,我把下面的指令列为关键指令:
3. 为什么要把这几行列为关键指令呢?这是因为这里出现了”r0, #0x10”和blx。这里的”r0, #0x10”是”r0 + 0x10“的意思,”blx” 是调用子函数的意思。再结合FontFileReference这个类的结构,我们可以合理的猜测到r0里面的地址应该指向了this指针,那么r0 + 0x10就是FontFileReference这个类里的成员变量”stream_”。
4. 我们已经知道了r0里面的是this指针,r3里面的是指向类型为ComPtr<IDWriteFontFileStream>的COM对象stream_,那么接下来r3+0x10又是什么呢,关于IDWriteFontFileStream的定义可以在dwrite.h中查到也可以使用”dt /v”显示出来。
我们知道从IUnknown继承下来的COM接口前三个函数一定是QueryInterfacy,Release和AddRef,那么r3+0x10就是ReleaseFileFragment这个函数了。布局大致如下:
5. 在这里需要解释一下为什么我们要进行“猜测”而不是直接调用”dt this”命令来直接查看当前的这个FontFileReference对象呢?因为这个dump是release版本产生的,很多的信息都已经无效了:
6. 经过上面的分析我们就知道了这段汇编代码的主要工作就是调用” stream_->ReleaseFileFragement”。对比”!analyze –v” 命令给出的信息可以验证我们的猜测并得出结论,正是 stream_这个变量为空导致了异常。再来看看这个线程堆里面还有什么有用的信息:
分析和定位我们的源代码
栈里面残留的信息也把问题指向了Font,FontFamily相关的内容,从native的callstack中我们没法定位到我们的代码,这里使用”!CLRStack”命令来看看我们CLR的代码栈里面的调用信息:
红线的代码就是我们自己的代码了,好了打开代码查看一下:
从这段代码看我们在动态创建TextBlock的时候确实没有指定FontFamily这个属性。程序员在开发代码和测试的时候这个异常并没有出现,从我们下载下来的excel文件来看,这个异常只出现在WP8.0平台,30天内导致的闪退累计次数是4000次。这个次数相比我们APP的活跃度来说非常小。所以我们可以推断,这个bug应该是系统的问题,很可能是stream_这个COM对象的ref count增减操作在没有指定FontFamily的一些特殊情况下发生的,它导致了stream_被提前释放掉了。既然是系统的问题,那我们能做什么呢?首先,我们可以指定一个FontFamily给控件TextBlock;其次,在CreateWideTileBackgroundImage这个函数里面的Render调用上添加try catch来捕获这个“System.ExecutionEngineException“。
小结
虽然有些时候问题是由系统引起的,但是通过分析我们还是可以采取一定的措施在我们的代码里面来处理的。