diff --git a/src/AsmResolver/AsmResolver.csproj b/src/AsmResolver/AsmResolver.csproj
index 0b2a3f83e..ddaa59dfb 100644
--- a/src/AsmResolver/AsmResolver.csproj
+++ b/src/AsmResolver/AsmResolver.csproj
@@ -28,7 +28,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/src/AsmResolver/IO/ByteArrayInputFile.cs b/src/AsmResolver/IO/ByteArrayInputFile.cs
index e76d080b8..d6e463782 100644
--- a/src/AsmResolver/IO/ByteArrayInputFile.cs
+++ b/src/AsmResolver/IO/ByteArrayInputFile.cs
@@ -49,6 +49,9 @@ public ByteArrayInputFile(string? filePath, byte[] data, ulong baseAddress)
///
public uint Length => (uint) _dataSource.Length;
+ ///
+ public ulong BaseAddress => 0;
+
///
public BinaryStreamReader CreateReader(ulong address, uint rva, uint length) =>
new(_dataSource, address, rva, length);
diff --git a/src/AsmResolver/IO/IInputFile.cs b/src/AsmResolver/IO/IInputFile.cs
index 6c5972fd7..2d0063ee8 100644
--- a/src/AsmResolver/IO/IInputFile.cs
+++ b/src/AsmResolver/IO/IInputFile.cs
@@ -27,6 +27,14 @@ uint Length
get;
}
+ ///
+ /// Gets the base address of the data in this input.
+ ///
+ ulong BaseAddress
+ {
+ get;
+ }
+
///
/// Creates a new binary reader at the provided address.
///
@@ -47,6 +55,6 @@ public static partial class IOExtensions
/// The factory to use.
/// The constructed reader.
public static BinaryStreamReader CreateReader(this IInputFile factory)
- => factory.CreateReader(0, 0, factory.Length);
+ => factory.CreateReader(factory.BaseAddress, 0, factory.Length);
}
}
diff --git a/src/AsmResolver/IO/MemoryMappedDataSource.cs b/src/AsmResolver/IO/MemoryMappedDataSource.cs
deleted file mode 100644
index 71d09193a..000000000
--- a/src/AsmResolver/IO/MemoryMappedDataSource.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-#if !NET35
-
-using System;
-using System.IO.MemoryMappedFiles;
-
-namespace AsmResolver.IO
-{
- ///
- /// Represents a data source that obtains its data from a memory mapped file.
- ///
- public sealed class MemoryMappedDataSource : IDataSource, IDisposable
-#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
- , ISpanDataSource
-#endif
- {
- private readonly MemoryMappedViewAccessor _accessor;
-
- ///
- /// Creates a new instance of the class.
- ///
- /// The memory accessor to use.
- /// The length of the data.
- public MemoryMappedDataSource(MemoryMappedViewAccessor accessor, ulong length)
- {
- Length = length;
- _accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));
- }
-
- ///
- public ulong BaseAddress => 0;
-
- ///
- public byte this[ulong address] => _accessor.ReadByte((long) address);
-
- ///
- public ulong Length
- {
- get;
- }
-
- ///
- public bool IsValidAddress(ulong address) => address < Length;
-
- ///
- public int ReadBytes(ulong address, byte[] buffer, int index, int count) =>
- _accessor.ReadArray((long) address, buffer, index, count);
-
-#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
- ///
- public unsafe int ReadBytes(ulong address, Span buffer)
- {
- if (!IsValidAddress(address))
- return 0;
-
- var handle = _accessor.SafeMemoryMappedViewHandle;
- int actualLength = (int) Math.Min(Length - address, (uint) buffer.Length);
-
-#if NET6_0_OR_GREATER
- handle.ReadSpan(address, buffer[..actualLength]);
-#else
- byte* pointer = null;
-
- try
- {
- handle.AcquirePointer(ref pointer);
- new ReadOnlySpan(pointer, actualLength).CopyTo(buffer);
- }
- finally
- {
- if (pointer != null)
- {
- handle.ReleasePointer();
- }
- }
-#endif
- return actualLength;
- }
-#endif
-
- ///
- public void Dispose() => _accessor?.Dispose();
- }
-}
-
-#endif
diff --git a/src/AsmResolver/IO/MemoryMappedFileService.cs b/src/AsmResolver/IO/MemoryMappedFileService.cs
index 6f96181cf..ab372ebe9 100644
--- a/src/AsmResolver/IO/MemoryMappedFileService.cs
+++ b/src/AsmResolver/IO/MemoryMappedFileService.cs
@@ -1,5 +1,3 @@
-#if !NET35
-
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@@ -39,5 +37,3 @@ public void Dispose()
}
}
}
-
-#endif
diff --git a/src/AsmResolver/IO/MemoryMappedInputFile.cs b/src/AsmResolver/IO/MemoryMappedInputFile.cs
index 7092dae0d..56fd2443d 100644
--- a/src/AsmResolver/IO/MemoryMappedInputFile.cs
+++ b/src/AsmResolver/IO/MemoryMappedInputFile.cs
@@ -1,8 +1,6 @@
-#if !NET35
-
using System;
using System.IO;
-using System.IO.MemoryMappedFiles;
+using AsmResolver.Shims;
namespace AsmResolver.IO
{
@@ -11,19 +9,18 @@ namespace AsmResolver.IO
///
public sealed class MemoryMappedInputFile : IInputFile
{
- private readonly MemoryMappedFile _file;
- private readonly MemoryMappedDataSource _dataSource;
+ private readonly MemoryMappedFileShim _file;
+ private readonly UnmanagedDataSource _dataSource;
///
/// Creates a new reader factory for the provided file.
///
/// The path to the file to read.
- 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);
}
///
@@ -35,6 +32,9 @@ public string FilePath
///
public uint Length => (uint) _dataSource.Length;
+ ///
+ public unsafe ulong BaseAddress => (ulong)_file.BasePointer;
+
///
public BinaryStreamReader CreateReader(ulong address, uint rva, uint length) =>
new(_dataSource, address, rva, length);
@@ -43,9 +43,6 @@ public string FilePath
public void Dispose()
{
_file.Dispose();
- _dataSource.Dispose();
}
}
}
-
-#endif
diff --git a/src/AsmResolver/Shims/MemoryMappedFileShim.Unix.cs b/src/AsmResolver/Shims/MemoryMappedFileShim.Unix.cs
new file mode 100644
index 000000000..8c7ed4a0e
--- /dev/null
+++ b/src/AsmResolver/Shims/MemoryMappedFileShim.Unix.cs
@@ -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();
+
+ _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);
+ }
+}
diff --git a/src/AsmResolver/Shims/MemoryMappedFileShim.Windows.cs b/src/AsmResolver/Shims/MemoryMappedFileShim.Windows.cs
new file mode 100644
index 000000000..bc13cb567
--- /dev/null
+++ b/src/AsmResolver/Shims/MemoryMappedFileShim.Windows.cs
@@ -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);
+
+ [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);
+ }
+}
diff --git a/src/AsmResolver/Shims/MemoryMappedFileShim.cs b/src/AsmResolver/Shims/MemoryMappedFileShim.cs
new file mode 100644
index 000000000..954cb5ee2
--- /dev/null
+++ b/src/AsmResolver/Shims/MemoryMappedFileShim.cs
@@ -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();
+ }
+}
diff --git a/test/AsmResolver.Tests/IO/MemoryMappedIOTest.cs b/test/AsmResolver.Tests/IO/MemoryMappedIOTest.cs
index 636f8d3d7..d7df8a91d 100644
--- a/test/AsmResolver.Tests/IO/MemoryMappedIOTest.cs
+++ b/test/AsmResolver.Tests/IO/MemoryMappedIOTest.cs
@@ -1,3 +1,4 @@
+using System;
using System.IO;
using AsmResolver.IO;
using AsmResolver.Tests.Runners;
@@ -34,7 +35,7 @@ public void CreateReaderPastStreamShouldThrow()
File.WriteAllBytes(tempPath, contents);
using var service = new MemoryMappedFileService();
- Assert.Throws(() =>
+ Assert.Throws(() =>
service.OpenFile(tempPath).CreateReader(0, 0, (uint) (contents.Length + 1)));
}
}