diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a725a5d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto + +# Native line endings. +*.c text +*.cpp text +*.h text +*.rc text + +# Windows line endings. +*.bat text eol=crlf +*.eln text eol=crlf diff --git a/.gitignore b/.gitignore index 6c07d24..508c3dc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ *.exe tests/*.dll tests/*.res + +tests/SampleExports.cpp +tests/SampleExports.h diff --git a/.travis.yml b/.travis.yml index e9dcb84..0bb4efe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,41 +1,95 @@ -sudo: false +sudo: true -env: - - PLATFORM=x86_64 WINE=wine64 UNICODE= CMAKE= - - PLATFORM=i686 WINE=wine UNICODE= CMAKE= - - PLATFORM=x86_64 WINE=wine64 UNICODE=1 CMAKE= - - PLATFORM=i686 WINE=wine UNICODE=1 CMAKE= - - PLATFORM=x86_64 WINE=wine64 UNICODE= CMAKE=1 - - PLATFORM=i686 WINE=wine UNICODE= CMAKE=1 - - PLATFORM=x86_64 WINE=wine64 UNICODE=1 CMAKE=1 - - PLATFORM=i686 WINE=wine UNICODE=1 CMAKE=1 +matrix: + include: + - env: PLATFORM=x86_64 UNICODE= CMAKE= WINE_PACKAGE=winehq-stable:amd64 + addons: + apt: + packages: + - binutils-mingw-w64-x86-64 + - mingw-w64-x86-64-dev + - g++-mingw-w64-x86-64 + - gcc-mingw-w64-x86-64 + - env: PLATFORM=i686 UNICODE= CMAKE= WINE_PACKAGE=winehq-stable:i386 + addons: + apt: + packages: + - binutils-mingw-w64-i686 + - mingw-w64-i686-dev + - g++-mingw-w64-i686 + - gcc-mingw-w64-i686 + - env: PLATFORM=x86_64 UNICODE=1 CMAKE= WINE_PACKAGE=winehq-stable:amd64 + addons: + apt: + packages: + - binutils-mingw-w64-x86-64 + - mingw-w64-x86-64-dev + - g++-mingw-w64-x86-64 + - gcc-mingw-w64-x86-64 + - env: PLATFORM=i686 UNICODE=1 CMAKE= WINE_PACKAGE=winehq-stable:i386 + addons: + apt: + packages: + - binutils-mingw-w64-i686 + - mingw-w64-i686-dev + - g++-mingw-w64-i686 + - gcc-mingw-w64-i686 + - env: PLATFORM=x86_64 UNICODE= CMAKE=1 WINE_PACKAGE=winehq-stable:amd64 + addons: + apt: + packages: + - binutils-mingw-w64-x86-64 + - mingw-w64-x86-64-dev + - g++-mingw-w64-x86-64 + - gcc-mingw-w64-x86-64 + - env: PLATFORM=i686 UNICODE= CMAKE=1 WINE_PACKAGE=winehq-stable:i386 + addons: + apt: + packages: + - binutils-mingw-w64-i686 + - cmake + - mingw-w64-i686-dev + - g++-mingw-w64-i686 + - gcc-mingw-w64-i686 + - env: PLATFORM=x86_64 UNICODE=1 CMAKE=1 WINE_PACKAGE=winehq-stable:amd64 + addons: + apt: + packages: + - binutils-mingw-w64-x86-64 + - cmake + - mingw-w64-x86-64-dev + - g++-mingw-w64-x86-64 + - gcc-mingw-w64-x86-64 + - env: PLATFORM=i686 UNICODE=1 CMAKE=1 WINE_PACKAGE=winehq-stable:i386 + addons: + apt: + packages: + - binutils-mingw-w64-i686 + - cmake + - mingw-w64-i686-dev + - g++-mingw-w64-i686 + - gcc-mingw-w64-i686 language: cpp +dist: xenial + cache: - apt - ccache -addons: - apt: - packages: - - binutils-mingw-w64-i686 - - binutils-mingw-w64-x86-64 - - cmake - - mingw-w64-dev - - g++-mingw-w64-i686 - - g++-mingw-w64-x86-64 - - gcc-mingw-w64-i686 - - gcc-mingw-w64-x86-64 - - wine - before_script: - - if [ ! -z "$CMAKE" ]; then cmake -DPLATFORM=$PLATFORM -D UNICODE=$UNICODE -H. -B.; fi + - curl https://dl.winehq.org/wine-builds/winehq.key | sudo apt-key add - + - echo "deb https://dl.winehq.org/wine-builds/ubuntu/ xenial main" | sudo tee /etc/apt/sources.list.d/winehq.list + - sudo apt-get -y update && sudo apt-get -y install --install-recommends $WINE_PACKAGE + - if [ ! -z "$CMAKE" ]; then cmake -DPLATFORM=$PLATFORM -DUNICODE=$UNICODE -DTESTSUITE=ON -H. -B.; fi script: - if [ -z "$CMAKE" ]; then make PLATFORM=$PLATFORM UNICODE=$UNICODE; fi - if [ -z "$CMAKE" ]; then make test PLATFORM=$PLATFORM UNICODE=$UNICODE; fi - if [ ! -z "$CMAKE" ]; then cmake --build .; fi - cd example/DllLoader - - WINEPREFIX=`pwd`/$WINE WINEPATH=/usr/lib/gcc/$PLATFORM-w64-mingw32/4.6/ $WINE ./DllLoader.exe - - WINEPREFIX=`pwd`/$WINE WINEPATH=/usr/lib/gcc/$PLATFORM-w64-mingw32/4.6/ $WINE ./DllLoaderLoader.exe + - ../../tests/runwine.sh $PLATFORM ./DllLoader.exe + - ../../tests/runwine.sh $PLATFORM ./DllLoaderLoader.exe + - cd ../../tests + - ./runwine.sh $PLATFORM ./TestSuite.exe diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ddd49a..cfddbfb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ endif () if (NOT MSVC) add_definitions ("-Wall") else () + # Show level 4 warnings. add_definitions ("-W4") endif () @@ -39,11 +40,21 @@ else () message (STATUS "Compile without UNICODE support") endif () +option(TESTSUITE "Compile with TESTSUITE support" OFF) +if (TESTSUITE) + message (STATUS "Compile with TESTSUITE support") + add_definitions ("-DTESTSUITE") +else () + message (STATUS "Compile without TESTSUITE support") +endif () + add_library (MemoryModule STATIC MemoryModule.c MemoryModule.h) +target_include_directories(MemoryModule PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") if (NOT MSVC) set_target_properties ("MemoryModule" PROPERTIES PREFIX "") endif () add_subdirectory (example) +add_subdirectory (tests) enable_language (RC) diff --git a/MemoryModule.c b/MemoryModule.c index d8d3f3a..9f95a70 100644 --- a/MemoryModule.c +++ b/MemoryModule.c @@ -22,12 +22,17 @@ * Portions created by Joachim Bauch are Copyright (C) 2004-2015 * Joachim Bauch. All Rights Reserved. * + * + * THeller: Added binary search in MemoryGetProcAddress function + * (#define USE_BINARY_SEARCH to enable it). This gives a very large + * speedup for libraries that exports lots of functions. + * + * These portions are Copyright (C) 2013 Thomas Heller. */ #include #include #include -#include #include #ifdef DEBUG_OUTPUT #include @@ -36,6 +41,12 @@ #if _MSC_VER // Disable warning about data -> function pointer conversion #pragma warning(disable:4055) + // C4244: conversion from 'uintptr_t' to 'DWORD', possible loss of data. +#pragma warning(error: 4244) +// C4267: conversion from 'size_t' to 'int', possible loss of data. +#pragma warning(error: 4267) + +#define inline __inline #endif #ifndef IMAGE_SIZEOF_BASE_RELOCATION @@ -43,11 +54,29 @@ #define IMAGE_SIZEOF_BASE_RELOCATION (sizeof(IMAGE_BASE_RELOCATION)) #endif +#ifdef _WIN64 +#define HOST_MACHINE IMAGE_FILE_MACHINE_AMD64 +#else +#define HOST_MACHINE IMAGE_FILE_MACHINE_I386 +#endif + #include "MemoryModule.h" +struct ExportNameEntry { + LPCSTR name; + WORD idx; +}; + typedef BOOL (WINAPI *DllEntryProc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); typedef int (WINAPI *ExeEntryProc)(void); +#ifdef _WIN64 +typedef struct POINTER_LIST { + struct POINTER_LIST *next; + void *address; +} POINTER_LIST; +#endif + typedef struct { PIMAGE_NT_HEADERS headers; unsigned char *codeBase; @@ -61,27 +90,51 @@ typedef struct { CustomLoadLibraryFunc loadLibrary; CustomGetProcAddressFunc getProcAddress; CustomFreeLibraryFunc freeLibrary; + struct ExportNameEntry *nameExportsTable; void *userdata; ExeEntryProc exeEntry; DWORD pageSize; +#ifdef _WIN64 + POINTER_LIST *blockedMemory; +#endif } MEMORYMODULE, *PMEMORYMODULE; typedef struct { LPVOID address; LPVOID alignedAddress; - DWORD size; + SIZE_T size; DWORD characteristics; BOOL last; } SECTIONFINALIZEDATA, *PSECTIONFINALIZEDATA; #define GET_HEADER_DICTIONARY(module, idx) &(module)->headers->OptionalHeader.DataDirectory[idx] -#define ALIGN_DOWN(address, alignment) (LPVOID)((uintptr_t)(address) & ~((alignment) - 1)) -#define ALIGN_VALUE_UP(value, alignment) (((value) + (alignment) - 1) & ~((alignment) - 1)) -#ifdef DEBUG_OUTPUT -static void +static inline uintptr_t +AlignValueDown(uintptr_t value, uintptr_t alignment) { + return value & ~(alignment - 1); +} + +static inline LPVOID +AlignAddressDown(LPVOID address, uintptr_t alignment) { + return (LPVOID) AlignValueDown((uintptr_t) address, alignment); +} + +static inline size_t +AlignValueUp(size_t value, size_t alignment) { + return (value + alignment - 1) & ~(alignment - 1); +} + +static inline void* +OffsetPointer(void* data, ptrdiff_t offset) { + return (void*) ((uintptr_t) data + offset); +} + +static inline void OutputLastError(const char *msg) { +#ifndef DEBUG_OUTPUT + UNREFERENCED_PARAMETER(msg); +#else LPVOID tmp; char *tmpmsg; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, @@ -91,6 +144,21 @@ OutputLastError(const char *msg) OutputDebugString(tmpmsg); LocalFree(tmpmsg); LocalFree(tmp); +#endif +} + +#ifdef _WIN64 +static void +FreePointerList(POINTER_LIST *head, CustomFreeFunc freeMemory, void *userdata) +{ + POINTER_LIST *node = head; + while (node) { + POINTER_LIST *next; + freeMemory(node->address, 0, MEM_RELEASE, userdata); + next = node->next; + free(node); + node = next; + } } #endif @@ -127,9 +195,11 @@ CopySections(const unsigned char *data, size_t size, PIMAGE_NT_HEADERS old_heade } // Always use position from file to support alignments smaller - // than page size. + // than page size (allocation above will align to page size). dest = codeBase + section->VirtualAddress; - section->Misc.PhysicalAddress = (DWORD) (uintptr_t) dest; + // NOTE: On 64bit systems we truncate to 32bit here but expand + // again later when "PhysicalAddress" is used. + section->Misc.PhysicalAddress = (DWORD) ((uintptr_t) dest & 0xffffffff); memset(dest, 0, section_size); } @@ -152,10 +222,12 @@ CopySections(const unsigned char *data, size_t size, PIMAGE_NT_HEADERS old_heade } // Always use position from file to support alignments smaller - // than page size. + // than page size (allocation above will align to page size). dest = codeBase + section->VirtualAddress; memcpy(dest, data + section->PointerToRawData, section->SizeOfRawData); - section->Misc.PhysicalAddress = (DWORD) (uintptr_t) dest; + // NOTE: On 64bit systems we truncate to 32bit here but expand + // again later when "PhysicalAddress" is used. + section->Misc.PhysicalAddress = (DWORD) ((uintptr_t) dest & 0xffffffff); } return TRUE; @@ -174,7 +246,7 @@ static int ProtectionFlags[2][2][2] = { }, }; -static DWORD +static SIZE_T GetRealSectionSize(PMEMORYMODULE module, PIMAGE_SECTION_HEADER section) { DWORD size = section->SizeOfRawData; if (size == 0) { @@ -184,7 +256,7 @@ GetRealSectionSize(PMEMORYMODULE module, PIMAGE_SECTION_HEADER section) { size = module->headers->OptionalHeader.SizeOfUninitializedData; } } - return size; + return (SIZE_T) size; } static BOOL @@ -222,9 +294,7 @@ FinalizeSection(PMEMORYMODULE module, PSECTIONFINALIZEDATA sectionData) { // change memory access flags if (VirtualProtect(sectionData->address, sectionData->size, protect, &oldProtect) == 0) { -#ifdef DEBUG_OUTPUT - OutputLastError("Error protecting memory page") -#endif + OutputLastError("Error protecting memory page"); return FALSE; } @@ -237,13 +307,15 @@ FinalizeSections(PMEMORYMODULE module) int i; PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); #ifdef _WIN64 - uintptr_t imageOffset = (module->headers->OptionalHeader.ImageBase & 0xffffffff00000000); + // "PhysicalAddress" might have been truncated to 32bit above, expand to + // 64bits again. + uintptr_t imageOffset = ((uintptr_t) module->headers->OptionalHeader.ImageBase & 0xffffffff00000000); #else - #define imageOffset 0 + static const uintptr_t imageOffset = 0; #endif SECTIONFINALIZEDATA sectionData; sectionData.address = (LPVOID)((uintptr_t)section->Misc.PhysicalAddress | imageOffset); - sectionData.alignedAddress = ALIGN_DOWN(sectionData.address, module->pageSize); + sectionData.alignedAddress = AlignAddressDown(sectionData.address, module->pageSize); sectionData.size = GetRealSectionSize(module, section); sectionData.characteristics = section->Characteristics; sectionData.last = FALSE; @@ -252,8 +324,8 @@ FinalizeSections(PMEMORYMODULE module) // loop through all sections and change access flags for (i=1; iheaders->FileHeader.NumberOfSections; i++, section++) { LPVOID sectionAddress = (LPVOID)((uintptr_t)section->Misc.PhysicalAddress | imageOffset); - LPVOID alignedAddress = ALIGN_DOWN(sectionAddress, module->pageSize); - DWORD sectionSize = GetRealSectionSize(module, section); + LPVOID alignedAddress = AlignAddressDown(sectionAddress, module->pageSize); + SIZE_T sectionSize = GetRealSectionSize(module, section); // Combine access flags of all sections that share a page // TODO(fancycode): We currently share flags of a trailing large section // with the page of a first small section. This should be optimized. @@ -264,7 +336,7 @@ FinalizeSections(PMEMORYMODULE module) } else { sectionData.characteristics |= section->Characteristics; } - sectionData.size = (((uintptr_t)sectionAddress) + sectionSize) - (uintptr_t) sectionData.address; + sectionData.size = (((uintptr_t)sectionAddress) + ((uintptr_t) sectionSize)) - (uintptr_t) sectionData.address; continue; } @@ -280,9 +352,6 @@ FinalizeSections(PMEMORYMODULE module) if (!FinalizeSection(module, §ionData)) { return FALSE; } -#ifndef _WIN64 -#undef imageOffset -#endif return TRUE; } @@ -324,18 +393,12 @@ PerformBaseRelocation(PMEMORYMODULE module, ptrdiff_t delta) for (; relocation->VirtualAddress > 0; ) { DWORD i; unsigned char *dest = codeBase + relocation->VirtualAddress; - unsigned short *relInfo = (unsigned short *)((unsigned char *)relocation + IMAGE_SIZEOF_BASE_RELOCATION); + unsigned short *relInfo = (unsigned short*) OffsetPointer(relocation, IMAGE_SIZEOF_BASE_RELOCATION); for (i=0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++) { - DWORD *patchAddrHL; -#ifdef _WIN64 - ULONGLONG *patchAddr64; -#endif - int type, offset; - // the upper 4 bits define the type of relocation - type = *relInfo >> 12; + int type = *relInfo >> 12; // the lower 12 bits define the offset - offset = *relInfo & 0xfff; + int offset = *relInfo & 0xfff; switch (type) { @@ -345,14 +408,18 @@ PerformBaseRelocation(PMEMORYMODULE module, ptrdiff_t delta) case IMAGE_REL_BASED_HIGHLOW: // change complete 32 bit address - patchAddrHL = (DWORD *) (dest + offset); - *patchAddrHL += (DWORD) delta; + { + DWORD *patchAddrHL = (DWORD *) (dest + offset); + *patchAddrHL += (DWORD) delta; + } break; #ifdef _WIN64 case IMAGE_REL_BASED_DIR64: - patchAddr64 = (ULONGLONG *) (dest + offset); - *patchAddr64 += (ULONGLONG) delta; + { + ULONGLONG *patchAddr64 = (ULONGLONG *) (dest + offset); + *patchAddr64 += (ULONGLONG) delta; + } break; #endif @@ -363,7 +430,7 @@ PerformBaseRelocation(PMEMORYMODULE module, ptrdiff_t delta) } // advance to next relocation block - relocation = (PIMAGE_BASE_RELOCATION) (((char *) relocation) + relocation->SizeOfBlock); + relocation = (PIMAGE_BASE_RELOCATION) OffsetPointer(relocation, relocation->SizeOfBlock); } return TRUE; } @@ -493,6 +560,9 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data, size_t size, size_t optionalSectionSize; size_t lastSectionEnd = 0; size_t alignedImageSize; +#ifdef _WIN64 + POINTER_LIST *blockedMemory = NULL; +#endif if (!CheckSize(size, sizeof(IMAGE_DOS_HEADER))) { return NULL; @@ -512,11 +582,7 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data, size_t size, return NULL; } -#ifdef _WIN64 - if (old_header->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) { -#else - if (old_header->FileHeader.Machine != IMAGE_FILE_MACHINE_I386) { -#endif + if (old_header->FileHeader.Machine != HOST_MACHINE) { SetLastError(ERROR_BAD_EXE_FORMAT); return NULL; } @@ -544,8 +610,8 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data, size_t size, } GetNativeSystemInfo(&sysInfo); - alignedImageSize = ALIGN_VALUE_UP(old_header->OptionalHeader.SizeOfImage, sysInfo.dwPageSize); - if (alignedImageSize != ALIGN_VALUE_UP(lastSectionEnd, sysInfo.dwPageSize)) { + alignedImageSize = AlignValueUp(old_header->OptionalHeader.SizeOfImage, sysInfo.dwPageSize); + if (alignedImageSize != AlignValueUp(lastSectionEnd, sysInfo.dwPageSize)) { SetLastError(ERROR_BAD_EXE_FORMAT); return NULL; } @@ -572,9 +638,40 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data, size_t size, } } +#ifdef _WIN64 + // Memory block may not span 4 GB boundaries. + while ((((uintptr_t) code) >> 32) < (((uintptr_t) (code + alignedImageSize)) >> 32)) { + POINTER_LIST *node = (POINTER_LIST*) malloc(sizeof(POINTER_LIST)); + if (!node) { + freeMemory(code, 0, MEM_RELEASE, userdata); + FreePointerList(blockedMemory, freeMemory, userdata); + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + + node->next = blockedMemory; + node->address = code; + blockedMemory = node; + + code = (unsigned char *)allocMemory(NULL, + alignedImageSize, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE, + userdata); + if (code == NULL) { + FreePointerList(blockedMemory, freeMemory, userdata); + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + } +#endif + result = (PMEMORYMODULE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MEMORYMODULE)); if (result == NULL) { freeMemory(code, 0, MEM_RELEASE, userdata); +#ifdef _WIN64 + FreePointerList(blockedMemory, freeMemory, userdata); +#endif SetLastError(ERROR_OUTOFMEMORY); return NULL; } @@ -588,6 +685,9 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data, size_t size, result->freeLibrary = freeLibrary; result->userdata = userdata; result->pageSize = sysInfo.dwPageSize; +#ifdef _WIN64 + result->blockedMemory = blockedMemory; +#endif if (!CheckSize(size, old_header->OptionalHeader.SizeOfHeaders)) { goto error; @@ -662,12 +762,27 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data, size_t size, return NULL; } -FARPROC MemoryGetProcAddress(HMEMORYMODULE module, LPCSTR name) +static int _compare(const void *a, const void *b) +{ + const struct ExportNameEntry *p1 = (const struct ExportNameEntry*) a; + const struct ExportNameEntry *p2 = (const struct ExportNameEntry*) b; + return strcmp(p1->name, p2->name); +} + +static int _find(const void *a, const void *b) { - unsigned char *codeBase = ((PMEMORYMODULE)module)->codeBase; + LPCSTR *name = (LPCSTR *) a; + const struct ExportNameEntry *p = (const struct ExportNameEntry*) b; + return strcmp(*name, p->name); +} + +FARPROC MemoryGetProcAddress(HMEMORYMODULE mod, LPCSTR name) +{ + PMEMORYMODULE module = (PMEMORYMODULE)mod; + unsigned char *codeBase = module->codeBase; DWORD idx = 0; PIMAGE_EXPORT_DIRECTORY exports; - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((PMEMORYMODULE)module, IMAGE_DIRECTORY_ENTRY_EXPORT); + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_EXPORT); if (directory->Size == 0) { // no export table found SetLastError(ERROR_PROC_NOT_FOUND); @@ -689,25 +804,44 @@ FARPROC MemoryGetProcAddress(HMEMORYMODULE module, LPCSTR name) } idx = LOWORD(name) - exports->Base; + } else if (!exports->NumberOfNames) { + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; } else { - // search function name in list of exported names - DWORD i; - DWORD *nameRef = (DWORD *) (codeBase + exports->AddressOfNames); - WORD *ordinal = (WORD *) (codeBase + exports->AddressOfNameOrdinals); - BOOL found = FALSE; - for (i=0; iNumberOfNames; i++, nameRef++, ordinal++) { - if (_stricmp(name, (const char *) (codeBase + (*nameRef))) == 0) { - idx = *ordinal; - found = TRUE; - break; + const struct ExportNameEntry *found; + + // Lazily build name table and sort it by names + if (!module->nameExportsTable) { + DWORD i; + DWORD *nameRef = (DWORD *) (codeBase + exports->AddressOfNames); + WORD *ordinal = (WORD *) (codeBase + exports->AddressOfNameOrdinals); + struct ExportNameEntry *entry = (struct ExportNameEntry*) malloc(exports->NumberOfNames * sizeof(struct ExportNameEntry)); + module->nameExportsTable = entry; + if (!entry) { + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + for (i=0; iNumberOfNames; i++, nameRef++, ordinal++, entry++) { + entry->name = (const char *) (codeBase + (*nameRef)); + entry->idx = *ordinal; } + qsort(module->nameExportsTable, + exports->NumberOfNames, + sizeof(struct ExportNameEntry), _compare); } + // search function name in list of exported names with binary search + found = (const struct ExportNameEntry*) bsearch(&name, + module->nameExportsTable, + exports->NumberOfNames, + sizeof(struct ExportNameEntry), _find); if (!found) { // exported symbol not found SetLastError(ERROR_PROC_NOT_FOUND); return NULL; } + + idx = found->idx; } if (idx > exports->NumberOfFunctions) { @@ -733,6 +867,7 @@ void MemoryFreeLibrary(HMEMORYMODULE mod) (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0); } + free(module->nameExportsTable); if (module->modules != NULL) { // free previously opened libraries int i; @@ -750,6 +885,9 @@ void MemoryFreeLibrary(HMEMORYMODULE mod) module->free(module->codeBase, 0, MEM_RELEASE, module->userdata); } +#ifdef _WIN64 + FreePointerList(module->blockedMemory, module->free, module->userdata); +#endif HeapFree(GetProcessHeap(), 0, module); } @@ -846,11 +984,15 @@ static PIMAGE_RESOURCE_DIRECTORY_ENTRY _MemorySearchResourceEntry( int cmp; PIMAGE_RESOURCE_DIR_STRING_U resourceString; middle = (start + end) >> 1; - resourceString = (PIMAGE_RESOURCE_DIR_STRING_U) (((char *) root) + (entries[middle].Name & 0x7FFFFFFF)); + resourceString = (PIMAGE_RESOURCE_DIR_STRING_U) OffsetPointer(root, entries[middle].Name & 0x7FFFFFFF); cmp = _wcsnicmp(searchKey, resourceString->NameString, resourceString->Length); if (cmp == 0) { // Handle partial match - cmp = searchKeyLen - resourceString->Length; + if (searchKeyLen > resourceString->Length) { + cmp = 1; + } else if (searchKeyLen < resourceString->Length) { + cmp = -1; + } } if (cmp < 0) { end = (middle != end ? middle : middle-1); @@ -974,7 +1116,7 @@ MemoryLoadStringEx(HMEMORYMODULE module, UINT id, LPTSTR buffer, int maxsize, WO data = (PIMAGE_RESOURCE_DIR_STRING_U) MemoryLoadResource(module, resource); id = id & 0x0f; while (id--) { - data = (PIMAGE_RESOURCE_DIR_STRING_U) (((char *) data) + (data->Length + 1) * sizeof(WCHAR)); + data = (PIMAGE_RESOURCE_DIR_STRING_U) OffsetPointer(data, (data->Length + 1) * sizeof(WCHAR)); } if (data->Length == 0) { SetLastError(ERROR_RESOURCE_NAME_NOT_FOUND); @@ -995,3 +1137,66 @@ MemoryLoadStringEx(HMEMORYMODULE module, UINT id, LPTSTR buffer, int maxsize, WO #endif return size; } + +#ifdef TESTSUITE +#include + +#ifndef PRIxPTR +#ifdef _WIN64 +#define PRIxPTR "I64x" +#else +#define PRIxPTR "x" +#endif +#endif + +static const uintptr_t AlignValueDownTests[][3] = { + {16, 16, 16}, + {17, 16, 16}, + {32, 16, 32}, + {33, 16, 32}, +#ifdef _WIN64 + {0x12345678abcd1000, 0x1000, 0x12345678abcd1000}, + {0x12345678abcd101f, 0x1000, 0x12345678abcd1000}, +#endif + {0, 0, 0}, +}; + +static const uintptr_t AlignValueUpTests[][3] = { + {16, 16, 16}, + {17, 16, 32}, + {32, 16, 32}, + {33, 16, 48}, +#ifdef _WIN64 + {0x12345678abcd1000, 0x1000, 0x12345678abcd1000}, + {0x12345678abcd101f, 0x1000, 0x12345678abcd2000}, +#endif + {0, 0, 0}, +}; + +BOOL MemoryModuleTestsuite() { + BOOL success = TRUE; + size_t idx; + for (idx = 0; AlignValueDownTests[idx][0]; ++idx) { + const uintptr_t* tests = AlignValueDownTests[idx]; + uintptr_t value = AlignValueDown(tests[0], tests[1]); + if (value != tests[2]) { + printf("AlignValueDown failed for 0x%" PRIxPTR "/0x%" PRIxPTR ": expected 0x%" PRIxPTR ", got 0x%" PRIxPTR "\n", + tests[0], tests[1], tests[2], value); + success = FALSE; + } + } + for (idx = 0; AlignValueDownTests[idx][0]; ++idx) { + const uintptr_t* tests = AlignValueUpTests[idx]; + uintptr_t value = AlignValueUp(tests[0], tests[1]); + if (value != tests[2]) { + printf("AlignValueUp failed for 0x%" PRIxPTR "/0x%" PRIxPTR ": expected 0x%" PRIxPTR ", got 0x%" PRIxPTR "\n", + tests[0], tests[1], tests[2], value); + success = FALSE; + } + } + if (success) { + printf("OK\n"); + } + return success; +} +#endif diff --git a/appveyor.yml b/appveyor.yml index 8fb52dd..5bc1608 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,12 +1,16 @@ # Status available at # https://ci.appveyor.com/project/fancycode/memorymodule -version: #{build} +version: 1.0.{build} os: - Visual Studio 2015 environment: matrix: + - GENERATOR: "Visual Studio 9 2008" + UNICODE: ON + - GENERATOR: "Visual Studio 9 2008" + UNICODE: OFF - GENERATOR: "Visual Studio 10 2010" UNICODE: ON - GENERATOR: "Visual Studio 10 2010" @@ -31,20 +35,9 @@ platform: configuration: - Debug -build: - verbosity: normal - -build_script: - - ps: if($env:PLATFORM -eq "x64") { $env:CMAKE_GEN_SUFFIX=" Win64" } - - cmake "-G%GENERATOR%%CMAKE_GEN_SUFFIX%" -H. -Bbuild - - cmake --build build --config %CONFIGURATION% - -before_test: - - copy /y build\example\DllLoader\%CONFIGURATION%\DllLoader.exe build\example\DllLoader\ - - copy /y build\example\DllLoader\%CONFIGURATION%\DllLoaderLoader.exe build\example\DllLoader\ - - copy /y build\example\SampleDLL\%CONFIGURATION%\SampleDLL.dll build\example\SampleDLL\ +install: + - call scripts\run-appveyor.bat -test_script: - - cd build\example\DllLoader - - DllLoader.exe - - DllLoaderLoader.exe +build: off +test: off +deploy: off diff --git a/example/DllLoader/DllLoader.cpp b/example/DllLoader/DllLoader.cpp index 973f5b4..4355ea8 100644 --- a/example/DllLoader/DllLoader.cpp +++ b/example/DllLoader/DllLoader.cpp @@ -46,8 +46,8 @@ void LoadFromFile(void) FreeLibrary(handle); } -void* ReadLibrary(long* pSize) { - long read; +void* ReadLibrary(size_t* pSize) { + size_t read; void* result; FILE* fp; @@ -59,8 +59,8 @@ void* ReadLibrary(long* pSize) { } fseek(fp, 0, SEEK_END); - *pSize = ftell(fp); - if (*pSize < 0) + *pSize = static_cast(ftell(fp)); + if (*pSize == 0) { fclose(fp); return NULL; @@ -75,7 +75,7 @@ void* ReadLibrary(long* pSize) { fseek(fp, 0, SEEK_SET); read = fread(result, 1, *pSize, fp); fclose(fp); - if (read != static_cast(*pSize)) + if (read != *pSize) { free(result); return NULL; @@ -87,7 +87,7 @@ void* ReadLibrary(long* pSize) { void LoadFromMemory(void) { void *data; - long size; + size_t size; HMEMORYMODULE handle; addNumberProc addNumber; HMEMORYRSRC resourceInfo; @@ -195,7 +195,7 @@ void InitFreeFunc(CallList* calls, CustomFreeFunc freeFunc) { calls->current_free_call = 0; } -void TestFailingAllocation(void *data, long size) { +void TestFailingAllocation(void *data, size_t size) { CallList expected_calls; HMEMORYMODULE handle; @@ -214,7 +214,7 @@ void TestFailingAllocation(void *data, long size) { assert(expected_calls.current_free_call == 0); } -void TestCleanupAfterFailingAllocation(void *data, long size) { +void TestCleanupAfterFailingAllocation(void *data, size_t size) { CallList expected_calls; HMEMORYMODULE handle; int free_calls_after_loading; @@ -238,7 +238,7 @@ void TestCleanupAfterFailingAllocation(void *data, long size) { assert(expected_calls.current_free_call == free_calls_after_loading); } -void TestFreeAfterDefaultAlloc(void *data, long size) { +void TestFreeAfterDefaultAlloc(void *data, size_t size) { CallList expected_calls; HMEMORYMODULE handle; int free_calls_after_loading; @@ -257,10 +257,59 @@ void TestFreeAfterDefaultAlloc(void *data, long size) { assert(expected_calls.current_free_call == free_calls_after_loading + 1); } +#ifdef _WIN64 + +LPVOID MemoryAllocHigh(LPVOID address, SIZE_T size, DWORD allocationType, DWORD protect, void* userdata) +{ + int* counter = static_cast(userdata); + if (*counter == 0) { + // Make sure the image gets loaded to an address above 32bit. + uintptr_t offset = 0x10000000000; + address = (LPVOID) ((uintptr_t) address + offset); + } + (*counter)++; + return MemoryDefaultAlloc(address, size, allocationType, protect, NULL); +} + +void TestAllocHighMemory(void *data, size_t size) { + HMEMORYMODULE handle; + int counter = 0; + addNumberProc addNumber; + HMEMORYRSRC resourceInfo; + DWORD resourceSize; + LPVOID resourceData; + TCHAR buffer[100]; + + handle = MemoryLoadLibraryEx( + data, size, MemoryAllocHigh, MemoryDefaultFree, MemoryDefaultLoadLibrary, + MemoryDefaultGetProcAddress, MemoryDefaultFreeLibrary, &counter); + + assert(handle != NULL); + + addNumber = (addNumberProc)MemoryGetProcAddress(handle, "addNumbers"); + _tprintf(_T("From memory: %d\n"), addNumber(1, 2)); + + resourceInfo = MemoryFindResource(handle, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION); + _tprintf(_T("MemoryFindResource returned 0x%p\n"), resourceInfo); + + resourceSize = MemorySizeofResource(handle, resourceInfo); + resourceData = MemoryLoadResource(handle, resourceInfo); + _tprintf(_T("Memory resource data: %ld bytes at 0x%p\n"), resourceSize, resourceData); + + MemoryLoadString(handle, 1, buffer, sizeof(buffer)); + _tprintf(_T("String1: %s\n"), buffer); + + MemoryLoadString(handle, 20, buffer, sizeof(buffer)); + _tprintf(_T("String2: %s\n"), buffer); + + MemoryFreeLibrary(handle); +} +#endif // _WIN64 + void TestCustomAllocAndFree(void) { void *data; - long size; + size_t size; data = ReadLibrary(&size); if (data == NULL) @@ -274,6 +323,10 @@ void TestCustomAllocAndFree(void) TestCleanupAfterFailingAllocation(data, size); _tprintf(_T("Test custom free function after MemoryLoadLibraryEx\n")); TestFreeAfterDefaultAlloc(data, size); +#ifdef _WIN64 + _tprintf(_T("Test allocating in high memory\n")); + TestAllocHighMemory(data, size); +#endif free(data); } diff --git a/readme.md b/readme.md index aa9f497..4e9d82d 100644 --- a/readme.md +++ b/readme.md @@ -16,5 +16,5 @@ file gets deleted. `MemoryModule` is a library that can be used to load a DLL completely from memory - without storing on the disk first. -See `doc/readme.txt` for more informations about the format of a DLL file and +See `doc/readme.rst` for more informations about the format of a DLL file and a tutorial how they can be loaded directly. diff --git a/scripts/run-appveyor.bat b/scripts/run-appveyor.bat new file mode 100644 index 0000000..6662d99 --- /dev/null +++ b/scripts/run-appveyor.bat @@ -0,0 +1,48 @@ +@echo off +setlocal + +if /I "%PLATFORM%" == "x64" ( + set CMAKE_GEN_SUFFIX= Win64 + if /I "%GENERATOR%" == "Visual Studio 9 2008" ( + echo Skipping %CONFIGURATION% build using %GENERATOR%%CMAKE_GEN_SUFFIX% on %PLATFORM% + exit 0 + ) +) else ( + set CMAKE_GEN_SUFFIX= +) + +echo. +echo Preparing %CONFIGURATION% build environment for %GENERATOR%%CMAKE_GEN_SUFFIX% ... +cmake "-G%GENERATOR%%CMAKE_GEN_SUFFIX%" -DPLATFORM=%PLATFORM% -DUNICODE=%UNICODE% -DTESTSUITE=ON -H. -Bbuild +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo Building ... +cmake --build build --config %CONFIGURATION% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo Copying generated files ... +copy /y build\example\DllLoader\%CONFIGURATION%\DllLoader.exe build\example\DllLoader\ > NUL +copy /y build\example\DllLoader\%CONFIGURATION%\DllLoaderLoader.exe build\example\DllLoader\ > NUL +copy /y build\example\SampleDLL\%CONFIGURATION%\SampleDLL.dll build\example\SampleDLL\ > NUL +copy /y build\tests\%CONFIGURATION%\TestSuite.exe build\tests\ > NUL + +cd build\example\DllLoader + +echo. +echo Running DllLoader.exe ... +DllLoader.exe +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo Running DllLoaderLoader.exe ... +DllLoaderLoader.exe +if %errorlevel% neq 0 exit /b %errorlevel% + +cd ..\..\tests + +echo. +echo Running TestSuite.exe ... +TestSuite.exe +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..8b1007f --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,14 @@ +set (sources_testsuite + TestSuite.c +) + +if (NOT MSVC) + set (CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "-static") + set (CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "-static") +endif () + +add_executable (TestSuite ${sources_testsuite}) +target_link_libraries ("TestSuite" "MemoryModule") +if (NOT MSVC) + set_target_properties ("TestSuite" PROPERTIES SUFFIX ".exe") +endif () diff --git a/tests/LoadDll.cpp b/tests/LoadDll.cpp index 07a2538..8cb215b 100644 --- a/tests/LoadDll.cpp +++ b/tests/LoadDll.cpp @@ -11,6 +11,7 @@ #include "../MemoryModule.h" +typedef int (*addProc)(int); typedef int (*addNumberProc)(int, int); // Thanks to Tim Cooper (from http://stackoverflow.com/a/8584708) @@ -127,6 +128,11 @@ BOOL LoadFromMemory(char *filename) } addNumber = (addNumberProc)MemoryGetProcAddress(handle, "addNumbers"); + if (!addNumber) { + _tprintf(_T("MemoryGetProcAddress(\"addNumber\") returned NULL\n")); + result = FALSE; + goto exit; + } _tprintf(_T("From memory: %d\n"), addNumber(1, 2)); // the DLL only exports one function, try to load by ordinal value @@ -218,6 +224,65 @@ BOOL LoadFromMemory(char *filename) return result; } +BOOL LoadExportsFromMemory(char *filename) +{ + FILE *fp; + unsigned char *data=NULL; + long size; + size_t read; + HMEMORYMODULE handle = NULL; + int i; + BOOL result = TRUE; + + fp = fopen(filename, "rb"); + if (fp == NULL) + { + printf("Can't open DLL file \"%s\".", filename); + result = FALSE; + goto exit; + } + + fseek(fp, 0, SEEK_END); + size = ftell(fp); + assert(size > 0); + data = (unsigned char *)malloc(size); + assert(data != NULL); + fseek(fp, 0, SEEK_SET); + read = fread(data, 1, size, fp); + assert(read == static_cast(size)); + fclose(fp); + + handle = MemoryLoadLibrary(data, size); + if (handle == NULL) + { + _tprintf(_T("Can't load library from memory.\n")); + result = FALSE; + goto exit; + } + + for (i = 1; i <= 100; i++) { + char name[100]; + sprintf(name, "add%d", i); + addProc addNumber = (addProc)MemoryGetProcAddress(handle, name); + if (!addNumber) { + _tprintf(_T("MemoryGetProcAddress(\"%s\") returned NULL\n"), name); + result = FALSE; + goto exit; + } + int result = addNumber(1); + if (result != 1 + i) { + _tprintf(_T("(\"%s\") returned %d, expected %d\n"), name, result, 1 + i); + result = FALSE; + goto exit; + } + _tprintf(_T("%s: %d\n"), name, result); + } +exit: + MemoryFreeLibrary(handle); + free(data); + return result; +} + int main(int argc, char* argv[]) { if (argc < 2) { @@ -225,8 +290,14 @@ int main(int argc, char* argv[]) return 1; } - if (!LoadFromMemory(argv[1])) { - return 2; + if (!strstr((const char *) argv[1], "exports")) { + if (!LoadFromMemory(argv[1])) { + return 2; + } + } else { + if (!LoadExportsFromMemory(argv[1])) { + return 2; + } } return 0; diff --git a/tests/Makefile b/tests/Makefile index f167a72..028bcad 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -16,7 +16,7 @@ RC = rc endif RM = rm -CFLAGS = -Wall -g +CFLAGS = -Wall -g -DTESTSUITE LDFLAGS = RCFLAGS = -O coff @@ -47,15 +47,23 @@ TEST_DLLS = \ test-align-800.dll \ test-align-900.dll \ test-relocate.dll \ + test-exports.dll LOADDLL_OBJ = LoadDll.o ../MemoryModule.o +TESTSUITE_OBJ = TestSuite.o ../MemoryModule.o DLL_OBJ = SampleDLL.o SampleDLL.res -all: LoadDll.exe $(TEST_DLLS) +all: prepare_testsuite LoadDll.exe TestSuite.exe $(TEST_DLLS) + +prepare_testsuite: + rm -f $(TESTSUITE_OBJ) LoadDll.exe: $(LOADDLL_OBJ) $(CC) $(LDFLAGS_EXE) $(LDFLAGS) -Wl,--image-base -Wl,0x20000000 -o LoadDll.exe $(LOADDLL_OBJ) +TestSuite.exe: $(TESTSUITE_OBJ) + $(CC) $(LDFLAGS_EXE) $(LDFLAGS) -o TestSuite.exe $(TESTSUITE_OBJ) + LoadDll.o: LoadDll.cpp $(CXX) $(CFLAGS) $(CFLAGS_EXE) -c $< @@ -65,6 +73,12 @@ test-align-%.dll: $(DLL_OBJ) test-relocate.dll: $(DLL_OBJ) $(CXX) $(LDFLAGS_DLL) $(LDFLAGS) -Wl,--image-base -Wl,0x20000000 -o $@ $(DLL_OBJ) +test-exports.dll: SampleExports.o + $(CXX) $(LDFLAGS_DLL) $(LDFLAGS) -o $@ SampleExports.o + +SampleExports.cpp: generate-exports.sh + ./generate-exports.sh + %.o: %.cpp $(CXX) $(CFLAGS) $(CFLAGS_DLL) -c $< @@ -75,7 +89,8 @@ test-relocate.dll: $(DLL_OBJ) $(RC) $(RCFLAGS) -o $*.res $< clean: - $(RM) -rf LoadDll.exe $(TEST_DLLS) $(LOADDLL_OBJ) $(DLL_OBJ) + $(RM) -rf LoadDll.exe $(TEST_DLLS) $(LOADDLL_OBJ) $(DLL_OBJ) $(TESTSUITE_OBJ) SampleExports.o test: all + ./runwine.sh $(PLATFORM) TestSuite.exe ./runtests.sh $(PLATFORM) "$(TEST_DLLS)" diff --git a/tests/TestSuite.c b/tests/TestSuite.c new file mode 100644 index 0000000..f4bd325 --- /dev/null +++ b/tests/TestSuite.c @@ -0,0 +1,19 @@ +#define WIN32_LEAN_AND_MEAN +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include + +extern BOOL MemoryModuleTestsuite(); + +int main(int argc, char* argv[]) +{ + UNREFERENCED_PARAMETER(argc); + UNREFERENCED_PARAMETER(argv); + if (!MemoryModuleTestsuite()) { + return 1; + } + + return 0; +} diff --git a/tests/generate-exports.sh b/tests/generate-exports.sh new file mode 100755 index 0000000..974b2a6 --- /dev/null +++ b/tests/generate-exports.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +## +## Generate header file. +## + +cat > SampleExports.h << EOF +extern "C" { + +#ifdef SAMPLEDLL_EXPORTS +#define SAMPLEDLL_API __declspec(dllexport) +#else +#define SAMPLEDLL_API __declspec(dllimport) +#endif + +EOF + +for i in `seq 1 100`; +do +cat >> SampleExports.h << EOF +SAMPLEDLL_API int add$i(int a); +EOF +done + +cat >> SampleExports.h << EOF +} +EOF + + +## +## Generate source file. +## + +cat > SampleExports.cpp << EOF +#include "SampleExports.h" + +extern "C" { +EOF + +for i in `seq 1 100 | sort -R`; +do +cat >> SampleExports.cpp << EOF +SAMPLEDLL_API int add$i(int a) +{ + return a + $i; +} +EOF +done + +cat >> SampleExports.cpp << EOF +} +EOF diff --git a/tests/runtests.sh b/tests/runtests.sh index 57d70c6..a2c6520 100755 --- a/tests/runtests.sh +++ b/tests/runtests.sh @@ -1,13 +1,5 @@ #!/bin/bash PLATFORM=$1 -if [ "${PLATFORM}" = "x86_64" ]; then - export WINEPREFIX=${HOME}/.wine64/ - WINE=wine64 -else - export WINEPREFIX=${HOME}/.wine/ - WINE=wine -fi -export WINEPATH=/usr/lib/gcc/${PLATFORM}-w64-mingw32/4.6/ read -a TEST_DLLS <<< $2 @@ -15,7 +7,7 @@ for filename in "${TEST_DLLS[@]}" do : echo "Testing $filename" - ${WINE} ./LoadDll.exe $filename + ./runwine.sh "${PLATFORM}" ./LoadDll.exe $filename if [ "$?" != "0" ]; then exit 1 fi diff --git a/tests/runwine.sh b/tests/runwine.sh new file mode 100755 index 0000000..826b8c3 --- /dev/null +++ b/tests/runwine.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -eu +PLATFORM=$1 +shift + +if [ "${PLATFORM}" = "x86_64" ]; then + export WINEPREFIX=${HOME}/.wine64/ + WINE=wine64 +else + export WINEPREFIX=${HOME}/.wine/ + WINE=wine +fi +export WINEPATH=/usr/lib/gcc/${PLATFORM}-w64-mingw32/4.8/:/usr/${PLATFORM}-w64-mingw32/lib + +exec ${WINE} $@ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy