读取和写入内存地址以验证不安全代码中的内存分配

时间:2016-04-17 13:24:38

标签: c# memory allocation unsafe verify

我出现了这段c#代码,它应该验证在早期阶段完成的内存分配。

for (int i = 0; i < Size; i++)
{
    var b = *(BaseAddress + i); // type of BaseAddress is byte*
    *(BaseAddress + i) = b;
}

在我看来,所有代码都是将原始内存中的一个字节复制到临时变量b,然后将其写回原始内存。

从写入内存位置读取是否使内存有效且可以安全访问? 是否存在实际上会在内存中发现损坏的情况?

1 个答案:

答案 0 :(得分:0)

它实际上会验证从BaseAddressBaseAddress+(sizeof(ElementType))*(Size-1)的内存可以读取,读取。

例如,假设BaseAddress的内存具有protection attributes PAGE_EXECUTE_READ

在这种情况下,读取失败,而写入会导致访问冲突(据我所知)。

但正如已经指出的那样,我们只能猜到为什么有人需要它。

理想情况下,应该不需要它,假设应用程序的托管和非托管部分正确实现它们之间的契约。而且,显然,假设合同是合理的(比如模块A承诺不分配缓冲内存,无论是不可读还是不可写)。

优化为无操作或不操作?

正如@AndrewMedico所指出的

  

假设编译器并不只是优化它(将...   循环在逻辑上是一种无操作的方式

我怀疑所有C \ C ++编译器都会优化类似代码,但我们在这里谈论C#,所以有两件事需要考虑

  1. C#编译器
  2. 假设以下代码

    private unsafe static void Test(Int32* ptr, Int32 size)
    {
        for (int i = 0; i < size; i++)
        {
            var a = * (ptr + i);
            *(ptr + i) = a;
        }
    }
    

    ...

    var array = new Int32[] { 1, 2, 3 };
    
    fixed (Int32* ptr = array)
    {
        Test(ptr, array.Length);
    }
    

    测试方法将成为:

    .method private hidebysig static void  Test(int32* ptr,
                                                int32 size) cil managed
    {
      // Code size       29 (0x1d)
      .maxstack  3
      .locals init ([0] int32 i,
               [1] int32 a)
      IL_0000:  ldc.i4.0
      IL_0001:  stloc.0
      IL_0002:  br.s       IL_0018
      IL_0004:  ldarg.0
      IL_0005:  ldloc.0
      IL_0006:  conv.i
      IL_0007:  ldc.i4.4
      IL_0008:  mul
      IL_0009:  add
      IL_000a:  ldind.i4
      IL_000b:  stloc.1
      IL_000c:  ldarg.0
      IL_000d:  ldloc.0
      IL_000e:  conv.i
      IL_000f:  ldc.i4.4
      IL_0010:  mul
      IL_0011:  add
      IL_0012:  ldloc.1
      IL_0013:  stind.i4
      IL_0014:  ldloc.0
      IL_0015:  ldc.i4.1
      IL_0016:  add
      IL_0017:  stloc.0
      IL_0018:  ldloc.0
      IL_0019:  ldarg.1
      IL_001a:  blt.s      IL_0004
      IL_001c:  ret
    } // end of method Program::Test
    

    不是无操作

    1. 但是还有 JIT编译器。对于那个我无法保证,所以 @AndrewMedico 可能是正确的如果不是现在,那么对于一些假设的未来JIT编译器实现。
    2. 但是现在我已经在我自己的机器上检查了发布模式反汇编,使用了simplest available method并且看似已经开启了优化

      Debug settings - Just my code = Off, Suppress JIT optimization = Off

      以下代码:

      private unsafe static void Test(Int32* ptr, Int32 size)
      {
          Console.WriteLine("test");
      
          for (int i = 0; i < size; i++)
          {
              var a = * (ptr + i);
              *(ptr + i) = a;
          }
      }
      

      我们有

              Console.WriteLine("test");
      00742E70  push        ebp  
      00742E71  mov         ebp,esp  
      00742E73  push        edi  
      00742E74  push        esi  
      00742E75  mov         esi,ecx  
      00742E77  mov         edi,edx  
      00742E79  mov         ecx,dword ptr ds:[27B2310h]  
      00742E7F  call        65BDD4B0  
      
              for (int i = 0; i < size; i++)
      00742E84  xor         edx,edx  
              for (int i = 0; i < size; i++)
      00742E86  test        edi,edi  
      00742E88  jle         00742E95  
              {
                  var a = * (ptr + i);
      00742E8A  mov         ecx,dword ptr [esi+edx*4]  
                  *(ptr + i) = a;
      00742E8D  mov         dword ptr [esi+edx*4],ecx  
              for (int i = 0; i < size; i++)
      00742E90  inc         edx  
      00742E91  cmp         edx,edi  
      00742E93  jl          00742E8A  
      00742E95  pop         esi  
      00742E96  pop         edi  
      00742E97  pop         ebp  
      00742E98  ret  
      

      所以它没有优化,但是如果我删除Console.WriteLine,我就无法在Test中找到断点。起初我虽然优化了,但该方法只进行了内存处理,但它实际上只是在Main中内联:

              Test(ptr, array.Length);
      00482DFD  mov         ecx,dword ptr [ebp-20h]  
      00482E00  mov         edi,dword ptr [edx+4]  
      00482E03  xor         esi,esi  
      00482E05  test        edi,edi  
      00482E07  jle         00482E14  
      00482E09  mov         edx,dword ptr [ecx+esi*4]  
      00482E0C  mov         dword ptr [ecx+esi*4],edx  
      00482E0F  inc         esi  
      00482E10  cmp         esi,edi  
      00482E12  jl          00482E09  
      00482E14  mov         dword ptr [ebp-18h],0  
      00482E1B  mov         dword ptr [ebp-14h],0FCh  
      00482E22  push        482E36h  
      00482E27  jmp         00482E2E  
      00482E29  call        66DBBDC2  
      00482E2E  xor         edx,edx  
      00482E30  mov         dword ptr [ebp-20h],edx  
      00482E33  pop         eax  
      00482E34  jmp         eax  
      00482E36  mov         dword ptr [ebp-14h],0  
      00482E3D  jmp         00482E4B  
      

      所以,TLDR:它做了我所说的。至少在VS2015上的.NET 4.6.1上,Windows 8.1