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:
kbe
2026-07-22 16:04:15 +02:00
parent e8c84f0ba1
commit f0faca3112
7 changed files with 504 additions and 0 deletions
+58
View File
@@ -156,3 +156,61 @@ public static class ContextFlags
/// <summary>AMD64: control, integer, and segment registers.</summary>
public const uint Amd64Full = Amd64Control | Amd64Integer | Amd64Segments;
}
/// <summary>
/// Values that describe the state of memory pages returned by <c>VirtualQueryEx</c>.
/// </summary>
public enum MemoryState : uint
{
/// <summary>Indicates committed pages for which physical storage has been allocated.</summary>
Commit = 0x1000,
/// <summary>Indicates reserved pages where a range of the virtual address space is reserved without any physical storage being allocated.</summary>
Reserve = 0x2000,
/// <summary>Indicates free pages not accessible to the calling process and available to be allocated.</summary>
Free = 0x10000,
}
/// <summary>
/// Values that describe the type of memory pages returned by <c>VirtualQueryEx</c>.
/// </summary>
public enum MemoryType : uint
{
/// <summary>Indicates that the memory pages within the region are private.</summary>
Private = 0x20000,
/// <summary>Indicates that the memory pages within the region are mapped into the view of a section.</summary>
Mapped = 0x40000,
/// <summary>Indicates that the memory pages within the region are mapped into the view of an image section.</summary>
Image = 0x1000000,
}
/// <summary>
/// Flags used by <c>CreateToolhelp32Snapshot</c> to specify the portions of the system to include in the snapshot.
/// </summary>
[Flags]
public enum SnapshotFlags : uint
{
/// <summary>Enumerate the heap list.</summary>
HeapList = 0x00000001,
/// <summary>Enumerate the process list.</summary>
Process = 0x00000002,
/// <summary>Enumerate the thread list.</summary>
Thread = 0x00000004,
/// <summary>Enumerate the module list.</summary>
Module = 0x00000008,
/// <summary>Enumerate the 32-bit module list for the specified process.</summary>
Module32 = 0x00000010,
/// <summary>Include all processes and threads in the system.</summary>
All = 0x0000001F,
/// <summary>Indicate that the snapshot handle is to be inheritable.</summary>
Inherit = 0x80000000,
}
+42
View File
@@ -173,4 +173,46 @@ internal static partial class NativeMethods
SafeMemoryHandle handle,
uint milliseconds);
// ── Memory query ───────────────────────────────────────────────────────
/// <summary>Retrieves information about a range of pages in the virtual address space of a specified process.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
internal static partial nuint VirtualQueryEx(
SafeMemoryHandle process,
IntPtr address,
out MemoryBasicInformation buffer,
nuint length);
// ── Thread enumeration ─────────────────────────────────────────────────
/// <summary>Takes a snapshot of the specified processes, as well as the heaps, modules, and threads used by these processes.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
internal static partial SafeMemoryHandle CreateToolhelp32Snapshot(
SnapshotFlags dwFlags,
int th32ProcessID);
/// <summary>Retrieves information about the first thread of any process encountered in a system snapshot.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool Thread32First(
SafeMemoryHandle hSnapshot,
ref ThreadEntry32 lpte);
/// <summary>Retrieves information about the next thread of any process encountered in a system snapshot.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool Thread32Next(
SafeMemoryHandle hSnapshot,
ref ThreadEntry32 lpte);
/// <summary>Retrieves timing information for the specified thread.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool GetThreadTimes(
SafeMemoryHandle thread,
out long creationTime,
out long exitTime,
out long kernelTime,
out long userTime);
}
+58
View File
@@ -205,3 +205,61 @@ public unsafe struct Context64
/// <summary>The source RIP of the last exception.</summary>
public ulong LastExceptionFromRip;
}
/// <summary>
/// Layout matches <c>MEMORY_BASIC_INFORMATION</c>. Uses pointer-sized fields so the
/// structure is 28 bytes on x86 and 48 bytes on x64, matching the layout the OS expects
/// from a caller of those bitnesses.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct MemoryBasicInformation
{
/// <summary>A pointer to the base address of the region of pages.</summary>
public nint BaseAddress;
/// <summary>A pointer to the base address of a range of pages allocated by the VirtualAllocEx function.</summary>
public nint AllocationBase;
/// <summary>The memory protection option when the region was initially allocated.</summary>
public uint AllocationProtect;
/// <summary>The size of the region beginning at the base address, in bytes.</summary>
public nuint RegionSize;
/// <summary>The state of the pages in the region.</summary>
public uint State;
/// <summary>The access protection of the pages in the region.</summary>
public uint Protect;
/// <summary>The type of pages in the region.</summary>
public uint Type;
}
/// <summary>
/// Layout matches <c>THREADENTRY32</c> used by <c>Thread32First</c>/<c>Thread32Next</c>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct ThreadEntry32
{
/// <summary>The size of the structure, in bytes.</summary>
public uint dwSize;
/// <summary>This member is no longer used and is always zero.</summary>
public uint cntUsage;
/// <summary>The thread identifier.</summary>
public uint th32ThreadID;
/// <summary>The identifier of the process that owns the thread.</summary>
public uint th32OwnerProcessID;
/// <summary>The kernel base priority level assigned to the thread.</summary>
public int tpBasePri;
/// <summary>This member is no longer used.</summary>
public int tpDeltaPri;
/// <summary>This member is reserved.</summary>
public uint dwFlags;
}