[原]调试PInvoke导致的内存破坏
缘起
最近项目中遇到一个诡异的问题,程序在升级到.net4.6.1
后,执行某个功能时会崩溃,提示访问只读内存区。大概规律如下:
debug
版不崩溃,release
版稳定崩溃。只有
x64
位的程序崩溃,32位
及anycpu
编译出来的程序运行不会崩溃。出问题的代码范围很小(按钮点击事件代码不多)。
根据以上信息,各位小伙伴有什么思路吗?
排查
由于release
版可以稳定重现,而且范围不大,故通过二分法(每次注释掉一半代码,看看是否崩溃,如果崩溃,接着注释掉一半代码,如果不崩溃说明崩溃跟注释掉的那段代码有关...)很快定位到了导致问题的代码。
最后发现并不是由于升级.net
版本导致的,而是程序本身的问题:
代码中通过P/Invoke
调用了原生 API GlobalMemoryStatus()
。在定义MemoryStatus
结构体的时候强制按4
字节定义了每一个字段。而在x64
下MemoryStatus
结构体中的成员有些不是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 target
是Any 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/
写留言
[原]调试PInvoke导致的内存破坏相关推荐
- [原]调试TerminateThread导致的死锁
这是继上一篇 [原]调试DLL卸载时的死锁 后的又一篇使用windbg调试死锁的文章.希望能对大家有所帮助. 前言 之前项目里的一个升级程序偶尔会死锁,查看dump后发现是死在了ShellExecut ...
- [原]调试实战——程序CPU占用率飙升,你知道如何快速定位吗?
前言 如果我们自己的程序的CPU Usage(CPU占用率)飙升,并且居高不下,很有可能陷入了死循环.你知道怎么快速定位并解决吗?今天跟大家分享几种定位方法,希望对你有所帮助. 如何判断是否有死循环? ...
- linux中非法内存,Linux下数组非法访问导致内存破坏 —— 引发segmentation fault的原因...
2012-02-05 wcdj 1, 调试时必需的栈知识 2, 数组非法访问导致内存破坏 调试时必需的栈知识 栈(stack)是程序存放数据的内存区域之一,其特征是LIFO(Last In First ...
- C++内存写越界导致堆内存被破坏致使new失败的问题定位总结
问题描述: C++内存写越界导致堆内存被破坏致使new失败的问题定位总结. 报错信息:test_CRGraph: malloc.c:2379: sysmalloc: Assertion `(old_t ...
- VC使用CRT调试功能来检测内存泄漏
信息来源:csdn C/C++ 编程语言的最强大功能之一便是其动态分配和释放内存,但是中国有句古话:"最大的长处也可能成为最大的弱点",那么 C/C++ 应用程序正好印证 ...
- VC++ 6.0 中如何使用 CRT 调试功能来检测内存泄漏[转]
/C++ 编程语言的最强大功能之一便是其动态分配和释放内存,但是中国有句古话:"最大的长处也可能成为最大的弱点",那么 C/C++ 应用程序正好印证了这句话.在 C/C++ 应用程 ...
- Linux内核的l2tp实现,Linux Kernel gdth实现内核内存破坏漏洞
Linux Kernel gdth实现内核内存破坏漏洞 发布日期:2010-11-04 更新日期:2010-11-16 受影响系统: Linux kernel 2.6.x 描述: ---------- ...
- SQL Server 内存泄露(memory leak)——游标导致的内存问题
原文:SQL Server 内存泄露(memory leak)--游标导致的内存问题 转自:http://blogs.msdn.com/b/apgcdsd/archive/2011/07/01/sql ...
- 使用CRT调试功能来检测内存泄漏
C/C++ 编程语言的最强大功能之一便是其动态分配和释放内存,但是中国有句古话:"最大的长处也可能成为最大的弱点",那么 C/C++ 应用程序正好印证了这句话.在 C/C++ 应用 ...
最新文章
- AI+IOT战火升级:未来人工智能抢人大战
- 深入理解JAVA虚拟机 虚拟机性能监控和故障处理工具
- 在CentOS7上部署.net core 控制台应用程序部署为后台服务
- Win11手机应用大改!全新界面来袭
- 从生物神经网络到人工神经网络
- NodeJS开源系统Mili简介
- (40)System Verilog线程停止(disable fork)
- JDBC09 CLOB文本大对象
- 自适应巡航跟车距离怎么调_2020款奔驰GLS450改装原厂配件 ACC自适应巡航系统 香氛香薰负离子...
- 圈圈教你玩usb第一版件软件使用说明
- jmeter安装包双击没反应_Jmeter下载安装及使用
- MATLAB--求一个矩阵中所有元素的平均值
- 计算机无法写入U盘,电脑无法拷贝U盘文件怎么办|解除U盘写保护设置的方法
- 3D NAND“大连造”
- 小武与论文的bug -CUDA -CUDANN -YOLOV3
- coreseek 词库 导入搜狗词库
- 【Windows】关于Windows Powershell找不到打不开修复方法
- 北航计算机考博经验,最新的北航考博经验
- 人脸识别论文:Partial FC: Training 10 Million Identities on a Single Machine
- creo草绘工程图线条粗细设置