Skip to content

Instantly share code, notes, and snippets.

@michel-pi
Created October 21, 2023 11:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michel-pi/2436e2d3260b21d914aecb3845a83f7a to your computer and use it in GitHub Desktop.
Save michel-pi/2436e2d3260b21d914aecb3845a83f7a to your computer and use it in GitHub Desktop.
Mimicking .NET objects in memory.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Snippets
{
/// <summary>
/// Mimicking .NET objects in memory.
/// </summary>
/// <remarks>
/// Probably compatible with .NET 5 and higher but that could change.
/// </remarks>
public unsafe static class FakeObjectFactory
{
public static string CreateString(char[] chars)
{
ClrObject<ClrStringObject>* destObject = null;
fixed (char* charsPtr = string.Empty)
{
// address of FirstChar - sizeof(ClrStringObject.StringLength) - sizeof(ClrObject.MethodTable) - sizeof(ClrObject.SyncBlockIndex)
var sourceObject = (ClrObject<ClrStringObject>*)((byte*)charsPtr - sizeof(uint) - sizeof(nint) - sizeof(uint));
var objectSize = (int)sourceObject->MethodTable->BaseSize;
// reserve enough space for the actual string content.
objectSize += chars.Length * sizeof(char);
// round up to a multiple of the address size
var objectSizeRemainder = objectSize % sizeof(nint);
if (objectSizeRemainder != 0)
{
objectSize += sizeof(nint) - objectSizeRemainder;
}
// create object
destObject = (ClrObject<ClrStringObject>*)Marshal.AllocHGlobal(objectSize);
GC.AddMemoryPressure(objectSize);
// copy default object content
Unsafe.CopyBlockUnaligned(destObject, sourceObject, (uint)objectSize);
}
// copy characters
fixed (char* charsPtr = &chars[0])
{
var destChars = (byte*)destObject + sizeof(ClrObject<ClrStringObject>) - sizeof(char);
Unsafe.CopyBlockUnaligned(destChars, charsPtr, (uint)chars.Length * sizeof(char));
destObject->Value.StringLength = (uint)chars.Length;
}
destObject->SyncBlockIndex = 0;
var castableAddress = (byte*)destObject + sizeof(uint);
#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
var castedObject = *(string*)&castableAddress;
#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
return castedObject;
}
public static void FreeString(string value, bool zeroMemory = true)
{
var calculateAddress = (*(byte**)Unsafe.AsPointer(ref value)) - sizeof(uint);
var objAddress = (ClrObject<ClrStringObject>*)calculateAddress;
// calculate size in bytes
var objectSize = (int)objAddress->MethodTable->BaseSize;
// reserve enough space for the actual string content.
objectSize += value.Length * sizeof(char);
// round up to a multiple of the address size
var objectSizeRemainder = objectSize % sizeof(nint);
if (objectSizeRemainder != 0)
{
objectSize += sizeof(nint) - objectSizeRemainder;
}
if (zeroMemory)
{
Unsafe.InitBlockUnaligned(objAddress, 0, (uint)objectSize);
}
Marshal.FreeHGlobal((nint)objAddress);
GC.RemoveMemoryPressure(objectSize);
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct ClrObject<T> where T : unmanaged
{
public uint SyncBlockIndex;
public ClrMethodTable* MethodTable;
public T Value;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct ClrStringObject
{
public uint StringLength;
public char FirstChar;
}
[StructLayout(LayoutKind.Explicit, Pack = 1)]
internal struct ClrMethodTable
{
// Get the address of an clr object method table and feed WinDbg with this command: dt coreclr!MethodTable 0xADDRESS
[FieldOffset(0x000)] public uint Flags;
/// <summary>
/// The base size of the type in bytes. It's the size of an ClrObject<T> + padding if required.
/// </summary>
[FieldOffset(0x004)] public uint BaseSize;
[FieldOffset(0x008)] public ushort Flags2;
[FieldOffset(0x00a)] public ushort Token;
[FieldOffset(0x00c)] public ushort NumVirtuals;
[FieldOffset(0x00e)] public ushort NumInterfaces;
[FieldOffset(0x010)] public nint ParentMethodTable;
[FieldOffset(0x018)] public nint LoaderModule;
[FieldOffset(0x020)] public nint WritableData;
[FieldOffset(0x028)] public nint EEClass;
[FieldOffset(0x028)] public nint CanonMT;
[FieldOffset(0x030)] public nint PerInstInfo;
[FieldOffset(0x030)] public nint ElementTypeHnd;
[FieldOffset(0x030)] public nint MultipurposeSlot1;
[FieldOffset(0x038)] public nint InterfaceMap;
[FieldOffset(0x038)] public nint MultipurposeSlot2;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment