2015-06-30 37 views
0

我有一些代码有时(但不总是)抛出使用String costructor的a particular forma Microsoft kb article描述的异常。什么情况会引发.net 2.0的String构造函数抛出异常?

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

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

KB文章表明,这“可”导致引发异常,但我单元测试和大部分时间在野外的,它从不出现。

我想知道什么会引发异常,这样我可以在我的单元测试复制它并验证它在我们的代码库永久固定。

回答

4

此错误出现在SRC/VM/comstring.cpp,COMString :: StringInitCharHelper()函数。这是恶人:

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

或者换句话说,它会偷看长度+ 1,并采取陷入低谷时IsBadReadPtr()返回false。是的,你必须是不幸的,你的charArray3将不得不精确分配在内存页的末尾,并且下一页必须是不可访问的。这并不经常发生。

不那么肯定有在试图摄制的bug的任何一点上,它是太随意。只需让阵列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字节)。

在中间有一些测试,检查,如果页面可以被读取/写入。

一般而言,这是难以检查该条件,因为它很难有一个内存块,这正是在所分配的可读存储器空间的末尾。

0

如果任何人的兴趣,这是如何复制其在C++/CLI(完全基于萨那托斯的回答):

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); 
相关问题