UnixPageAllocator.cs 3.7 KB

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