博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[原]调试PInvoke导致的内存破坏
阅读量:4031 次
发布时间:2019-05-24

本文共 3757 字,大约阅读时间需要 12 分钟。

缘起

最近项目中遇到一个诡异的问题,程序在升级到.net4.6.1后,执行某个功能时会崩溃,提示访问只读内存区。大概规律如下:

  1. debug版不崩溃,release版稳定崩溃。

  2. 只有x64位的程序崩溃,32位anycpu编译出来的程序运行不会崩溃。

  3. 出问题的代码范围很小(按钮点击事件代码不多)。

根据以上信息,各位小伙伴有什么思路吗?

排查

由于release版可以稳定重现,而且范围不大,故通过二分法(每次注释掉一半代码,看看是否崩溃,如果崩溃,接着注释掉一半代码,如果不崩溃说明崩溃跟注释掉的那段代码有关...)很快定位到了导致问题的代码。

最后发现并不是由于升级.net版本导致的,而是程序本身的问题:

代码中通过P/Invoke调用了原生 API GlobalMemoryStatus()。在定义MemoryStatus结构体的时候强制按4字节定义了每一个字段。而在x64MemoryStatus结构体中的成员有些不是4字节大小,而是8字节大小!这样,传递给GlobalMemoryStatus()MemoryStatus参数(32字节)比GlobalMemoryStatus()预期的(56字节)小,导致GlobalMemoryStatus写了不该写的内存!????????????

重现

我把有问题的代码独立出来了,完整的测试代码如下(请编译x64版本):

using System; using System.Runtime.InteropServices; namespace ConsoleApplication1 {
class Program {
[StructLayout(LayoutKind.Sequential)] public struct MemoryStatus {
[MarshalAs(UnmanagedType.U4)] public uint dwLength; [MarshalAs(UnmanagedType.U4)] public uint dwMemoryLoad; [MarshalAs(UnmanagedType.U4)] public uint dwTotalPhys; [MarshalAs(UnmanagedType.U4)] public uint dwAvailPhys; [MarshalAs(UnmanagedType.U4)] public uint dwTotalPageFile; [MarshalAs(UnmanagedType.U4)] public uint dwAvailPageFile; [MarshalAs(UnmanagedType.U4)] public uint dwTotalVirtual; [MarshalAs(UnmanagedType.U4)] public uint dwAvailVirtual; } [DllImport("kernel32.dll")] public static extern void GlobalMemoryStatus(ref MemoryStatus memoryStatus); class CMyClass {
public int n1 = 0; } struct CMyStruct {
public CMyClass data; } static void Main(string[] args) {
CMyStruct myObj = new CMyStruct(); myObj.data = new CMyClass(); MemoryStatus memoryStatus = new MemoryStatus(); // this line will corrupt the stack if we run in x64. // because memoryStatus is defined on the stack. GlobalMemoryStatus(ref memoryStatus); // myObj.data is corrupted System.Console.WriteLine("{0}", myObj.data); } } }

修复

只需要定义MemoryStatus的时候,注意字段的大小即可。正确的MemoryStatus定义如下:

public struct MemoryStatus {
[MarshalAs(UnmanagedType.U4)] public uint dwLength; [MarshalAs(UnmanagedType.U4)] public uint dwMemoryLoad; // 以下字段 4 bytes on 32-bit Windows, 8 bytes on 64-bit Windows. [MarshalAs(UnmanagedType.SysUInt)] public IntPtr dwTotalPhys; [MarshalAs(UnmanagedType.SysUInt)] public IntPtr dwAvailPhys; [MarshalAs(UnmanagedType.SysUInt)] public IntPtr dwTotalPageFile; [MarshalAs(UnmanagedType.SysUInt)] public IntPtr dwAvailPageFile; [MarshalAs(UnmanagedType.SysUInt)] public IntPtr dwTotalVirtual; [MarshalAs(UnmanagedType.SysUInt)] public IntPtr dwAvailVirtual; }

思考

  • 为什么debug版不崩溃?而release版会崩溃?

    我在测试机器上调查的原因是debug版本运行的时候,关键内存恰巧没被破坏(太“幸运”或者太不幸了),而在release版本中暴露了问题。可能在其它机器上debug版本也会崩溃或者发生其它诡异的问题。

    说明:测试代码与项目中的实际代码不一样,有可能现象不一样,但问题的本质是一样的。

  • 为什么运行Any CPU编译出来的程序不崩溃?

    Platform targetAny CPU的时候,在工程属性,Build下的Prefer 32-bit的选项默认是勾选的,编译的程序会作为 32 位进程运行,所以不会崩溃。如果取消勾选,则编译出来的程序会作为 64 位应用程序运行,会崩溃。

build settings

关于Platform target的作用,具体参考《CLR via C#》,下图是从《CLR via C#》中文版第 4 版上截取的。

/platform option 截自《CLR via C#》

总结

.net程序中,令人头疼的内存破坏问题很难出现了,这极大的提高了程序的稳定性。如果出现堆破坏,很有可能跟P/Invoke或者unsafe代码相关,可以重点排查相关代码。

启用托管调试助手(Managed Debugging Assistants, 下文简称MDAs) 有时候会对调试问题有极大的帮助,虽然我这次调试没有借助MDAs,但我第一个想到的就是MDAs

关于MDAs的介绍请参考参考资料第一条

参考资料

  • Managed Debugging Assistants[1]

  • GlobalMemoryStatus[2]

  • 《CLR via C#》[3]

References

[1]  Managed Debugging Assistants:
https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/diagnosing-errors-with-managed-debugging-assistants
[2]  GlobalMemoryStatus:
https://docs.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-globalmemorystatus?redirectedfrom=MSDN
[3] 《CLR via C#》:

https://book.douban.com/subject/4924165/

写留言

转载地址:http://qigbi.baihongyu.com/

你可能感兴趣的文章
WAV文件解析
查看>>
DAC输出音乐2-解决pu pu 声
查看>>
WPF中PATH使用AI导出SVG的方法
查看>>
WPF UI&控件免费开源库
查看>>
QT打开项目提示no valid settings file could be found
查看>>
Win10+VS+ESP32环境搭建
查看>>
Ubuntu+win10远程桌面
查看>>
flutter-实现圆角带边框的view(android无效)
查看>>
flutter-实现一个下拉刷新上拉加载的列表
查看>>
android 代码实现圆角
查看>>
flutter-解析json
查看>>
android中shader的使用
查看>>
java LinkedList与ArrayList迭代器遍历和for遍历对比
查看>>
drat中构造方法
查看>>
JavaScript的一些基础-数据类型
查看>>
JavaScript基础知识(2)
查看>>
转载一个webview开车指南以及实际项目中的使用
查看>>
android中对于非属性动画的整理
查看>>
一个简单的TabLayout的使用
查看>>
ReactNative使用Redux例子
查看>>