Collection of DPF-based plugins for packaging
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

512 lines
20KB

  1. //
  2. // ██████ ██  ██  ██████  ██████
  3. // ██      ██  ██ ██    ██ ██       ** Classy Header-Only Classes **
  4. // ██  ███████ ██  ██ ██
  5. // ██  ██   ██ ██  ██ ██ https://github.com/Tracktion/choc
  6. //  ██████ ██  ██  ██████   ██████
  7. //
  8. // CHOC is (C)2022 Tracktion Corporation, and is offered under the terms of the ISC license:
  9. //
  10. // Permission to use, copy, modify, and/or distribute this software for any purpose with or
  11. // without fee is hereby granted, provided that the above copyright notice and this permission
  12. // notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  13. // WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  14. // AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
  15. // CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  16. // WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  17. // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  18. #ifndef CHOC_MEMORYDLL_HEADER_INCLUDED
  19. #define CHOC_MEMORYDLL_HEADER_INCLUDED
  20. #include <stddef.h>
  21. #include <memory>
  22. #include <string>
  23. START_NAMESPACE_DISTRHO
  24. /**
  25. MemoryDLL is an egregious hack that allows you to load DLL files from a chunk
  26. of memory in the same way you might load them from a file on disk.
  27. This opens up the ability to do horrible things such as embedding a random DLL
  28. in a chunk of C++ code, so that it gets baked directly into your executable..
  29. That means that if your app requires a 3rd-party DLL but you don't want the hassle
  30. of having to install the DLL file in the right place on your users' machines, you
  31. could use this trick to embed it invisibly inside your executable...
  32. Currently this is only implemented for Windows DLLs, but a linux/OSX loader for
  33. .dylibs is also totally feasible if anyone fancies having a go :)
  34. @see DynamicLibrary
  35. */
  36. struct MemoryDLL
  37. {
  38. MemoryDLL() = default;
  39. MemoryDLL (MemoryDLL&&) = default;
  40. MemoryDLL& operator= (MemoryDLL&&) = default;
  41. ~MemoryDLL();
  42. /// Attempts to load a chunk of memory that contains a DLL file image.
  43. MemoryDLL (const void* data, size_t size);
  44. /// Returns a pointer to the function with this name, or nullptr if not found.
  45. void* findFunction (std::string_view functionName);
  46. /// Returns true if the library was successfully loaded
  47. operator bool() const { return pimpl != nullptr; }
  48. private:
  49. struct Pimpl;
  50. std::unique_ptr<Pimpl> pimpl;
  51. };
  52. END_NAMESPACE_DISTRHO
  53. //==============================================================================
  54. // _ _ _ _
  55. // __| | ___ | |_ __ _ (_)| | ___
  56. // / _` | / _ \| __| / _` || || |/ __|
  57. // | (_| || __/| |_ | (_| || || |\__ \ _ _ _
  58. // \__,_| \___| \__| \__,_||_||_||___/(_)(_)(_)
  59. //
  60. // Code beyond this point is implementation detail...
  61. //
  62. //==============================================================================
  63. #include <vector>
  64. #include <unordered_map>
  65. #undef WIN32_LEAN_AND_MEAN
  66. #define WIN32_LEAN_AND_MEAN
  67. #undef NOMINMAX
  68. #define NOMINMAX
  69. #define Rectangle Rectangle_renamed_to_avoid_name_collisions
  70. #include <windows.h>
  71. #undef Rectangle
  72. START_NAMESPACE_DISTRHO
  73. struct MemoryDLL::Pimpl
  74. {
  75. Pimpl() = default;
  76. ~Pimpl()
  77. {
  78. if (entryFunction != nullptr)
  79. (*reinterpret_cast<DLLEntryFn> (entryFunction)) ((HINSTANCE) imageData, DLL_PROCESS_DETACH, nullptr);
  80. for (auto& m : loadedModules)
  81. FreeLibrary (m);
  82. if (imageData != nullptr)
  83. VirtualFree (imageData, 0, MEM_RELEASE);
  84. for (auto m : virtualBlocks)
  85. VirtualFree (m, 0, MEM_RELEASE);
  86. }
  87. bool initialise (const void* data, size_t size)
  88. {
  89. if (size < sizeof (IMAGE_DOS_HEADER))
  90. return false;
  91. auto dosHeader = static_cast<const IMAGE_DOS_HEADER*> (data);
  92. if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE
  93. || size < static_cast<size_t> (dosHeader->e_lfanew) + sizeof (IMAGE_NT_HEADERS))
  94. return false;
  95. const auto& headers = *getOffsetAs<IMAGE_NT_HEADERS> (data, dosHeader->e_lfanew);
  96. if (headers.Signature != IMAGE_NT_SIGNATURE
  97. || (headers.OptionalHeader.SectionAlignment & 1) != 0)
  98. return false;
  99. #ifdef _M_ARM64
  100. if (headers.FileHeader.Machine != IMAGE_FILE_MACHINE_ARM64) return false;
  101. #elif defined (_WIN64)
  102. if (headers.FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) return false;
  103. #else
  104. if (headers.FileHeader.Machine != IMAGE_FILE_MACHINE_I386) return false;
  105. #endif
  106. SYSTEM_INFO systemInfo;
  107. GetNativeSystemInfo (std::addressof (systemInfo));
  108. auto alignedImageSize = roundUp (headers.OptionalHeader.SizeOfImage, systemInfo.dwPageSize);
  109. if (alignedImageSize != roundUp (getLastSectionEnd (headers), systemInfo.dwPageSize))
  110. return false;
  111. imageData = VirtualAlloc (reinterpret_cast<void*> (headers.OptionalHeader.ImageBase),
  112. alignedImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
  113. if (imageData == nullptr)
  114. {
  115. imageData = VirtualAlloc (nullptr, alignedImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
  116. if (imageData == nullptr)
  117. return false;
  118. }
  119. while ((((uint64_t) imageData) >> 32) < (((uint64_t) imageData + alignedImageSize) >> 32))
  120. {
  121. virtualBlocks.push_back (imageData);
  122. imageData = VirtualAlloc (nullptr, alignedImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
  123. if (imageData == nullptr)
  124. return false;
  125. }
  126. isDLL = (headers.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0;
  127. pageSize = systemInfo.dwPageSize;
  128. if (size < headers.OptionalHeader.SizeOfHeaders)
  129. return false;
  130. auto newHeaders = VirtualAlloc (imageData, headers.OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE);
  131. memcpy (newHeaders, dosHeader, headers.OptionalHeader.SizeOfHeaders);
  132. imageHeaders = getOffsetAs<IMAGE_NT_HEADERS> (newHeaders, dosHeader->e_lfanew);
  133. imageHeaders->OptionalHeader.ImageBase = reinterpret_cast<uintptr_t> (imageData);
  134. if (copySections (data, size, headers.OptionalHeader.SectionAlignment))
  135. {
  136. if (auto locationDelta = (ptrdiff_t) (imageHeaders->OptionalHeader.ImageBase - headers.OptionalHeader.ImageBase))
  137. performRelocation (locationDelta);
  138. if (loadImports() && prepareSections())
  139. {
  140. executeTLS();
  141. loadNameTable();
  142. if (imageHeaders->OptionalHeader.AddressOfEntryPoint != 0)
  143. {
  144. entryFunction = getOffsetAs<char> (imageData, imageHeaders->OptionalHeader.AddressOfEntryPoint);
  145. if (isDLL)
  146. return (*reinterpret_cast<DLLEntryFn> (entryFunction)) ((HINSTANCE) imageData, DLL_PROCESS_ATTACH, nullptr);
  147. return true;
  148. }
  149. }
  150. }
  151. return false;
  152. }
  153. void* findFunction (std::string_view name)
  154. {
  155. if (auto found = exportedFunctionOffsets.find (std::string (name)); found != exportedFunctionOffsets.end())
  156. return getOffsetAs<char> (imageData, found->second);
  157. return {};
  158. }
  159. private:
  160. PIMAGE_NT_HEADERS imageHeaders = {};
  161. void* imageData = nullptr;
  162. std::vector<HMODULE> loadedModules;
  163. uint32_t pageSize = 0;
  164. bool isDLL = false;
  165. std::vector<void*> virtualBlocks;
  166. std::unordered_map<std::string, size_t> exportedFunctionOffsets;
  167. using DLLEntryFn = BOOL(WINAPI*)(HINSTANCE, DWORD, void*);
  168. void* entryFunction = {};
  169. template <typename Type, typename Diff>
  170. static const Type* getOffsetAs (const void* address, Diff offset)
  171. {
  172. return reinterpret_cast<const Type*> (static_cast<const char*> (address) + offset);
  173. }
  174. template <typename Type, typename Diff>
  175. static Type* getOffsetAs (void* address, Diff offset)
  176. {
  177. return reinterpret_cast<Type*> (static_cast<char*> (address) + offset);
  178. }
  179. template <typename Type>
  180. Type* getDataDirectoryAddress (int type) const
  181. {
  182. auto& dd = imageHeaders->OptionalHeader.DataDirectory[type];
  183. if (dd.Size > 0)
  184. return getOffsetAs<Type> (imageData, dd.VirtualAddress);
  185. return {};
  186. }
  187. static size_t getLastSectionEnd (const IMAGE_NT_HEADERS& headers)
  188. {
  189. auto section = IMAGE_FIRST_SECTION (&headers);
  190. auto optionalSectionSize = headers.OptionalHeader.SectionAlignment;
  191. size_t lastSectionEnd = 0;
  192. for (uint32_t i = 0; i < headers.FileHeader.NumberOfSections; ++i, ++section)
  193. {
  194. auto end = section->VirtualAddress + (section->SizeOfRawData == 0 ? optionalSectionSize
  195. : section->SizeOfRawData);
  196. if (end > lastSectionEnd)
  197. lastSectionEnd = end;
  198. }
  199. return lastSectionEnd;
  200. }
  201. void loadNameTable()
  202. {
  203. if (auto exports = getDataDirectoryAddress<IMAGE_EXPORT_DIRECTORY> (IMAGE_DIRECTORY_ENTRY_EXPORT))
  204. {
  205. if (exports->NumberOfNames > 0 && exports->NumberOfFunctions > 0)
  206. {
  207. auto name = getOffsetAs<const DWORD> (imageData, exports->AddressOfNames);
  208. auto ordinal = getOffsetAs<const WORD> (imageData, exports->AddressOfNameOrdinals);
  209. exportedFunctionOffsets.reserve (exports->NumberOfNames);
  210. for (size_t i = 0; i < exports->NumberOfNames; ++i, ++name, ++ordinal)
  211. if (*ordinal <= exports->NumberOfFunctions)
  212. exportedFunctionOffsets[std::string (getOffsetAs<const char> (imageData, *name))]
  213. = getOffsetAs<DWORD> (imageData, exports->AddressOfFunctions)[*ordinal];
  214. }
  215. }
  216. }
  217. void executeTLS()
  218. {
  219. if (auto tls = getDataDirectoryAddress<IMAGE_TLS_DIRECTORY> (IMAGE_DIRECTORY_ENTRY_TLS))
  220. if (auto callback = reinterpret_cast<PIMAGE_TLS_CALLBACK*> (tls->AddressOfCallBacks))
  221. while (*callback != nullptr)
  222. (*callback++) ((void*) imageData, DLL_PROCESS_ATTACH, nullptr);
  223. }
  224. bool prepareSections()
  225. {
  226. auto section = IMAGE_FIRST_SECTION (imageHeaders);
  227. auto size = getSectionSize (*section);
  228. auto address = getSectionAddress (*section);
  229. auto addressPage = getPageBase (address);
  230. auto characteristics = section->Characteristics;
  231. for (WORD i = 1; i < imageHeaders->FileHeader.NumberOfSections; ++i)
  232. {
  233. ++section;
  234. auto nextSize = getSectionSize (*section);
  235. auto nextAddress = getSectionAddress (*section);
  236. auto nextAddressPage = getPageBase (nextAddress);
  237. if (addressPage == nextAddressPage || address + size > nextAddressPage)
  238. {
  239. if ((section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0 || (characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0)
  240. characteristics = (characteristics | section->Characteristics) & ~static_cast<DWORD> (IMAGE_SCN_MEM_DISCARDABLE);
  241. else
  242. characteristics |= section->Characteristics;
  243. size = static_cast<size_t> ((nextAddress + nextSize) - address);
  244. continue;
  245. }
  246. if (! setProtectionFlags (size, characteristics, address, addressPage, false))
  247. return false;
  248. size = nextSize;
  249. address = nextAddress;
  250. addressPage = nextAddressPage;
  251. characteristics = section->Characteristics;
  252. }
  253. return setProtectionFlags (size, characteristics, address, addressPage, true);
  254. }
  255. bool setProtectionFlags (size_t sectionSize, DWORD sectionCharacteristics, void* sectionAddress, void* addressPage, bool isLast)
  256. {
  257. if (sectionSize == 0)
  258. return true;
  259. if ((sectionCharacteristics & IMAGE_SCN_MEM_DISCARDABLE) != 0)
  260. {
  261. if (sectionAddress == addressPage
  262. && (isLast || imageHeaders->OptionalHeader.SectionAlignment == pageSize || (sectionSize % pageSize) == 0))
  263. VirtualFree (sectionAddress, sectionSize, MEM_DECOMMIT);
  264. return true;
  265. }
  266. auto getProtectionFlags = [] (DWORD type)
  267. {
  268. return ((type & IMAGE_SCN_MEM_NOT_CACHED) ? PAGE_NOCACHE : 0)
  269. | ((type & IMAGE_SCN_MEM_EXECUTE)
  270. ? ((type & IMAGE_SCN_MEM_READ)
  271. ? ((type & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ)
  272. : ((type & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_WRITECOPY : PAGE_EXECUTE))
  273. : ((type & IMAGE_SCN_MEM_READ)
  274. ? ((type & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY)
  275. : ((type & IMAGE_SCN_MEM_WRITE) ? PAGE_WRITECOPY : PAGE_NOACCESS)));
  276. };
  277. DWORD oldProtectValue;
  278. return VirtualProtect (sectionAddress, sectionSize,
  279. static_cast<DWORD> (getProtectionFlags (sectionCharacteristics)),
  280. std::addressof (oldProtectValue)) != 0;
  281. }
  282. bool loadImports()
  283. {
  284. auto imp = getDataDirectoryAddress<IMAGE_IMPORT_DESCRIPTOR> (IMAGE_DIRECTORY_ENTRY_IMPORT);
  285. if (imp == nullptr)
  286. return false;
  287. while (! IsBadReadPtr (imp, sizeof (IMAGE_IMPORT_DESCRIPTOR)) && imp->Name != 0)
  288. {
  289. auto handle = LoadLibraryA (getOffsetAs<const char> (imageData, imp->Name));
  290. if (handle == nullptr)
  291. return false;
  292. loadedModules.push_back (handle);
  293. auto thunkRef = getOffsetAs<uintptr_t> (imageData, imp->OriginalFirstThunk ? imp->OriginalFirstThunk
  294. : imp->FirstThunk);
  295. auto funcRef = getOffsetAs<FARPROC> (imageData, imp->FirstThunk);
  296. for (; *thunkRef != 0; ++thunkRef, ++funcRef)
  297. {
  298. auto name = IMAGE_SNAP_BY_ORDINAL(*thunkRef)
  299. ? (LPCSTR) IMAGE_ORDINAL(*thunkRef)
  300. : (LPCSTR) std::addressof (getOffsetAs<IMAGE_IMPORT_BY_NAME> (imageData, *thunkRef)->Name);
  301. *funcRef = GetProcAddress (handle, name);
  302. if (*funcRef == 0)
  303. return false;
  304. }
  305. ++imp;
  306. }
  307. return true;
  308. }
  309. size_t getSectionSize (const IMAGE_SECTION_HEADER& section) const
  310. {
  311. if (auto size = section.SizeOfRawData)
  312. return size;
  313. if (section.Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA)
  314. return imageHeaders->OptionalHeader.SizeOfInitializedData;
  315. if (section.Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA)
  316. return imageHeaders->OptionalHeader.SizeOfUninitializedData;
  317. return 0;
  318. }
  319. static size_t roundUp (size_t value, size_t alignment) { return (value + alignment - 1) & ~(alignment - 1); }
  320. char* getPageBase (void* address) const { return (char*) (reinterpret_cast<uintptr_t> (address) & ~static_cast<uintptr_t> (pageSize - 1)); }
  321. char* getSectionAddress (const IMAGE_SECTION_HEADER& section) const
  322. {
  323. #ifdef _WIN64
  324. return reinterpret_cast<char*> (static_cast<uintptr_t> (section.Misc.PhysicalAddress)
  325. | (static_cast<uintptr_t> (imageHeaders->OptionalHeader.ImageBase & 0xffffffff00000000)));
  326. #else
  327. return reinterpret_cast<char*> (section.Misc.PhysicalAddress);
  328. #endif
  329. }
  330. bool copySections (const void* data, size_t size, size_t sectionAlignment) const
  331. {
  332. auto section = IMAGE_FIRST_SECTION (imageHeaders);
  333. for (int i = 0; i < imageHeaders->FileHeader.NumberOfSections; ++i, ++section)
  334. {
  335. if (section->SizeOfRawData == 0)
  336. {
  337. if (sectionAlignment == 0)
  338. continue;
  339. if (auto dest = VirtualAlloc (getOffsetAs<char> (imageData, section->VirtualAddress),
  340. sectionAlignment, MEM_COMMIT, PAGE_READWRITE))
  341. {
  342. dest = getOffsetAs<char> (imageData, section->VirtualAddress);
  343. section->Misc.PhysicalAddress = static_cast<uint32_t> (reinterpret_cast<uintptr_t> (dest));
  344. memset (dest, 0, sectionAlignment);
  345. continue;
  346. }
  347. return false;
  348. }
  349. if (size < section->PointerToRawData + section->SizeOfRawData)
  350. return false;
  351. if (auto dest = VirtualAlloc (getOffsetAs<char> (imageData, section->VirtualAddress),
  352. section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE))
  353. {
  354. dest = getOffsetAs<char> (imageData, section->VirtualAddress);
  355. memcpy (dest, static_cast<const char*> (data) + section->PointerToRawData, section->SizeOfRawData);
  356. section->Misc.PhysicalAddress = static_cast<uint32_t> (reinterpret_cast<uintptr_t> (dest));
  357. continue;
  358. }
  359. return false;
  360. }
  361. return true;
  362. }
  363. void performRelocation (ptrdiff_t delta)
  364. {
  365. auto directory = imageHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
  366. if (directory.Size != 0)
  367. {
  368. auto relocation = getOffsetAs<IMAGE_BASE_RELOCATION> (imageData, directory.VirtualAddress);
  369. while (relocation->VirtualAddress > 0)
  370. {
  371. auto dest = getOffsetAs<char> (imageData, relocation->VirtualAddress);
  372. auto offset = getOffsetAs<uint16_t> (relocation, sizeof (IMAGE_BASE_RELOCATION));
  373. for (uint32_t i = 0; i < (relocation->SizeOfBlock - sizeof (IMAGE_BASE_RELOCATION)) / 2; ++i, ++offset)
  374. {
  375. switch (*offset >> 12)
  376. {
  377. case IMAGE_REL_BASED_HIGHLOW: addDelta<uint32_t> (dest + (*offset & 0xfff), delta); break;
  378. case IMAGE_REL_BASED_DIR64: addDelta<uint64_t> (dest + (*offset & 0xfff), delta); break;
  379. case IMAGE_REL_BASED_ABSOLUTE:
  380. default: break;
  381. }
  382. }
  383. relocation = getOffsetAs<IMAGE_BASE_RELOCATION> (relocation, relocation->SizeOfBlock);
  384. }
  385. }
  386. }
  387. template <typename Type>
  388. static void addDelta (char* addr, ptrdiff_t delta)
  389. {
  390. *reinterpret_cast<Type*> (addr) = static_cast<Type> (static_cast<ptrdiff_t> (*reinterpret_cast<Type*> (addr)) + delta);
  391. }
  392. };
  393. inline MemoryDLL::~MemoryDLL() = default;
  394. inline MemoryDLL::MemoryDLL (const void* data, size_t size) : pimpl (std::make_unique<Pimpl>())
  395. {
  396. if (! pimpl->initialise (data, size))
  397. pimpl.reset();
  398. }
  399. inline void* MemoryDLL::findFunction (std::string_view name)
  400. {
  401. return pimpl != nullptr ? pimpl->findFunction (name) : nullptr;
  402. }
  403. END_NAMESPACE_DISTRHO
  404. #endif // CHOC_MEMORYDLL_HEADER_INCLUDED