HookUtils.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. #if !(UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX)
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Reflection;
  5. using System.Runtime.InteropServices;
  6. using System.Text;
  7. using UnityEngine;
  8. namespace MonoHook
  9. {
  10. public static unsafe class HookUtils
  11. {
  12. [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
  13. delegate void DelegateFlushICache(void* code, int size); // delegate * unmanaged[Cdecl] <void, byte, uint> native_flush_cache_fun_ptr; // unsupported at C# 8.0
  14. static DelegateFlushICache flush_icache;
  15. private static readonly long _Pagesize;
  16. static HookUtils()
  17. {
  18. PropertyInfo p_SystemPageSize = typeof(Environment).GetProperty("SystemPageSize");
  19. if (p_SystemPageSize == null)
  20. throw new NotSupportedException("Unsupported runtime");
  21. _Pagesize = (int)p_SystemPageSize.GetValue(null, new object[0]);
  22. SetupFlushICacheFunc();
  23. }
  24. public static void MemCpy(void* pDst, void* pSrc, int len)
  25. {
  26. byte* pDst_ = (byte*)pDst;
  27. byte* pSrc_ = (byte*)pSrc;
  28. for (int i = 0; i < len; i++)
  29. *pDst_++ = *pSrc_++;
  30. }
  31. public static void MemCpy_Jit(void* pDst, byte[] src)
  32. {
  33. fixed (void* p = &src[0])
  34. {
  35. MemCpy(pDst, p, src.Length);
  36. }
  37. }
  38. /// <summary>
  39. /// set flags of address to `read write execute`
  40. /// </summary>
  41. public static void SetAddrFlagsToRWX(IntPtr ptr, int size)
  42. {
  43. if (ptr == IntPtr.Zero)
  44. return;
  45. #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
  46. uint oldProtect;
  47. bool ret = VirtualProtect(ptr, (uint)size, Protection.PAGE_EXECUTE_READWRITE, out oldProtect);
  48. UnityEngine.Debug.Assert(ret);
  49. #else
  50. SetMemPerms(ptr,(ulong)size,MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
  51. #endif
  52. }
  53. public static void FlushICache(void* code, int size)
  54. {
  55. if (code == null)
  56. return;
  57. flush_icache?.Invoke(code, size);
  58. #if ENABLE_HOOK_DEBUG
  59. Debug.Log($"flush icache at 0x{(IntPtr.Size == 4 ? (uint)code : (ulong)code):x}, size:{size}");
  60. #endif
  61. }
  62. public static KeyValuePair<long, long> GetPageAlignedAddr(long code, int size)
  63. {
  64. long pagesize = _Pagesize;
  65. long startPage = (code) & ~(pagesize - 1);
  66. long endPage = (code + size + pagesize - 1) & ~(pagesize - 1);
  67. return new KeyValuePair<long, long>(startPage, endPage);
  68. }
  69. const int PRINT_SPLIT = 4;
  70. const int PRINT_COL_SIZE = PRINT_SPLIT * 4;
  71. public static string HexToString(void* ptr, int size, int offset = 0)
  72. {
  73. Func<IntPtr, string> formatAddr = (IntPtr addr__) => IntPtr.Size == 4 ? $"0x{(uint)addr__:x}" : $"0x{(ulong)addr__:x}";
  74. byte* addr = (byte*)ptr;
  75. StringBuilder sb = new StringBuilder(1024);
  76. sb.AppendLine($"addr:{formatAddr(new IntPtr(addr))}");
  77. addr += offset;
  78. size += Math.Abs(offset);
  79. int count = 0;
  80. while (true)
  81. {
  82. sb.Append($"\r\n{formatAddr(new IntPtr(addr + count))}: ");
  83. for (int i = 1; i < PRINT_COL_SIZE + 1; i++)
  84. {
  85. if (count >= size)
  86. goto END;
  87. sb.Append($"{*(addr + count):x2}");
  88. if (i % PRINT_SPLIT == 0)
  89. sb.Append(" ");
  90. count++;
  91. }
  92. }
  93. END:;
  94. return sb.ToString();
  95. }
  96. static void SetupFlushICacheFunc()
  97. {
  98. string processorType = SystemInfo.processorType.ToLowerInvariant();
  99. if (processorType.Contains("intel") || processorType.Contains("amd"))
  100. return;
  101. if (IntPtr.Size == 4)
  102. {
  103. // never release, so save GCHandle is unnecessary
  104. s_ptr_flush_icache_arm32 = GCHandle.Alloc(s_flush_icache_arm32, GCHandleType.Pinned).AddrOfPinnedObject().ToPointer();
  105. SetAddrFlagsToRWX(new IntPtr(s_ptr_flush_icache_arm32), s_flush_icache_arm32.Length);
  106. flush_icache = Marshal.GetDelegateForFunctionPointer<DelegateFlushICache>(new IntPtr(s_ptr_flush_icache_arm32));
  107. }
  108. else
  109. {
  110. s_ptr_flush_icache_arm64 = GCHandle.Alloc(s_flush_icache_arm64, GCHandleType.Pinned).AddrOfPinnedObject().ToPointer();
  111. SetAddrFlagsToRWX(new IntPtr(s_ptr_flush_icache_arm64), s_flush_icache_arm64.Length);
  112. flush_icache = Marshal.GetDelegateForFunctionPointer<DelegateFlushICache>(new IntPtr(s_ptr_flush_icache_arm64));
  113. }
  114. #if ENABLE_HOOK_DEBUG
  115. Debug.Log($"flush_icache delegate is {((flush_icache != null) ? "not " : "")}null");
  116. #endif
  117. }
  118. static void* s_ptr_flush_icache_arm32, s_ptr_flush_icache_arm64;
  119. private static byte[] s_flush_icache_arm32 = new byte[]
  120. {
  121. // void cdecl mono_arch_flush_icache (guint8 *code, gint size)
  122. 0x00, 0x48, 0x2D, 0xE9, // PUSH {R11,LR}
  123. 0x0D, 0xB0, 0xA0, 0xE1, // MOV R11, SP
  124. 0x08, 0xD0, 0x4D, 0xE2, // SUB SP, SP, #8
  125. 0x04, 0x00, 0x8D, 0xE5, // STR R0, [SP,#8+var_4]
  126. 0x00, 0x10, 0x8D, 0xE5, // STR R1, [SP,#8+var_8]
  127. 0x04, 0x00, 0x9D, 0xE5, // LDR R0, [SP,#8+var_4]
  128. 0x04, 0x10, 0x9D, 0xE5, // LDR R1, [SP,#8+var_4]
  129. 0x00, 0x20, 0x9D, 0xE5, // LDR R2, [SP,#8+var_8]
  130. 0x02, 0x10, 0x81, 0xE0, // ADD R1, R1, R2
  131. 0x01, 0x00, 0x00, 0xEB, // BL __clear_cache
  132. 0x0B, 0xD0, 0xA0, 0xE1, // MOV SP, R11
  133. 0x00, 0x88, 0xBD, 0xE8, // POP {R11,PC}
  134. // __clear_cache ; CODE XREF: j___clear_cache+8↑j
  135. 0x80, 0x00, 0x2D, 0xE9, // PUSH { R7 }
  136. 0x02, 0x70, 0x00, 0xE3, 0x0F, 0x70, 0x40, 0xE3, // MOV R7, #0xF0002
  137. 0x00, 0x20, 0xA0, 0xE3, // MOV R2, #0
  138. 0x00, 0x00, 0x00, 0xEF, // SVC 0
  139. 0x80, 0x00, 0xBD, 0xE8, // POP {R7}
  140. 0x1E, 0xFF, 0x2F, 0xE1, // BX LR
  141. };
  142. private static byte[] s_flush_icache_arm64 = new byte[] // X0: code, W1: size
  143. {
  144. // void cdecl mono_arch_flush_icache (guint8 *code, gint size)
  145. 0xFF, 0xC3, 0x00, 0xD1, // SUB SP, SP, #0x30
  146. 0xE8, 0x03, 0x7E, 0xB2, // MOV X8, #4
  147. 0xE0, 0x17, 0x00, 0xF9, // STR X0, [SP,#0x30+var_8]
  148. 0xE1, 0x27, 0x00, 0xB9, // STR W1, [SP,#0x30+var_C]
  149. 0xE0, 0x17, 0x40, 0xF9, // LDR X0, [SP,#0x30+var_8]
  150. 0xE9, 0x27, 0x80, 0xB9, // LDRSW X9, [SP,#0x30+var_C]
  151. 0x09, 0x00, 0x09, 0x8B, // ADD X9, X0, X9
  152. 0xE9, 0x0F, 0x00, 0xF9, // STR X9, [SP,#0x30+var_18]
  153. 0xE8, 0x07, 0x00, 0xF9, // STR X8, [SP,#0x30+var_28]
  154. 0xE8, 0x03, 0x00, 0xF9, // STR X8, [SP,#0x30+var_30]
  155. 0xE8, 0x17, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_8]
  156. 0x08, 0xF5, 0x7E, 0x92, // AND X8, X8, #0xFFFFFFFFFFFFFFFC
  157. 0xE8, 0x0B, 0x00, 0xF9, // STR X8, [SP,#0x30+var_20]
  158. // loc_590 ; CODE XREF: mono_arch_flush_icache(uchar*, int)+58↓j
  159. 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20]
  160. 0xE9, 0x0F, 0x40, 0xF9, // LDR X9, [SP,#0x30+var_18]
  161. 0x1F, 0x01, 0x09, 0xEB, // CMP X8, X9
  162. 0xE2, 0x00, 0x00, 0x54, // B.CS loc_5B8
  163. 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20]
  164. 0x28, 0x7E, 0x0B, 0xD5, // SYS #3, c7, c14, #1, X8
  165. 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20]
  166. 0x08, 0x11, 0x00, 0x91, // ADD X8, X8, #4
  167. 0xE8, 0x0B, 0x00, 0xF9, // STR X8, [SP,#0x30+var_20]
  168. 0xF7, 0xFF, 0xFF, 0x17, // B loc_590
  169. // ; ---------------------------------------------------------------------------
  170. // loc_5B8 ; CODE XREF: mono_arch_flush_icache(uchar *, int)+40↑j
  171. 0x9F, 0x3B, 0x03, 0xD5, // DSB ISH
  172. 0xE8, 0x17, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_8]
  173. 0x08, 0xF5, 0x7E, 0x92, // AND X8, X8, #0xFFFFFFFFFFFFFFFC
  174. 0xE8, 0x0B, 0x00, 0xF9, // STR X8, [SP,#0x30+var_20]
  175. // loc_5C8 ; CODE XREF: mono_arch_flush_icache(uchar *, int)+90↓j
  176. 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20]
  177. 0xE9, 0x0F, 0x40, 0xF9, // LDR X9, [SP,#0x30+var_18]
  178. 0x1F, 0x01, 0x09, 0xEB, // CMP X8, X9
  179. 0xE2, 0x00, 0x00, 0x54, // B.CS loc_5F0
  180. 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20]
  181. 0x28, 0x75, 0x0B, 0xD5, // SYS #3, c7, c5, #1, X8
  182. 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20]
  183. 0x08, 0x11, 0x00, 0x91, // ADD X8, X8, #4
  184. 0xE8, 0x0B, 0x00, 0xF9, // STR X8, [SP,#0x30+var_20]
  185. 0xF7, 0xFF, 0xFF, 0x17, // B loc_5C8
  186. // ; ---------------------------------------------------------------------------
  187. // loc_5F0 ; CODE XREF: mono_arch_flush_icache(uchar *, int)+78↑j
  188. 0x9F, 0x3B, 0x03, 0xD5, // DSB ISH
  189. 0xDF, 0x3F, 0x03, 0xD5, // ISB
  190. 0xFF, 0xC3, 0x00, 0x91, // ADD SP, SP, #0x30 ; '0'
  191. 0xC0, 0x03, 0x5F, 0xD6, // RET
  192. };
  193. #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
  194. [Flags]
  195. public enum Protection
  196. {
  197. PAGE_NOACCESS = 0x01,
  198. PAGE_READONLY = 0x02,
  199. PAGE_READWRITE = 0x04,
  200. PAGE_WRITECOPY = 0x08,
  201. PAGE_EXECUTE = 0x10,
  202. PAGE_EXECUTE_READ = 0x20,
  203. PAGE_EXECUTE_READWRITE = 0x40,
  204. PAGE_EXECUTE_WRITECOPY = 0x80,
  205. PAGE_GUARD = 0x100,
  206. PAGE_NOCACHE = 0x200,
  207. PAGE_WRITECOMBINE = 0x400
  208. }
  209. [DllImport("kernel32")]
  210. public static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, Protection flNewProtect, out uint lpflOldProtect);
  211. #else
  212. [Flags]
  213. public enum MmapProts : int {
  214. PROT_READ = 0x1,
  215. PROT_WRITE = 0x2,
  216. PROT_EXEC = 0x4,
  217. PROT_NONE = 0x0,
  218. PROT_GROWSDOWN = 0x01000000,
  219. PROT_GROWSUP = 0x02000000,
  220. }
  221. [DllImport("libc", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
  222. private static extern int mprotect(IntPtr start, IntPtr len, MmapProts prot);
  223. public static unsafe void SetMemPerms(IntPtr start, ulong len, MmapProts prot) {
  224. var requiredAddr = GetPageAlignedAddr(start.ToInt64(), (int)len);
  225. long startPage = requiredAddr.Key;
  226. long endPage = requiredAddr.Value;
  227. if (mprotect((IntPtr) startPage, (IntPtr) (endPage - startPage), prot) != 0)
  228. throw new Exception($"mprotect with prot:{prot} fail!");
  229. }
  230. #endif
  231. }
  232. }
  233. #endif