Memory API
Info
Reloaded.Memory
provides various abstractions that can be used to wrap around contiguous regions of memory.
All APIs listed here are zero overhead.
Stuff listed here isn't that impressive, but it's the basic building block for what follows next.
Memory & ExternalMemory
Info
The Memory
and ExternalMemory
classes are the most basic abstractions provided by Reloaded.Memory
.
They allow you to access memory either within the current or a target process
.
while (ptr < maxAddress)
{
result += *ptr;
ptr += 1;
}
if (Polyfills.IsWindows())
return Kernel32.ReadProcessMemory(_processHandle, location, (nuint)buffer, numBytes, out _);
if (Polyfills.IsLinux())
return Posix.process_vm_readv_k32(_processHandle, location, (nuint)buffer, numBytes);
// And other cases!
// memory = Memory.Instance;
while (ptr < maxAddress)
{
result += memory.Read<nuint>((UIntPtr)ptr);
ptr += 1;
}
// memory = new ExternalMemory(process);
while (ptr < maxAddress)
{
result += memory.Read<nuint>((UIntPtr)ptr);
ptr += 1;
}
As you can see, with the library and its ICanReadWriteMemory
interface; usage is unified across all sources. Instead
of having to write different code for different sources (first 2 examples), you can now write the same code for all sources.
And of course, various different utility methods are provided to make your life easier.
while (ptr < maxAddress)
Marshal.StructureToPtr(items[x++], (nint)offset, false);
byte* bufferPtr = new byte[structSize];
bool succeeded = ReadProcessMemory(offset, bufferPtr, (nuint)structSize);
if (!succeeded)
ThrowHelpers.ThrowReadExternalMemoryExceptionWindows(offset, structSize);
Marshal.PtrToStructure((nint)bufferPtr, value);
while (ptr < maxAddress)
memory.WriteWithMarshalling(ptr, items[x++]);
while (ptr < maxAddress)
memory.WriteWithMarshalling(ptr, items[x++]);
All silly boilerplate needed to manipulate different sources is gone; and this is all done with zero-overhead.
Unified Memory Allocation API
Info
Structs like Memory
and ExternalMemory
employ ICanAllocateMemory
API to make memory allocations convenient.
var allocation = NativeMemory.Alloc(100);
var allocation = Marshal.AllocHGlobal(100);
var allocation = memory.Allocate(100);
var allocation = memory.Allocate(100);
Now you can allocate in another process in a consistent manner. Useful?
ICanAllocateMemory
for ExternalMemory
currently implemented in Windows only; PRs for Linux and OSX.
Unified Permission Change API
Info
Structs like Memory
and ExternalMemory
employ ICanChangeMemoryProtection
API to allow you to change memory permissions.
This allows you to make existing code etc. in memory writable for editing.
if (Polyfills.IsWindows())
{
bool result = Kernel32.VirtualProtect(memoryAddress, (nuint)size, (Kernel32.MEM_PROTECTION)newProtection,
out Kernel32.MEM_PROTECTION oldPermissions);
if (!result)
ThrowHelpers.ThrowMemoryPermissionExceptionWindows(memoryAddress, size, newProtection);
return (nuint)oldPermissions;
}
if (Polyfills.IsLinux() || Polyfills.IsMacOS())
{
// ... lot more boilerplate
}
var oldPermissions = source.ChangeProtection(address, length, MemoryProtection.READ);
var oldPermissions = source.ChangeProtection(address, length, MemoryProtection.READ);
Pretty useful huh?
ICanChangeMemoryProtection
for ExternalMemory
currently implemented in Windows only; PRs for Linux and OSX are welcome.
Extensions
Info
These interfaces, and combinations of them allow for some very useful utility methods to be made.
Temporary allocate a buffer:
// Automatically disposed, even on exception
using var alloc = memory.AllocateDisposable(DataSize);
Temporary change memory permission:
using var alloc = memory.ChangeProtectionDisposable(DataSize);
Temporary change memory permission, write data, and restore:
memory.SafeWrite(Alloc.Address, Data.AsSpanFast());
Reference Benchmarks
In most cases, the abstractions generate 1:1 code that matches exactly the same performance as working with raw pointers.
| Method | Mean | Error | StdDev | Code Size | Allocated |
|------------------------------ |---------:|--------:|--------:|----------:|----------:|
| ReadViaPointer | 130.7 ns | 0.77 ns | 0.69 ns | 49 B | - |
| ReadViaMemory | 132.4 ns | 0.71 ns | 0.66 ns | 52 B | - |
| ReadViaMemory_ViaOutParameter | 132.2 ns | 1.81 ns | 1.70 ns | 52 B | - |
| WriteViaPointer | 117.4 ns | 1.55 ns | 1.45 ns | 48 B | - |
| WriteViaMemory | 117.8 ns | 1.67 ns | 1.57 ns | 48 B | - |
ReadViaPointer
and WriteViaPointer
are using raw pointers; remaining tests are using the abstractions.