【问题标题】:What situations provoke a .net 2.0 String constructor to throw an exception?什么情况会引发 .net 2.0 String 构造函数抛出异常?
【发布时间】:2015-06-30 10:35:15
【问题描述】:

我有一些代码在使用String costructor 的a particular form 时有时(但并非总是)抛出a Microsoft kb article 中描述的异常。

本质上,我的代码看起来像这样(除了输入字符串数组的长度因输入而异):

int arraySize = 8;
char* charArray3 = new char[arraySize];
memset(charArray3, 0x61, arraySize);
char * pstr3 = &charArray3[0];
String^ szAsciiUpper = gcnew String(pstr3, 0, arraySize);

kb 文章建议这“可能”会导致抛出异常,但我的单元测试和大多数在野外,它从未出现过。

我想知道什么会引发异常,以便我可以在我的单元测试中复制它并验证它是否已在我们的代码库中永久修复。

【问题讨论】:

    标签: c++-cli .net-2.0


    【解决方案1】:

    这个错误出现在 src/vm/comstring.cpp, COMString::StringInitCharHelper() 函数中。这是恶人:

       if( IsBadReadPtr(pszSource, (UINT_PTR)length + 1)) {
           COMPlusThrowArgumentOutOfRange(L"ptr", L"ArgumentOutOfRange_PartialWCHAR");
       }
    

    或者换句话说,当 IsBadReadPtr() 返回 false 时,它​​会在 length+1 处窥视并急剧下降。是的,您一定很不走运,您的 charArray3 必须准确地分配在内存页面的末尾,并且下一页必须不可访问。这种情况并不经常发生。

    不太确定尝试重现该错误是否有任何意义,它太随机了。只需使您的数组 1 元素更大以避免它。或者迁移到 .NET 4,他们确实只是通过完全删除检查来修复它。

    【讨论】:

      【解决方案2】:

      他们在 4.0 中修复了它,在 2.0 中仍然损坏:

      using System;
      using System.Runtime.InteropServices;
      
      namespace ConsoleApplication13
      {
          class Program
          {
              [DllImport("kernel32.dll", SetLastError = true)]
              static extern IntPtr VirtualAlloc(IntPtr lpAddress, IntPtr dwSize, uint flAllocationType, uint flProtect);
      
              [DllImport("kernel32.dll", SetLastError = true)]
              static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);
      
              // For .NET 4.0
              //[System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions]
              static unsafe void Main(string[] args)
              {
                  IntPtr ptr = VirtualAlloc(
                      IntPtr.Zero, 
                      (IntPtr)(4096 * 2), 
                      0x1000 /* MEM_COMMIT */ | 0x2000 /* MEM_RESERVE */, 
                      0x04 /* PAGE_READWRITE */);
      
                  IntPtr page1 = ptr;
                  IntPtr page2 = (IntPtr)((long)ptr + 4096);
      
                  uint oldAccess;
                  bool res = VirtualProtect(page2, 4096, 0x01 /* PAGE_NOACCESS */, out oldAccess);
      
                  try
                  {
                      Marshal.WriteByte(page1, 1);
                      Console.WriteLine("OK");
                  }
                  catch (AccessViolationException)
                  {
                      Console.WriteLine("KO");
                  }
      
                  try
                  {
                      Marshal.WriteByte(page2, 1);
                      Console.WriteLine("KO");
                  }
                  catch (AccessViolationException)
                  {
                      Console.WriteLine("OK");
                  }
      
                  try
                  {
                      byte b1 = Marshal.ReadByte(page1);
                      Console.WriteLine("OK");
                  }
                  catch (AccessViolationException)
                  {
                      Console.WriteLine("KO");
                  }
      
                  try
                  {
                      byte b2 = Marshal.ReadByte(page2);
                      Console.WriteLine("KO");
                  }
                  catch (AccessViolationException)
                  {
                      Console.WriteLine("OK");
                  }
      
                  for (int i = 0; i < 4096; i++)
                  {
                      Marshal.WriteByte(page1, i, (byte)'A');
                  }
      
                  sbyte* ptr2 = (sbyte*)page1;
      
                  try
                  {
                      var st1 = new string(ptr2, 0, 4096);
                      Console.WriteLine("OK");
                  }
                  catch (ArgumentOutOfRangeException)
                  {
                      Console.WriteLine("KO");
                  }
              }
          }
      }
      

      您必须取消注释 .NET 4.0 中的一行。请注意,这段代码不会释放它分配的内存,但这不是什么大问题,因为当进程结束时,内存会被操作系统回收。

      这个程序是做什么的?它使用VirtualAlloc 分配8192 字节(2 页)。通过使用VirtualAlloc,这两个页面是页面对齐的。它禁止访问第二页(使用VirtualProtect)。然后它用'A' 填充第一页。然后它尝试从第一页创建一个string。在 .NET 2.0 上,string 构造函数尝试读取第二页的第一个字节(即使您告诉它该字符串只有 4096 个字节)。

      中间有一些测试检查页面是否可以读/写。

      通常很难检查这种情况,因为很难有一块内存恰好位于分配的可读内存空间的末尾。

      【讨论】:

        【解决方案3】:

        如果有人感兴趣,这是如何在 C++/CLI 中复制它(完全基于 xanatos 的回答):

        LPVOID ptr = VirtualAlloc(0, 4096 * 2, 0x1000, 0x04); // ReadWrite
        
        LPVOID page1 = ptr;
        LPVOID page2 = (LPVOID)((long)ptr + 4096);
        
        DWORD oldAccess;
        bool res = VirtualProtect(page2, 4096, 0x01, &oldAccess);
        
        char* ptr2 = (char*)page1;
        
        String^ st1 = gcnew String(ptr2, 0, 4096); // <-- This will cause the exception.
        
        Console::WriteLine(st1);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-04-24
          • 2016-08-31
          • 1970-01-01
          • 2010-09-09
          • 2010-10-29
          • 2015-08-29
          • 1970-01-01
          • 2011-11-04
          相关资源
          最近更新 更多