123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- using System;
- using System.Collections.Generic;
- using System.Runtime.CompilerServices;
- using MonoMod.Utils;
- namespace BepInEx.IL2CPP.Allocator
- {
- public class PageAllocatorException : Exception
- {
- public PageAllocatorException(string message) : base(message) { }
- }
- /// <summary>
- /// A general purpose page allocator for patching purposes.
- /// Allows to allocate pages (4k memory chunks) within the 1GB radius of a given address.
- /// </summary>
- /// <remarks>Based on https://github.com/kubo/funchook</remarks>
- internal abstract class PageAllocator
- {
- /// <summary>
- /// Common page size on Unix and Windows (4k).
- /// Call to <see cref="Allocate" /> will allocate a single page of this size.
- /// </summary>
- public const int PAGE_SIZE = 0x1000;
- /// <summary>
- /// Allocation granularity on Windows (but can be reused in other implementations).
- /// </summary>
- protected const int ALLOCATION_UNIT = 0x100000;
- protected const int PAGES_PER_UNIT = ALLOCATION_UNIT / PAGE_SIZE;
- private static PageAllocator instance;
- private readonly List<PageChunk> allocatedChunks = new List<PageChunk>();
- /// <summary>
- /// Platform-specific instance of page allocator.
- /// </summary>
- public static PageAllocator Instance => instance ??= Init();
- /// <summary>
- /// Allocates a single 64k chunk of memory near the given address
- /// </summary>
- /// <param name="hint">Address near which to attempt allocate the chunk</param>
- /// <returns>Allocated chunk</returns>
- /// <exception cref="PageAllocatorException">Allocation failed</exception>
- protected abstract IntPtr AllocateChunk(IntPtr hint);
- /// <summary>
- /// Allocates a single page of size <see cref="PAGE_SIZE" /> near the provided address.
- /// Attempts to allocate the page within the +-1GB region of the hinted address.
- /// </summary>
- /// <param name="hint">Address near which to attempt to allocate the page.</param>
- /// <returns>Address to the allocated page.</returns>
- public virtual IntPtr Allocate(IntPtr hint)
- {
- foreach (var allocatedChunk in allocatedChunks)
- {
- // Small shortcut to speed up page lookup
- if (allocatedChunk.UsedPages == PAGES_PER_UNIT)
- continue;
- for (var i = 0; i < allocatedChunk.Pages.Length; i++)
- {
- if (allocatedChunk.Pages[i])
- continue;
- var pageAddr = allocatedChunk.GetPage(i);
- if (!IsInRelJmpRange(hint, pageAddr))
- continue;
- allocatedChunk.Pages[i] = true;
- allocatedChunk.UsedPages++;
- return pageAddr;
- }
- }
- var chunk = new PageChunk
- {
- BaseAddress = AllocateChunk(hint)
- };
- allocatedChunks.Add(chunk);
- chunk.Pages[0] = true;
- chunk.UsedPages++;
- return chunk.BaseAddress;
- }
- /// <summary>
- /// Frees the page allocated with <see cref="Allocate" />
- /// </summary>
- /// <param name="page"></param>
- public void Free(IntPtr page)
- {
- foreach (var allocatedChunk in allocatedChunks)
- {
- long index = (page.ToInt64() - allocatedChunk.BaseAddress.ToInt64()) / PAGE_SIZE;
- if (index < 0 || index > PAGES_PER_UNIT)
- continue;
- allocatedChunk.Pages[index] = false;
- return;
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected static long RoundUp(long num, long unit)
- {
- return (num + unit - 1) & ~ (unit - 1);
- }
- /// <summary>
- /// Checks if the given address is within the relative jump range.
- /// </summary>
- /// <param name="src">Source address to jump from.</param>
- /// <param name="dst">Destination address to jump to.</param>
- /// <returns>True, if the distance between the addresses is within the relative jump range (usually 1GB), otherwise false.</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool IsInRelJmpRange(IntPtr src, IntPtr dst)
- {
- long diff = dst.ToInt64() - src.ToInt64();
- return int.MinValue <= diff && diff <= int.MaxValue;
- }
- private static PageAllocator Init()
- {
- return PlatformHelper.Current switch
- {
- var v when v.Is(Platform.Windows) => new WindowsPageAllocator(),
- var v when v.Is(Platform.Linux) => new LinuxPageAllocator(),
- var v when v.Is(Platform.MacOS) => new MacOsPageAllocator(),
- _ => throw new NotImplementedException()
- };
- }
- private class PageChunk
- {
- public readonly bool[] Pages = new bool[PAGES_PER_UNIT];
- public IntPtr BaseAddress;
- public int UsedPages;
- public IntPtr GetPage(int index)
- {
- return BaseAddress + index * PAGE_SIZE;
- }
- }
- }
- internal static class PlatformExt
- {
- public static bool Is(this Platform pl, Platform val)
- {
- return (pl & val) == val;
- }
- }
- }
|