This is the sixth part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack

We already know how to allocate object in any place in memory. We can invoke constructor for already created object. We know how to hijack new operator and dispatch allocations to our code. We can parse lambda and have compile time support for unsafe allocations. Unfortunately, we have another thing: problems.

The code

I will describe some problems with our mechanisms using the following code:

using CustomMemoryAllocator;
using CustomStackAllocator;
using System;
using System.Linq.Expressions;

namespace Demo
{
    public struct Simple
    {
        public int X, Y, Z;
        public override string ToString()
        {
            return $"{X} {Y} {Z}";
        }
    }

    public enum Direction
    {
        North = 1,
        South = 2
    }

    public class BaseDummy
    {
        public BaseDummy()
        {
            Console.WriteLine("Creating base");
        }

        ~BaseDummy()
        {
            Console.WriteLine("Deleting base");
        }
    }

    public class VerySimple
    {
        public int Integer;
        public double Double;
        public float Float;
        public short Short;
        public long Long;
        public byte Byte;
        public bool Bool;
        public Simple ValueType;
        public Simple BoxedValueType;
        public object ReferenceType;
        public Direction Enum;
        public VerySimple(int @int,
            double @double,
            float @float,
            short @short,
            long @long,
            byte @byte,
            bool @bool,
            Simple valueType,
            object referenceType,
            Direction @enum,
            object boxedValueType)
        {
            Console.WriteLine("Creating VerySimple at: " + AllocationHelper.GetAddress(this));
            Integer = @int;
            Double = @double;
            Float = @float;
            Short = @short;
            Long = @long;
            Byte = @byte;
            Bool = @bool;
            ValueType = valueType;
            ReferenceType = referenceType;
            Enum = @enum;
            BoxedValueType = (Simple)boxedValueType;
        }

        public override string ToString()
        {
            return $"{Integer} {Double} {Float} {Short} {Long} {Byte} {Bool} {ValueType} {ReferenceType} {Enum} {BoxedValueType}";
        }

        public void Dispose()
        {
            Console.WriteLine("Disposing");
        }
    }

    class Dummy : BaseDummy, IDisposable
    {
        public int Integer;
        public double Double;
        public float Float;
        public short Short;
        public long Long;
        public byte Byte;
        public bool Bool;
        public Simple ValueType;
        public Simple BoxedValueType;
        public object ReferenceType;
        public Direction Enum;
        public Dummy(int @int, 
            double @double, 
            float @float, 
            short @short, 
            long @long, 
            byte @byte, 
            bool @bool, 
            Simple valueType, 
            object referenceType, 
            Direction @enum, 
            object boxedValueType)
        {
            Console.WriteLine("Creating child at: " + AllocationHelper.GetAddress(this));
            Integer = @int;
            Double = @double;
            Float = @float;
            Short = @short;
            Long = @long;
            Byte = @byte;
            Bool = @bool;
            ValueType = valueType;
            ReferenceType = referenceType;
            Enum = @enum;
            BoxedValueType = (Simple)boxedValueType;
        }

        public override string ToString()
        {
            return $"{Integer} {Double} {Float} {Short} {Long} {Byte} {Bool} {ValueType} {ReferenceType} {Enum} {BoxedValueType}";
        }

        public void Dispose()
        {
            Console.WriteLine("Disposing");
        }
    }

    class Program
    {
        static void Present(object dummy)
        {
            lock (dummy)
            {
                Console.WriteLine($"Generation: {GC.GetGeneration(dummy)}");
                Console.WriteLine($"Object: {dummy}");
            }
        }

        static void Main(string[] args)
        {
            using (IAllocator allocator = new BasicHeapAllocator(100000))
            {
                Expression< Func< Dummy>> lambda = () => new Dummy(15, 30.0, (float)45.5, 60, 75, 90, true, new Simple { X = 12, Y = 23, Z = 34 }, "String", Direction.North, new Simple { X = 22, Y = 33, Z = 88 });
                var o = allocator.Alloc(lambda);
                Present(o);
                allocator.Free(o);
            }
            using (IAllocator allocator = new BasicHeapAllocator(100000))
            {
                AllocationHelper.PatchNew(allocator);
                var o = new VerySimple(1, 2.0, 3, 4, 5, 6, true, new Simple { X = 7, Y = 8, Z = 9 }, "New String", Direction.North, new Simple { X = 10, Y = 11, Z = 12 });
                AllocationHelper.RestoreNew();
                Present(o);
                allocator.Free(o);
            }
            using (BasicStackAllocator allocator = new BasicStackAllocator())
            {
                Expression< Func< Dummy>> lambda = () => new Dummy(-3, -7.0, (float)-11.23, -5, -9, 5, true, new Simple { X = 2, Y = 3, Z = 4 }, null, Direction.South, new Simple { X = -5, Y = -4, Z = -7 });
                unsafe
                {
                    var o = allocator.Alloc(lambda);
                    Present(o);
                    allocator.Free(o);
                }
            }
        }
    }
}

The code is long by design. I wanted to test passing multiple parameters with different types and verify different allocators. And here are the conclusions.

Simple allocation on a heap

Ordinary allocation on a heap works pretty stable. As you can see, I was able to allocate object with multiple parameters, I could use finalizer, and inherit from other class. However, this was the only one stable method.

Problems with hijacking new

When we examined the new operator we hijacked only one of its implementations. Unfortunately, there are others which we should patch as well. Let’s see the problematic code:

using (IAllocator allocator = new BasicHeapAllocator(100000))
            {
                AllocationHelper.PatchNew(allocator);
                var o = new VerySimple(1, 2.0, 3, 4, 5, 6, true, new Simple { X = 7, Y = 8, Z = 9 }, "New String", Direction.North, new Simple { X = 10, Y = 11, Z = 12 });
                AllocationHelper.RestoreNew();
                Present(o);
                allocator.Free(o);
            }

We patch operator to direct it to our custom allocator. Next, we create an object with multiple parameters, and restores operator. This works as designed, however, if we replace VerySimple with Dummy it breaks in the Free method because allocator says that it didn’t allocate this object. Why? Because Dummy inherits from something different then System.Object. Because of different allocation mechanisms, allocator only handles call to base class allocation, not to the derived class.

Problems with allocating on a stack

Allocation on a stack looks pretty easy. We have the following code:

using (BasicStackAllocator allocator = new BasicStackAllocator())
            {
                Expression< Func< Dummy>> lambda = () => new Dummy(-3, -7.0, (float)-11.23, -5, -9, 5, true, new Simple { X = 2, Y = 3, Z = 4 }, null, Direction.South, new Simple { X = -5, Y = -4, Z = -7 });
                unsafe
                {
                    var o = allocator.Alloc(lambda);
                    Present(o);
                    allocator.Free(o);
                }
            }

Observe, that we are passing null to reference type. If we now replace it with something else (string for instance), we get the following error:

Memory exception for Dummy type

It looks pretty strange. If you debug the code you will see that the exception is thrown in this line in constructor:

ReferenceType = referenceType;

Suspicious line in constructor

Well, looks bad. But if we try to read or write values using Immediate Window we will have no errors. Well, what can be wrong? Let’s debug the machine code:

Even more suspicious machine code

What is going on here? This is the call to call clr!JIT_WriteBarrierEAX method. I am not going to describe here what are memory barriers, but there is one important thing inside their implementation in .NET. Inside a write barrier, a check is made to see whether the object being assigned to has an address lower that the start address of generation 1 (i.e., generation 1 or 2). In that case .NET updates some addresses to obtain lock on an address. Since lock must be made on a specific segment address, .NET simply performs some bitwise operations on the address of written object and modify memory there — but this memory might not be mapped to our process. Unfortunately, when you try to access memory on a stack, you must be careful. 1 out of 20 executions had no error because of address randomization.

Patching new once again

When we were implementing new hijacking, we were manually attaching debugger to our application and we couldn’t start it using F5. This is another reason to not do that — if we cannot debug application, we are screwed. Of course, we could try to come up with different patching mechanism and detected in runtime whether we have debugger connected or not, but then we would probably hit other errors.

Summary

Messing with memory is funny. We were able to implement multiple different scenarios and handle pretty nice cases. However, memory issues are hard to debug and this is why .NET relies on GC implementation. If we need to perform these dirty hacks maybe we should just take different language?