我们在Windows CE和Windows Mobile上运行相当复杂的软件,用于在不同设备类型上获取移动数据。在安装了Windows CE 6.0的唯一设备类型上,我们的客户端随机冻结操作系统(因此需要热启动)。客户端可能会在冻结前运行一两天,但也可能是五分钟(已经检查过句柄和内存泄漏)。在设备制造商的日志文件中,当设备冻结时会显示此类条目:
异常'数据中止'(4):Thread-Id = 070a003e(pth = 89ca07e0), Proc-Id = 0709003e(pprc = 8a01d3d0)'OurClient.exe', VM-active = 0709003e(pprc = 8a01d3d0)'OurClient.exe' PC = 41a66b28(mscoree3_5.dll + 0x00056b28) RA = 41a64ab4(mscoree3_5.dll + 0x00054ab4)SP = 0003e28c,BVA = 00000132
消息不时有所不同(我说我到目前为止计算了20个不同的消息,但在kernel.dll,k.core.dll或nk.exe中有例外)。
所以我的问题基本上是,如何在.NET框架和内核的深处调试这样的错误?例如,如何将程序计数器转换为mscorlib内的方法(返回地址相同)?我们的程序可能不适用于CE 6,或者这也可能是驱动程序问题吗?
更新:事实证明,其中一个设备驱动程序干扰了我们的键盘挂钩实现。
答案 0 :(得分:2)
老实说,我不相信你所提供的(在MS之外)有可能调试这样的本机异常,并找出当时正在调用mscoree3_5的内容。我做了这件事已经有一段时间了,但我确实记得发现这是不可能的。也许是因为我没有调试符号,或者可能是.NET运行时的其他特性,我不记得了。
但是,我成功地翻译了数据中止消息,以了解异常的含义,这有助于......
在你的例外中:
RA:回复地址 BVA:基本虚拟地址 PC:程序计数器 SP:堆栈指针 FSR:故障状态寄存器我很惊讶你的异常不包括FSR,你截断它了吗?这有助于找到错误对齐的读取等。我有一个很好的链接,有助于描述如何调试这些消息:
答案 1 :(得分:2)
正如艾伦所指出的那样,如果你没有符号作为事物破坏的来源(并且使用mscoree3_5.dll
,那么你没有),那么中止信息就没用了。即使使用源代码,如果没有编译器符号输出,也无法将其返回。
此时你只能接受有根据的猜测。异常信息看起来都有效(即RA或SP非零)的事实向我表明它不是堆栈问题,它更可能是数据问题(可能是对齐,可能是错误的读取或写入指针)。
我的猜测是来自不正确的P / Invoke。它“移动”的事实表明,由于收集或压缩,传递给P / Invoke的对象引用或地址可能无效。
想象一下以下场景。
你有一个本机API,它接受一个指向某个数据blob的指针,该数据blob表示API不仅会立即使用,而且会定期使用。也许它从中读取或写入它,但关键是API在调用时不仅需要同步数据。 API必须存储该指针,以供稍后使用。
您创建了一些通过P / Invoke调用此API的托管代码。要传递数据指针,您需要定义一个表示数据的类,创建该类的实例并将其传递。让我们说,为了这个例子,地址是0x500。
你运行你的应用程序,API被调用,一切都很顺利。 API从0x500读取并继续其业务。
直到应用程序触发GC。现在GC说“嘿,我在堆里有一些空的空间,我会移动一些东西来修复它”。它移动托管对象,使其现在为0x200,并将内存释放为0x500。在那之后的某个时刻,API会转到它的指针,仍然是0x500并进行读取。操作系统说“嘿,那个未分配的空间,你不能这样做!”它就会中止。
此方案的修复方法是使用固定GCHandle。您不必将类传递给API,而是将类固定并传入GCHandle的地址,GC在收集或压缩期间无法移动。这确保了GCHandle的地址保持不变,并且可以安全地通过原生边界。
请注意,这种情况根本不使用不安全的代码,但您可以对不安全的代码执行相同的操作。事实上,我认为对于不安全的代码,你可能会更加认识到它可能发生的地方,并且可能比没有标记为不安全的代码“更安全”。避免unsafe
关键字不会阻止不安全的代码。