.NET框架4.5或更高版本中的AccessViolationException

3

我试图运行从互联网上找到的 .NET C# 控制台应用程序 ( 文章, 代码 )。

它使用了一些 pInvoke 调用,并在某个时候触发了 Marshal.PtrToStringUni(IntPtr ptr, int len)

非常奇怪的是,我无法修复这个问题:

  • 如果将项目编译为 .NET Framework 4.0,则可以正常工作。
  • 在 .NET Framework 4.5 或更高版本下,会抛出 AccessViolationException (臭名昭著的“尝试读取或写入受保护的内存。这通常表明其他内存已损坏。”)。

堆栈跟踪告诉我:

在 System.Buffer.Memmove(Byte* dest, Byte* src, UInt64 len) 中 at System.String.CtorCharPtrStartLength(Char* ptr, Int32 startIndex, Int32 length) 在 System.Runtime.InteropServices.Marshal.PtrToStringUni(IntPtr ptr, Int32 len)

我在网上搜索了几个小时,看到了类似的问题,但没有什么能帮助我的。我绝对需要在我的项目中实现这个功能,并且感觉将其编译为 .Net Framework 4.0 而其他部分是更高版本可能会在未来引起一些麻烦。无论如何,总有一种方法可以让它工作。

有办法让这段代码工作吗?

编辑:我刚刚更新了“code”链接。作者提交了一个新版本。

编辑2:我明白没有直接在这里提供代码不会让人想要帮助。我认为将重点部分链接到文章中会更有效率。

因此,这里是一些背景和代码:

该代码旨在列出由进程锁定的所有文件。首先获取 SYSTEM_HANDLE_INFORMATION 的列表。以下是结构体:

 [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct SYSTEM_HANDLE_INFORMATION
    { 
        public int ProcessID;
        public byte ObjectTypeNumber;
        public byte Flags; // 0x01 = PROTECT_FROM_CLOSE, 0x02 = INHERIT
        public ushort Handle;
        public int Object_Pointer;
        public UInt32 GrantedAccess;
    }

它遍历集合,并将每个项目发送到GetFileDetails方法中。
以下是该方法,我只复制到try/catch语句,.NET 4.5中失败,在.NET 4.0中成功:
private static FileDetails GetFileDetails(Win32API.SYSTEM_HANDLE_INFORMATION sYSTEM_HANDLE_INFORMATION)
    {
        FileDetails fd = new FileDetails();
        fd.Name = "";
        IntPtr ipHandle = IntPtr.Zero;
        Win32API.OBJECT_BASIC_INFORMATION objBasic = new Win32API.OBJECT_BASIC_INFORMATION();
        IntPtr ipBasic = IntPtr.Zero;
        Win32API.OBJECT_TYPE_INFORMATION objObjectType = new Win32API.OBJECT_TYPE_INFORMATION();
        IntPtr ipObjectType = IntPtr.Zero;
        Win32API.OBJECT_NAME_INFORMATION objObjectName = new Win32API.OBJECT_NAME_INFORMATION();
        IntPtr ipObjectName = IntPtr.Zero;
        string strObjectTypeName = "";
        string strObjectName = "";
        int nLength = 0;
        int nReturn = 0;
        IntPtr ipTemp = IntPtr.Zero;


        if (!Win32API.DuplicateHandle(m_ipProcessHwnd, sYSTEM_HANDLE_INFORMATION.Handle,Win32API.GetCurrentProcess(), out ipHandle, 0, false, Win32API.DUPLICATE_SAME_ACCESS)) return fd;

        ipBasic = Marshal.AllocHGlobal(Marshal.SizeOf(objBasic));
        Win32API.NtQueryObject(ipHandle, (int) Win32API.ObjectInformationClass.ObjectBasicInformation, ipBasic, Marshal.SizeOf(objBasic), ref nLength);
        objBasic = (Win32API.OBJECT_BASIC_INFORMATION)Marshal.PtrToStructure(ipBasic, objBasic.GetType());
        Marshal.FreeHGlobal(ipBasic);


        ipObjectType = Marshal.AllocHGlobal(objBasic.TypeInformationLength);
        nLength = objBasic.TypeInformationLength;
        while ((uint)(nReturn = Win32API.NtQueryObject(ipHandle, (int)Win32API.ObjectInformationClass.ObjectTypeInformation, ipObjectType, nLength, ref nLength)) == Win32API.STATUS_INFO_LENGTH_MISMATCH)
        {
            Marshal.FreeHGlobal(ipObjectType);
            ipObjectType = Marshal.AllocHGlobal(nLength);
        }

        objObjectType = (Win32API.OBJECT_TYPE_INFORMATION)Marshal.PtrToStructure(ipObjectType, objObjectType.GetType());
        if (Is64Bits())
        {
            ipTemp = new IntPtr(Convert.ToInt64(objObjectType.Name.Buffer.ToString(), 10) >> 32);             
        }
        else
        {
            ipTemp = objObjectType.Name.Buffer;          
        }

        try
        {
            strObjectTypeName = Marshal.PtrToStringUni(ipTemp, objObjectType.Name.Length >> 1);
        }
        catch (AccessViolationException)
        {
            return null;
        }

如果您有兴趣,完整的代码可在上面的链接中找到。 感谢您的帮助。


如果代码太长,请将其缩小到一个 mcve ,您需要编辑并添加引发异常的代码到问题中。 - Alex K.
代码出了问题。如果没有代码,我们怎么能告诉您出了什么问题呢?[mcve] - David Heffernan
并不是没有代码。'article'链接显示了重要的部分,完整的代码也可用。我没有在这里包含代码,因为它需要显示pInvoke原型和在出现问题之前调用的不同方法,并且我认为这会变得很长。我相信如果有人遇到过这种情况,或者对这个主题有了解的人看到堆栈跟踪和描述的问题,他就能提供帮助。我希望其他人能抽出时间阅读,尽管有些人投了反对票。我将看看是否可以在小代码中复现问题。 - Eric P.
4.5 不应该破坏在 4 中正常运行的代码,所以没有简单的答案,AccessViolationException 的原因是最终生成 AccessViolationException 的代码,因此我们需要看到它。(链接到代码受到严重反对,并且当前情况下它会出现 404 错误)。 - Alex K.
好的,发现了。作者提交了另一个版本。我更新了链接(它没有解决这里描述的问题)。添加了一些代码。谢谢! - Eric P.
无论您使用.NET 4.5还是.NET 4.0,这都行不通,因为您在此行ipTemp = new IntPtr(Convert.ToInt64(objObjectType.Name.Buffer.ToString(), 10) >> 32);创建了随机指针。为什么要坚持转换Buffer的值?它也被声明为IntPtr,所以只需将其传递给Marshal.PtrToStringUni方法即可。 - Anateus
1个回答

0

访问冲突异常来自操作系统,因为存在一些错误的指针。由于.NET旨在避免裸指针,因此只有少数可能的来源:

  • 通过P/Invoke或COM互操作调用非托管代码。
  • 二进制不匹配。如果您调用的非托管代码是x32,并且您从x64调用它(反之亦然),它将无法工作。解决方法是硬编码二进制或使用具有适当二进制的辅助进程。链接的代码据说专门运行于x64。如果您没有定义二进制,则框架可以选择“任何”二进制文件,就像今天感觉一样。您是否相应地选择了项目的目标?
  • 第三方干扰。几年前我们曾经遇到过这样一个案例,某个病毒扫描器会杀死其他所有Windows Forms应用程序。尝试排除程序/暂时关闭后台保护。

我会在第一种情况下失败。是的,我已经相应地设置了目标平台。您是否遇到过一种 .Net 版本会出现此异常而另一种则不会的情况?谢谢。 - Eric P.
也许这是一个JiT问题?JiT可以剪掉死代码。而且它的检测方式非常不同。当x64还很新的时候,有一个问题是在x64中一个函数需要更长的时间才能完成。原因是什么呢?那里有大量的死代码。但是当时的x64 JiT无法完全剪掉它们。也许异常来自于某些Framework版本的JiT没有正确剪掉死代码? - Christopher
不知道。我尝试开启和关闭“在模块加载时抑制JIT优化”,但没有帮助。你有其他建议吗?谢谢。 - Eric P.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接