UnixPageAllocator.cs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Runtime.CompilerServices;
  5. using System.Runtime.InteropServices;
  6. using MonoMod.Utils;
  7. namespace BepInEx.IL2CPP.Allocator
  8. {
  9. /// <summary>
  10. /// Based on https://github.com/kubo/funchook
  11. /// </summary>
  12. internal abstract class UnixPageAllocator : PageAllocator
  13. {
  14. protected abstract IMemoryMapper OpenMemoryMap();
  15. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  16. private static bool CheckFreeRegionBefore(IntPtr start, IntPtr hint, IntPtr[] result)
  17. {
  18. if (start.ToInt64() < hint.ToInt64())
  19. {
  20. var addr = start - PAGE_SIZE;
  21. if (hint.ToInt64() - addr.ToInt64() < int.MaxValue)
  22. result[0] = addr;
  23. }
  24. return false;
  25. }
  26. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  27. private static bool CheckFreeRegionAfter(IntPtr end, IntPtr hint, IntPtr[] result)
  28. {
  29. if (hint.ToInt64() < end.ToInt64())
  30. {
  31. if (end.ToInt64() - hint.ToInt64() < int.MaxValue)
  32. result[1] = end;
  33. return true;
  34. }
  35. return false;
  36. }
  37. private IntPtr[] GetFreeAddresses(IntPtr hint)
  38. {
  39. var result = new IntPtr[2];
  40. var prevEnd = IntPtr.Zero;
  41. using var mapper = OpenMemoryMap();
  42. while (mapper.FindNextFree(out var start, out var end))
  43. {
  44. if ((prevEnd + PAGE_SIZE).ToInt64() <= start.ToInt64())
  45. if (CheckFreeRegionBefore(start, hint, result) || CheckFreeRegionAfter(prevEnd, hint, result))
  46. return result;
  47. prevEnd = end;
  48. }
  49. if (CheckFreeRegionAfter(prevEnd, hint, result))
  50. return result;
  51. throw new PageAllocatorException($"Could not find free region near {hint.ToInt64():X8}");
  52. }
  53. protected override IntPtr AllocateChunk(IntPtr hint)
  54. {
  55. /* From https://github.com/kubo/funchook/blob/master/src/funchook_unix.c#L251-L254:
  56. * Loop three times just to avoid rare cases such as
  57. * unused memory region is used between 'get_free_address()'
  58. * and 'mmap()'.
  59. */
  60. const int retryCount = 3;
  61. for (var attempt = 0; attempt < retryCount; attempt++)
  62. {
  63. var freeAdrresses = GetFreeAddresses(hint);
  64. // Try to use addr[1] (allocated after original method) first, then try before
  65. for (int i = freeAdrresses.Length - 1; i >= 0; i--)
  66. {
  67. var addr = freeAdrresses[i];
  68. if (addr == IntPtr.Zero)
  69. continue;
  70. var result = Unix.mmap(freeAdrresses[i], (UIntPtr)PAGE_SIZE, Unix.Protection.PROT_READ | Unix.Protection.PROT_WRITE, Unix.MapFlags.MAP_PRIVATE | Unix.MapFlags.MAP_ANONYMOUS, -1, 0);
  71. if (result == freeAdrresses[i])
  72. return result;
  73. if (result == Unix.MAP_FAILED)
  74. throw new Win32Exception(Marshal.GetLastWin32Error()); // Yes, this should work on unix too
  75. Unix.munmap(result, (UIntPtr)PAGE_SIZE);
  76. }
  77. }
  78. throw new PageAllocatorException("Failed to allocate memory in unused regions");
  79. }
  80. protected interface IMemoryMapper : IDisposable
  81. {
  82. bool FindNextFree(out IntPtr start, out IntPtr end);
  83. }
  84. private static class Unix
  85. {
  86. public static readonly IntPtr MAP_FAILED = new IntPtr(-1);
  87. [DynDllImport("mmap")]
  88. public static mmapDelegate mmap;
  89. [DynDllImport("munmap")]
  90. public static munmapDelegate munmap;
  91. public delegate IntPtr mmapDelegate(IntPtr addr, UIntPtr length, Protection prot, MapFlags flags, int fd, int offset);
  92. public delegate int munmapDelegate(IntPtr addr, UIntPtr length);
  93. [Flags]
  94. public enum MapFlags
  95. {
  96. MAP_PRIVATE = 0x02,
  97. MAP_ANONYMOUS = 0x20
  98. }
  99. [Flags]
  100. public enum Protection
  101. {
  102. PROT_READ = 0x1,
  103. PROT_WRITE = 0x2
  104. }
  105. static Unix()
  106. {
  107. typeof(Unix).ResolveDynDllImports(new Dictionary<string, List<DynDllMapping>>
  108. {
  109. ["libc"] = new List<DynDllMapping>
  110. {
  111. "libc.so.6", // Ubuntu glibc
  112. "libc", // Linux glibc,
  113. "/usr/lib/libSystem.dylib" // OSX POSIX
  114. }
  115. });
  116. }
  117. }
  118. }
  119. }