2015-06-30 4 views
0

У меня есть код, который иногда (но не всегда) генерирует исключение, описанное в a Microsoft kb article при использовании a particular form бизнес-специалиста String.Какие ситуации спровоцировали конструктор .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); 

кб статья предполагает, что это «может» вызвать исключение быть выброшен, но мой модульные тесты и больше всего времени в дикой природе, он никогда не появляется.

Я хотел бы знать, что вызовет исключение, чтобы я мог воспроизвести его в своих модульных тестах и ​​убедиться, что он постоянно установлен в нашей кодовой базе.

ответ

4

Эта ошибка появляется в функции src/vm/comstring.cpp, COMString :: StringInitCharHelper(). Это злодея:

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

Или, другими словами, он будет заглядывать в длину + 1 и взять пикирует, когда IsBadReadPtr() возвращает ложь. Да, вам не повезло, ваш 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. Обратите внимание, что этот код не освобождает память, которую он выделяет, но это не большая проблема, потому что когда процесс заканчивается, память восстанавливается ОС.

Что делает эта программа? Он выделяет 8192 байта (2 страницы), используя VirtualAlloc. Используя 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);