Index: .github/workflows/win-build.yml ================================================================== --- .github/workflows/win-build.yml +++ .github/workflows/win-build.yml @@ -22,10 +22,11 @@ matrix: config: - "" - "CHECKS=nodep" - "OPTS=static" + - "OPTS=memorymodule" - "OPTS=noembed" - "OPTS=symbols" - "OPTS=symbols STATS=compdbg,memdbg" # Using powershell means we need to explicitly stop on failure steps: @@ -67,10 +68,11 @@ config: - "" - "CFLAGS=-DTCL_NO_DEPRECATED=1" - "--disable-shared" - "--disable-zipfs" + - "--enable-memorymodule" - "--enable-symbols" - "--enable-symbols=mem" - "--enable-symbols=all" # Using powershell means we need to explicitly stop on failure steps: Index: generic/tclEvent.c ================================================================== --- generic/tclEvent.c +++ generic/tclEvent.c @@ -1069,10 +1069,13 @@ #if (defined(_WIN32) || (ULONG_MAX == 0xffffffffUL)) && !defined(_WIN64) ".ilp32" #endif #ifdef TCL_MEM_DEBUG ".memdebug" +#endif +#if defined(_WIN32) && defined(TCL_LOAD_FROM_MEMORY) + ".memorymodule-0004" #endif #if defined(_MSC_VER) ".msvc-" STRINGIFY(_MSC_VER) #endif #ifdef USE_NMAKE Index: generic/tclInt.h ================================================================== --- generic/tclInt.h +++ generic/tclInt.h @@ -3261,10 +3261,11 @@ TclFindSymbolProc* findSymbolProcPtr; /* Procedure that resolves symbols in a * loaded module */ Tcl_FSUnloadFileProc* unloadFileProcPtr; /* Procedure that unloads a loaded module */ + char name[TCLFLEXARRAY]; }; /* Flags for conversion of doubles to digit strings */ #define TCL_DD_E_FORMAT 0x2 /* Use a fixed-length string of digits, ADDED tests/memorymodule.test Index: tests/memorymodule.test ================================================================== --- /dev/null +++ tests/memorymodule.test @@ -0,0 +1,49 @@ +if {"::tcltest" ni [namespace children]} { + package require tcltest 2.5 + namespace import -force ::tcltest::* +} + +testConstraint thread [expr {0 == [catch {package require Thread 2.7-}]}] +testConstraint memorymodule [expr {0 != [tcl::build-info memorymodule]}] +testConstraint memorymoduletest [expr {0 == [catch {zipfs mount dltest/memorymoduletest.zip [file join [zipfs root] memorymoduletest]}]}] + +lappend auto_path [file join [zipfs root] memorymoduletest] + +test memorymodule-1.0 {info loaded} memorymoduletest { + package require memorymoduletest + info loaded {} Memorymoduletest +} [file join [zipfs root] memorymoduletest/tcl9memorymoduletest.dll] + +test memorymodule-1.1 {GetModuleFileNameA (WIP)} {memorymoduletest memorymodule} { + package require memorymoduletest + GetModuleFileNameA +} //zipfs:/memorymoduletest/tcl9memorymoduletest.dll + +test memorymodule-1.2 {GetModuleFileNameW (WIP)} {memorymoduletest memorymodule} { + package require memorymoduletest + GetModuleFileNameW +} //zipfs:/memorymoduletest/tcl9memorymoduletest.dll + +test memorymodule-2.0 {LTS} -constraints {memorymoduletest thread} -body { + package require Thread + package require memorymoduletest + set t1 [thread::create] + ThreadVar 15; # Set ThreadVar to 15 in the main thread + thread::preserve $t1 + thread::send $t1 { + lappend auto_path [file join [zipfs root] memorymoduletest] + package require memorymoduletest + # set ThreadVar to 16 in the subthread + ThreadVar 16 + return [ThreadVar] + } result + thread::release $t1 + # ThreadVar in main thread should be unchanged (15) + list [ThreadVar] $result +} -result {15 16} + + +# cleanup +::tcltest::cleanupTests +return + Index: win/Makefile.in ================================================================== --- win/Makefile.in +++ win/Makefile.in @@ -545,14 +545,14 @@ ${TCL_ZIP_FILE}: ${ZIP_INSTALL_OBJS} ${DDE_DLL_FILE} ${REG_DLL_FILE} @rm -rf ${TCL_VFS_ROOT} @mkdir -p ${TCL_VFS_PATH} @echo "creating ${TCL_VFS_PATH} (prepare compression)" @( \ - $(COPY) -a $(TOP_DIR)/library/* ${TCL_VFS_PATH}; \ - $(COPY) -a ${TCL_VFS_PATH}/manifest.txt ${TCL_VFS_PATH}/pkgIndex.tcl; \ - rm -rf ${TCL_VFS_PATH}/dde; \ - rm -rf ${TCL_VFS_PATH}/registry; \ + $(COPY) -a $(TOP_DIR)/library/* ${TCL_VFS_PATH}; \ + $(COPY) -a ${TCL_VFS_PATH}/manifest.txt ${TCL_VFS_PATH}/pkgIndex.tcl; \ + rm -rf ${TCL_VFS_PATH}/dde; \ + rm -rf ${TCL_VFS_PATH}/registry; \ ) (zip=`(realpath '${NATIVE_ZIP}' || readlink -m '${NATIVE_ZIP}') 2>/dev/null || \ (echo '${NATIVE_ZIP}' | sed "s?^\./?$$(pwd)/?")`; \ cd ${TCL_VFS_ROOT} && \ $$zip ${ZIP_PROG_OPTIONS} ../${TCL_ZIP_FILE} ${ZIP_PROG_VFSSEARCH} >/dev/null && \ @@ -665,10 +665,13 @@ $(CC) -c $(CC_SWITCHES) -DTCL_TEST -DUNICODE -D_UNICODE $(EXTFLAGS) @DEPARG@ $(CC_OBJNAME) tclWinInit.${OBJEXT}: tclWinInit.c $(CC) -c $(CC_SWITCHES) -DBUILD_tcl $(EXTFLAGS) @DEPARG@ $(CC_OBJNAME) +tclWinLoad.${OBJEXT}: tclWinLoad.c + $(CC) -c $(CC_SWITCHES) -DBUILD_tcl -DUNICODE -D_UNICODE $(EXTFLAGS) @DEPARG@ $(CC_OBJNAME) + tclWinPipe.${OBJEXT}: tclWinPipe.c $(CC) -c $(CC_SWITCHES) -DBUILD_tcl $(EXTFLAGS) @DEPARG@ $(CC_OBJNAME) tclWinReg.${OBJEXT}: tclWinReg.c $(CC) -c $(CC_SWITCHES) $(EXTFLAGS) @DEPARG@ $(CC_OBJNAME) @@ -1013,14 +1016,17 @@ # tcltest, i.e.: # % make test TESTFLAGS="-verbose bps -file fileName.test" test: test-tcl test-packages -test-tcl: tcltest +test-tcl: tcltest dltest/memorymoduletest.zip TCL_LIBRARY="$(LIBRARY_DIR)"; export TCL_LIBRARY; \ $(WINE) ./$(TCLSH) "$(ROOT_DIR_NATIVE)/tests/all.tcl" $(TESTFLAGS) \ -load "$(TEST_LOAD_FACILITIES)" + +dltest/memorymoduletest.zip: + ( cd dltest; make; ) # Useful target to launch a built tclsh with the proper path,... runtest: tcltest @TCL_LIBRARY="$(LIBRARY_DIR)"; export TCL_LIBRARY; \ $(WINE) ./$(TCLSH) $(TESTFLAGS) -load "$(TEST_LOAD_FACILITIES)" $(SCRIPT) ADDED win/MemoryModule.c Index: win/MemoryModule.c ================================================================== --- /dev/null +++ win/MemoryModule.c @@ -0,0 +1,1232 @@ +/* + * Memory DLL loading code + * Version 0.0.4 + * + * Copyright (c) 2004-2015 by Joachim Bauch / mail@joachim-bauch.de + * http://www.joachim-bauch.de + * + * The contents of this file are subject to the Mozilla Public License Version + * 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is MemoryModule.c + * + * The Initial Developer of the Original Code is Joachim Bauch. + * + * 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 +#ifdef DEBUG_OUTPUT +#include +#endif + +#ifdef _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 + +#ifndef _WIN64 +#define HOST_MACHINE IMAGE_FILE_MACHINE_I386 +#elif defined(_M_ARM64) || defined(__aarch64__) +#define HOST_MACHINE IMAGE_FILE_MACHINE_ARM64 +#else +#define HOST_MACHINE IMAGE_FILE_MACHINE_AMD64 +#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; + HCUSTOMMODULE *modules; + int numModules; + 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; + SIZE_T size; + DWORD characteristics; + BOOL last; +} SECTIONFINALIZEDATA, *PSECTIONFINALIZEDATA; + +#define GET_HEADER_DICTIONARY(module, idx) &(module)->headers->OptionalHeader.DataDirectory[idx] + +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, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&tmp, 0, NULL); + tmpmsg = (char *)LocalAlloc(LPTR, strlen(msg) + strlen(tmp) + 3); + sprintf(tmpmsg, "%s: %s", msg, tmp); + 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 +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, section_size; + unsigned char *codeBase = module->codeBase; + unsigned char *dest; + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); + for (i=0; iheaders->FileHeader.NumberOfSections; i++, section++) { + if (section->SizeOfRawData == 0) { + // section doesn't contain data in the dll itself, but may define + // uninitialized data + section_size = old_headers->OptionalHeader.SectionAlignment; + if (section_size > 0) { + dest = (unsigned char *)module->alloc(codeBase + section->VirtualAddress, + section_size, + MEM_COMMIT, + PAGE_READWRITE, + module->userdata); + if (dest == NULL) { + return FALSE; + } + + // Always use position from file to support alignments smaller + // than page size (allocation above will align to page size). + dest = codeBase + section->VirtualAddress; + // 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 *)module->alloc(codeBase + section->VirtualAddress, + section->SizeOfRawData, + MEM_COMMIT, + PAGE_READWRITE, + module->userdata); + if (dest == NULL) { + return FALSE; + } + + // Always use position from file to support alignments smaller + // than page size (allocation above will align to page size). + dest = codeBase + section->VirtualAddress; + memcpy(dest, data + section->PointerToRawData, section->SizeOfRawData); + // 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; +} + +// Protection flags for memory pages (Executable, Readable, Writeable) +static int ProtectionFlags[2][2][2] = { + { + // not executable + {PAGE_NOACCESS, PAGE_WRITECOPY}, + {PAGE_READONLY, PAGE_READWRITE}, + }, { + // executable + {PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY}, + {PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE}, + }, +}; + +static SIZE_T +GetRealSectionSize(PMEMORYMODULE module, PIMAGE_SECTION_HEADER section) { + DWORD size = section->SizeOfRawData; + if (size == 0) { + if (section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) { + size = module->headers->OptionalHeader.SizeOfInitializedData; + } else if (section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) { + size = module->headers->OptionalHeader.SizeOfUninitializedData; + } + } + return (SIZE_T) size; +} + +static BOOL +FinalizeSection(PMEMORYMODULE module, PSECTIONFINALIZEDATA sectionData) { + DWORD protect, oldProtect; + BOOL executable; + BOOL readable; + BOOL writeable; + + if (sectionData->size == 0) { + return TRUE; + } + + if (sectionData->characteristics & IMAGE_SCN_MEM_DISCARDABLE) { + // section is not needed any more and can safely be freed + if (sectionData->address == sectionData->alignedAddress && + (sectionData->last || + module->headers->OptionalHeader.SectionAlignment == module->pageSize || + (sectionData->size % module->pageSize) == 0) + ) { + // Only allowed to decommit whole pages + module->free(sectionData->address, sectionData->size, MEM_DECOMMIT, module->userdata); + } + return TRUE; + } + + // determine protection flags based on characteristics + executable = (sectionData->characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + readable = (sectionData->characteristics & IMAGE_SCN_MEM_READ) != 0; + writeable = (sectionData->characteristics & IMAGE_SCN_MEM_WRITE) != 0; + protect = ProtectionFlags[executable][readable][writeable]; + if (sectionData->characteristics & IMAGE_SCN_MEM_NOT_CACHED) { + protect |= PAGE_NOCACHE; + } + + // change memory access flags + if (VirtualProtect(sectionData->address, sectionData->size, protect, &oldProtect) == 0) { + OutputLastError("Error protecting memory page"); + return FALSE; + } + + return TRUE; +} + +static BOOL +FinalizeSections(PMEMORYMODULE module) +{ + int i; + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); +#ifdef _WIN64 + // "PhysicalAddress" might have been truncated to 32bit above, expand to + // 64bits again. + uintptr_t imageOffset = ((uintptr_t) module->headers->OptionalHeader.ImageBase & 0xffffffff00000000); +#else + static const uintptr_t imageOffset = 0; +#endif + SECTIONFINALIZEDATA sectionData; + sectionData.address = (LPVOID)((uintptr_t)section->Misc.PhysicalAddress | imageOffset); + sectionData.alignedAddress = AlignAddressDown(sectionData.address, module->pageSize); + sectionData.size = GetRealSectionSize(module, section); + sectionData.characteristics = section->Characteristics; + sectionData.last = FALSE; + section++; + + // 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 = 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. + if (sectionData.alignedAddress == alignedAddress || (uintptr_t) sectionData.address + sectionData.size > (uintptr_t) alignedAddress) { + // Section shares page with previous + if ((section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0 || (sectionData.characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0) { + sectionData.characteristics = (sectionData.characteristics | section->Characteristics) & ~IMAGE_SCN_MEM_DISCARDABLE; + } else { + sectionData.characteristics |= section->Characteristics; + } + sectionData.size = (((uintptr_t)sectionAddress) + ((uintptr_t) sectionSize)) - (uintptr_t) sectionData.address; + continue; + } + + if (!FinalizeSection(module, §ionData)) { + return FALSE; + } + sectionData.address = sectionAddress; + sectionData.alignedAddress = alignedAddress; + sectionData.size = sectionSize; + sectionData.characteristics = section->Characteristics; + } + sectionData.last = TRUE; + if (!FinalizeSection(module, §ionData)) { + return FALSE; + } + return TRUE; +} + +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; + } + + tls = (PIMAGE_TLS_DIRECTORY) (codeBase + directory->VirtualAddress); + callback = (PIMAGE_TLS_CALLBACK *) tls->AddressOfCallBacks; + if (callback) { + while (*callback) { + (*callback)((LPVOID) codeBase, DLL_PROCESS_ATTACH, NULL); + callback++; + } + } + return TRUE; +} + +static BOOL +PerformBaseRelocation(PMEMORYMODULE module, ptrdiff_t delta) +{ + unsigned char *codeBase = module->codeBase; + DWORD relocation_size; + PIMAGE_BASE_RELOCATION relocation; + + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_BASERELOC); + if (directory->Size == 0) { + return (delta == 0); + } + + relocation_size = directory->Size; + relocation = (PIMAGE_BASE_RELOCATION) (codeBase + directory->VirtualAddress); + + for (;relocation_size; ) { + DWORD i; + unsigned char *dest = codeBase + relocation->VirtualAddress; + unsigned short *relInfo = (unsigned short*) OffsetPointer(relocation, IMAGE_SIZEOF_BASE_RELOCATION); + + relocation_size -= relocation->SizeOfBlock; + + for (i=0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++) { + // the upper 4 bits define the type of relocation + int type = *relInfo >> 12; + // the lower 12 bits define the offset + int offset = *relInfo & 0xfff; + + switch (type) + { + case IMAGE_REL_BASED_ABSOLUTE: + // skip relocation + break; + + case IMAGE_REL_BASED_HIGHLOW: + // change complete 32 bit address + { + DWORD *patchAddrHL = (DWORD *) (dest + offset); + *patchAddrHL += (DWORD) delta; + } + break; + +#ifdef _WIN64 + case IMAGE_REL_BASED_DIR64: + { + ULONGLONG *patchAddr64 = (ULONGLONG *) (dest + offset); + *patchAddr64 += (ULONGLONG) delta; + } + break; +#endif + + default: + //printf("Unknown relocation: %d\n", type); + break; + } + } + + // advance to next relocation block + relocation = (PIMAGE_BASE_RELOCATION) OffsetPointer(relocation, relocation->SizeOfBlock); + } + return TRUE; +} + +#ifdef _WIN64 +static BOOL +RegisterExceptionHandling(PMEMORYMODULE module) +{ + PIMAGE_DATA_DIRECTORY pDir = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_EXCEPTION); + PRUNTIME_FUNCTION pEntry = (PRUNTIME_FUNCTION)(module->codeBase + pDir->VirtualAddress); + UINT count = (pDir->Size / sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY)) - 1; + return RtlAddFunctionTable(pEntry, count, (DWORD64)module->codeBase); +} +#endif + +static BOOL +BuildImportTable(PMEMORYMODULE module) +{ + unsigned char *codeBase = module->codeBase; + PIMAGE_IMPORT_DESCRIPTOR importDesc; + BOOL result = TRUE; + + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_IMPORT); + if (directory->Size == 0) { + return TRUE; + } + + importDesc = (PIMAGE_IMPORT_DESCRIPTOR) (codeBase + directory->VirtualAddress); + for (; !IsBadReadPtr(importDesc, sizeof(IMAGE_IMPORT_DESCRIPTOR)) && importDesc->Name; importDesc++) { + uintptr_t *thunkRef; + FARPROC *funcRef; + HCUSTOMMODULE *tmp; + HCUSTOMMODULE handle = module->loadLibrary((LPCSTR) (codeBase + importDesc->Name), module->userdata); + if (handle == NULL) { + SetLastError(ERROR_MOD_NOT_FOUND); + result = FALSE; + break; + } + + tmp = (HCUSTOMMODULE *) realloc(module->modules, (module->numModules+1)*(sizeof(HCUSTOMMODULE))); + if (tmp == NULL) { + module->freeLibrary(handle, module->userdata); + SetLastError(ERROR_OUTOFMEMORY); + result = FALSE; + break; + } + module->modules = tmp; + + module->modules[module->numModules++] = handle; + if (importDesc->OriginalFirstThunk) { + thunkRef = (uintptr_t *) (codeBase + importDesc->OriginalFirstThunk); + funcRef = (FARPROC *) (codeBase + importDesc->FirstThunk); + } else { + // no hint table + thunkRef = (uintptr_t *) (codeBase + importDesc->FirstThunk); + funcRef = (FARPROC *) (codeBase + importDesc->FirstThunk); + } + for (; *thunkRef; thunkRef++, funcRef++) { + if (IMAGE_SNAP_BY_ORDINAL(*thunkRef)) { + *funcRef = module->getProcAddress(handle, (LPCSTR)IMAGE_ORDINAL(*thunkRef), module->userdata); + } else { + PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME) (codeBase + (*thunkRef)); + *funcRef = module->getProcAddress(handle, (LPCSTR)&thunkData->Name, module->userdata); + } + if (*funcRef == 0) { + result = FALSE; + break; + } + } + + if (!result) { + module->freeLibrary(handle, module->userdata); + SetLastError(ERROR_PROC_NOT_FOUND); + break; + } + } + + return result; +} + +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) +{ + 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; + } + + return (HCUSTOMMODULE) result; +} + +FARPROC MemoryDefaultGetProcAddress(HCUSTOMMODULE module, LPCSTR name, void *userdata) +{ + UNREFERENCED_PARAMETER(userdata); + return (FARPROC) GetProcAddress((HMODULE) module, name); +} + +void MemoryDefaultFreeLibrary(HCUSTOMMODULE module, void *userdata) +{ + UNREFERENCED_PARAMETER(userdata); + FreeLibrary((HMODULE) module); +} + +HMEMORYMODULE MemoryLoadLibrary(const void *data, size_t size) +{ + return MemoryLoadLibraryEx(data, size, MemoryDefaultAlloc, MemoryDefaultFree, MemoryDefaultLoadLibrary, MemoryDefaultGetProcAddress, MemoryDefaultFreeLibrary, NULL); +} + +HMEMORYMODULE MemoryLoadLibraryEx(const void *data, size_t size, + CustomAllocFunc allocMemory, + CustomFreeFunc freeMemory, + CustomLoadLibraryFunc loadLibrary, + CustomGetProcAddressFunc getProcAddress, + CustomFreeLibraryFunc freeLibrary, + void *userdata) +{ + PMEMORYMODULE result = NULL; + PIMAGE_DOS_HEADER dos_header; + PIMAGE_NT_HEADERS old_header; + unsigned char *code, *headers; + ptrdiff_t locationDelta; + 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; + } + + if (old_header->FileHeader.Machine != HOST_MACHINE) { + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + + if (old_header->OptionalHeader.SectionAlignment & 1) { + // Only support section alignments that are a multiple of 2 + SetLastError(ERROR_BAD_EXE_FORMAT); + 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; + } + } + + alignedImageSize = AlignValueUp(old_header->OptionalHeader.SizeOfImage, old_header->OptionalHeader.SectionAlignment); + if (alignedImageSize != AlignValueUp(lastSectionEnd, old_header->OptionalHeader.SectionAlignment)) { + 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 *)allocMemory((LPVOID)(old_header->OptionalHeader.ImageBase), + alignedImageSize, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE, + userdata); + + if (code == NULL) { + // try to allocate memory at arbitrary position + code = (unsigned char *)allocMemory(NULL, + alignedImageSize, + MEM_RESERVE | MEM_COMMIT, + 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) { + 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; + result->pageSize = old_header->OptionalHeader.SectionAlignment; +#ifdef _WIN64 + result->blockedMemory = blockedMemory; +#endif + + if (!CheckSize(size, old_header->OptionalHeader.SizeOfHeaders)) { + goto error; + } + + // commit memory for headers + headers = (unsigned char *)allocMemory(code, + old_header->OptionalHeader.SizeOfHeaders, + MEM_COMMIT, + PAGE_READWRITE, + userdata); + + // copy PE header to code + memcpy(headers, dos_header, old_header->OptionalHeader.SizeOfHeaders); + result->headers = (PIMAGE_NT_HEADERS)&((const unsigned char *)(headers))[dos_header->e_lfanew]; + + // update position + result->headers->OptionalHeader.ImageBase = (uintptr_t)code; + + // copy sections from DLL file block to new memory location + if (!CopySections((const unsigned char *) data, size, old_header, result)) { + goto error; + } + + // adjust base address of imported data + locationDelta = (ptrdiff_t)(result->headers->OptionalHeader.ImageBase - old_header->OptionalHeader.ImageBase); + if (locationDelta != 0) { + result->isRelocated = PerformBaseRelocation(result, locationDelta); + } else { + result->isRelocated = TRUE; + } + + // load required dlls and adjust function table of imports + if (!BuildImportTable(result)) { + goto error; + } + +#ifdef _WIN64 + if (!RegisterExceptionHandling(result)) { + goto error; + } +#endif + + // mark memory pages depending on section headers and release + // sections that are marked as "discardable" + if (!FinalizeSections(result)) { + goto error; + } + + // TLS callbacks are executed BEFORE the main loading + if (!ExecuteTLS(result)) { + goto error; + } + + // get entry point of loaded library + if (result->headers->OptionalHeader.AddressOfEntryPoint != 0) { + if (result->isDLL) { + 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) { + SetLastError(ERROR_DLL_INIT_FAILED); + goto error; + } + result->initialized = TRUE; + } else { + result->exeEntry = (ExeEntryProc)(LPVOID)(code + result->headers->OptionalHeader.AddressOfEntryPoint); + } + } else { + result->exeEntry = NULL; + } + + return (HMEMORYMODULE)result; + +error: + // cleanup + MemoryFreeLibrary(result); + return NULL; +} + +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) +{ + 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(module, IMAGE_DIRECTORY_ENTRY_EXPORT); + if (directory->Size == 0) { + // no export table found + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + + exports = (PIMAGE_EXPORT_DIRECTORY) (codeBase + directory->VirtualAddress); + //if (exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0) { + /*2019.12.24.zoand*/ + if (exports->NumberOfNames == 0 && exports->NumberOfFunctions == 0) { + // DLL doesn't export anything + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + + if (HIWORD(name) == 0) { + // load function by ordinal value + if (LOWORD(name) < exports->Base) { + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + + 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 (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)(LPVOID)(codeBase + (*(DWORD *) (codeBase + exports->AddressOfFunctions + (idx*4)))); +} + +void MemoryFreeLibrary(HMEMORYMODULE mod) +{ + PMEMORYMODULE module = (PMEMORYMODULE)mod; + + if (module == NULL) { + return; + } + if (module->initialized) { + // notify library about detaching from process + 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; + for (i=0; inumModules; i++) { + if (module->modules[i] != NULL) { + module->freeLibrary(module->modules[i], module->userdata); + } + } + + free(module->modules); + } + + if (module->codeBase != NULL) { + // release memory of library + module->free(module->codeBase, 0, MEM_RELEASE, module->userdata); + } + +#ifdef _WIN64 + FreePointerList(module->blockedMemory, module->free, module->userdata); +#endif + HeapFree(GetProcessHeap(), 0, module); +} + +int MemoryCallEntryPoint(HMEMORYMODULE mod) +{ + PMEMORYMODULE module = (PMEMORYMODULE)mod; + + if (module == NULL || module->isDLL || module->exeEntry == NULL || !module->isRelocated) { + return -1; + } + + return module->exeEntry(); +} + +#define DEFAULT_LANGUAGE MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) + +HMEMORYRSRC MemoryFindResource(HMEMORYMODULE module, LPCTSTR name, LPCTSTR type) +{ + return MemoryFindResourceEx(module, name, type, DEFAULT_LANGUAGE); +} + +static PIMAGE_RESOURCE_DIRECTORY_ENTRY _MemorySearchResourceEntry( + void *root, + PIMAGE_RESOURCE_DIRECTORY resources, + LPCTSTR key) +{ + PIMAGE_RESOURCE_DIRECTORY_ENTRY entries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY) (resources + 1); + PIMAGE_RESOURCE_DIRECTORY_ENTRY result = NULL; + DWORD start; + DWORD end; + DWORD middle; + + if (!IS_INTRESOURCE(key) && key[0] == TEXT('#')) { + // special case: resource id given as string + TCHAR *endpos = NULL; + long int tmpkey = (WORD) _tcstol((TCHAR *) &key[1], &endpos, 10); + if (tmpkey <= 0xffff && lstrlen(endpos) == 0) { + key = MAKEINTRESOURCE(tmpkey); + } + } + + // entries are stored as ordered list of named entries, + // followed by an ordered list of id entries - we can do + // a binary search to find faster... + if (IS_INTRESOURCE(key)) { + WORD check = (WORD) (uintptr_t) key; + start = resources->NumberOfNamedEntries; + end = start + resources->NumberOfIdEntries; + + while (end > start) { + WORD entryName; + middle = (start + end) >> 1; + entryName = (WORD) entries[middle].Name; + if (check < entryName) { + end = (end != middle ? middle : middle-1); + } else if (check > entryName) { + start = (start != middle ? middle : middle+1); + } else { + result = &entries[middle]; + break; + } + } + } else { + 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) { + int cmp; + PIMAGE_RESOURCE_DIR_STRING_U resourceString; + middle = (start + end) >> 1; + 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; + } + } + if (cmp < 0) { + end = (middle != end ? middle : middle-1); + } else if (cmp > 0) { + start = (middle != start ? middle : middle+1); + } else { + result = &entries[middle]; + break; + } + } +#if !defined(UNICODE) + if (searchKeyLen > MAX_LOCAL_KEY_LENGTH) { + free(_searchKey); + } +#undef MAX_LOCAL_KEY_LENGTH +#endif + } + + return result; +} + +HMEMORYRSRC MemoryFindResourceEx(HMEMORYMODULE module, LPCTSTR name, LPCTSTR type, WORD language) +{ + unsigned char *codeBase = ((PMEMORYMODULE) module)->codeBase; + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((PMEMORYMODULE) module, IMAGE_DIRECTORY_ENTRY_RESOURCE); + PIMAGE_RESOURCE_DIRECTORY rootResources; + PIMAGE_RESOURCE_DIRECTORY nameResources; + PIMAGE_RESOURCE_DIRECTORY typeResources; + PIMAGE_RESOURCE_DIRECTORY_ENTRY foundType; + PIMAGE_RESOURCE_DIRECTORY_ENTRY foundName; + PIMAGE_RESOURCE_DIRECTORY_ENTRY foundLanguage; + if (directory->Size == 0) { + // no resource table found + SetLastError(ERROR_RESOURCE_DATA_NOT_FOUND); + return NULL; + } + + if (language == DEFAULT_LANGUAGE) { + // use language from current thread + language = LANGIDFROMLCID(GetThreadLocale()); + } + + // resources are stored as three-level tree + // - first node is the type + // - second node is the name + // - third node is the language + rootResources = (PIMAGE_RESOURCE_DIRECTORY) (codeBase + directory->VirtualAddress); + foundType = _MemorySearchResourceEntry(rootResources, rootResources, type); + if (foundType == NULL) { + SetLastError(ERROR_RESOURCE_TYPE_NOT_FOUND); + return NULL; + } + + typeResources = (PIMAGE_RESOURCE_DIRECTORY) (codeBase + directory->VirtualAddress + (foundType->OffsetToData & 0x7fffffff)); + foundName = _MemorySearchResourceEntry(rootResources, typeResources, name); + if (foundName == NULL) { + SetLastError(ERROR_RESOURCE_NAME_NOT_FOUND); + return NULL; + } + + nameResources = (PIMAGE_RESOURCE_DIRECTORY) (codeBase + directory->VirtualAddress + (foundName->OffsetToData & 0x7fffffff)); + foundLanguage = _MemorySearchResourceEntry(rootResources, nameResources, (LPCTSTR) (uintptr_t) language); + if (foundLanguage == NULL) { + // requested language not found, use first available + if (nameResources->NumberOfIdEntries == 0) { + SetLastError(ERROR_RESOURCE_LANG_NOT_FOUND); + return NULL; + } + + foundLanguage = (PIMAGE_RESOURCE_DIRECTORY_ENTRY) (nameResources + 1); + } + + return (codeBase + directory->VirtualAddress + (foundLanguage->OffsetToData & 0x7fffffff)); +} + +DWORD MemorySizeofResource(HMEMORYMODULE module, HMEMORYRSRC resource) +{ + PIMAGE_RESOURCE_DATA_ENTRY entry; + UNREFERENCED_PARAMETER(module); + entry = (PIMAGE_RESOURCE_DATA_ENTRY) resource; + if (entry == NULL) { + return 0; + } + + return entry->Size; +} + +LPVOID MemoryLoadResource(HMEMORYMODULE module, HMEMORYRSRC resource) +{ + unsigned char *codeBase = ((PMEMORYMODULE) module)->codeBase; + PIMAGE_RESOURCE_DATA_ENTRY entry = (PIMAGE_RESOURCE_DATA_ENTRY) resource; + if (entry == NULL) { + return NULL; + } + + return codeBase + entry->OffsetToData; +} + +HMODULE MemoryGetCodeBase(HMEMORYMODULE module) +{ + return (HMODULE)((PMEMORYMODULE)module)->codeBase; +} + +int +MemoryLoadString(HMEMORYMODULE module, UINT id, LPTSTR buffer, int maxsize) +{ + return MemoryLoadStringEx(module, id, buffer, maxsize, DEFAULT_LANGUAGE); +} + +int +MemoryLoadStringEx(HMEMORYMODULE module, UINT id, LPTSTR buffer, int maxsize, WORD language) +{ + HMEMORYRSRC resource; + PIMAGE_RESOURCE_DIR_STRING_U data; + DWORD size; + if (maxsize == 0) { + return 0; + } + + resource = MemoryFindResourceEx(module, MAKEINTRESOURCE((id >> 4) + 1), RT_STRING, language); + if (resource == NULL) { + buffer[0] = 0; + return 0; + } + + data = (PIMAGE_RESOURCE_DIR_STRING_U) MemoryLoadResource(module, resource); + id = id & 0x0f; + while (id--) { + data = (PIMAGE_RESOURCE_DIR_STRING_U) OffsetPointer(data, (data->Length + 1) * sizeof(WCHAR)); + } + if (data->Length == 0) { + SetLastError(ERROR_RESOURCE_NAME_NOT_FOUND); + buffer[0] = 0; + return 0; + } + + size = data->Length; + if (size >= (DWORD) maxsize) { + size = maxsize; + } else { + buffer[size] = 0; + } +#if defined(UNICODE) + wcsncpy(buffer, data->NameString, size); +#else + wcstombs(buffer, data->NameString, size); +#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 ADDED win/MemoryModule.h Index: win/MemoryModule.h ================================================================== --- /dev/null +++ win/MemoryModule.h @@ -0,0 +1,173 @@ +/* + * Memory DLL loading code + * Version 0.0.4 + * + * Copyright (c) 2004-2015 by Joachim Bauch / mail@joachim-bauch.de + * http://www.joachim-bauch.de + * + * The contents of this file are subject to the Mozilla Public License Version + * 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is MemoryModule.h + * + * The Initial Developer of the Original Code is Joachim Bauch. + * + * Portions created by Joachim Bauch are Copyright (C) 2004-2015 + * Joachim Bauch. All Rights Reserved. + * + */ + +#ifndef __MEMORY_MODULE_HEADER +#define __MEMORY_MODULE_HEADER + +#include + +typedef void *HMEMORYMODULE; + +typedef void *HMEMORYRSRC; + +typedef void *HCUSTOMMODULE; + +#ifdef __cplusplus +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 with the given size. + * + * All dependencies are resolved using default LoadLibrary/GetProcAddress + * calls through the Windows API. + */ +HMEMORYMODULE MemoryLoadLibrary(const void *, size_t); + +/** + * 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 *, size_t, + CustomAllocFunc, + CustomFreeFunc, + CustomLoadLibraryFunc, + CustomGetProcAddressFunc, + CustomFreeLibraryFunc, + void *); + +/** + * Get address of exported method. Supports loading both by name and by + * ordinal value. + */ +FARPROC MemoryGetProcAddress(HMEMORYMODULE, LPCSTR); + +/** + * Free previously loaded EXE/DLL. + */ +void MemoryFreeLibrary(HMEMORYMODULE); + +/** + * Execute entry point (EXE only). The entry point can only be executed + * if the EXE has been loaded to the correct base address or it could + * be relocated (i.e. relocation information have not been stripped by + * the linker). + * + * Important: calling this function will not return, i.e. once the loaded + * EXE finished running, the process will terminate. + * + * Returns a negative value if the entry point could not be executed. + */ +int MemoryCallEntryPoint(HMEMORYMODULE); + +/** + * Find the location of a resource with the specified type and name. + */ +HMEMORYRSRC MemoryFindResource(HMEMORYMODULE, LPCTSTR, LPCTSTR); + +/** + * Find the location of a resource with the specified type, name and language. + */ +HMEMORYRSRC MemoryFindResourceEx(HMEMORYMODULE, LPCTSTR, LPCTSTR, WORD); + +/** + * Get the size of the resource in bytes. + */ +DWORD MemorySizeofResource(HMEMORYMODULE, HMEMORYRSRC); + +/** + * Get a pointer to the contents of the resource. + */ +LPVOID MemoryLoadResource(HMEMORYMODULE, HMEMORYRSRC); + +/** + * Get a pointer to the hInstance. + */ +HMODULE MemoryGetCodeBase(HMEMORYMODULE); + +/** + * Load a string resource. + */ +int MemoryLoadString(HMEMORYMODULE, UINT, LPTSTR, int); + +/** + * Load a string resource with a given language. + */ +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 + +#endif // __MEMORY_MODULE_HEADER Index: win/configure ================================================================== --- win/configure +++ win/configure @@ -710,10 +710,11 @@ VC_MANIFEST_EMBED_EXE VC_MANIFEST_EMBED_DLL CPP LDFLAGS_DEFAULT CFLAGS_DEFAULT +TCL_LOAD_FROM_MEMORY INSTALL_MSGS INSTALL_LIBRARIES TCL_ZIP_FILE ZIPFS_BUILD ZIP_INSTALL_OBJS @@ -795,10 +796,11 @@ enable_option_checking with_encoding enable_shared enable_64bit enable_zipfs +enable_memorymodule enable_symbols enable_embedded_manifest ' ac_precious_vars='build_alias host_alias @@ -1428,10 +1430,12 @@ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-shared build and link with shared libraries (default: on) --enable-64bit enable 64bit support (where applicable) --enable-zipfs build with Zipfs support (default: on) + --enable-memorymodule load dll's from a vfs in memory in stead of copying + to a temporary directory (default: off) --enable-symbols build with debugging symbols (default: off) --enable-embedded-manifest embed manifest if possible (default: yes) Optional Packages: @@ -5259,10 +5263,29 @@ + +#-------------------------------------------------------------------- +# MemoryModule support - Tip 708 +#-------------------------------------------------------------------- +# Check whether --enable-memorymodule was given. +if test ${enable_memorymodule+y} +then : + enableval=$enable_memorymodule; tcl_ok=$enableval +else case e in #( + e) tcl_ok=no ;; +esac +fi + +if test "$tcl_ok" != "no" ; then + +printf "%s\n" "#define TCL_LOAD_FROM_MEMORY 1" >>confdefs.h + +fi + #-------------------------------------------------------------------- # Perform additinal compiler tests. #-------------------------------------------------------------------- @@ -6004,11 +6027,11 @@ -ac_config_files="$ac_config_files Makefile tclConfig.sh tclsh.exe.manifest" +ac_config_files="$ac_config_files Makefile dltest/Makefile tclConfig.sh tclsh.exe.manifest" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. @@ -6708,10 +6731,11 @@ # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "dltest/Makefile") CONFIG_FILES="$CONFIG_FILES dltest/Makefile" ;; "tclConfig.sh") CONFIG_FILES="$CONFIG_FILES tclConfig.sh" ;; "tclsh.exe.manifest") CONFIG_FILES="$CONFIG_FILES tclsh.exe.manifest" ;; *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; esac Index: win/configure.ac ================================================================== --- win/configure.ac +++ win/configure.ac @@ -206,10 +206,22 @@ AC_SUBST(ZIPFS_BUILD) AC_SUBST(TCL_ZIP_FILE) AC_SUBST(INSTALL_LIBRARIES) AC_SUBST(INSTALL_MSGS) + +#-------------------------------------------------------------------- +# MemoryModule support - Tip 708 +#-------------------------------------------------------------------- +AC_ARG_ENABLE(memorymodule, + AS_HELP_STRING([--enable-memorymodule], + [load dll's from a vfs in memory in stead of copying to a temporary directory (default: off)]), + [tcl_ok=$enableval], [tcl_ok=no]) +if test "$tcl_ok" != "no" ; then + AC_DEFINE(TCL_LOAD_FROM_MEMORY, 1, [Are we building with MemoryModule enabled?]) +fi +AC_SUBST(TCL_LOAD_FROM_MEMORY) #-------------------------------------------------------------------- # Perform additinal compiler tests. #-------------------------------------------------------------------- @@ -466,11 +478,11 @@ AC_SUBST(RC_INCLUDE) AC_SUBST(RC_DEFINE) AC_SUBST(RC_DEFINES) AC_SUBST(RES) -AC_CONFIG_FILES([Makefile tclConfig.sh tclsh.exe.manifest]) +AC_CONFIG_FILES([Makefile dltest/Makefile tclConfig.sh tclsh.exe.manifest]) AC_OUTPUT dnl Local Variables: dnl mode: autoconf dnl End: ADDED win/dltest/Makefile.in Index: win/dltest/Makefile.in ================================================================== --- /dev/null +++ win/dltest/Makefile.in @@ -0,0 +1,56 @@ +# This Makefile is used to create several test cases for Tcl's load +# command. It also illustrates how to take advantage of configuration +# exported by Tcl to set up Makefiles for shared libraries. + +CC = @CC@ +LIBS = @TCL_BUILD_STUB_LIB_SPEC@ +AC_FLAGS = @DEFS@ +SHLIB_LD = @SHLIB_LD@ +SHLIB_CFLAGS = @SHLIB_CFLAGS@ +SHLIB_LD_LIBS = @SHLIB_LD_LIBS@ +SHLIB_SUFFIX = @SHLIB_SUFFIX@ +DLTEST_LD = @DLTEST_LD@ +SRC_DIR = @TCL_SRC_DIR@/win/dltest +BUILD_DIR = @builddir@ +NATIVE_ZIP = @ZIP_PROG@ +TCL_VERSION = @TCL_VERSION@ + +CFLAGS_DEBUG = @CFLAGS_DEBUG@ +CFLAGS_OPTIMIZE = @CFLAGS_OPTIMIZE@ +CFLAGS = @CFLAGS_DEFAULT@ @CFLAGS@ -DTCL_NO_DEPRECATED=1 -Wall -Wextra -Wc++-compat -Wconversion -Werror +LDFLAGS_DEBUG = @LDFLAGS_DEBUG@ +LDFLAGS_OPTIMIZE = @LDFLAGS_OPTIMIZE@ +LDFLAGS = @LDFLAGS_DEFAULT@ @LDFLAGS@ -static-libgcc + +CC_SWITCHES = $(CFLAGS) -I${SRC_DIR}/../../generic \ + ${SHLIB_CFLAGS} -DUSE_TCL_STUBS ${AC_FLAGS} + +all: memorymoduletest.zip + +memorymoduletest.zip: tcl9memorymoduletest${SHLIB_SUFFIX} + @if test -n "${NATIVE_ZIP}"; then \ + if test -f ../minizip.exe; then \ + cp ../minizip.exe . ; \ + fi; \ + (zip=`(realpath '${NATIVE_ZIP}' || readlink -m '${NATIVE_ZIP}') 2>/dev/null || \ + (echo '${NATIVE_ZIP}' | sed "s?^\./?$$(pwd)/?")`; \ + $$zip memorymoduletest.zip pkgIndex.tcl *.dll >/dev/null && \ + echo "memorymoduletest.zip successful created with $$zip" \ + ); \ + fi + +memorymoduletest.o: $(SRC_DIR)/memorymoduletest.c + $(CC) -c $(CC_SWITCHES) $(SRC_DIR)/memorymoduletest.c + +tcl9memorymoduletest${SHLIB_SUFFIX}: memorymoduletest.o + ${SHLIB_LD} -o $@ memorymoduletest.o ${SHLIB_LD_LIBS} + +clean: + rm -f embtest *.o lib.exp ../dltest.marker + @if test "$(SHLIB_SUFFIX)" != ""; then \ + echo "rm -f *${SHLIB_SUFFIX}" ; \ + rm -f *${SHLIB_SUFFIX} ; \ + fi + +distclean: clean + rm -f Makefile ADDED win/dltest/makefile.vc Index: win/dltest/makefile.vc ================================================================== --- /dev/null +++ win/dltest/makefile.vc @@ -0,0 +1,44 @@ +TOP = . + +# optional build flags +LOC = + +# variables +SHAREDLIB = tcl9memorymoduletest.dll +IMPLIB = memorymoduletest.lib + +CC = cl +AS = ml +LD = link +CFLAGS = -nologo -MD -W3 -O2 -Oy- -Zi $(LOC) -DUSE_TCL_STUBS -I../../generic +WFLAGS = -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE +LDFLAGS = -nologo -debug -incremental:no -opt:ref + +OBJS = memorymoduletest.obj + +# targets +all: $(SHAREDLIB) $(IMPLIB) + +$(IMPLIB): $(SHAREDLIB) + +$(SHAREDLIB): $(OBJS) + $(LD) $(LDFLAGS) -dll -implib:$(IMPLIB) tclstub.lib \ + -out:$@ $(OBJS) + +{$(TOP)}.c.obj: + $(CC) -c $(WFLAGS) $(CFLAGS) $< + +memorymoduletest.obj: $(TOP)/memorymoduletest.c + +# cleanup +clean: + -del $(STATICLIB) + -del $(SHAREDLIB) + -del $(IMPLIB) + -del *.obj + -del *.res + -del *.exp + -del *.exe + -del *.pdb + -del *.manifest + -del foo.gz ADDED win/dltest/memorymoduletest.c Index: win/dltest/memorymoduletest.c ================================================================== --- /dev/null +++ win/dltest/memorymoduletest.c @@ -0,0 +1,215 @@ +/* + * memorymoduletest.c -- + * + * This file contains a simple Tcl package "memorymoduletest" that is intended for + * testing the Tcl dynamic loading facilities using MemoryModule. + * + * Copyright © 2024 Jan Nijtmans + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#undef STATIC_BUILD +#include +#include +#include "tcl.h" + +static HMODULE hModule = NULL; + +static bool threadAttachCalled = false; + +#ifdef _MSC_VER +__declspec(thread) +#elif defined __GNUC__ +__thread +#elif __cplusplus +thread_local +#endif +int threadVar = 0; + +BOOL APIENTRY +DllMain( + HINSTANCE hInst, /* Library instance handle. */ + DWORD reason, /* Reason this function is being called. */ + LPVOID unused) +{ + (void)reason; + (void)unused; + + switch (reason) { + case DLL_PROCESS_ATTACH: + hModule = hInst; + break; + case DLL_THREAD_ATTACH: + threadAttachCalled = true; + break; + default: + break; + } + return TRUE; +} + + +/* + *---------------------------------------------------------------------- + * + * Mm_ModuleFileNameACmd -- + * + * This procedure is invoked to process the "GetModuleFileNameA" Tcl command. + * It calls the Windows GetModuleFileNameA(), and gives back the result. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ + +static int +Mmt_ModuleFileNameACmd( + void *dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + char buffer[MAX_PATH]; + (void)dummy; + + if (objc != 1) { + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + if (GetModuleFileNameA(hModule, buffer, MAX_PATH) == 0) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf("could not determine ModuleFileName: %s", + Tcl_PosixError(interp))); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, Tcl_NewStringObj(buffer, -1)); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * Mm_ModuleFileNameWCmd -- + * + * This procedure is invoked to process the "GetModuleFileNameW" Tcl command. + * It calls the Windows GetModuleFileNameW(), and gives back the result. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ + +static int +Mmt_ModuleFileNameWCmd( + void *dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument strings. */ +{ + WCHAR buffer[MAX_PATH]; + Tcl_DString ds; + (void)dummy; + + if (objc != 1) { + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + if (GetModuleFileNameW(hModule, buffer, MAX_PATH) == 0) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf("could not determine ModuleFileName: %s", + Tcl_PosixError(interp))); + return TCL_ERROR; + } + Tcl_DStringInit(&ds); + Tcl_WCharToUtfDString(buffer, -1, &ds); + Tcl_SetObjResult(interp, Tcl_DStringToObj(&ds)); + return TCL_OK; +} + +static int +Mmt_ThreadAttachCalled( + void *dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument strings. */ +{ + (void)dummy; + + if (objc != 1) { + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(threadAttachCalled)); + return TCL_OK; +} + +static int +Mmt_ThreadVar( + void *dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument strings. */ +{ + (void)dummy; + + if (objc > 2) { + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + if (objc == 2) { + if (Tcl_GetIntFromObj(interp, objv[1], &threadVar) != TCL_OK) { + return TCL_ERROR; + } + } + Tcl_SetObjResult(interp, Tcl_NewIntObj(threadVar)); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * Memorymoduletest_Init -- + * + * This is a package initialization procedure, which is called by Tcl + * when this package is to be added to an interpreter. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +DLLEXPORT int +Memorymoduletest_Init( + Tcl_Interp *interp) /* Interpreter in which the package is to be + * made available. */ +{ + int code; + + if (Tcl_InitStubs(interp, "8.7-", 0) == NULL) { + /* Tcl 8.6 doesn't have Tcl_DStringToObj() */ + return TCL_ERROR; + } + code = Tcl_PkgProvide(interp, "memorymoduletest", "1.0.0"); + if (code != TCL_OK) { + return code; + } + Tcl_CreateObjCommand(interp, "GetModuleFileNameA", Mmt_ModuleFileNameACmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "GetModuleFileNameW", Mmt_ModuleFileNameWCmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "ThreadAttachCalled", Mmt_ThreadAttachCalled, NULL, NULL); + Tcl_CreateObjCommand(interp, "ThreadVar", Mmt_ThreadVar, NULL, NULL); + return TCL_OK; +} ADDED win/dltest/pkgIndex.tcl Index: win/dltest/pkgIndex.tcl ================================================================== --- /dev/null +++ win/dltest/pkgIndex.tcl @@ -0,0 +1,12 @@ +# Tcl package index file, version 1.1 +# This file is generated by the "pkg_mkIndex -direct" command +# and sourced either when an application starts up or +# by a "package unknown" script. It invokes the +# "package ifneeded" command to set up package-related +# information so that packages will be loaded automatically +# in response to "package require" commands. When this +# script is sourced, the variable $dir must contain the +# full path name of this file's directory. + +if {![package vsatisfies [package provide Tcl] 9.0]} {return} +package ifneeded memorymoduletest 1.0.0 [list load [file join $dir tcl9memorymoduletest.dll]] Index: win/makefile.vc ================================================================== --- win/makefile.vc +++ win/makefile.vc @@ -50,15 +50,17 @@ # SDK (not expressly needed), run setenv.bat after vcvars32.bat # according to the instructions for it. This can also turn on the # 64-bit compiler, if your SDK has it. # # Basic macros and options usable on the commandline (see rules.vc for more info): -# OPTS=nomsvcrt,noembed,nothreads,pdbs,profile,static,symbols,thrdalloc,unchecked,none +# OPTS=memorymodule,nomsvcrt,noembed,nothreads,pdbs,profile,static,symbols,thrdalloc,unchecked,none # Sets special options for the core. The default is for none. # Any combination of the above may be used (comma separated). # 'none' will over-ride everything to nothing. # +# memorymodule = load dll's from a vfs in memory in stead of +# copying to a temporary directory. # noembed = Without this option, the Tcl core library scripts # are embedded into the executable if "static" is # specified in OPTS, or into the DLL otherwise. If # "noembed" is specified, the scripts are not embedded # but copied to the installation target (as in 8.6). @@ -162,10 +164,17 @@ !message *** NOTE: The "staticpkg" option redundant in 8.7. !else !message *** NOTE: The "staticpkg" option ignored for shared library builds. !endif !endif + +!if [nmakehlp -f $(OPTS) "memorymodule"] +!message *** Option memorymodule specified. +TCL_LOAD_FROM_MEMORY = 1 +!else +TCL_LOAD_FROM_MEMORY = 0 +!endif !if [nmakehlp -f $(OPTS) "noembed"] !message *** Option noembed specified. Tcl script library will not be appended to the binary. TCL_EMBED_SCRIPTS = 0 TCL_TEST_LIBRARY=$(ROOT:\=/)/library @@ -471,10 +480,14 @@ # Additional include and C macro definitions for the implicit rules # defined in rules.vc PRJ_INCLUDES = -I"$(TOMMATHDIR)" PRJ_DEFINES = /DMP_PREC=4 /Dinline=__inline /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE /DMP_FIXED_CUTOFFS + +!if $(TCL_LOAD_FROM_MEMORY) +PRJ_DEFINES = $(PRJ_DEFINES) /DTCL_LOAD_FROM_MEMORY +!endif !if $(STATIC_BUILD) PRJ_DEFINES = $(PRJ_DEFINES) /DTCL_WITH_INTERNAL_ZLIB !endif @@ -895,10 +908,14 @@ $(TMP_DIR)\tclTestObj.obj: $(GENERICDIR)\tclTestObj.c $(cc32) $(appcflags) -Fo$@ $? $(TMP_DIR)\tclTestABSList.obj: $(GENERICDIR)\tclTestABSList.c $(cc32) $(appcflags) -Fo$@ $? + +$(TMP_DIR)\tclWinLoad.obj: $(WIN_DIR)\tclWinLoad.c + $(cc32) $(pkgcflags) /DUNICODE /D_UNICODE \ + -Fo$@ $? $(TMP_DIR)\tclWinTest.obj: $(WIN_DIR)\tclWinTest.c $(CCAPPCMD) $? $(TMP_DIR)\tclZipfs.obj: $(GENERICDIR)\tclZipfs.c Index: win/tclWinLoad.c ================================================================== --- win/tclWinLoad.c +++ win/tclWinLoad.c @@ -10,10 +10,13 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclWinInt.h" +#ifdef TCL_LOAD_FROM_MEMORY +#include "MemoryModule.c" +#endif /* * Native name of the directory in the native filesystem where DLLs used in * this process are copied prior to loading, and mutex used to protect its * allocation. @@ -61,11 +64,11 @@ * (*unloadProcPtr)() to unload the file. */ Tcl_FSUnloadFileProc **unloadProcPtr, /* Filled with address of Tcl_FSUnloadFileProc * function which should be used for this * file. */ - TCL_UNUSED(int) /*flags*/) + TCL_UNUSED(int) /* flags */) { HINSTANCE hInstance = NULL; const WCHAR *nativeName; Tcl_LoadHandle handlePtr; DWORD firstError; @@ -91,11 +94,11 @@ Tcl_DString ds; /* * Remember the first error on load attempt to be used if the * second load attempt below also fails. - */ + */ firstError = (nativeName == NULL) ? ERROR_MOD_NOT_FOUND : GetLastError(); Tcl_DStringInit(&ds); nativeName = Tcl_UtfToWCharDString(TclGetString(pathPtr), TCL_INDEX_NONE, &ds); @@ -173,11 +176,11 @@ /* * Succeded; package everything up for Tcl. */ handlePtr = (Tcl_LoadHandle)Tcl_Alloc(sizeof(struct Tcl_LoadHandle_)); - handlePtr->clientData = (void *)hInstance; + handlePtr->clientData = hInstance; handlePtr->findSymbolProcPtr = &FindSymbol; handlePtr->unloadFileProcPtr = &UnloadFile; *loadHandle = handlePtr; *unloadProcPtr = &UnloadFile; return TCL_OK; @@ -254,11 +257,11 @@ UnloadFile( Tcl_LoadHandle loadHandle) /* loadHandle returned by a previous call to * TclpDlopen(). The loadHandle is a token * that represents the loaded file. */ { - HINSTANCE hInstance = (HINSTANCE) loadHandle->clientData; + HINSTANCE hInstance = (HINSTANCE)loadHandle->clientData; FreeLibrary(hInstance); Tcl_Free(loadHandle); } @@ -393,12 +396,145 @@ dllDirectoryName = (WCHAR *)Tcl_Alloc((nameLen+1) * sizeof(WCHAR)); wcscpy(dllDirectoryName, name); return TCL_OK; } +#ifdef TCL_LOAD_FROM_MEMORY + +static HMODULE kernel32 = NULL; +static Tcl_HashTable vfsPathTable; + +static DWORD fake_GetModuleFileNameW(HMODULE module, WCHAR *path, WORD nSize) { + Tcl_MutexLock(&dllDirectoryNameMutex); + Tcl_HashEntry *entry = Tcl_FindHashEntry(&vfsPathTable, module); + Tcl_MutexUnlock(&dllDirectoryNameMutex); + if (entry == NULL) { + return GetModuleFileNameW(module, path, nSize); + } + return MultiByteToWideChar(CP_UTF8, 0, (const char *)Tcl_GetHashValue(entry), -1 , path, nSize); +} + +static DWORD fake_GetModuleFileNameA(HMODULE module, LPSTR lpFilename, WORD nSize) { + Tcl_MutexLock(&dllDirectoryNameMutex); + Tcl_HashEntry *entry = Tcl_FindHashEntry(&vfsPathTable, module); + Tcl_MutexUnlock(&dllDirectoryNameMutex); + if (entry == NULL) { + return GetModuleFileNameA(module, lpFilename, nSize); + } + strncpy(lpFilename, (const char *)Tcl_GetHashValue(entry), nSize); + lpFilename[nSize] = 0; + return (DWORD)strlen(lpFilename); +} + +MODULE_SCOPE void * +TclpLoadMemoryGetBuffer( + size_t size) +{ + return Tcl_Alloc(size); +} + +static void +UnloadMemory( + Tcl_LoadHandle loadHandle) /* loadHandle returned by a previous call to + * TclpDlopen(). The loadHandle is a token + * that represents the loaded file. */ +{ + MemoryFreeLibrary(loadHandle->clientData); + Tcl_Free(loadHandle); +} + +static void * +FindMemSymbol( + Tcl_Interp *interp, /* For error reporting. */ + Tcl_LoadHandle loadHandle, /* Handle from TclpDlopen. */ + const char *symbol) /* Symbol name to look up. */ +{ + void *res = MemoryGetProcAddress(loadHandle->clientData, symbol); + + if (res == NULL && interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "cannot find symbol \"%s\" in memory-loaded dll", symbol)); + Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "LOAD_SYMBOL", symbol, + (char *)NULL); + } + return res; +} + +static FARPROC +FakeDefaultGetProcAddress(HCUSTOMMODULE module, LPCSTR name, void *userdata) +{ + if (kernel32 == NULL) { + Tcl_MutexLock(&dllDirectoryNameMutex); + if (kernel32 == NULL) { + kernel32 = GetModuleHandleW(L"KERNEL32"); + Tcl_InitHashTable(&vfsPathTable, TCL_ONE_WORD_KEYS); + } + Tcl_MutexUnlock(&dllDirectoryNameMutex); + } + if ((module == kernel32) && !strcmp(name, "GetModuleFileNameW")) { + return (FARPROC)(void *)fake_GetModuleFileNameW; + } else if ((module == kernel32) && !strcmp(name, "GetModuleFileNameA")) { + return (FARPROC)(void *)fake_GetModuleFileNameA; + } + return MemoryDefaultGetProcAddress(module, name, userdata); +} + +MODULE_SCOPE int +TclpLoadMemory( + void *data, /* Buffer containing the desired code + * (allocated with TclpLoadMemoryGetBuffer). */ + size_t size, /* Allocation size of buffer. */ + Tcl_Size codeSize, /* Size of code data read into buffer or TCL_INDEX_NONE + * if an error occurred and buffer should be + * free'd. */ + const char *path, /* path in VFS, or NULL if the path is unknown */ + Tcl_LoadHandle *loadHandle, /* Filled with token for dynamically loaded + * file which will be passed back to + * (*unloadProcPtr)() to unload the file. */ + Tcl_FSUnloadFileProc **unloadProcPtr, + /* Filled with address of Tcl_FSUnloadFileProc + * function which should be used for this + * file. */ + TCL_UNUSED(int)) /* flags */ +{ + Tcl_LoadHandle handlePtr; + void *hInstance; + size_t handleSize = offsetof(struct Tcl_LoadHandle_, name) + (path ? strlen(path) : 1); + int isNew; + + if (codeSize < 1) { + Tcl_Free(data); + return TCL_ERROR; + } + handlePtr = (Tcl_LoadHandle)Tcl_Alloc(handleSize); + handlePtr->findSymbolProcPtr = &FindMemSymbol; + handlePtr->unloadFileProcPtr = &UnloadMemory; + if (path) { + strcpy(handlePtr->name, path); + } else { + handlePtr->name[0] = 0; + } + hInstance = MemoryLoadLibraryEx(data, size, MemoryDefaultAlloc, MemoryDefaultFree, + MemoryDefaultLoadLibrary, FakeDefaultGetProcAddress, MemoryDefaultFreeLibrary, handlePtr); + Tcl_MutexLock(&dllDirectoryNameMutex); + Tcl_HashEntry *entry = Tcl_CreateHashEntry(&vfsPathTable, MemoryGetCodeBase(hInstance), &isNew); + Tcl_SetHashValue(entry, handlePtr->name); + Tcl_MutexLock(&dllDirectoryNameMutex); + if (hInstance == NULL) { + Tcl_Free(handlePtr); + return TCL_ERROR; + } + handlePtr->clientData = hInstance; + *loadHandle = handlePtr; + *unloadProcPtr = &UnloadMemory; + return TCL_OK; +} + +#endif /* TCL_LOAD_FROM_MEMORY */ + /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */