Add memory-region query, enumeration, and scoped protection
Implements VirtualQueryEx + MEMORY_BASIC_INFORMATION wrappers, the immutable MemoryRegion record, the ProtectionScope disposable helper, and MemoryBase.QueryRegion/EnumerateRegions/ChangeProtection. Closes section 1 of add-thread-region-finder.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using WhiteMagic.Native;
|
||||
|
||||
namespace WhiteMagic.Memory;
|
||||
|
||||
/// <summary>
|
||||
/// An immutable snapshot of a memory region as reported by <c>VirtualQueryEx</c>.
|
||||
/// </summary>
|
||||
public readonly record struct MemoryRegion
|
||||
{
|
||||
/// <summary>The base address of the region of pages.</summary>
|
||||
public IntPtr BaseAddress { get; }
|
||||
|
||||
/// <summary>The size of the region, in bytes.</summary>
|
||||
public nuint Size { get; }
|
||||
|
||||
/// <summary>The access protection of the pages in the region.</summary>
|
||||
public MemoryProtectionType Protection { get; }
|
||||
|
||||
/// <summary>The state of the pages in the region.</summary>
|
||||
public MemoryState State { get; }
|
||||
|
||||
/// <summary>The type of pages in the region.</summary>
|
||||
public MemoryType Type { get; }
|
||||
|
||||
/// <summary>The base address of a range of pages allocated by VirtualAllocEx.</summary>
|
||||
public IntPtr AllocationBase { get; }
|
||||
|
||||
/// <summary>The memory protection option when the region was initially allocated.</summary>
|
||||
public MemoryProtectionType AllocationProtect { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="MemoryRegion"/> from explicit values.
|
||||
/// </summary>
|
||||
public MemoryRegion(
|
||||
IntPtr baseAddress,
|
||||
nuint size,
|
||||
MemoryProtectionType protection,
|
||||
MemoryState state,
|
||||
MemoryType type,
|
||||
IntPtr allocationBase,
|
||||
MemoryProtectionType allocationProtect)
|
||||
{
|
||||
BaseAddress = baseAddress;
|
||||
Size = size;
|
||||
Protection = protection;
|
||||
State = state;
|
||||
Type = type;
|
||||
AllocationBase = allocationBase;
|
||||
AllocationProtect = allocationProtect;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="MemoryRegion"/> from a raw <c>MEMORY_BASIC_INFORMATION</c>.
|
||||
/// </summary>
|
||||
internal MemoryRegion(MemoryBasicInformation info)
|
||||
{
|
||||
BaseAddress = info.BaseAddress;
|
||||
Size = info.RegionSize;
|
||||
AllocationBase = info.AllocationBase;
|
||||
AllocationProtect = (MemoryProtectionType)info.AllocationProtect;
|
||||
Protection = (MemoryProtectionType)info.Protect;
|
||||
State = (MemoryState)info.State;
|
||||
Type = (MemoryType)info.Type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if <paramref name="address"/> is inside the region,
|
||||
/// defined as <c>[BaseAddress, BaseAddress + Size)</c>.
|
||||
/// </summary>
|
||||
public bool Contains(IntPtr address)
|
||||
{
|
||||
return (nuint)(address - BaseAddress) < Size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using WhiteMagic.Native;
|
||||
|
||||
namespace WhiteMagic.Memory;
|
||||
|
||||
/// <summary>
|
||||
/// A scope that temporarily changes page protection via <c>VirtualProtectEx</c> and
|
||||
/// restores the original protection when disposed, including when the guarded body throws.
|
||||
/// </summary>
|
||||
public sealed class ProtectionScope : IDisposable
|
||||
{
|
||||
private readonly MemoryBase _memory;
|
||||
private readonly IntPtr _address;
|
||||
private readonly nint _size;
|
||||
private readonly MemoryProtectionType _originalProtection;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new protection scope, applying <paramref name="newProtection"/> to the
|
||||
/// specified range immediately.
|
||||
/// </summary>
|
||||
internal ProtectionScope(MemoryBase memory, IntPtr address, nint size, MemoryProtectionType newProtection)
|
||||
{
|
||||
_memory = memory ?? throw new ArgumentNullException(nameof(memory));
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
throw new ArgumentException("Address cannot be zero.", nameof(address));
|
||||
|
||||
if (size <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(size), "Size must be positive.");
|
||||
|
||||
_address = address;
|
||||
_size = size;
|
||||
|
||||
if (!NativeMethods.VirtualProtectEx(
|
||||
memory.Handle,
|
||||
address,
|
||||
size,
|
||||
newProtection,
|
||||
out _originalProtection))
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException(
|
||||
$"VirtualProtectEx failed to change protection: error {error}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Restores the original page protection if it has not already been restored.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
NativeMethods.VirtualProtectEx(_memory.Handle, _address, _size, _originalProtection, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user