Created
October 21, 2023 11:59
-
-
Save michel-pi/2436e2d3260b21d914aecb3845a83f7a to your computer and use it in GitHub Desktop.
Mimicking .NET objects in memory.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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