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 108523e..0bb4efe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,95 @@ -env: - - PLATFORM=x86_64 WINE=wine64 UNICODE= - - PLATFORM=i686 WINE=wine UNICODE= - - PLATFORM=x86_64 WINE=wine64 UNICODE=1 - - PLATFORM=i686 WINE=wine UNICODE=1 +sudo: true + +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 -before_install: - - sudo apt-get update -qq - - sh -c "if [ '$PLATFORM' = 'i686' ]; then sudo apt-get install -qq wine gcc-mingw-w64-i686 g++-mingw-w64-i686 binutils-mingw-w64-i686 mingw-w64-dev; fi" - - sh -c "if [ '$PLATFORM' = 'x86_64' ]; then sudo apt-get install -qq wine gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 binutils-mingw-w64-x86-64 mingw-w64-dev; fi" +before_script: + - 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: - - make PLATFORM=$PLATFORM UNICODE=$UNICODE - - make test PLATFORM=$PLATFORM UNICODE=$UNICODE + - 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 new file mode 100644 index 0000000..cfddbfb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,60 @@ +project (MemoryModule) +cmake_minimum_required (VERSION 2.8.7) + +set (PLATFORM "x86_64" CACHE STRING "Platform to compile for") +message (STATUS "Compile for ${PLATFORM} platform") + +if (NOT MSVC) + set (CMAKE_SYSTEM_NAME Windows) + set (CMAKE_POSITION_INDEPENDENT_CODE False) + + set (COMPILER_PREFIX "${PLATFORM}-w64-mingw32") + set (CMAKE_C_COMPILER "${COMPILER_PREFIX}-gcc") + set (CMAKE_CXX_COMPILER "${COMPILER_PREFIX}-g++") + set (CMAKE_RC_COMPILER "${COMPILER_PREFIX}-windres") + set (CMAKE_AR "${COMPILER_PREFIX}-ar") + set (CMAKE_RANLIB "${COMPILER_PREFIX}-ranlib") + + set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + + set (CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + set (CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "") + + set (CMAKE_RC_COMPILE_OBJECT "${CMAKE_RC_COMPILER} -O coff -I${CMAKE_CURRENT_SOURCE_DIR} ") +endif () + +if (NOT MSVC) + add_definitions ("-Wall") +else () + # Show level 4 warnings. + add_definitions ("-W4") +endif () + +option(UNICODE "Compile with UNICODE support" OFF) +if (UNICODE) + message (STATUS "Compile with UNICODE support") + add_definitions ("-DUNICODE" "-D_UNICODE") +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 2efb4f3..9f95a70 100644 --- a/MemoryModule.c +++ b/MemoryModule.c @@ -22,13 +22,14 @@ * 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. */ -#ifndef __GNUC__ -// disable warnings about pointer <-> DWORD conversions -#pragma warning( disable : 4311 4312 ) -#endif - #include #include #include @@ -37,16 +38,45 @@ #include #endif +#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 // Vista SDKs no longer define IMAGE_SIZEOF_BASE_RELOCATION!? #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; @@ -55,29 +85,56 @@ typedef struct { BOOL initialized; BOOL isDLL; BOOL isRelocated; + CustomAllocFunc alloc; + CustomFreeFunc free; 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)) -#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, @@ -87,13 +144,38 @@ 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 static BOOL -CopySections(const unsigned char *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module) +CheckSize(size_t size, size_t expected) { + if (size < expected) { + SetLastError(ERROR_INVALID_DATA); + return FALSE; + } + + return TRUE; +} + +static BOOL +CopySections(const unsigned char *data, size_t size, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module) { - int i, size; + int i, section_size; unsigned char *codeBase = module->codeBase; unsigned char *dest; PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); @@ -101,41 +183,51 @@ CopySections(const unsigned char *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMO if (section->SizeOfRawData == 0) { // section doesn't contain data in the dll itself, but may define // uninitialized data - size = old_headers->OptionalHeader.SectionAlignment; - if (size > 0) { - dest = (unsigned char *)VirtualAlloc(codeBase + section->VirtualAddress, - size, + section_size = old_headers->OptionalHeader.SectionAlignment; + if (section_size > 0) { + dest = (unsigned char *)module->alloc(codeBase + section->VirtualAddress, + section_size, MEM_COMMIT, - PAGE_READWRITE); + PAGE_READWRITE, + module->userdata); if (dest == NULL) { return FALSE; } // 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; - memset(dest, 0, size); + // 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); } // section is empty continue; } + if (!CheckSize(size, section->PointerToRawData + section->SizeOfRawData)) { + return FALSE; + } + // commit memory block and copy data from dll - dest = (unsigned char *)VirtualAlloc(codeBase + section->VirtualAddress, + dest = (unsigned char *)module->alloc(codeBase + section->VirtualAddress, section->SizeOfRawData, MEM_COMMIT, - PAGE_READWRITE); + PAGE_READWRITE, + module->userdata); if (dest == NULL) { return FALSE; } // 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; @@ -154,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) { @@ -164,7 +256,7 @@ GetRealSectionSize(PMEMORYMODULE module, PIMAGE_SECTION_HEADER section) { size = module->headers->OptionalHeader.SizeOfUninitializedData; } } - return size; + return (SIZE_T) size; } static BOOL @@ -186,7 +278,7 @@ FinalizeSection(PMEMORYMODULE module, PSECTIONFINALIZEDATA sectionData) { (sectionData->size % module->pageSize) == 0) ) { // Only allowed to decommit whole pages - VirtualFree(sectionData->address, sectionData->size, MEM_DECOMMIT); + module->free(sectionData->address, sectionData->size, MEM_DECOMMIT, module->userdata); } return TRUE; } @@ -202,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; } @@ -217,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; @@ -232,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. @@ -244,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; } @@ -260,9 +352,6 @@ FinalizeSections(PMEMORYMODULE module) if (!FinalizeSection(module, §ionData)) { return FALSE; } -#ifndef _WIN64 -#undef imageOffset -#endif return TRUE; } @@ -270,14 +359,16 @@ static BOOL ExecuteTLS(PMEMORYMODULE module) { unsigned char *codeBase = module->codeBase; + PIMAGE_TLS_DIRECTORY tls; + PIMAGE_TLS_CALLBACK* callback; PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_TLS); if (directory->VirtualAddress == 0) { return TRUE; } - PIMAGE_TLS_DIRECTORY tls = (PIMAGE_TLS_DIRECTORY) (codeBase + directory->VirtualAddress); - PIMAGE_TLS_CALLBACK* callback = (PIMAGE_TLS_CALLBACK *) tls->AddressOfCallBacks; + tls = (PIMAGE_TLS_DIRECTORY) (codeBase + directory->VirtualAddress); + callback = (PIMAGE_TLS_CALLBACK *) tls->AddressOfCallBacks; if (callback) { while (*callback) { (*callback)((LPVOID) codeBase, DLL_PROCESS_ATTACH, NULL); @@ -288,7 +379,7 @@ ExecuteTLS(PMEMORYMODULE module) } static BOOL -PerformBaseRelocation(PMEMORYMODULE module, SIZE_T delta) +PerformBaseRelocation(PMEMORYMODULE module, ptrdiff_t delta) { unsigned char *codeBase = module->codeBase; PIMAGE_BASE_RELOCATION relocation; @@ -302,18 +393,12 @@ PerformBaseRelocation(PMEMORYMODULE module, SIZE_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) { @@ -323,14 +408,18 @@ PerformBaseRelocation(PMEMORYMODULE module, SIZE_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 @@ -341,7 +430,7 @@ PerformBaseRelocation(PMEMORYMODULE module, SIZE_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; } @@ -411,9 +500,23 @@ BuildImportTable(PMEMORYMODULE module) return result; } -static HCUSTOMMODULE _LoadLibrary(LPCSTR filename, void *userdata) +LPVOID MemoryDefaultAlloc(LPVOID address, SIZE_T size, DWORD allocationType, DWORD protect, void* userdata) +{ + UNREFERENCED_PARAMETER(userdata); + return VirtualAlloc(address, size, allocationType, protect); +} + +BOOL MemoryDefaultFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType, void* userdata) { - HMODULE result = LoadLibraryA(filename); + UNREFERENCED_PARAMETER(userdata); + return VirtualFree(lpAddress, dwSize, dwFreeType); +} + +HCUSTOMMODULE MemoryDefaultLoadLibrary(LPCSTR filename, void *userdata) +{ + HMODULE result; + UNREFERENCED_PARAMETER(userdata); + result = LoadLibraryA(filename); if (result == NULL) { return NULL; } @@ -421,51 +524,65 @@ static HCUSTOMMODULE _LoadLibrary(LPCSTR filename, void *userdata) return (HCUSTOMMODULE) result; } -static FARPROC _GetProcAddress(HCUSTOMMODULE module, LPCSTR name, void *userdata) +FARPROC MemoryDefaultGetProcAddress(HCUSTOMMODULE module, LPCSTR name, void *userdata) { + UNREFERENCED_PARAMETER(userdata); return (FARPROC) GetProcAddress((HMODULE) module, name); } -static void _FreeLibrary(HCUSTOMMODULE module, void *userdata) +void MemoryDefaultFreeLibrary(HCUSTOMMODULE module, void *userdata) { + UNREFERENCED_PARAMETER(userdata); FreeLibrary((HMODULE) module); } -HMEMORYMODULE MemoryLoadLibrary(const void *data) +HMEMORYMODULE MemoryLoadLibrary(const void *data, size_t size) { - return MemoryLoadLibraryEx(data, _LoadLibrary, _GetProcAddress, _FreeLibrary, NULL); + return MemoryLoadLibraryEx(data, size, MemoryDefaultAlloc, MemoryDefaultFree, MemoryDefaultLoadLibrary, MemoryDefaultGetProcAddress, MemoryDefaultFreeLibrary, NULL); } -HMEMORYMODULE MemoryLoadLibraryEx(const void *data, +HMEMORYMODULE MemoryLoadLibraryEx(const void *data, size_t size, + CustomAllocFunc allocMemory, + CustomFreeFunc freeMemory, CustomLoadLibraryFunc loadLibrary, CustomGetProcAddressFunc getProcAddress, CustomFreeLibraryFunc freeLibrary, void *userdata) { - PMEMORYMODULE result; + PMEMORYMODULE result = NULL; PIMAGE_DOS_HEADER dos_header; PIMAGE_NT_HEADERS old_header; unsigned char *code, *headers; - SIZE_T locationDelta; + ptrdiff_t locationDelta; SYSTEM_INFO sysInfo; + PIMAGE_SECTION_HEADER section; + DWORD i; + 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; + } dos_header = (PIMAGE_DOS_HEADER)data; if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) { SetLastError(ERROR_BAD_EXE_FORMAT); return NULL; } + if (!CheckSize(size, dos_header->e_lfanew + sizeof(IMAGE_NT_HEADERS))) { + return NULL; + } old_header = (PIMAGE_NT_HEADERS)&((const unsigned char *)(data))[dos_header->e_lfanew]; if (old_header->Signature != IMAGE_NT_SIGNATURE) { SetLastError(ERROR_BAD_EXE_FORMAT); 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; } @@ -476,48 +593,112 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data, return NULL; } + section = IMAGE_FIRST_SECTION(old_header); + optionalSectionSize = old_header->OptionalHeader.SectionAlignment; + for (i=0; iFileHeader.NumberOfSections; i++, section++) { + size_t endOfSection; + if (section->SizeOfRawData == 0) { + // Section without data in the DLL + endOfSection = section->VirtualAddress + optionalSectionSize; + } else { + endOfSection = section->VirtualAddress + section->SizeOfRawData; + } + + if (endOfSection > lastSectionEnd) { + lastSectionEnd = endOfSection; + } + } + + GetNativeSystemInfo(&sysInfo); + alignedImageSize = AlignValueUp(old_header->OptionalHeader.SizeOfImage, sysInfo.dwPageSize); + if (alignedImageSize != AlignValueUp(lastSectionEnd, sysInfo.dwPageSize)) { + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + // reserve memory for image of library // XXX: is it correct to commit the complete memory region at once? // calling DllEntry raises an exception if we don't... - code = (unsigned char *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), - old_header->OptionalHeader.SizeOfImage, + code = (unsigned char *)allocMemory((LPVOID)(old_header->OptionalHeader.ImageBase), + alignedImageSize, MEM_RESERVE | MEM_COMMIT, - PAGE_READWRITE); + PAGE_READWRITE, + userdata); if (code == NULL) { // try to allocate memory at arbitrary position - code = (unsigned char *)VirtualAlloc(NULL, - old_header->OptionalHeader.SizeOfImage, + code = (unsigned char *)allocMemory(NULL, + alignedImageSize, MEM_RESERVE | MEM_COMMIT, - PAGE_READWRITE); + PAGE_READWRITE, + userdata); if (code == NULL) { SetLastError(ERROR_OUTOFMEMORY); return NULL; } } +#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) { - VirtualFree(code, 0, MEM_RELEASE); + freeMemory(code, 0, MEM_RELEASE, userdata); +#ifdef _WIN64 + FreePointerList(blockedMemory, freeMemory, userdata); +#endif SetLastError(ERROR_OUTOFMEMORY); return NULL; } result->codeBase = code; result->isDLL = (old_header->FileHeader.Characteristics & IMAGE_FILE_DLL) != 0; + result->alloc = allocMemory; + result->free = freeMemory; result->loadLibrary = loadLibrary; result->getProcAddress = getProcAddress; result->freeLibrary = freeLibrary; result->userdata = userdata; - - GetNativeSystemInfo(&sysInfo); result->pageSize = sysInfo.dwPageSize; +#ifdef _WIN64 + result->blockedMemory = blockedMemory; +#endif + + if (!CheckSize(size, old_header->OptionalHeader.SizeOfHeaders)) { + goto error; + } // commit memory for headers - headers = (unsigned char *)VirtualAlloc(code, + headers = (unsigned char *)allocMemory(code, old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, - PAGE_READWRITE); + PAGE_READWRITE, + userdata); // copy PE header to code memcpy(headers, dos_header, old_header->OptionalHeader.SizeOfHeaders); @@ -527,12 +708,12 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data, result->headers->OptionalHeader.ImageBase = (uintptr_t)code; // copy sections from DLL file block to new memory location - if (!CopySections((const unsigned char *) data, old_header, result)) { + if (!CopySections((const unsigned char *) data, size, old_header, result)) { goto error; } // adjust base address of imported data - locationDelta = (SIZE_T)(code - old_header->OptionalHeader.ImageBase); + locationDelta = (ptrdiff_t)(result->headers->OptionalHeader.ImageBase - old_header->OptionalHeader.ImageBase); if (locationDelta != 0) { result->isRelocated = PerformBaseRelocation(result, locationDelta); } else { @@ -558,7 +739,7 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data, // get entry point of loaded library if (result->headers->OptionalHeader.AddressOfEntryPoint != 0) { if (result->isDLL) { - DllEntryProc DllEntry = (DllEntryProc) (code + result->headers->OptionalHeader.AddressOfEntryPoint); + DllEntryProc DllEntry = (DllEntryProc)(LPVOID)(code + result->headers->OptionalHeader.AddressOfEntryPoint); // notify library about attaching to process BOOL successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0); if (!successfull) { @@ -567,7 +748,7 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data, } result->initialized = TRUE; } else { - result->exeEntry = (ExeEntryProc) (code + result->headers->OptionalHeader.AddressOfEntryPoint); + result->exeEntry = (ExeEntryProc)(LPVOID)(code + result->headers->OptionalHeader.AddressOfEntryPoint); } } else { result->exeEntry = NULL; @@ -581,14 +762,27 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data, 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; - int idx=-1; - DWORD i, *nameRef; - WORD *ordinal; + 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); @@ -602,30 +796,62 @@ FARPROC MemoryGetProcAddress(HMEMORYMODULE module, LPCSTR name) return NULL; } - // search function name in list of exported names - nameRef = (DWORD *) (codeBase + exports->AddressOfNames); - ordinal = (WORD *) (codeBase + exports->AddressOfNameOrdinals); - for (i=0; iNumberOfNames; i++, nameRef++, ordinal++) { - if (_stricmp(name, (const char *) (codeBase + (*nameRef))) == 0) { - idx = *ordinal; - break; + if (HIWORD(name) == 0) { + // load function by ordinal value + if (LOWORD(name) < exports->Base) { + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; } - } - if (idx == -1) { - // exported symbol not found + idx = LOWORD(name) - exports->Base; + } else if (!exports->NumberOfNames) { SetLastError(ERROR_PROC_NOT_FOUND); return NULL; + } else { + 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 ((DWORD)idx > exports->NumberOfFunctions) { + if (idx > exports->NumberOfFunctions) { // name <-> ordinal number don't match SetLastError(ERROR_PROC_NOT_FOUND); return NULL; } // AddressOfFunctions contains the RVAs to the "real" functions - return (FARPROC) (codeBase + (*(DWORD *) (codeBase + exports->AddressOfFunctions + (idx*4)))); + return (FARPROC)(LPVOID)(codeBase + (*(DWORD *) (codeBase + exports->AddressOfFunctions + (idx*4)))); } void MemoryFreeLibrary(HMEMORYMODULE mod) @@ -637,10 +863,11 @@ void MemoryFreeLibrary(HMEMORYMODULE mod) } if (module->initialized) { // notify library about detaching from process - DllEntryProc DllEntry = (DllEntryProc) (module->codeBase + module->headers->OptionalHeader.AddressOfEntryPoint); + DllEntryProc DllEntry = (DllEntryProc)(LPVOID)(module->codeBase + module->headers->OptionalHeader.AddressOfEntryPoint); (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0); } + free(module->nameExportsTable); if (module->modules != NULL) { // free previously opened libraries int i; @@ -655,9 +882,12 @@ void MemoryFreeLibrary(HMEMORYMODULE mod) if (module->codeBase != NULL) { // release memory of library - VirtualFree(module->codeBase, 0, MEM_RELEASE); + module->free(module->codeBase, 0, MEM_RELEASE, module->userdata); } +#ifdef _WIN64 + FreePointerList(module->blockedMemory, module->free, module->userdata); +#endif HeapFree(GetProcessHeap(), 0, module); } @@ -721,32 +951,49 @@ static PIMAGE_RESOURCE_DIRECTORY_ENTRY _MemorySearchResourceEntry( } } } else { -#if !defined(UNICODE) - char *searchKey = NULL; - int searchKeyLength = 0; + LPCWSTR searchKey; + size_t searchKeyLen = _tcslen(key); +#if defined(UNICODE) + searchKey = key; +#else + // Resource names are always stored using 16bit characters, need to + // convert string we search for. +#define MAX_LOCAL_KEY_LENGTH 2048 + // In most cases resource names are short, so optimize for that by + // using a pre-allocated array. + wchar_t _searchKeySpace[MAX_LOCAL_KEY_LENGTH+1]; + LPWSTR _searchKey; + if (searchKeyLen > MAX_LOCAL_KEY_LENGTH) { + size_t _searchKeySize = (searchKeyLen + 1) * sizeof(wchar_t); + _searchKey = (LPWSTR) malloc(_searchKeySize); + if (_searchKey == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + } else { + _searchKey = &_searchKeySpace[0]; + } + + mbstowcs(_searchKey, key, searchKeyLen); + _searchKey[searchKeyLen] = 0; + searchKey = _searchKey; #endif start = 0; end = resources->NumberOfNamedEntries; while (end > start) { - // resource names are always stored using 16bit characters int cmp; PIMAGE_RESOURCE_DIR_STRING_U resourceString; middle = (start + end) >> 1; - resourceString = (PIMAGE_RESOURCE_DIR_STRING_U) (((char *) root) + (entries[middle].Name & 0x7FFFFFFF)); -#if !defined(UNICODE) - if (searchKey == NULL || searchKeyLength < resourceString->Length) { - void *tmp = realloc(searchKey, resourceString->Length); - if (tmp == NULL) { - break; + 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 + if (searchKeyLen > resourceString->Length) { + cmp = 1; + } else if (searchKeyLen < resourceString->Length) { + cmp = -1; } - - searchKey = (char *) tmp; } - wcstombs(searchKey, resourceString->NameString, resourceString->Length); - cmp = strncmp(key, searchKey, resourceString->Length); -#else - cmp = wcsncmp(key, resourceString->NameString, resourceString->Length); -#endif if (cmp < 0) { end = (middle != end ? middle : middle-1); } else if (cmp > 0) { @@ -757,11 +1004,13 @@ static PIMAGE_RESOURCE_DIRECTORY_ENTRY _MemorySearchResourceEntry( } } #if !defined(UNICODE) - free(searchKey); + if (searchKeyLen > MAX_LOCAL_KEY_LENGTH) { + free(_searchKey); + } +#undef MAX_LOCAL_KEY_LENGTH #endif } - return result; } @@ -821,7 +1070,9 @@ HMEMORYRSRC MemoryFindResourceEx(HMEMORYMODULE module, LPCTSTR name, LPCTSTR typ DWORD MemorySizeofResource(HMEMORYMODULE module, HMEMORYRSRC resource) { - PIMAGE_RESOURCE_DATA_ENTRY entry = (PIMAGE_RESOURCE_DATA_ENTRY) resource; + PIMAGE_RESOURCE_DATA_ENTRY entry; + UNREFERENCED_PARAMETER(module); + entry = (PIMAGE_RESOURCE_DATA_ENTRY) resource; if (entry == NULL) { return 0; } @@ -865,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); @@ -886,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/MemoryModule.h b/MemoryModule.h index 9174ad2..a728f6b 100644 --- a/MemoryModule.h +++ b/MemoryModule.h @@ -39,31 +39,37 @@ typedef void *HCUSTOMMODULE; extern "C" { #endif +typedef LPVOID (*CustomAllocFunc)(LPVOID, SIZE_T, DWORD, DWORD, void*); +typedef BOOL (*CustomFreeFunc)(LPVOID, SIZE_T, DWORD, void*); typedef HCUSTOMMODULE (*CustomLoadLibraryFunc)(LPCSTR, void *); typedef FARPROC (*CustomGetProcAddressFunc)(HCUSTOMMODULE, LPCSTR, void *); typedef void (*CustomFreeLibraryFunc)(HCUSTOMMODULE, void *); /** - * Load EXE/DLL from memory location. + * Load EXE/DLL from memory location with the given size. * * All dependencies are resolved using default LoadLibrary/GetProcAddress * calls through the Windows API. */ -HMEMORYMODULE MemoryLoadLibrary(const void *); +HMEMORYMODULE MemoryLoadLibrary(const void *, size_t); /** - * Load EXE/DLL from memory location using custom dependency resolvers. + * Load EXE/DLL from memory location with the given size using custom dependency + * resolvers. * * Dependencies will be resolved using passed callback methods. */ -HMEMORYMODULE MemoryLoadLibraryEx(const void *, +HMEMORYMODULE MemoryLoadLibraryEx(const void *, size_t, + CustomAllocFunc, + CustomFreeFunc, CustomLoadLibraryFunc, CustomGetProcAddressFunc, CustomFreeLibraryFunc, void *); /** - * Get address of exported method. + * Get address of exported method. Supports loading both by name and by + * ordinal value. */ FARPROC MemoryGetProcAddress(HMEMORYMODULE, LPCSTR); @@ -115,6 +121,46 @@ int MemoryLoadString(HMEMORYMODULE, UINT, LPTSTR, int); */ int MemoryLoadStringEx(HMEMORYMODULE, UINT, LPTSTR, int, WORD); +/** +* Default implementation of CustomAllocFunc that calls VirtualAlloc +* internally to allocate memory for a library +* +* This is the default as used by MemoryLoadLibrary. +*/ +LPVOID MemoryDefaultAlloc(LPVOID, SIZE_T, DWORD, DWORD, void *); + +/** +* Default implementation of CustomFreeFunc that calls VirtualFree +* internally to free the memory used by a library +* +* This is the default as used by MemoryLoadLibrary. +*/ +BOOL MemoryDefaultFree(LPVOID, SIZE_T, DWORD, void *); + +/** + * Default implementation of CustomLoadLibraryFunc that calls LoadLibraryA + * internally to load an additional libary. + * + * This is the default as used by MemoryLoadLibrary. + */ +HCUSTOMMODULE MemoryDefaultLoadLibrary(LPCSTR, void *); + +/** + * Default implementation of CustomGetProcAddressFunc that calls GetProcAddress + * internally to get the address of an exported function. + * + * This is the default as used by MemoryLoadLibrary. + */ +FARPROC MemoryDefaultGetProcAddress(HCUSTOMMODULE, LPCSTR, void *); + +/** + * Default implementation of CustomFreeLibraryFunc that calls FreeLibrary + * internally to release an additional libary. + * + * This is the default as used by MemoryLoadLibrary. + */ +void MemoryDefaultFreeLibrary(HCUSTOMMODULE, void *); + #ifdef __cplusplus } #endif diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..5bc1608 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,43 @@ +# Status available at +# https://ci.appveyor.com/project/fancycode/memorymodule +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" + UNICODE: OFF + - GENERATOR: "Visual Studio 11 2012" + UNICODE: ON + - GENERATOR: "Visual Studio 11 2012" + UNICODE: OFF + - GENERATOR: "Visual Studio 12 2013" + UNICODE: ON + - GENERATOR: "Visual Studio 12 2013" + UNICODE: OFF + - GENERATOR: "Visual Studio 14 2015" + UNICODE: ON + - GENERATOR: "Visual Studio 14 2015" + UNICODE: OFF + +platform: + - x86 + - x64 + +configuration: + - Debug + +install: + - call scripts\run-appveyor.bat + +build: off +test: off +deploy: off diff --git a/doc/readme.txt b/doc/readme.rst similarity index 99% rename from doc/readme.txt rename to doc/readme.rst index 7e02c1b..ba6513c 100644 --- a/doc/readme.txt +++ b/doc/readme.rst @@ -511,7 +511,7 @@ The interface is very similar to the standard methods for loading of libraries:: typedef void *HMEMORYMODULE; - HMEMORYMODULE MemoryLoadLibrary(const void *); + HMEMORYMODULE MemoryLoadLibrary(const void *, size_t); FARPROC MemoryGetProcAddress(HMEMORYMODULE, const char *); void MemoryFreeLibrary(HMEMORYMODULE); diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..dff006d --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory (DllLoader) +add_subdirectory (SampleDLL) diff --git a/example/DllLoader/CMakeLists.txt b/example/DllLoader/CMakeLists.txt new file mode 100644 index 0000000..c2be9a7 --- /dev/null +++ b/example/DllLoader/CMakeLists.txt @@ -0,0 +1,26 @@ +set (sources_dllloader + DllLoader.cpp +) + +set (sources_dllloaderloader + DllLoaderLoader.cpp +) + +if (NOT MSVC) + set (CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "-static") + set (CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "-static") +endif () + +add_executable (DllLoader ${sources_dllloader}) +target_link_libraries ("DllLoader" "MemoryModule") +if (NOT MSVC) + set_target_properties ("DllLoader" PROPERTIES SUFFIX ".exe") + set_target_properties ("DllLoader" PROPERTIES LINK_FLAGS "-Wl,--image-base -Wl,0x20000000") +endif () + +add_executable (DllLoaderLoader ${sources_dllloaderloader}) +target_link_libraries ("DllLoaderLoader" "MemoryModule") +if (NOT MSVC) + set_target_properties ("DllLoaderLoader" PROPERTIES SUFFIX ".exe") + set_target_properties ("DllLoaderLoader" PROPERTIES LINK_FLAGS "-Wl,--image-base -Wl,0x10000000") +endif () diff --git a/example/DllLoader/DllLoader.cpp b/example/DllLoader/DllLoader.cpp index 3fda631..4355ea8 100644 --- a/example/DllLoader/DllLoader.cpp +++ b/example/DllLoader/DllLoader.cpp @@ -1,5 +1,9 @@ #define WIN32_LEAN_AND_MEAN +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif +#include #include #include #include @@ -42,10 +46,47 @@ void LoadFromFile(void) FreeLibrary(handle); } +void* ReadLibrary(size_t* pSize) { + size_t read; + void* result; + FILE* fp; + + fp = _tfopen(DLL_FILE, _T("rb")); + if (fp == NULL) + { + _tprintf(_T("Can't open DLL file \"%s\"."), DLL_FILE); + return NULL; + } + + fseek(fp, 0, SEEK_END); + *pSize = static_cast(ftell(fp)); + if (*pSize == 0) + { + fclose(fp); + return NULL; + } + + result = (unsigned char *)malloc(*pSize); + if (result == NULL) + { + return NULL; + } + + fseek(fp, 0, SEEK_SET); + read = fread(result, 1, *pSize, fp); + fclose(fp); + if (read != *pSize) + { + free(result); + return NULL; + } + + return result; +} + void LoadFromMemory(void) { - FILE *fp; - unsigned char *data=NULL; + void *data; size_t size; HMEMORYMODULE handle; addNumberProc addNumber; @@ -54,21 +95,13 @@ void LoadFromMemory(void) LPVOID resourceData; TCHAR buffer[100]; - fp = _tfopen(DLL_FILE, _T("rb")); - if (fp == NULL) + data = ReadLibrary(&size); + if (data == NULL) { - _tprintf(_T("Can't open DLL file \"%s\"."), DLL_FILE); - goto exit; + return; } - fseek(fp, 0, SEEK_END); - size = ftell(fp); - data = (unsigned char *)malloc(size); - fseek(fp, 0, SEEK_SET); - fread(data, 1, size, fp); - fclose(fp); - - handle = MemoryLoadLibrary(data); + handle = MemoryLoadLibrary(data, size); if (handle == NULL) { _tprintf(_T("Can't load library from memory.\n")); @@ -94,15 +127,217 @@ void LoadFromMemory(void) MemoryFreeLibrary(handle); exit: - if (data) - free(data); + free(data); +} + +#define MAX_CALLS 20 + +struct CallList { + int current_alloc_call, current_free_call; + CustomAllocFunc alloc_calls[MAX_CALLS]; + CustomFreeFunc free_calls[MAX_CALLS]; +}; + +LPVOID MemoryFailingAlloc(LPVOID address, SIZE_T size, DWORD allocationType, DWORD protect, void* userdata) +{ + UNREFERENCED_PARAMETER(address); + UNREFERENCED_PARAMETER(size); + UNREFERENCED_PARAMETER(allocationType); + UNREFERENCED_PARAMETER(protect); + UNREFERENCED_PARAMETER(userdata); + return NULL; +} + +LPVOID MemoryMockAlloc(LPVOID address, SIZE_T size, DWORD allocationType, DWORD protect, void* userdata) +{ + CallList* calls = (CallList*)userdata; + CustomAllocFunc current_func = calls->alloc_calls[calls->current_alloc_call++]; + assert(current_func != NULL); + return current_func(address, size, allocationType, protect, NULL); +} + +BOOL MemoryMockFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType, void* userdata) +{ + CallList* calls = (CallList*)userdata; + CustomFreeFunc current_func = calls->free_calls[calls->current_free_call++]; + assert(current_func != NULL); + return current_func(lpAddress, dwSize, dwFreeType, NULL); +} + +void InitFuncs(void** funcs, va_list args) { + for (int i = 0; ; i++) { + assert(i < MAX_CALLS); + funcs[i] = va_arg(args, void*); + if (funcs[i] == NULL) break; + } +} + +void InitAllocFuncs(CallList* calls, ...) { + va_list args; + va_start(args, calls); + InitFuncs((void**)calls->alloc_calls, args); + va_end(args); + calls->current_alloc_call = 0; +} + +void InitFreeFuncs(CallList* calls, ...) { + va_list args; + va_start(args, calls); + InitFuncs((void**)calls->free_calls, args); + va_end(args); + calls->current_free_call = 0; +} + +void InitFreeFunc(CallList* calls, CustomFreeFunc freeFunc) { + for (int i = 0; i < MAX_CALLS; i++) { + calls->free_calls[i] = freeFunc; + } + calls->current_free_call = 0; +} + +void TestFailingAllocation(void *data, size_t size) { + CallList expected_calls; + HMEMORYMODULE handle; + + InitAllocFuncs(&expected_calls, MemoryFailingAlloc, MemoryFailingAlloc, NULL); + InitFreeFuncs(&expected_calls, NULL); + + handle = MemoryLoadLibraryEx( + data, size, MemoryMockAlloc, MemoryMockFree, MemoryDefaultLoadLibrary, + MemoryDefaultGetProcAddress, MemoryDefaultFreeLibrary, &expected_calls); + + assert(handle == NULL); + assert(GetLastError() == ERROR_OUTOFMEMORY); + assert(expected_calls.current_free_call == 0); + + MemoryFreeLibrary(handle); + assert(expected_calls.current_free_call == 0); +} + +void TestCleanupAfterFailingAllocation(void *data, size_t size) { + CallList expected_calls; + HMEMORYMODULE handle; + int free_calls_after_loading; + + InitAllocFuncs(&expected_calls, + MemoryDefaultAlloc, + MemoryDefaultAlloc, + MemoryDefaultAlloc, + MemoryDefaultAlloc, + MemoryFailingAlloc, + NULL); + InitFreeFuncs(&expected_calls, MemoryDefaultFree, NULL); + + handle = MemoryLoadLibraryEx( + data, size, MemoryMockAlloc, MemoryMockFree, MemoryDefaultLoadLibrary, + MemoryDefaultGetProcAddress, MemoryDefaultFreeLibrary, &expected_calls); + + free_calls_after_loading = expected_calls.current_free_call; + + MemoryFreeLibrary(handle); + assert(expected_calls.current_free_call == free_calls_after_loading); +} + +void TestFreeAfterDefaultAlloc(void *data, size_t size) { + CallList expected_calls; + HMEMORYMODULE handle; + int free_calls_after_loading; + + // Note: free might get called internally multiple times + InitFreeFunc(&expected_calls, MemoryDefaultFree); + + handle = MemoryLoadLibraryEx( + data, size, MemoryDefaultAlloc, MemoryMockFree, MemoryDefaultLoadLibrary, + MemoryDefaultGetProcAddress, MemoryDefaultFreeLibrary, &expected_calls); + + assert(handle != NULL); + free_calls_after_loading = expected_calls.current_free_call; + + MemoryFreeLibrary(handle); + assert(expected_calls.current_free_call == free_calls_after_loading + 1); } -int main(int argc, char* argv[]) +#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; + size_t size; + + data = ReadLibrary(&size); + if (data == NULL) + { + return; + } + + _tprintf(_T("Test MemoryLoadLibraryEx after initially failing allocation function\n")); + TestFailingAllocation(data, size); + _tprintf(_T("Test cleanup after MemoryLoadLibraryEx with failing allocation function\n")); + 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); +} + +int main() { LoadFromFile(); printf("\n\n"); LoadFromMemory(); + printf("\n\n"); + TestCustomAllocAndFree(); return 0; } diff --git a/example/DllLoader/DllLoaderLoader.cpp b/example/DllLoader/DllLoaderLoader.cpp index e49979e..b7174f5 100644 --- a/example/DllLoader/DllLoaderLoader.cpp +++ b/example/DllLoader/DllLoaderLoader.cpp @@ -1,5 +1,9 @@ #define WIN32_LEAN_AND_MEAN +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif +#include #include #include #include @@ -13,7 +17,8 @@ int RunFromMemory(void) { FILE *fp; unsigned char *data=NULL; - size_t size; + long size; + size_t read; HMEMORYMODULE handle; int result = -1; @@ -26,12 +31,15 @@ int RunFromMemory(void) fseek(fp, 0, SEEK_END); size = ftell(fp); + assert(size >= 0); data = (unsigned char *)malloc(size); + assert(data != NULL); fseek(fp, 0, SEEK_SET); - fread(data, 1, size, fp); + read = fread(data, 1, size, fp); + assert(read == static_cast(size)); fclose(fp); - handle = MemoryLoadLibrary(data); + handle = MemoryLoadLibrary(data, size); if (handle == NULL) { _tprintf(_T("Can't load library from memory.\n")); @@ -45,12 +53,11 @@ int RunFromMemory(void) MemoryFreeLibrary(handle); exit: - if (data) - free(data); + free(data); return result; } -int main(int argc, char* argv[]) +int main() { return RunFromMemory(); } diff --git a/example/SampleDLL/CMakeLists.txt b/example/SampleDLL/CMakeLists.txt new file mode 100644 index 0000000..e3a3af3 --- /dev/null +++ b/example/SampleDLL/CMakeLists.txt @@ -0,0 +1,12 @@ +set (sources + SampleDLL.cpp + SampleDLL.h + SampleDLL.rc +) + +add_definitions (-DSAMPLEDLL_EXPORTS) +add_library (SampleDLL MODULE ${sources}) +if (NOT MSVC) + set_target_properties ("SampleDLL" PROPERTIES PREFIX "") + set_target_properties ("SampleDLL" PROPERTIES SUFFIX ".dll") +endif () diff --git a/readme.md b/readme.md index ab35ac6..4e9d82d 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@ MemoryModule ============ -[![Build Status](https://travis-ci.org/fancycode/MemoryModule.svg?branch=master)](https://travis-ci.org/fancycode/MemoryModule) +[![Build Status](https://travis-ci.org/fancycode/MemoryModule.svg?branch=master)](https://travis-ci.org/fancycode/MemoryModule)[![Build status](https://ci.appveyor.com/api/projects/status/qcrfxbno0jbbl9cx/branch/master?svg=true)](https://ci.appveyor.com/project/fancycode/memorymodule) The default windows API functions to load external libraries into a program (`LoadLibrary`, `LoadLibraryEx`) only work with files on the filesystem. It's @@ -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 b5074a7..8cb215b 100644 --- a/tests/LoadDll.cpp +++ b/tests/LoadDll.cpp @@ -1,5 +1,9 @@ #define WIN32_LEAN_AND_MEAN +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif +#include #include #include #include @@ -7,15 +11,76 @@ #include "../MemoryModule.h" +typedef int (*addProc)(int); typedef int (*addNumberProc)(int, int); +// Thanks to Tim Cooper (from http://stackoverflow.com/a/8584708) +const char *sstrstr(const char *haystack, const char *needle, size_t length) { + size_t needle_length = strlen(needle); + size_t i; + + for (i = 0; i < length; i++) { + if (i + needle_length > length) { + return NULL; + } + + if (strncmp(&haystack[i], needle, needle_length) == 0) { + return &haystack[i]; + } + } + return NULL; +} + +const wchar_t *swcsstr(const wchar_t *haystack, const wchar_t *needle, size_t length) { + size_t needle_length = wcslen(needle); + size_t i; + + for (i = 0; i < length; i++) { + if (i + needle_length > length) { + return NULL; + } + + if (wcsncmp(&haystack[i], needle, needle_length) == 0) { + return &haystack[i]; + } + } + return NULL; +} + +BOOL CheckResourceStrings(LPVOID data, DWORD size, const char *first, const wchar_t *second) { + const char *first_pos; + const wchar_t *second_pos; + const wchar_t *src; + + if (data == NULL || size == 0) { + return FALSE; + } + + first_pos = sstrstr((const char *) data, first, size); + if (first_pos == NULL) { + fprintf(stderr, "ERROR: data doesn't start with %s\n", first); + return FALSE; + } + + src = (const wchar_t *) (((const char *) data) + strlen(first) + 1); + second_pos = swcsstr(src, second, (size - strlen(first) - 1) / sizeof(wchar_t)); + if (second_pos == NULL) { + fwprintf(stderr, L"ERROR: data doesn't continue with %s\n", second); + return FALSE; + } + + return TRUE; +} + BOOL LoadFromMemory(char *filename) { FILE *fp; unsigned char *data=NULL; - size_t size; - HMEMORYMODULE handle; + long size; + size_t read; + HMEMORYMODULE handle = NULL; addNumberProc addNumber; + addNumberProc addNumber2; HMEMORYRSRC resourceInfo; DWORD resourceSize; LPVOID resourceData; @@ -32,12 +97,15 @@ BOOL LoadFromMemory(char *filename) fseek(fp, 0, SEEK_END); size = ftell(fp); + assert(size > 0); data = (unsigned char *)malloc(size); + assert(data != NULL); fseek(fp, 0, SEEK_SET); - fread(data, 1, size, fp); + read = fread(data, 1, size, fp); + assert(read == static_cast(size)); fclose(fp); - handle = MemoryLoadLibrary(data); + handle = MemoryLoadLibrary(data, size); if (handle == NULL) { _tprintf(_T("Can't load library from memory.\n")); @@ -45,9 +113,36 @@ BOOL LoadFromMemory(char *filename) goto exit; } + addNumber = (addNumberProc)MemoryGetProcAddress(handle, NULL); + if (addNumber != NULL) { + _tprintf(_T("MemoryGetProcAddress(NULL) returned %p\n"), addNumber); + result = FALSE; + goto exit; + } + + addNumber = (addNumberProc)MemoryGetProcAddress(handle, reinterpret_cast(0xff)); + if (addNumber != NULL) { + _tprintf(_T("MemoryGetProcAddress(0xff) returned %p\n"), addNumber); + result = FALSE; + goto exit; + } + 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 + addNumber2 = (addNumberProc)MemoryGetProcAddress(handle, reinterpret_cast(0x01)); + if (addNumber != addNumber2) { + _tprintf(_T("MemoryGetProcAddress(0x01) returned %p (expected %p)\n"), addNumber2, addNumber); + result = FALSE; + goto exit; + } + resourceInfo = MemoryFindResource(handle, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION); _tprintf(_T("MemoryFindResource returned 0x%p\n"), resourceInfo); @@ -65,11 +160,126 @@ BOOL LoadFromMemory(char *filename) result = FALSE; } + resourceInfo = MemoryFindResource(handle, _T("stringres"), RT_RCDATA); + _tprintf(_T("MemoryFindResource returned 0x%p\n"), resourceInfo); + if (resourceInfo != NULL) { + resourceSize = MemorySizeofResource(handle, resourceInfo); + resourceData = MemoryLoadResource(handle, resourceInfo); + + _tprintf(_T("Memory resource data: %ld bytes at 0x%p\n"), resourceSize, resourceData); + if (!CheckResourceStrings(resourceData, resourceSize, "This is a ANSI string", L"This is a UNICODE string")) { + result = FALSE; + } + } else { + result = FALSE; + } + + resourceInfo = MemoryFindResource(handle, _T("stringres1"), RT_RCDATA); + _tprintf(_T("MemoryFindResource returned 0x%p\n"), resourceInfo); + if (resourceInfo != NULL) { + resourceSize = MemorySizeofResource(handle, resourceInfo); + resourceData = MemoryLoadResource(handle, resourceInfo); + + _tprintf(_T("Memory resource data: %ld bytes at 0x%p\n"), resourceSize, resourceData); + if (!CheckResourceStrings(resourceData, resourceSize, "This is ANSI string 1", L"This is UNICODE string 1")) { + result = FALSE; + } + } else { + result = FALSE; + } + + + resourceInfo = MemoryFindResource(handle, _T("stringres2"), RT_RCDATA); + _tprintf(_T("MemoryFindResource returned 0x%p\n"), resourceInfo); + if (resourceInfo != NULL) { + resourceSize = MemorySizeofResource(handle, resourceInfo); + resourceData = MemoryLoadResource(handle, resourceInfo); + + _tprintf(_T("Memory resource data: %ld bytes at 0x%p\n"), resourceSize, resourceData); + if (!CheckResourceStrings(resourceData, resourceSize, "This is ANSI string 2", L"This is UNICODE string 2")) { + result = FALSE; + } + } else { + result = FALSE; + } + + + resourceInfo = MemoryFindResource(handle, _T("stringres3"), RT_RCDATA); + _tprintf(_T("MemoryFindResource returned 0x%p\n"), resourceInfo); + if (resourceInfo != NULL) { + resourceSize = MemorySizeofResource(handle, resourceInfo); + resourceData = MemoryLoadResource(handle, resourceInfo); + + _tprintf(_T("Memory resource data: %ld bytes at 0x%p\n"), resourceSize, resourceData); + if (!CheckResourceStrings(resourceData, resourceSize, "This is ANSI string 3", L"This is UNICODE string 3")) { + result = FALSE; + } + } else { + result = FALSE; + } + +exit: MemoryFreeLibrary(handle); + free(data); + 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: - if (data) - free(data); + MemoryFreeLibrary(handle); + free(data); return result; } @@ -80,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/SampleDLL.rc b/tests/SampleDLL.rc index 5a0da61..bbe5774 100644 --- a/tests/SampleDLL.rc +++ b/tests/SampleDLL.rc @@ -32,3 +32,27 @@ STRINGTABLE IDS_HELLO, "Hello" IDS_WORLD, "World!" } + +STRINGRES RCDATA +{ + "This is a ANSI string\0", + L"This is a UNICODE string\0", +} + +STRINGRES1 RCDATA +{ + "This is ANSI string 1\0", + L"This is UNICODE string 1\0", +} + +STRINGRES2 RCDATA +{ + "This is ANSI string 2\0", + L"This is UNICODE string 2\0", +} + +STRINGRES3 RCDATA +{ + "This is ANSI string 3\0", + L"This is UNICODE string 3\0", +} 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} $@