Add observable alloc/free seams to BackgroundTaskExecutor and fix StubAllocator ownership
- Add RemoteAllocator / RemoteReleaser internal test seams for string/struct scratch memory. - Route all scratch allocation/freeing through the seams so tests can observe leaks. - Fix inverted StubAllocator ownership: a caller-provided stub is now never freed by the executor. - Rewrite Execute_releases_allocated_remote_memory_on_write_failure to fail pre-fix by tracking fake allocations through the seams. - Update WriteFailingMemoryBase to carry a valid self-handle so the executor reaches the marshal/write path. Tests: 198 passing, 4 integration/interactive skipped.
This commit is contained in:
@@ -43,6 +43,38 @@ public sealed class RemoteThreadExecutor
|
||||
/// </remarks>
|
||||
internal Func<IntPtr, nint, IntPtr>? StubAllocator { get; set; }
|
||||
|
||||
/// <summary>Test seam: overrides remote scratch allocation for string/struct args.
|
||||
/// Defaults to <see cref="NativeMethods.VirtualAllocEx"/>.</summary>
|
||||
internal Func<int, IntPtr>? RemoteAllocator { get; set; }
|
||||
|
||||
/// <summary>Test seam: overrides remote scratch release. Defaults to
|
||||
/// <see cref="NativeMethods.VirtualFreeEx"/>.</summary>
|
||||
internal Action<IntPtr>? RemoteReleaser { get; set; }
|
||||
|
||||
private IntPtr AllocateScratch(int size)
|
||||
{
|
||||
if (RemoteAllocator is not null)
|
||||
return RemoteAllocator(size);
|
||||
|
||||
return NativeMethods.VirtualAllocEx(
|
||||
_reader.Handle,
|
||||
IntPtr.Zero,
|
||||
size,
|
||||
MemoryAllocationType.Commit | MemoryAllocationType.Reserve,
|
||||
MemoryProtectionType.ReadWrite);
|
||||
}
|
||||
|
||||
private void ReleaseScratch(IntPtr address)
|
||||
{
|
||||
if (RemoteReleaser is not null)
|
||||
{
|
||||
RemoteReleaser(address);
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMethods.VirtualFreeEx(_reader.Handle, address, 0, MemoryFreeType.Release);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="RemoteThreadExecutor"/> for the process exposed by
|
||||
/// <paramref name="reader"/>.
|
||||
@@ -108,7 +140,9 @@ public sealed class RemoteThreadExecutor
|
||||
if (StubAllocator != null)
|
||||
{
|
||||
stubAddress = StubAllocator(address, stubBytes.Length);
|
||||
stubOwnedByExecutor = stubAddress != IntPtr.Zero;
|
||||
// A caller-provided stub region is owned by the caller; never free it.
|
||||
if (stubAddress != IntPtr.Zero)
|
||||
stubOwnedByExecutor = false;
|
||||
}
|
||||
|
||||
if (stubAddress == IntPtr.Zero)
|
||||
@@ -193,8 +227,7 @@ public sealed class RemoteThreadExecutor
|
||||
|
||||
foreach (IntPtr alloc in allocations)
|
||||
{
|
||||
NativeMethods.VirtualFreeEx(
|
||||
_reader.Handle, alloc, 0, MemoryFreeType.Release);
|
||||
ReleaseScratch(alloc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,12 +317,7 @@ public sealed class RemoteThreadExecutor
|
||||
bytes.CopyTo(buffer, 0);
|
||||
buffer[^1] = 0;
|
||||
|
||||
IntPtr remote = NativeMethods.VirtualAllocEx(
|
||||
_reader.Handle,
|
||||
IntPtr.Zero,
|
||||
buffer.Length,
|
||||
MemoryAllocationType.Commit | MemoryAllocationType.Reserve,
|
||||
MemoryProtectionType.ReadWrite);
|
||||
IntPtr remote = AllocateScratch(buffer.Length);
|
||||
|
||||
if (remote == IntPtr.Zero)
|
||||
{
|
||||
@@ -338,12 +366,7 @@ public sealed class RemoteThreadExecutor
|
||||
pin.Free();
|
||||
}
|
||||
|
||||
IntPtr remote = NativeMethods.VirtualAllocEx(
|
||||
_reader.Handle,
|
||||
IntPtr.Zero,
|
||||
size,
|
||||
MemoryAllocationType.Commit | MemoryAllocationType.Reserve,
|
||||
MemoryProtectionType.ReadWrite);
|
||||
IntPtr remote = AllocateScratch(size);
|
||||
|
||||
if (remote == IntPtr.Zero)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user