Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MemoryMappedFile for .NET Framework 3.5 #544

Merged
merged 5 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/AsmResolver/AsmResolver.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net35'">
<PackageReference Include="MonoMod.Backports" Version="1.1.0" />
</ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions src/AsmResolver/IO/ByteArrayInputFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ public ByteArrayInputFile(string? filePath, byte[] data, ulong baseAddress)
/// <inheritdoc />
public uint Length => (uint) _dataSource.Length;

/// <inheritdoc />
public ulong BaseAddress => 0;

/// <inheritdoc />
public BinaryStreamReader CreateReader(ulong address, uint rva, uint length) =>
new(_dataSource, address, rva, length);
Expand Down
10 changes: 9 additions & 1 deletion src/AsmResolver/IO/IInputFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ uint Length
get;
}

/// <summary>
/// Gets the base address of the data in this input.
/// </summary>
ulong BaseAddress
{
get;
}

/// <summary>
/// Creates a new binary reader at the provided address.
/// </summary>
Expand All @@ -47,6 +55,6 @@ public static partial class IOExtensions
/// <param name="factory">The factory to use.</param>
/// <returns>The constructed reader.</returns>
public static BinaryStreamReader CreateReader(this IInputFile factory)
=> factory.CreateReader(0, 0, factory.Length);
=> factory.CreateReader(factory.BaseAddress, 0, factory.Length);
}
}
85 changes: 0 additions & 85 deletions src/AsmResolver/IO/MemoryMappedDataSource.cs

This file was deleted.

4 changes: 0 additions & 4 deletions src/AsmResolver/IO/MemoryMappedFileService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#if !NET35

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -39,5 +37,3 @@ public void Dispose()
}
}
}

#endif
21 changes: 9 additions & 12 deletions src/AsmResolver/IO/MemoryMappedInputFile.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#if !NET35

using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using AsmResolver.Shims;

namespace AsmResolver.IO
{
Expand All @@ -11,19 +9,18 @@ namespace AsmResolver.IO
/// </summary>
public sealed class MemoryMappedInputFile : IInputFile
{
private readonly MemoryMappedFile _file;
private readonly MemoryMappedDataSource _dataSource;
private readonly MemoryMappedFileShim _file;
private readonly UnmanagedDataSource _dataSource;

/// <summary>
/// Creates a new reader factory for the provided file.
/// </summary>
/// <param name="filePath">The path to the file to read.</param>
public MemoryMappedInputFile(string filePath)
public unsafe MemoryMappedInputFile(string filePath)
{
FilePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
_file = MemoryMappedFile.CreateFromFile(filePath);
long fileSize = new FileInfo(filePath).Length;
_dataSource = new MemoryMappedDataSource(_file.CreateViewAccessor(0, fileSize), (ulong) fileSize);
_file = new MemoryMappedFileShim(filePath);
_dataSource = new UnmanagedDataSource(_file.BasePointer, (ulong)_file.Size);
}

/// <inheritdoc />
Expand All @@ -35,6 +32,9 @@ public string FilePath
/// <inheritdoc />
public uint Length => (uint) _dataSource.Length;

/// <inheritdoc />
public unsafe ulong BaseAddress => (ulong)_file.BasePointer;

/// <inheritdoc />
public BinaryStreamReader CreateReader(ulong address, uint rva, uint length) =>
new(_dataSource, address, rva, length);
Expand All @@ -43,9 +43,6 @@ public string FilePath
public void Dispose()
{
_file.Dispose();
_dataSource.Dispose();
}
}
}

#endif
52 changes: 52 additions & 0 deletions src/AsmResolver/Shims/MemoryMappedFileShim.Unix.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace AsmResolver.Shims;

internal sealed unsafe partial class MemoryMappedFileShim
{
private const int SEEK_END = 2;
private const int PROT_READ = 1;
private const int MAP_PRIVATE = 2;

[DllImport("libc", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
private static extern int open([In] string path, uint flags);

[DllImport("libc", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool close(int fd);

[DllImport("libc", ExactSpelling = true, SetLastError = true)]
private static extern nint mmap(void* addr, nuint length, int prot, int flags, int fd, long offset);

[DllImport("libc", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool munmap(void* adrr, nuint length);

[DllImport("libc", ExactSpelling = true, SetLastError = true)]
private static extern long lseek(int fd, long offset, int whence);

private void AllocateFileUnix(string path)
{
int fd = open(path, 0);
if (fd == -1)
throw new Win32Exception();
Washi1337 marked this conversation as resolved.
Show resolved Hide resolved

_size = lseek(fd, 0, SEEK_END);
if (_size == -1)
throw new Win32Exception();

nint mapping = mmap(null, (nuint)_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapping == -1)
throw new Win32Exception();
_file = (byte*)mapping;

// mmap explicitly documents that it is fine to close the fd after mapping
close(fd);
}

private void DisposeCoreUnix(void* filePointer)
{
munmap(filePointer, (nuint)_size);
}
}
74 changes: 74 additions & 0 deletions src/AsmResolver/Shims/MemoryMappedFileShim.Windows.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;

namespace AsmResolver.Shims;

internal sealed unsafe partial class MemoryMappedFileShim
{
private const uint GENERIC_READ = 0x80000000;
private const uint FILE_SHARE_READ = 0x2;
private const uint OPEN_EXISTING = 3;
private const uint PAGE_READONLY = 2;
// not on msdn, taken from https://github.com/terrafx/terrafx.interop.windows/blob/e681ccb7239bf9f5629083cd1e396b2876c24aae/sources/Interop/Windows/Windows/um/memoryapi/FILE.cs#L14
private const uint FILE_MAP_READ = 4;

private void* _fileHandle;
private void* _mappingHandle;

[DllImport("kernel32", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
private static extern nint CreateFileW(char* name, uint access, uint shareMode, void* securityAttributes, uint create, uint attributes);

[DllImport("kernel32", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetFileSizeEx(void* handle, out long size);

[DllImport("kernel32", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(void* handle);

[DllImport("kernel32", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
private static extern nint CreateFileMappingA(void* handle, void* mapAttributes, uint protection, uint maxSizeHigh,
uint maxSizeLow, sbyte* name);

[DllImport("kernel32", ExactSpelling = true, SetLastError = true)]
private static extern void* MapViewOfFile(void* handle, uint access, uint offsetHigh, uint offsetLow, nuint size);
Washi1337 marked this conversation as resolved.
Show resolved Hide resolved

[DllImport("kernel32", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnmapViewOfFile(void* p);

private void AllocateFileWindows(string path)
{
nint handle;
fixed (char* pathPointer = path)
{
handle = CreateFileW(pathPointer, GENERIC_READ, FILE_SHARE_READ, null, OPEN_EXISTING, 0);
}
if (handle == -1)
throw new Win32Exception();

_fileHandle = (void*)handle;
if (!GetFileSizeEx(_fileHandle, out _size))
throw new Win32Exception();

handle = CreateFileMappingA(_fileHandle, null, PAGE_READONLY, 0, 0, null);
if (handle == -1)
throw new Win32Exception();
_mappingHandle = (void*)handle;

_file = (byte*)MapViewOfFile(_mappingHandle, FILE_MAP_READ, 0, 0, 0);
if (_file == null)
throw new Win32Exception();
}

private void DisposeCoreWindows(void* filePointer)
{
if (!UnmapViewOfFile(filePointer))
return;
if (!CloseHandle(_mappingHandle))
return;
CloseHandle(_fileHandle);
}
}
56 changes: 56 additions & 0 deletions src/AsmResolver/Shims/MemoryMappedFileShim.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Threading;

namespace AsmResolver.Shims;

internal sealed unsafe partial class MemoryMappedFileShim : IDisposable
{
private byte* _file;
private long _size;

public MemoryMappedFileShim(string path)
{
if (RuntimeInformationShim.IsRunningOnWindows)
AllocateFileWindows(path);
else
AllocateFileUnix(path);
}

public long Size => _size;
public byte* BasePointer => _file;

public byte ReadByte(long address)
{
if (_file == null)
throw new ObjectDisposedException("disposed");
if ((ulong)address >= (ulong)_size)
throw new ArgumentOutOfRangeException(nameof(address));
return _file[address];
}

private void DisposeCore()
{
void* filePointer;
fixed (byte** p = &_file)
{
filePointer = (void*)Interlocked.Exchange(ref *(IntPtr*)p, default);
}
if (filePointer == null)
return;
if (RuntimeInformationShim.IsRunningOnWindows)
DisposeCoreWindows(filePointer);
else
DisposeCoreUnix(filePointer);
}

public void Dispose()
{
DisposeCore();
GC.SuppressFinalize(this);
}

~MemoryMappedFileShim()
{
DisposeCore();
}
}
3 changes: 2 additions & 1 deletion test/AsmResolver.Tests/IO/MemoryMappedIOTest.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.IO;
using AsmResolver.IO;
using AsmResolver.Tests.Runners;
Expand Down Expand Up @@ -34,7 +35,7 @@ public void CreateReaderPastStreamShouldThrow()
File.WriteAllBytes(tempPath, contents);

using var service = new MemoryMappedFileService();
Assert.Throws<EndOfStreamException>(() =>
Assert.Throws<ArgumentOutOfRangeException>(() =>
service.OpenFile(tempPath).CreateReader(0, 0, (uint) (contents.Length + 1)));
}
}
Expand Down