Skip to content

Commit

Permalink
Added: Large Address Awareness Support to Reloaded.Memory.Buffers
Browse files Browse the repository at this point in the history
  • Loading branch information
Sewer56 committed Jun 18, 2022
1 parent 2fcea9a commit c8f896a
Show file tree
Hide file tree
Showing 19 changed files with 285 additions and 182 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Platforms>x86</Platforms>
<DefineConstants>$(DefineConstants);X86</DefineConstants>

<!-- Prevent warnings from unused code in dependencies -->
<TrimmerDefaultAction>link</TrimmerDefaultAction>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\Reloaded.Memory.Buffers.Tests\Helpers\RandomByteArray.cs" Link="Helpers/RandomByteArray.cs" />
<Compile Include="..\Reloaded.Memory.Buffers.Tests\Helpers\RandomIntStruct.cs" Link="Helpers/RandomIntStruct.cs" />
<Compile Include="..\Reloaded.Memory.Buffers.Tests\MemoryBufferTests.cs" Link="MemoryBufferTests.cs" />
</ItemGroup>

<ItemGroup>
<None Include="..\Reloaded.Memory.Buffers.Tests\HelloWorld.exe" />
</ItemGroup>

<ItemGroup>
<None Update="../Reloaded.Memory.Buffers.Tests/HelloWorld.exe" Link="HelloWorld.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="../Reloaded.Memory.Buffers.Tests/HelloWorldCore.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="../Reloaded.Memory.Buffers.Tests/xunit.runner.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>


<ItemGroup>
<!-- Analyze the whole library, even if attributed with "IsTrimmable" -->
<TrimmerRootAssembly Include="Reloaded.Memory.Buffers" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Reloaded.Memory.Buffers\Reloaded.Memory.Buffers.csproj" />
</ItemGroup>

</Project>
104 changes: 76 additions & 28 deletions Source/Reloaded.Memory.Buffers.Tests/MemoryBufferTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,37 @@ public void Dispose()
[Fact]
public unsafe void GetBuffersInRangeExternal() => GetBuffersInRange(_externalBufferHelper, GetMaxAddress(_externalBufferHelper));

#if X86
/// <summary>
/// Attempts to create a set of <see cref="MemoryBuffer"/>s at the beginning and end of the
/// address space, and then find the given buffers.
/// </summary>
[Fact]
public unsafe void GetBuffersInRangeInternal_LargeAddressAware()
{
AssertLargeAddressAware();
GetBuffersInRange(_bufferHelper, int.MaxValue, uint.MaxValue);
}

/// <summary>
/// Attempts to create a set of <see cref="MemoryBuffer"/>s at the beginning and end of the
/// address space, and then find the given buffers.
/// </summary>
[Fact]
public unsafe void GetBuffersInRangeExternal_LargeAddressAware()
{
AssertLargeAddressAware();
GetBuffersInRange(_externalBufferHelper, int.MaxValue, uint.MaxValue);
}

void AssertLargeAddressAware()
{
var maxAddress = GetMaxAddress(_externalBufferHelper, true);
if ((long)maxAddress <= int.MaxValue)
Assert.False(true, "Test host is not large address aware!!");
}
#endif

/* Same as above, except without cache. */

[Fact]
Expand Down Expand Up @@ -247,54 +278,67 @@ private void GetBuffers(MemoryBufferHelper bufferHelper)
/// Attempts to create a set of <see cref="MemoryBuffer"/>s at the beginning and end of the
/// address space, and then find the given buffers.
/// </summary>
private unsafe void GetBuffersInRange(MemoryBufferHelper bufferHelper, IntPtr maxApplicationAddress)
private unsafe void GetBuffersInRange(MemoryBufferHelper bufferHelper, nuint minAddress, nuint maxAddress)
{
/* The reason that testing the upper half is sufficient is because the buffer allocation
functions work in such a manner that they allocate from the lowest address.
As such, normally the only allocated addresses would be in the lower half... until enough memory is allocated to cross the upper half.
*/

// Options
int sizeStart = 0; // Default page size for x86 and x64.
int sizeStart = 0; // Default page size for x86 and x64.
int repetitions = 128;
int increment = 4096; // Equal to allocation granularity.

// Minimum address is start of upper half of 32/64 bit address range.
// Maximum is the maximum address in 32/64 bit address range.
long minAddress = (long) maxApplicationAddress - ((long)maxApplicationAddress / 2);
long maxAddress = (long) maxApplicationAddress;
int increment = 4096; // Equal to allocation granularity.

MemoryBuffer[] buffers = new MemoryBuffer[repetitions];

// Allocate <repetitions> buffers, and try to find them all.
for (int x = 0; x < repetitions; x++)
{
int newSize = sizeStart + (x * increment);
buffers[x] = bufferHelper.CreateMemoryBuffer(newSize, (long) minAddress, (long) maxAddress);
buffers[x] = bufferHelper.CreateMemoryBuffer(newSize, minAddress, maxAddress);
}

// Validate whether each buffer is present and in range.
for (int x = 0; x < repetitions; x++)
{
int newSize = sizeStart + (x * increment);
var foundBuffers = bufferHelper.FindBuffers(newSize, (IntPtr) minAddress, (IntPtr) maxAddress);
var foundBuffers = bufferHelper.FindBuffers(newSize, minAddress, maxAddress);

if (!foundBuffers.Contains(buffers[x]))
Assert.True(false, $"Failed to find existing buffer in memory of minimum size {newSize} bytes.");

foreach (var buffer in foundBuffers)
AssertBufferInRange(buffer, (IntPtr) minAddress, (IntPtr) maxAddress);
AssertBufferInRange(buffer, minAddress, maxAddress);
}

// Cleanup
for (int x = 0; x < buffers.Length; x++)
Internal.Testing.Buffers.FreeBuffer(buffers[x]);
}

/// <summary>
/// Attempts to create a set of <see cref="MemoryBuffer"/>s at the beginning and end of the
/// address space, and then find the given buffers.
/// </summary>
private unsafe void GetBuffersInRange(MemoryBufferHelper bufferHelper, UIntPtr maxApplicationAddress)
{
/* The reason that testing the upper half is sufficient is because the buffer allocation
functions work in such a manner that they allocate from the lowest address.
As such, normally the only allocated addresses would be in the lower half... until enough memory is allocated to cross the upper half.
*/

// Minimum address is start of upper half of 32/64 bit address range.
// Maximum is the maximum address in 32/64 bit address range.
long minAddress = (long)maxApplicationAddress - ((long)maxApplicationAddress / 2);
long maxAddress = (long)maxApplicationAddress;
GetBuffersInRange(bufferHelper, (nuint)minAddress, (nuint)maxAddress);
}

/// <summary>
/// Same as <see cref="GetBuffersInRange"/>, except disables the caching when acquiring <see cref="MemoryBuffer"/>s.
/// </summary>
private unsafe void GetBuffersInRangeNoCache(MemoryBufferHelper bufferHelper, IntPtr maxApplicationAddress)
private unsafe void GetBuffersInRangeNoCache(MemoryBufferHelper bufferHelper, UIntPtr maxApplicationAddress)
{
/* The reason that testing the upper half is sufficient is because the buffer allocation
functions work in such a manner that they allocate from the lowest address.
Expand All @@ -308,29 +352,29 @@ functions work in such a manner that they allocate from the lowest address.

// Minimum address is start of upper half of 32/64 bit address range.
// Maximum is the maximum address in 32/64 bit address range.
long minAddress = (long)maxApplicationAddress - ((long)maxApplicationAddress / 2);
long maxAddress = (long)maxApplicationAddress;
nuint minAddress = (nuint)maxApplicationAddress - ((nuint)maxApplicationAddress / 2);
nuint maxAddress = (nuint)maxApplicationAddress;

MemoryBuffer[] buffers = new MemoryBuffer[repetitions];

// Allocate <repetitions> buffers, and try to find them all.
for (int x = 0; x < repetitions; x++)
{
int newSize = sizeStart + (x * increment);
buffers[x] = bufferHelper.CreateMemoryBuffer(newSize, (long)minAddress, (long)maxAddress);
buffers[x] = bufferHelper.CreateMemoryBuffer(newSize, minAddress, maxAddress);
}

// Validate whether each buffer is present and in range.
for (int x = 0; x < repetitions; x++)
{
int newSize = sizeStart + (x * increment);
var foundBuffers = bufferHelper.FindBuffers(newSize, (IntPtr)minAddress, (IntPtr)maxAddress, false);
var foundBuffers = bufferHelper.FindBuffers(newSize, minAddress, maxAddress, false);

if (!foundBuffers.Contains(buffers[x]))
Assert.True(false, $"Failed to find existing buffer in memory of minimum size {newSize} bytes.");

foreach (var buffer in foundBuffers)
AssertBufferInRange(buffer, (IntPtr)minAddress, (IntPtr)maxAddress);
AssertBufferInRange(buffer, minAddress, maxAddress);
}

// Cleanup
Expand Down Expand Up @@ -365,18 +409,18 @@ private unsafe void MemoryBufferAddGeneric(MemoryBuffer buffer, Process process)
// Fill the buffer and verify each item as it's added.
for (int x = 0; x < itemsToFit; x++)
{
IntPtr writeAddress = buffer.Add(ref randomIntStructs[x], false, 1);
nuint writeAddress = buffer.Add(ref randomIntStructs[x], false, 1);

// Read back and compare.
externalMemory.Read(writeAddress, out RandomIntStruct actual);
Assert.Equal(randomIntStructs[x], actual);
}

// Compare again, running the entire array this time.
IntPtr bufferStartPtr = bufferHeader.DataPointer;
nuint bufferStartPtr = bufferHeader.DataPointer;
for (int x = 0; x < itemsToFit; x++)
{
IntPtr readAddress = bufferStartPtr + (x * structSize);
nuint readAddress = (UIntPtr)bufferStartPtr + (x * structSize);

// Read back and compare.
externalMemory.Read(readAddress, out RandomIntStruct actual);
Expand All @@ -388,7 +432,7 @@ private unsafe void MemoryBufferAddGeneric(MemoryBuffer buffer, Process process)

// Likewise, calling Add should return IntPtr.Zero.
var randIntStr = RandomIntStruct.BuildRandomStruct();
Assert.Equal(IntPtr.Zero, buffer.Add(ref randIntStr, false, 1));
Assert.Equal((nuint)0, buffer.Add(ref randIntStr, false, 1));
}

/// <summary>
Expand All @@ -413,10 +457,10 @@ private unsafe void MemoryBufferAddByteArray(MemoryBuffer buffer, Process proces
buffer.Add(rawArray, 1);

// Compare against the array written.
IntPtr bufferStartPtr = bufferHeader.DataPointer;
nuint bufferStartPtr = bufferHeader.DataPointer;
for (int x = 0; x < remainingBufferSpace; x++)
{
IntPtr readAddress = bufferStartPtr + x;
nuint readAddress = (UIntPtr)bufferStartPtr + x;

// Read back and compare.
externalMemory.Read(readAddress, out byte actual);
Expand All @@ -428,7 +472,7 @@ private unsafe void MemoryBufferAddByteArray(MemoryBuffer buffer, Process proces

// Likewise, calling Add should return IntPtr.Zero.
byte testByte = 55;
Assert.Equal(IntPtr.Zero, buffer.Add(ref testByte, false, 1));
Assert.Equal((nuint)0, buffer.Add(ref testByte, false, 1));
}

/*
Expand All @@ -450,9 +494,9 @@ private MemoryBuffer CreatePrivateMemoryBuffer(MemoryBufferHelper helper)
/// <summary>
/// Asserts whether the contents of a given <see cref="MemoryBuffer"/> lie in the <see cref="minAddress"/> to <see cref="maxAddress"/> address range.
/// </summary>
private unsafe void AssertBufferInRange(MemoryBuffer buffer, IntPtr minAddress, IntPtr maxAddress)
private unsafe void AssertBufferInRange(MemoryBuffer buffer, nuint minAddress, nuint maxAddress)
{
IntPtr bufferDataPtr = buffer.Properties.DataPointer;
nuint bufferDataPtr = buffer.Properties.DataPointer;
if ((void*)bufferDataPtr < (void*)minAddress ||
(void*)bufferDataPtr > (void*)maxAddress)
{
Expand All @@ -463,18 +507,22 @@ private unsafe void AssertBufferInRange(MemoryBuffer buffer, IntPtr minAddress,
/// <summary>
/// Returns the max addressable address of the process sitting behind the <see cref="MemoryBufferHelper"/>.
/// </summary>
private IntPtr GetMaxAddress(MemoryBufferHelper helper)
private UIntPtr GetMaxAddress(MemoryBufferHelper helper, bool largeAddressAware = false)
{
// Is this Windows on Windows 64? (x86 app running on x64 Windows)
IsWow64Process(helper.Process.Handle, out bool isWow64);
GetSystemInfo(out SYSTEM_INFO systemInfo);
long maxAddress = 0x7FFFFFFF;

// Check for large address aware
if (largeAddressAware && IntPtr.Size == 4 && (uint)systemInfo.lpMaximumApplicationAddress > maxAddress)
maxAddress = (uint)systemInfo.lpMaximumApplicationAddress;

// Check if 64bit.
if (systemInfo.wProcessorArchitecture == ProcessorArchitecture.PROCESSOR_ARCHITECTURE_AMD64 && !isWow64)
maxAddress = (long)systemInfo.lpMaximumApplicationAddress;

return (IntPtr) maxAddress;
return (UIntPtr)maxAddress;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Platforms>AnyCPU;x86</Platforms>
<Platforms>x64</Platforms>

<!-- Prevent warnings from unused code in dependencies -->
<TrimmerDefaultAction>link</TrimmerDefaultAction>
Expand All @@ -15,16 +15,6 @@
<TrimmerRootAssembly Include="Reloaded.Memory.Buffers" />
</ItemGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Prefer32Bit>false</Prefer32Bit>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<Prefer32Bit>false</Prefer32Bit>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
Expand Down
28 changes: 12 additions & 16 deletions Source/Reloaded.Memory.Buffers.sln
Original file line number Diff line number Diff line change
@@ -1,36 +1,32 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28822.285
# Visual Studio Version 17
VisualStudioVersion = 17.3.32519.111
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reloaded.Memory.Buffers.Tests", "Reloaded.Memory.Buffers.Tests\Reloaded.Memory.Buffers.Tests.csproj", "{7F503240-976D-44EF-9DCB-5C491EDB8A04}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reloaded.Memory.Buffers", "Reloaded.Memory.Buffers\Reloaded.Memory.Buffers.csproj", "{63205E76-CB23-4EA8-B58B-71A911EF3788}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reloaded.Memory.Buffers.Tests.x86", "Reloaded.Memory.Buffers.Tests.x86\Reloaded.Memory.Buffers.Tests.x86.csproj", "{F5184076-F06E-4687-9063-596095694576}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7F503240-976D-44EF-9DCB-5C491EDB8A04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F503240-976D-44EF-9DCB-5C491EDB8A04}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F503240-976D-44EF-9DCB-5C491EDB8A04}.Debug|x86.ActiveCfg = Debug|x86
{7F503240-976D-44EF-9DCB-5C491EDB8A04}.Debug|x86.Build.0 = Debug|x86
{7F503240-976D-44EF-9DCB-5C491EDB8A04}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F503240-976D-44EF-9DCB-5C491EDB8A04}.Release|Any CPU.Build.0 = Release|Any CPU
{7F503240-976D-44EF-9DCB-5C491EDB8A04}.Release|x86.ActiveCfg = Release|x86
{7F503240-976D-44EF-9DCB-5C491EDB8A04}.Release|x86.Build.0 = Release|x86
{7F503240-976D-44EF-9DCB-5C491EDB8A04}.Debug|Any CPU.ActiveCfg = Debug|x64
{7F503240-976D-44EF-9DCB-5C491EDB8A04}.Debug|Any CPU.Build.0 = Debug|x64
{7F503240-976D-44EF-9DCB-5C491EDB8A04}.Release|Any CPU.ActiveCfg = Release|x64
{7F503240-976D-44EF-9DCB-5C491EDB8A04}.Release|Any CPU.Build.0 = Release|x64
{63205E76-CB23-4EA8-B58B-71A911EF3788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{63205E76-CB23-4EA8-B58B-71A911EF3788}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63205E76-CB23-4EA8-B58B-71A911EF3788}.Debug|x86.ActiveCfg = Debug|x86
{63205E76-CB23-4EA8-B58B-71A911EF3788}.Debug|x86.Build.0 = Debug|x86
{63205E76-CB23-4EA8-B58B-71A911EF3788}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63205E76-CB23-4EA8-B58B-71A911EF3788}.Release|Any CPU.Build.0 = Release|Any CPU
{63205E76-CB23-4EA8-B58B-71A911EF3788}.Release|x86.ActiveCfg = Release|x86
{63205E76-CB23-4EA8-B58B-71A911EF3788}.Release|x86.Build.0 = Release|x86
{F5184076-F06E-4687-9063-596095694576}.Debug|Any CPU.ActiveCfg = Debug|x86
{F5184076-F06E-4687-9063-596095694576}.Debug|Any CPU.Build.0 = Debug|x86
{F5184076-F06E-4687-9063-596095694576}.Release|Any CPU.ActiveCfg = Release|x86
{F5184076-F06E-4687-9063-596095694576}.Release|Any CPU.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Loading

0 comments on commit c8f896a

Please sign in to comment.