virtmem.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. // [AsmJit]
  2. // Machine Code Generation for C++.
  3. //
  4. // [License]
  5. // Zlib - See LICENSE.md file in the package.
  6. #define ASMJIT_EXPORTS
  7. #include "../core/build.h"
  8. #ifndef ASMJIT_NO_JIT
  9. #include "../core/osutils.h"
  10. #include "../core/string.h"
  11. #include "../core/support.h"
  12. #include "../core/virtmem.h"
  13. #if !defined(_WIN32)
  14. #include <errno.h>
  15. #include <fcntl.h>
  16. #include <sys/mman.h>
  17. #include <sys/stat.h>
  18. #include <sys/types.h>
  19. #include <unistd.h>
  20. // Linux has a `memfd_create` syscall that we would like to use, if available.
  21. #if defined(__linux__)
  22. #include <sys/syscall.h>
  23. #endif
  24. // Apple recently introduced MAP_JIT flag, which we want to use.
  25. #if defined(__APPLE__)
  26. #include <TargetConditionals.h>
  27. #if TARGET_OS_OSX
  28. #include <sys/utsname.h>
  29. #endif
  30. // Older SDK doesn't define `MAP_JIT`.
  31. #ifndef MAP_JIT
  32. #define MAP_JIT 0x800
  33. #endif
  34. #endif
  35. // BSD/OSX: `MAP_ANONYMOUS` is not defined, `MAP_ANON` is.
  36. #if !defined(MAP_ANONYMOUS)
  37. #define MAP_ANONYMOUS MAP_ANON
  38. #endif
  39. #endif
  40. #include <atomic>
  41. #if defined(__APPLE__)
  42. #define ASMJIT_VM_SHM_DETECT 0
  43. #else
  44. #define ASMJIT_VM_SHM_DETECT 1
  45. #endif
  46. ASMJIT_BEGIN_NAMESPACE
  47. // ============================================================================
  48. // [asmjit::VirtMem - Utilities]
  49. // ============================================================================
  50. static const uint32_t VirtMem_dualMappingFilter[2] = {
  51. VirtMem::kAccessWrite,
  52. VirtMem::kAccessExecute
  53. };
  54. // ============================================================================
  55. // [asmjit::VirtMem - Virtual Memory [Windows]]
  56. // ============================================================================
  57. #if defined(_WIN32)
  58. struct ScopedHandle {
  59. inline ScopedHandle() noexcept
  60. : value(nullptr) {}
  61. inline ~ScopedHandle() noexcept {
  62. if (value != nullptr)
  63. ::CloseHandle(value);
  64. }
  65. HANDLE value;
  66. };
  67. static void VirtMem_getInfo(VirtMem::Info& vmInfo) noexcept {
  68. SYSTEM_INFO systemInfo;
  69. ::GetSystemInfo(&systemInfo);
  70. vmInfo.pageSize = Support::alignUpPowerOf2<uint32_t>(systemInfo.dwPageSize);
  71. vmInfo.pageGranularity = systemInfo.dwAllocationGranularity;
  72. }
  73. // Windows specific implementation that uses `VirtualAlloc` and `VirtualFree`.
  74. static DWORD VirtMem_accessToWinProtectFlags(uint32_t flags) noexcept {
  75. DWORD protectFlags;
  76. // READ|WRITE|EXECUTE.
  77. if (flags & VirtMem::kAccessExecute)
  78. protectFlags = (flags & VirtMem::kAccessWrite) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ;
  79. else if (flags & VirtMem::kAccessReadWrite)
  80. protectFlags = (flags & VirtMem::kAccessWrite) ? PAGE_READWRITE : PAGE_READONLY;
  81. else
  82. protectFlags = PAGE_NOACCESS;
  83. // Any other flags to consider?
  84. return protectFlags;
  85. }
  86. static DWORD VirtMem_accessToWinDesiredAccess(uint32_t flags) noexcept {
  87. DWORD access = (flags & VirtMem::kAccessWrite) ? FILE_MAP_WRITE : FILE_MAP_READ;
  88. if (flags & VirtMem::kAccessExecute)
  89. access |= FILE_MAP_EXECUTE;
  90. return access;
  91. }
  92. Error VirtMem::alloc(void** p, size_t size, uint32_t flags) noexcept {
  93. *p = nullptr;
  94. if (size == 0)
  95. return DebugUtils::errored(kErrorInvalidArgument);
  96. DWORD protectFlags = VirtMem_accessToWinProtectFlags(flags);
  97. void* result = ::VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, protectFlags);
  98. if (!result)
  99. return DebugUtils::errored(kErrorOutOfMemory);
  100. *p = result;
  101. return kErrorOk;
  102. }
  103. Error VirtMem::release(void* p, size_t size) noexcept {
  104. ASMJIT_UNUSED(size);
  105. if (ASMJIT_UNLIKELY(!::VirtualFree(p, 0, MEM_RELEASE)))
  106. return DebugUtils::errored(kErrorInvalidArgument);
  107. return kErrorOk;
  108. }
  109. Error VirtMem::protect(void* p, size_t size, uint32_t flags) noexcept {
  110. DWORD protectFlags = VirtMem_accessToWinProtectFlags(flags);
  111. DWORD oldFlags;
  112. if (::VirtualProtect(p, size, protectFlags, &oldFlags))
  113. return kErrorOk;
  114. return DebugUtils::errored(kErrorInvalidArgument);
  115. }
  116. Error VirtMem::allocDualMapping(DualMapping* dm, size_t size, uint32_t flags) noexcept {
  117. dm->ro = nullptr;
  118. dm->rw = nullptr;
  119. if (size == 0)
  120. return DebugUtils::errored(kErrorInvalidArgument);
  121. ScopedHandle handle;
  122. handle.value = ::CreateFileMappingW(
  123. INVALID_HANDLE_VALUE,
  124. nullptr,
  125. PAGE_EXECUTE_READWRITE,
  126. (DWORD)(uint64_t(size) >> 32),
  127. (DWORD)(size & 0xFFFFFFFFu),
  128. nullptr);
  129. if (ASMJIT_UNLIKELY(!handle.value))
  130. return DebugUtils::errored(kErrorOutOfMemory);
  131. void* ptr[2];
  132. for (uint32_t i = 0; i < 2; i++) {
  133. DWORD desiredAccess = VirtMem_accessToWinDesiredAccess(flags & ~VirtMem_dualMappingFilter[i]);
  134. ptr[i] = ::MapViewOfFile(handle.value, desiredAccess, 0, 0, size);
  135. if (ptr[i] == nullptr) {
  136. if (i == 0)
  137. ::UnmapViewOfFile(ptr[0]);
  138. return DebugUtils::errored(kErrorOutOfMemory);
  139. }
  140. }
  141. dm->ro = ptr[0];
  142. dm->rw = ptr[1];
  143. return kErrorOk;
  144. }
  145. Error VirtMem::releaseDualMapping(DualMapping* dm, size_t size) noexcept {
  146. ASMJIT_UNUSED(size);
  147. bool failed = false;
  148. if (!::UnmapViewOfFile(dm->ro))
  149. failed = true;
  150. if (dm->ro != dm->rw && !UnmapViewOfFile(dm->rw))
  151. failed = true;
  152. if (failed)
  153. return DebugUtils::errored(kErrorInvalidArgument);
  154. dm->ro = nullptr;
  155. dm->rw = nullptr;
  156. return kErrorOk;
  157. }
  158. #endif
  159. // ============================================================================
  160. // [asmjit::VirtMem - Virtual Memory [Posix]]
  161. // ============================================================================
  162. #if !defined(_WIN32)
  163. struct ScopedFD {
  164. inline ScopedFD() noexcept
  165. : value(-1) {}
  166. inline ~ScopedFD() noexcept {
  167. if (value != -1)
  168. close(value);
  169. }
  170. int value;
  171. };
  172. static void VirtMem_getInfo(VirtMem::Info& vmInfo) noexcept {
  173. uint32_t pageSize = uint32_t(::getpagesize());
  174. vmInfo.pageSize = pageSize;
  175. vmInfo.pageGranularity = Support::max<uint32_t>(pageSize, 65536);
  176. }
  177. // Some operating systems don't allow /dev/shm to be executable. On Linux this
  178. // happens when /dev/shm is mounted with 'noexec', which is enforced by systemd.
  179. // Other operating systems like OSX also restrict executable permissions regarding
  180. // /dev/shm, so we use a runtime detection before trying to allocate the requested
  181. // memory by the user. Sometimes we don't need the detection as we know it would
  182. // always result in 'kShmStrategyTmpDir'.
  183. enum ShmStrategy : uint32_t {
  184. kShmStrategyUnknown = 0,
  185. kShmStrategyDevShm = 1,
  186. kShmStrategyTmpDir = 2
  187. };
  188. // Posix specific implementation that uses `mmap()` and `munmap()`.
  189. static int VirtMem_accessToPosixProtection(uint32_t flags) noexcept {
  190. int protection = 0;
  191. if (flags & VirtMem::kAccessRead ) protection |= PROT_READ;
  192. if (flags & VirtMem::kAccessWrite ) protection |= PROT_READ | PROT_WRITE;
  193. if (flags & VirtMem::kAccessExecute) protection |= PROT_READ | PROT_EXEC;
  194. return protection;
  195. }
  196. // Translates libc errors specific to VirtualMemory mapping to `asmjit::Error`.
  197. static Error VirtMem_makeErrorFromErrno(int e) noexcept {
  198. switch (e) {
  199. case EACCES:
  200. case EAGAIN:
  201. case ENODEV:
  202. case EPERM:
  203. return kErrorInvalidState;
  204. case EFBIG:
  205. case ENOMEM:
  206. case EOVERFLOW:
  207. return kErrorOutOfMemory;
  208. case EMFILE:
  209. case ENFILE:
  210. return kErrorTooManyHandles;
  211. default:
  212. return kErrorInvalidArgument;
  213. }
  214. }
  215. #if defined(__APPLE__)
  216. // Detects whether the current process is hardened, which means that pages that
  217. // have WRITE and EXECUTABLE flags cannot be allocated without MAP_JIT flag.
  218. static ASMJIT_INLINE bool VirtMem_isHardened() noexcept {
  219. static volatile uint32_t globalHardenedFlag;
  220. enum HardenedFlag : uint32_t {
  221. kHardenedFlagUnknown = 0,
  222. kHardenedFlagDisabled = 1,
  223. kHardenedFlagEnabled = 2
  224. };
  225. uint32_t flag = globalHardenedFlag;
  226. if (flag == kHardenedFlagUnknown) {
  227. VirtMem::Info memInfo;
  228. VirtMem_getInfo(memInfo);
  229. void* ptr = mmap(nullptr, memInfo.pageSize, PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  230. if (ptr == MAP_FAILED) {
  231. flag = kHardenedFlagEnabled;
  232. }
  233. else {
  234. flag = kHardenedFlagDisabled;
  235. munmap(ptr, memInfo.pageSize);
  236. }
  237. globalHardenedFlag = flag;
  238. }
  239. return flag == kHardenedFlagEnabled;
  240. }
  241. // MAP_JIT flag required to run unsigned JIT code is only supported by kernel
  242. // version 10.14+ (Mojave) and IOS.
  243. static ASMJIT_INLINE bool VirtMem_hasMapJitSupport() noexcept {
  244. #if TARGET_OS_OSX
  245. static volatile uint32_t globalVersion;
  246. uint32_t ver = globalVersion;
  247. if (!ver) {
  248. struct utsname osname;
  249. uname(&osname);
  250. ver = atoi(osname.release);
  251. globalVersion = ver;
  252. }
  253. return ver >= 18;
  254. #else
  255. // Assume it's available.
  256. return true;
  257. #endif
  258. }
  259. static ASMJIT_INLINE uint32_t VirtMem_appleSpecificMMapFlags(uint32_t flags) {
  260. // Always use MAP_JIT flag if user asked for it (could be used for testing
  261. // on non-hardened processes) and detect whether it must be used when the
  262. // process is actually hardened (in that case it doesn't make sense to rely
  263. // on user `flags`).
  264. bool useMapJit = ((flags & VirtMem::kMMapEnableMapJit) != 0) || VirtMem_isHardened();
  265. if (useMapJit)
  266. return VirtMem_hasMapJitSupport() ? MAP_JIT : 0u;
  267. else
  268. return 0;
  269. }
  270. #else
  271. static ASMJIT_INLINE uint32_t VirtMem_appleSpecificMMapFlags(uint32_t flags) {
  272. ASMJIT_UNUSED(flags);
  273. return 0;
  274. }
  275. #endif
  276. static const char* VirtMem_getTmpDir() noexcept {
  277. const char* tmpDir = getenv("TMPDIR");
  278. return tmpDir ? tmpDir : "/tmp";
  279. }
  280. static Error VirtMem_openAnonymousMemory(int* fd, bool preferTmpOverDevShm) noexcept {
  281. #if defined(SYS_memfd_create)
  282. // Linux specific 'memfd_create' - if the syscall returns `ENOSYS` it means
  283. // it's not available and we will never call it again (would be pointless).
  284. // Zero initialized, if ever changed to '1' that would mean the syscall is not
  285. // available and we must use `shm_open()` and `shm_unlink()`.
  286. static volatile uint32_t memfd_create_not_supported;
  287. if (!memfd_create_not_supported) {
  288. *fd = (int)syscall(SYS_memfd_create, "vmem", 0);
  289. if (ASMJIT_LIKELY(*fd >= 0))
  290. return kErrorOk;
  291. int e = errno;
  292. if (e == ENOSYS)
  293. memfd_create_not_supported = 1;
  294. else
  295. return DebugUtils::errored(VirtMem_makeErrorFromErrno(e));
  296. }
  297. #endif
  298. #if defined(SHM_ANON)
  299. // Originally FreeBSD extension, apparently works in other BSDs too.
  300. ASMJIT_UNUSED(preferTmpOverDevShm);
  301. *fd = shm_open(SHM_ANON, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
  302. if (ASMJIT_LIKELY(*fd >= 0))
  303. return kErrorOk;
  304. else
  305. return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno));
  306. #else
  307. // POSIX API. We have to generate somehow a unique name. This is nothing
  308. // cryptographic, just using a bit from the stack address to always have
  309. // a different base for different threads (as threads have their own stack)
  310. // and retries for avoiding collisions. We use `shm_open()` with flags that
  311. // require creation of the file so we never open an existing shared memory.
  312. static std::atomic<uint32_t> internalCounter;
  313. StringTmp<128> uniqueName;
  314. const char* kShmFormat = "/shm-id-%08llX";
  315. uint32_t kRetryCount = 100;
  316. uint64_t bits = ((uintptr_t)(void*)&uniqueName) & 0x55555555u;
  317. for (uint32_t i = 0; i < kRetryCount; i++) {
  318. bits -= uint64_t(OSUtils::getTickCount()) * 773703683;
  319. bits = ((bits >> 14) ^ (bits << 6)) + uint64_t(++internalCounter) * 10619863;
  320. if (!ASMJIT_VM_SHM_DETECT || preferTmpOverDevShm) {
  321. uniqueName.assignString(VirtMem_getTmpDir());
  322. uniqueName.appendFormat(kShmFormat, (unsigned long long)bits);
  323. *fd = open(uniqueName.data(), O_RDWR | O_CREAT | O_EXCL, 0);
  324. if (ASMJIT_LIKELY(*fd >= 0)) {
  325. unlink(uniqueName.data());
  326. return kErrorOk;
  327. }
  328. }
  329. else {
  330. uniqueName.assignFormat(kShmFormat, (unsigned long long)bits);
  331. *fd = shm_open(uniqueName.data(), O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
  332. if (ASMJIT_LIKELY(*fd >= 0)) {
  333. shm_unlink(uniqueName.data());
  334. return kErrorOk;
  335. }
  336. }
  337. int e = errno;
  338. if (e == EEXIST)
  339. continue;
  340. else
  341. return DebugUtils::errored(VirtMem_makeErrorFromErrno(e));
  342. }
  343. return kErrorOk;
  344. #endif
  345. }
  346. #if ASMJIT_VM_SHM_DETECT
  347. static Error VirtMem_detectShmStrategy(uint32_t* strategyOut) noexcept {
  348. ScopedFD fd;
  349. VirtMem::Info vmInfo = VirtMem::info();
  350. ASMJIT_PROPAGATE(VirtMem_openAnonymousMemory(&fd.value, false));
  351. if (ftruncate(fd.value, off_t(vmInfo.pageSize)) != 0)
  352. return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno));
  353. void* ptr = mmap(nullptr, vmInfo.pageSize, PROT_READ | PROT_EXEC, MAP_SHARED, fd.value, 0);
  354. if (ptr == MAP_FAILED) {
  355. int e = errno;
  356. if (e == EINVAL) {
  357. *strategyOut = kShmStrategyTmpDir;
  358. return kErrorOk;
  359. }
  360. return DebugUtils::errored(VirtMem_makeErrorFromErrno(e));
  361. }
  362. else {
  363. munmap(ptr, vmInfo.pageSize);
  364. *strategyOut = kShmStrategyDevShm;
  365. return kErrorOk;
  366. }
  367. }
  368. #endif
  369. #if ASMJIT_VM_SHM_DETECT
  370. static Error VirtMem_getShmStrategy(uint32_t* strategyOut) noexcept {
  371. // Initially don't assume anything. It has to be tested whether
  372. // '/dev/shm' was mounted with 'noexec' flag or not.
  373. static volatile uint32_t globalShmStrategy = kShmStrategyUnknown;
  374. uint32_t strategy = globalShmStrategy;
  375. if (strategy == kShmStrategyUnknown) {
  376. ASMJIT_PROPAGATE(VirtMem_detectShmStrategy(&strategy));
  377. globalShmStrategy = strategy;
  378. }
  379. *strategyOut = strategy;
  380. return kErrorOk;
  381. }
  382. #else
  383. static Error VirtMem_getShmStrategy(uint32_t* strategyOut) noexcept {
  384. *strategyOut = kShmStrategyTmpDir;
  385. return kErrorOk;
  386. }
  387. #endif
  388. Error VirtMem::alloc(void** p, size_t size, uint32_t flags) noexcept {
  389. *p = nullptr;
  390. if (size == 0)
  391. return DebugUtils::errored(kErrorInvalidArgument);
  392. int protection = VirtMem_accessToPosixProtection(flags);
  393. int mmFlags = MAP_PRIVATE | MAP_ANONYMOUS | VirtMem_appleSpecificMMapFlags(flags);
  394. void* ptr = mmap(nullptr, size, protection, mmFlags, -1, 0);
  395. if (ptr == MAP_FAILED)
  396. return DebugUtils::errored(kErrorOutOfMemory);
  397. *p = ptr;
  398. return kErrorOk;
  399. }
  400. Error VirtMem::release(void* p, size_t size) noexcept {
  401. if (ASMJIT_UNLIKELY(munmap(p, size) != 0))
  402. return DebugUtils::errored(kErrorInvalidArgument);
  403. return kErrorOk;
  404. }
  405. Error VirtMem::protect(void* p, size_t size, uint32_t flags) noexcept {
  406. int protection = VirtMem_accessToPosixProtection(flags);
  407. if (mprotect(p, size, protection) == 0)
  408. return kErrorOk;
  409. return DebugUtils::errored(kErrorInvalidArgument);
  410. }
  411. Error VirtMem::allocDualMapping(DualMapping* dm, size_t size, uint32_t flags) noexcept {
  412. dm->ro = nullptr;
  413. dm->rw = nullptr;
  414. if (off_t(size) <= 0)
  415. return DebugUtils::errored(size == 0 ? kErrorInvalidArgument : kErrorTooLarge);
  416. bool preferTmpOverDevShm = (flags & kMappingPreferTmp) != 0;
  417. if (!preferTmpOverDevShm) {
  418. uint32_t strategy;
  419. ASMJIT_PROPAGATE(VirtMem_getShmStrategy(&strategy));
  420. preferTmpOverDevShm = (strategy == kShmStrategyTmpDir);
  421. }
  422. // ScopedFD will automatically close the file descriptor in its destructor.
  423. ScopedFD fd;
  424. ASMJIT_PROPAGATE(VirtMem_openAnonymousMemory(&fd.value, preferTmpOverDevShm));
  425. if (ftruncate(fd.value, off_t(size)) != 0)
  426. return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno));
  427. void* ptr[2];
  428. for (uint32_t i = 0; i < 2; i++) {
  429. ptr[i] = mmap(nullptr, size, VirtMem_accessToPosixProtection(flags & ~VirtMem_dualMappingFilter[i]), MAP_SHARED, fd.value, 0);
  430. if (ptr[i] == MAP_FAILED) {
  431. // Get the error now before `munmap` has a chance to clobber it.
  432. int e = errno;
  433. if (i == 1)
  434. munmap(ptr[0], size);
  435. return DebugUtils::errored(VirtMem_makeErrorFromErrno(e));
  436. }
  437. }
  438. dm->ro = ptr[0];
  439. dm->rw = ptr[1];
  440. return kErrorOk;
  441. }
  442. Error VirtMem::releaseDualMapping(DualMapping* dm, size_t size) noexcept {
  443. Error err = release(dm->ro, size);
  444. if (dm->ro != dm->rw)
  445. err |= release(dm->rw, size);
  446. if (err)
  447. return DebugUtils::errored(kErrorInvalidArgument);
  448. dm->ro = nullptr;
  449. dm->rw = nullptr;
  450. return kErrorOk;
  451. }
  452. #endif
  453. // ============================================================================
  454. // [asmjit::VirtMem - Virtual Memory [Memory Info]]
  455. // ============================================================================
  456. VirtMem::Info VirtMem::info() noexcept {
  457. static VirtMem::Info vmInfo;
  458. static std::atomic<uint32_t> vmInfoInitialized;
  459. if (!vmInfoInitialized.load()) {
  460. VirtMem::Info localMemInfo;
  461. VirtMem_getInfo(localMemInfo);
  462. vmInfo = localMemInfo;
  463. vmInfoInitialized.store(1u);
  464. }
  465. return vmInfo;
  466. }
  467. ASMJIT_END_NAMESPACE
  468. #endif