|
- //
- // ██████ ██ ██ ██████ ██████
- // ██ ██ ██ ██ ██ ██ ** Classy Header-Only Classes **
- // ██ ███████ ██ ██ ██
- // ██ ██ ██ ██ ██ ██ https://github.com/Tracktion/choc
- // ██████ ██ ██ ██████ ██████
- //
- // CHOC is (C)2022 Tracktion Corporation, and is offered under the terms of the ISC license:
- //
- // Permission to use, copy, modify, and/or distribute this software for any purpose with or
- // without fee is hereby granted, provided that the above copyright notice and this permission
- // notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
- // WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- // AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
- // CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
- // WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
- #ifndef CHOC_MEMORYDLL_HEADER_INCLUDED
- #define CHOC_MEMORYDLL_HEADER_INCLUDED
-
- #include <stddef.h>
- #include <memory>
- #include <string>
-
- START_NAMESPACE_DISTRHO
-
- /**
- MemoryDLL is an egregious hack that allows you to load DLL files from a chunk
- of memory in the same way you might load them from a file on disk.
-
- This opens up the ability to do horrible things such as embedding a random DLL
- in a chunk of C++ code, so that it gets baked directly into your executable..
- That means that if your app requires a 3rd-party DLL but you don't want the hassle
- of having to install the DLL file in the right place on your users' machines, you
- could use this trick to embed it invisibly inside your executable...
-
- Currently this is only implemented for Windows DLLs, but a linux/OSX loader for
- .dylibs is also totally feasible if anyone fancies having a go :)
-
- @see DynamicLibrary
- */
- struct MemoryDLL
- {
- MemoryDLL() = default;
- MemoryDLL (MemoryDLL&&) = default;
- MemoryDLL& operator= (MemoryDLL&&) = default;
- ~MemoryDLL();
-
- /// Attempts to load a chunk of memory that contains a DLL file image.
- MemoryDLL (const void* data, size_t size);
-
- /// Returns a pointer to the function with this name, or nullptr if not found.
- void* findFunction (std::string_view functionName);
-
- /// Returns true if the library was successfully loaded
- operator bool() const { return pimpl != nullptr; }
-
- private:
- struct Pimpl;
- std::unique_ptr<Pimpl> pimpl;
- };
-
- END_NAMESPACE_DISTRHO
-
-
-
- //==============================================================================
- // _ _ _ _
- // __| | ___ | |_ __ _ (_)| | ___
- // / _` | / _ \| __| / _` || || |/ __|
- // | (_| || __/| |_ | (_| || || |\__ \ _ _ _
- // \__,_| \___| \__| \__,_||_||_||___/(_)(_)(_)
- //
- // Code beyond this point is implementation detail...
- //
- //==============================================================================
-
- #include <vector>
- #include <unordered_map>
- #undef WIN32_LEAN_AND_MEAN
- #define WIN32_LEAN_AND_MEAN
- #undef NOMINMAX
- #define NOMINMAX
- #define Rectangle Rectangle_renamed_to_avoid_name_collisions
- #include <windows.h>
- #undef Rectangle
-
- START_NAMESPACE_DISTRHO
-
- struct MemoryDLL::Pimpl
- {
- Pimpl() = default;
-
- ~Pimpl()
- {
- if (entryFunction != nullptr)
- (*reinterpret_cast<DLLEntryFn> (entryFunction)) ((HINSTANCE) imageData, DLL_PROCESS_DETACH, nullptr);
-
- for (auto& m : loadedModules)
- FreeLibrary (m);
-
- if (imageData != nullptr)
- VirtualFree (imageData, 0, MEM_RELEASE);
-
- for (auto m : virtualBlocks)
- VirtualFree (m, 0, MEM_RELEASE);
- }
-
- bool initialise (const void* data, size_t size)
- {
- if (size < sizeof (IMAGE_DOS_HEADER))
- return false;
-
- auto dosHeader = static_cast<const IMAGE_DOS_HEADER*> (data);
-
- if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE
- || size < static_cast<size_t> (dosHeader->e_lfanew) + sizeof (IMAGE_NT_HEADERS))
- return false;
-
- const auto& headers = *getOffsetAs<IMAGE_NT_HEADERS> (data, dosHeader->e_lfanew);
-
- if (headers.Signature != IMAGE_NT_SIGNATURE
- || (headers.OptionalHeader.SectionAlignment & 1) != 0)
- return false;
-
- #ifdef _M_ARM64
- if (headers.FileHeader.Machine != IMAGE_FILE_MACHINE_ARM64) return false;
- #elif defined (_WIN64)
- if (headers.FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) return false;
- #else
- if (headers.FileHeader.Machine != IMAGE_FILE_MACHINE_I386) return false;
- #endif
-
- SYSTEM_INFO systemInfo;
- GetNativeSystemInfo (std::addressof (systemInfo));
-
- auto alignedImageSize = roundUp (headers.OptionalHeader.SizeOfImage, systemInfo.dwPageSize);
-
- if (alignedImageSize != roundUp (getLastSectionEnd (headers), systemInfo.dwPageSize))
- return false;
-
- imageData = VirtualAlloc (reinterpret_cast<void*> (headers.OptionalHeader.ImageBase),
- alignedImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
-
- if (imageData == nullptr)
- {
- imageData = VirtualAlloc (nullptr, alignedImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
-
- if (imageData == nullptr)
- return false;
- }
-
- while ((((uint64_t) imageData) >> 32) < (((uint64_t) imageData + alignedImageSize) >> 32))
- {
- virtualBlocks.push_back (imageData);
- imageData = VirtualAlloc (nullptr, alignedImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
-
- if (imageData == nullptr)
- return false;
- }
-
- isDLL = (headers.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0;
- pageSize = systemInfo.dwPageSize;
-
- if (size < headers.OptionalHeader.SizeOfHeaders)
- return false;
-
- auto newHeaders = VirtualAlloc (imageData, headers.OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE);
- memcpy (newHeaders, dosHeader, headers.OptionalHeader.SizeOfHeaders);
- imageHeaders = getOffsetAs<IMAGE_NT_HEADERS> (newHeaders, dosHeader->e_lfanew);
- imageHeaders->OptionalHeader.ImageBase = reinterpret_cast<uintptr_t> (imageData);
-
- if (copySections (data, size, headers.OptionalHeader.SectionAlignment))
- {
- if (auto locationDelta = (ptrdiff_t) (imageHeaders->OptionalHeader.ImageBase - headers.OptionalHeader.ImageBase))
- performRelocation (locationDelta);
-
- if (loadImports() && prepareSections())
- {
- executeTLS();
- loadNameTable();
-
- if (imageHeaders->OptionalHeader.AddressOfEntryPoint != 0)
- {
- entryFunction = getOffsetAs<char> (imageData, imageHeaders->OptionalHeader.AddressOfEntryPoint);
-
- if (isDLL)
- return (*reinterpret_cast<DLLEntryFn> (entryFunction)) ((HINSTANCE) imageData, DLL_PROCESS_ATTACH, nullptr);
-
- return true;
- }
- }
- }
-
- return false;
- }
-
- void* findFunction (std::string_view name)
- {
- if (auto found = exportedFunctionOffsets.find (std::string (name)); found != exportedFunctionOffsets.end())
- return getOffsetAs<char> (imageData, found->second);
-
- return {};
- }
-
- private:
- PIMAGE_NT_HEADERS imageHeaders = {};
- void* imageData = nullptr;
- std::vector<HMODULE> loadedModules;
- uint32_t pageSize = 0;
- bool isDLL = false;
- std::vector<void*> virtualBlocks;
- std::unordered_map<std::string, size_t> exportedFunctionOffsets;
- using DLLEntryFn = BOOL(WINAPI*)(HINSTANCE, DWORD, void*);
- void* entryFunction = {};
-
- template <typename Type, typename Diff>
- static const Type* getOffsetAs (const void* address, Diff offset)
- {
- return reinterpret_cast<const Type*> (static_cast<const char*> (address) + offset);
- }
-
- template <typename Type, typename Diff>
- static Type* getOffsetAs (void* address, Diff offset)
- {
- return reinterpret_cast<Type*> (static_cast<char*> (address) + offset);
- }
-
- template <typename Type>
- Type* getDataDirectoryAddress (int type) const
- {
- auto& dd = imageHeaders->OptionalHeader.DataDirectory[type];
-
- if (dd.Size > 0)
- return getOffsetAs<Type> (imageData, dd.VirtualAddress);
-
- return {};
- }
-
- static size_t getLastSectionEnd (const IMAGE_NT_HEADERS& headers)
- {
- auto section = IMAGE_FIRST_SECTION (&headers);
- auto optionalSectionSize = headers.OptionalHeader.SectionAlignment;
- size_t lastSectionEnd = 0;
-
- for (uint32_t i = 0; i < headers.FileHeader.NumberOfSections; ++i, ++section)
- {
- auto end = section->VirtualAddress + (section->SizeOfRawData == 0 ? optionalSectionSize
- : section->SizeOfRawData);
- if (end > lastSectionEnd)
- lastSectionEnd = end;
- }
-
- return lastSectionEnd;
- }
-
- void loadNameTable()
- {
- if (auto exports = getDataDirectoryAddress<IMAGE_EXPORT_DIRECTORY> (IMAGE_DIRECTORY_ENTRY_EXPORT))
- {
- if (exports->NumberOfNames > 0 && exports->NumberOfFunctions > 0)
- {
- auto name = getOffsetAs<const DWORD> (imageData, exports->AddressOfNames);
- auto ordinal = getOffsetAs<const WORD> (imageData, exports->AddressOfNameOrdinals);
-
- exportedFunctionOffsets.reserve (exports->NumberOfNames);
-
- for (size_t i = 0; i < exports->NumberOfNames; ++i, ++name, ++ordinal)
- if (*ordinal <= exports->NumberOfFunctions)
- exportedFunctionOffsets[std::string (getOffsetAs<const char> (imageData, *name))]
- = getOffsetAs<DWORD> (imageData, exports->AddressOfFunctions)[*ordinal];
- }
- }
- }
-
- void executeTLS()
- {
- if (auto tls = getDataDirectoryAddress<IMAGE_TLS_DIRECTORY> (IMAGE_DIRECTORY_ENTRY_TLS))
- if (auto callback = reinterpret_cast<PIMAGE_TLS_CALLBACK*> (tls->AddressOfCallBacks))
- while (*callback != nullptr)
- (*callback++) ((void*) imageData, DLL_PROCESS_ATTACH, nullptr);
- }
-
- bool prepareSections()
- {
- auto section = IMAGE_FIRST_SECTION (imageHeaders);
- auto size = getSectionSize (*section);
- auto address = getSectionAddress (*section);
- auto addressPage = getPageBase (address);
- auto characteristics = section->Characteristics;
-
- for (WORD i = 1; i < imageHeaders->FileHeader.NumberOfSections; ++i)
- {
- ++section;
- auto nextSize = getSectionSize (*section);
- auto nextAddress = getSectionAddress (*section);
- auto nextAddressPage = getPageBase (nextAddress);
-
- if (addressPage == nextAddressPage || address + size > nextAddressPage)
- {
- if ((section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0 || (characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0)
- characteristics = (characteristics | section->Characteristics) & ~static_cast<DWORD> (IMAGE_SCN_MEM_DISCARDABLE);
- else
- characteristics |= section->Characteristics;
-
- size = static_cast<size_t> ((nextAddress + nextSize) - address);
- continue;
- }
-
- if (! setProtectionFlags (size, characteristics, address, addressPage, false))
- return false;
-
- size = nextSize;
- address = nextAddress;
- addressPage = nextAddressPage;
- characteristics = section->Characteristics;
- }
-
- return setProtectionFlags (size, characteristics, address, addressPage, true);
- }
-
- bool setProtectionFlags (size_t sectionSize, DWORD sectionCharacteristics, void* sectionAddress, void* addressPage, bool isLast)
- {
- if (sectionSize == 0)
- return true;
-
- if ((sectionCharacteristics & IMAGE_SCN_MEM_DISCARDABLE) != 0)
- {
- if (sectionAddress == addressPage
- && (isLast || imageHeaders->OptionalHeader.SectionAlignment == pageSize || (sectionSize % pageSize) == 0))
- VirtualFree (sectionAddress, sectionSize, MEM_DECOMMIT);
-
- return true;
- }
-
- auto getProtectionFlags = [] (DWORD type)
- {
- return ((type & IMAGE_SCN_MEM_NOT_CACHED) ? PAGE_NOCACHE : 0)
- | ((type & IMAGE_SCN_MEM_EXECUTE)
- ? ((type & IMAGE_SCN_MEM_READ)
- ? ((type & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ)
- : ((type & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_WRITECOPY : PAGE_EXECUTE))
- : ((type & IMAGE_SCN_MEM_READ)
- ? ((type & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY)
- : ((type & IMAGE_SCN_MEM_WRITE) ? PAGE_WRITECOPY : PAGE_NOACCESS)));
- };
-
- DWORD oldProtectValue;
- return VirtualProtect (sectionAddress, sectionSize,
- static_cast<DWORD> (getProtectionFlags (sectionCharacteristics)),
- std::addressof (oldProtectValue)) != 0;
- }
-
- bool loadImports()
- {
- auto imp = getDataDirectoryAddress<IMAGE_IMPORT_DESCRIPTOR> (IMAGE_DIRECTORY_ENTRY_IMPORT);
-
- if (imp == nullptr)
- return false;
-
- while (! IsBadReadPtr (imp, sizeof (IMAGE_IMPORT_DESCRIPTOR)) && imp->Name != 0)
- {
- auto handle = LoadLibraryA (getOffsetAs<const char> (imageData, imp->Name));
-
- if (handle == nullptr)
- return false;
-
- loadedModules.push_back (handle);
- auto thunkRef = getOffsetAs<uintptr_t> (imageData, imp->OriginalFirstThunk ? imp->OriginalFirstThunk
- : imp->FirstThunk);
- auto funcRef = getOffsetAs<FARPROC> (imageData, imp->FirstThunk);
-
- for (; *thunkRef != 0; ++thunkRef, ++funcRef)
- {
- auto name = IMAGE_SNAP_BY_ORDINAL(*thunkRef)
- ? (LPCSTR) IMAGE_ORDINAL(*thunkRef)
- : (LPCSTR) std::addressof (getOffsetAs<IMAGE_IMPORT_BY_NAME> (imageData, *thunkRef)->Name);
-
- *funcRef = GetProcAddress (handle, name);
-
- if (*funcRef == 0)
- return false;
- }
-
- ++imp;
- }
-
- return true;
- }
-
- size_t getSectionSize (const IMAGE_SECTION_HEADER& section) const
- {
- if (auto size = section.SizeOfRawData)
- return size;
-
- if (section.Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA)
- return imageHeaders->OptionalHeader.SizeOfInitializedData;
-
- if (section.Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA)
- return imageHeaders->OptionalHeader.SizeOfUninitializedData;
-
- return 0;
- }
-
- static size_t roundUp (size_t value, size_t alignment) { return (value + alignment - 1) & ~(alignment - 1); }
- char* getPageBase (void* address) const { return (char*) (reinterpret_cast<uintptr_t> (address) & ~static_cast<uintptr_t> (pageSize - 1)); }
-
- char* getSectionAddress (const IMAGE_SECTION_HEADER& section) const
- {
- #ifdef _WIN64
- return reinterpret_cast<char*> (static_cast<uintptr_t> (section.Misc.PhysicalAddress)
- | (static_cast<uintptr_t> (imageHeaders->OptionalHeader.ImageBase & 0xffffffff00000000)));
- #else
- return reinterpret_cast<char*> (section.Misc.PhysicalAddress);
- #endif
- }
-
- bool copySections (const void* data, size_t size, size_t sectionAlignment) const
- {
- auto section = IMAGE_FIRST_SECTION (imageHeaders);
-
- for (int i = 0; i < imageHeaders->FileHeader.NumberOfSections; ++i, ++section)
- {
- if (section->SizeOfRawData == 0)
- {
- if (sectionAlignment == 0)
- continue;
-
- if (auto dest = VirtualAlloc (getOffsetAs<char> (imageData, section->VirtualAddress),
- sectionAlignment, MEM_COMMIT, PAGE_READWRITE))
- {
- dest = getOffsetAs<char> (imageData, section->VirtualAddress);
- section->Misc.PhysicalAddress = static_cast<uint32_t> (reinterpret_cast<uintptr_t> (dest));
- memset (dest, 0, sectionAlignment);
- continue;
- }
-
- return false;
- }
-
- if (size < section->PointerToRawData + section->SizeOfRawData)
- return false;
-
- if (auto dest = VirtualAlloc (getOffsetAs<char> (imageData, section->VirtualAddress),
- section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE))
- {
- dest = getOffsetAs<char> (imageData, section->VirtualAddress);
- memcpy (dest, static_cast<const char*> (data) + section->PointerToRawData, section->SizeOfRawData);
- section->Misc.PhysicalAddress = static_cast<uint32_t> (reinterpret_cast<uintptr_t> (dest));
- continue;
- }
-
- return false;
- }
-
- return true;
- }
-
- void performRelocation (ptrdiff_t delta)
- {
- auto directory = imageHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
-
- if (directory.Size != 0)
- {
- auto relocation = getOffsetAs<IMAGE_BASE_RELOCATION> (imageData, directory.VirtualAddress);
-
- while (relocation->VirtualAddress > 0)
- {
- auto dest = getOffsetAs<char> (imageData, relocation->VirtualAddress);
- auto offset = getOffsetAs<uint16_t> (relocation, sizeof (IMAGE_BASE_RELOCATION));
-
- for (uint32_t i = 0; i < (relocation->SizeOfBlock - sizeof (IMAGE_BASE_RELOCATION)) / 2; ++i, ++offset)
- {
- switch (*offset >> 12)
- {
- case IMAGE_REL_BASED_HIGHLOW: addDelta<uint32_t> (dest + (*offset & 0xfff), delta); break;
- case IMAGE_REL_BASED_DIR64: addDelta<uint64_t> (dest + (*offset & 0xfff), delta); break;
- case IMAGE_REL_BASED_ABSOLUTE:
- default: break;
- }
- }
-
- relocation = getOffsetAs<IMAGE_BASE_RELOCATION> (relocation, relocation->SizeOfBlock);
- }
- }
- }
-
- template <typename Type>
- static void addDelta (char* addr, ptrdiff_t delta)
- {
- *reinterpret_cast<Type*> (addr) = static_cast<Type> (static_cast<ptrdiff_t> (*reinterpret_cast<Type*> (addr)) + delta);
- }
- };
-
- inline MemoryDLL::~MemoryDLL() = default;
-
- inline MemoryDLL::MemoryDLL (const void* data, size_t size) : pimpl (std::make_unique<Pimpl>())
- {
- if (! pimpl->initialise (data, size))
- pimpl.reset();
- }
-
- inline void* MemoryDLL::findFunction (std::string_view name)
- {
- return pimpl != nullptr ? pimpl->findFunction (name) : nullptr;
- }
-
- END_NAMESPACE_DISTRHO
-
- #endif // CHOC_MEMORYDLL_HEADER_INCLUDED
|