2

I came along this piece of c# code which is supposed to verify memory allocations that were done in an earlier stage.

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

It seems to me that all the code does is copy a byte from raw memory to a temp variable b and then write it back to the raw memory.

Does reading from an writing to a memory location make the memory valid and safe to access? Is there a scenario where this will actually catch a corruption in memory?

Iftah
  • 346
  • 2
  • 7

1 Answers1

0

It actually verifies that memory starting from BaseAddress up to BaseAddress+(sizeof(ElementType))*(Size-1) can be both read from and written to.

For example, assume that memory at BaseAddress has protection attributes PAGE_EXECUTE_READ.

In such a case reading won't fail, while writing to it will cause an Access Violation (as far as I remember).


But as it has been already pointed, we can only guess why someone would need it.

Ideally, it should not be required, assuming that both managed and unmanaged parts of the application correctly implement the contract between them. And, obviously, assuming that the contract is reasonable (like module A promises not to allocate buffer memory that can be for no reason either non-readable or non-writeable).


Optimized out as no-op or not?

As pointed by @AndrewMedico

what will happen assume the compiler doesn't just optimize it out (the loop is logically a no-op)

I doubt that all C\C++ compilers will optimize the analogous code, but here are we talking about C#, so there are two things to consider

  1. C# compiler

Assuming the following code

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);
}

And the test method will become:

.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

That is not a no-op

  1. But there is also a JIT Compiler. For that one I can't vouch, so @AndrewMedico may be right if not now, then for some hypothetical future JIT compiler implementation.

But for now I have checked release mode disassembly on my own machine, using the simplest available method and with optimizations seemingly turned on

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

For the following code:

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;
    }
}

We have

        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  

So it is not optimized, but if I remove Console.WriteLine, I won't be able to hit the breakpoint in Test. At first I though that it got optimized out when the method does only the memory juggling, but it actually only got inlined in the 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  

So, TLDR: It does what I have told it does. At least on .NET 4.6.1 on VS2015, Windows 8.1

Community
  • 1
  • 1
Eugene Podskal
  • 10,270
  • 5
  • 31
  • 53
  • You're assuming a lot of facts not in evidence. – nobody Apr 17 '16 at 14:22
  • The claim about what memory addresses it will access assumes the type of the pointer variable BaseAddress which isn't given. All the claims about what will happen assume the compiler doesn't just optimize it out (the loop is logically a no-op). Do not learn anything from this answer. – nobody Apr 17 '16 at 14:58
  • @AndrewMedico Thanks for "type of the pointer variable ", don't know why I assumed it is `Int32`. – Eugene Podskal Apr 17 '16 at 15:45