You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1151 lines
30 KiB
1151 lines
30 KiB
/* |
|
library.c - custom dlls loader |
|
Copyright (C) 2008 Uncle Mike |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
*/ |
|
|
|
#if defined(_WIN32) |
|
#include "common.h" |
|
#include "library.h" |
|
|
|
#ifdef XASH_64BIT |
|
#include <dbghelp.h> |
|
|
|
void *COM_LoadLibrary( const char *dllname, int build_ordinals_table ) |
|
{ |
|
return LoadLibraryA( dllname ); |
|
} |
|
|
|
void COM_FreeLibrary( void *hInstance ) |
|
{ |
|
FreeLibrary( hInstance ); |
|
} |
|
|
|
void *COM_GetProcAddress( void *hInstance, const char *name ) |
|
{ |
|
return GetProcAddress( hInstance, name ); |
|
} |
|
|
|
void *COM_FunctionFromName( void *hInstance, const char *name ) |
|
{ |
|
return GetProcAddress( hInstance, name ); |
|
} |
|
|
|
const char *COM_NameForFunction( void *hInstance, void *function ) |
|
{ |
|
#if 0 |
|
static qboolean initialized = false; |
|
if( initialized ) |
|
{ |
|
char message[1024]; |
|
int len = 0; |
|
size_t i; |
|
HANDLE process = GetCurrentProcess(); |
|
HANDLE thread = GetCurrentThread(); |
|
IMAGEHLP_LINE64 line; |
|
DWORD dline = 0; |
|
DWORD options; |
|
CONTEXT context; |
|
STACKFRAME64 stackframe; |
|
DWORD image; |
|
char buffer[sizeof( IMAGEHLP_SYMBOL64) + MAX_SYM_NAME * sizeof(TCHAR)]; |
|
PIMAGEHLP_SYMBOL64 symbol = ( PIMAGEHLP_SYMBOL64)buffer; |
|
memset( symbol, 0, sizeof(IMAGEHLP_SYMBOL64) + MAX_SYM_NAME ); |
|
symbol->SizeOfStruct = sizeof( IMAGEHLP_SYMBOL64); |
|
symbol->MaxNameLength = MAX_SYM_NAME; |
|
DWORD displacement = 0; |
|
|
|
options = SymGetOptions(); |
|
SymSetOptions( options ); |
|
|
|
SymInitialize( process, NULL, TRUE ); |
|
|
|
if( SymGetSymFromAddr64( process, function, &displacement, symbol ) ) |
|
{ |
|
Msg( "%s\n", symbol->Name ); |
|
return copystring( symbol->Name ); |
|
} |
|
|
|
} |
|
#endif |
|
|
|
#ifdef XASH_ALLOW_SAVERESTORE_OFFSETS |
|
return COM_OffsetNameForFunction( function ); |
|
#endif |
|
|
|
return NULL; |
|
} |
|
#else // XASH_64BIT |
|
|
|
/* |
|
--------------------------------------------------------------- |
|
|
|
Custom dlls loader |
|
|
|
--------------------------------------------------------------- |
|
*/ |
|
|
|
#define DOS_SIGNATURE 0x5A4D // MZ |
|
#define NT_SIGNATURE 0x00004550 // PE00 |
|
#define NUMBER_OF_DIRECTORY_ENTRIES 16 |
|
#ifndef IMAGE_SIZEOF_BASE_RELOCATION |
|
#define IMAGE_SIZEOF_BASE_RELOCATION ( sizeof( IMAGE_BASE_RELOCATION )) |
|
#endif |
|
|
|
typedef struct |
|
{ |
|
// dos .exe header |
|
word e_magic; // magic number |
|
word e_cblp; // bytes on last page of file |
|
word e_cp; // pages in file |
|
word e_crlc; // relocations |
|
word e_cparhdr; // size of header in paragraphs |
|
word e_minalloc; // minimum extra paragraphs needed |
|
word e_maxalloc; // maximum extra paragraphs needed |
|
word e_ss; // initial (relative) SS value |
|
word e_sp; // initial SP value |
|
word e_csum; // checksum |
|
word e_ip; // initial IP value |
|
word e_cs; // initial (relative) CS value |
|
word e_lfarlc; // file address of relocation table |
|
word e_ovno; // overlay number |
|
word e_res[4]; // reserved words |
|
word e_oemid; // OEM identifier (for e_oeminfo) |
|
word e_oeminfo; // OEM information; e_oemid specific |
|
word e_res2[10]; // reserved words |
|
long e_lfanew; // file address of new exe header |
|
} DOS_HEADER; |
|
|
|
typedef struct |
|
{ |
|
// win .exe header |
|
word Machine; |
|
word NumberOfSections; |
|
dword TimeDateStamp; |
|
dword PointerToSymbolTable; |
|
dword NumberOfSymbols; |
|
word SizeOfOptionalHeader; |
|
word Characteristics; |
|
} PE_HEADER; |
|
|
|
typedef struct |
|
{ |
|
dword VirtualAddress; |
|
dword Size; |
|
} DATA_DIRECTORY; |
|
|
|
typedef struct |
|
{ |
|
word Magic; |
|
byte MajorLinkerVersion; |
|
byte MinorLinkerVersion; |
|
dword SizeOfCode; |
|
dword SizeOfInitializedData; |
|
dword SizeOfUninitializedData; |
|
dword AddressOfEntryPoint; |
|
dword BaseOfCode; |
|
dword BaseOfData; |
|
dword ImageBase; |
|
dword SectionAlignment; |
|
dword FileAlignment; |
|
word MajorOperatingSystemVersion; |
|
word MinorOperatingSystemVersion; |
|
word MajorImageVersion; |
|
word MinorImageVersion; |
|
word MajorSubsystemVersion; |
|
word MinorSubsystemVersion; |
|
dword Win32VersionValue; |
|
dword SizeOfImage; |
|
dword SizeOfHeaders; |
|
dword CheckSum; |
|
word Subsystem; |
|
word DllCharacteristics; |
|
dword SizeOfStackReserve; |
|
dword SizeOfStackCommit; |
|
dword SizeOfHeapReserve; |
|
dword SizeOfHeapCommit; |
|
dword LoaderFlags; |
|
dword NumberOfRvaAndSizes; |
|
|
|
DATA_DIRECTORY DataDirectory[NUMBER_OF_DIRECTORY_ENTRIES]; |
|
} OPTIONAL_HEADER; |
|
|
|
typedef struct |
|
{ |
|
dword Characteristics; |
|
dword TimeDateStamp; |
|
word MajorVersion; |
|
word MinorVersion; |
|
dword Name; |
|
dword Base; |
|
dword NumberOfFunctions; |
|
dword NumberOfNames; |
|
dword AddressOfFunctions; // RVA from base of image |
|
dword AddressOfNames; // RVA from base of image |
|
dword AddressOfNameOrdinals; // RVA from base of image |
|
} EXPORT_DIRECTORY; |
|
|
|
typedef struct |
|
{ |
|
PIMAGE_NT_HEADERS headers; |
|
byte *codeBase; |
|
void **modules; |
|
int numModules; |
|
int initialized; |
|
} MEMORYMODULE, *PMEMORYMODULE; |
|
|
|
typedef struct |
|
{ |
|
byte Name[8]; // dos name length |
|
|
|
union |
|
{ |
|
dword PhysicalAddress; |
|
dword VirtualSize; |
|
} Misc; |
|
|
|
dword VirtualAddress; |
|
dword SizeOfRawData; |
|
dword PointerToRawData; |
|
dword PointerToRelocations; |
|
dword PointerToLinenumbers; |
|
word NumberOfRelocations; |
|
word NumberOfLinenumbers; |
|
dword Characteristics; |
|
} SECTION_HEADER; |
|
|
|
// Protection flags for memory pages (Executable, Readable, Writeable) |
|
static int ProtectionFlags[2][2][2] = |
|
{ |
|
{ |
|
{ PAGE_NOACCESS, PAGE_WRITECOPY }, // not executable |
|
{ PAGE_READONLY, PAGE_READWRITE }, |
|
}, |
|
{ |
|
{ PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY }, // executable |
|
{ PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE }, |
|
}, |
|
}; |
|
|
|
typedef BOOL (WINAPI *DllEntryProc)( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ); |
|
|
|
#define GET_HEADER_DICTIONARY( module, idx ) &(module)->headers->OptionalHeader.DataDirectory[idx] |
|
#define CALCULATE_ADDRESS( base, offset ) (((DWORD)(base)) + (offset)) |
|
|
|
static void CopySections( const byte *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) |
|
{ |
|
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); |
|
byte *codeBase = module->codeBase; |
|
int i, size; |
|
byte *dest; |
|
|
|
for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) |
|
{ |
|
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 = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), size, MEM_COMMIT, PAGE_READWRITE ); |
|
section->Misc.PhysicalAddress = (DWORD)dest; |
|
memset( dest, 0, size ); |
|
} |
|
// section is empty |
|
continue; |
|
} |
|
|
|
// commit memory block and copy data from dll |
|
dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE ); |
|
memcpy( dest, (byte *)CALCULATE_ADDRESS(data, section->PointerToRawData), section->SizeOfRawData ); |
|
section->Misc.PhysicalAddress = (DWORD)dest; |
|
} |
|
} |
|
|
|
static void FreeSections( PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) |
|
{ |
|
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); |
|
byte *codeBase = module->codeBase; |
|
int i, size; |
|
|
|
for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) |
|
{ |
|
if( section->SizeOfRawData == 0 ) |
|
{ |
|
size = old_headers->OptionalHeader.SectionAlignment; |
|
if( size > 0 ) |
|
{ |
|
VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), size, MEM_DECOMMIT ); |
|
section->Misc.PhysicalAddress = 0; |
|
} |
|
continue; |
|
} |
|
|
|
VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), section->SizeOfRawData, MEM_DECOMMIT ); |
|
section->Misc.PhysicalAddress = 0; |
|
} |
|
} |
|
|
|
static void FinalizeSections( MEMORYMODULE *module ) |
|
{ |
|
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); |
|
int i; |
|
|
|
// loop through all sections and change access flags |
|
for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) |
|
{ |
|
DWORD protect, oldProtect, size; |
|
int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; |
|
int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; |
|
int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; |
|
|
|
if( section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE ) |
|
{ |
|
// section is not needed any more and can safely be freed |
|
VirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT); |
|
continue; |
|
} |
|
|
|
// determine protection flags based on characteristics |
|
protect = ProtectionFlags[executable][readable][writeable]; |
|
if( section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED ) |
|
protect |= PAGE_NOCACHE; |
|
|
|
// determine size of region |
|
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; |
|
} |
|
|
|
if( size > 0 ) |
|
{ |
|
// change memory access flags |
|
if( !VirtualProtect((LPVOID)section->Misc.PhysicalAddress, size, protect, &oldProtect )) |
|
Sys_Error( "error protecting memory page\n" ); |
|
} |
|
} |
|
} |
|
|
|
static void PerformBaseRelocation( MEMORYMODULE *module, DWORD delta ) |
|
{ |
|
PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_BASERELOC ); |
|
byte *codeBase = module->codeBase; |
|
DWORD i; |
|
|
|
if( directory->Size > 0 ) |
|
{ |
|
PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); |
|
for( ; relocation->VirtualAddress > 0; ) |
|
{ |
|
byte *dest = (byte *)CALCULATE_ADDRESS( codeBase, relocation->VirtualAddress ); |
|
word *relInfo = (word *)((byte *)relocation + IMAGE_SIZEOF_BASE_RELOCATION ); |
|
|
|
for( i = 0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++ ) |
|
{ |
|
DWORD *patchAddrHL; |
|
int type, offset; |
|
|
|
// the upper 4 bits define the type of relocation |
|
type = *relInfo >> 12; |
|
// the lower 12 bits define the offset |
|
offset = *relInfo & 0xfff; |
|
|
|
switch( type ) |
|
{ |
|
case IMAGE_REL_BASED_ABSOLUTE: |
|
// skip relocation |
|
break; |
|
case IMAGE_REL_BASED_HIGHLOW: |
|
// change complete 32 bit address |
|
patchAddrHL = (DWORD *)CALCULATE_ADDRESS( dest, offset ); |
|
*patchAddrHL += delta; |
|
break; |
|
default: |
|
Con_Reportf( S_ERROR "PerformBaseRelocation: unknown relocation: %d\n", type ); |
|
break; |
|
} |
|
} |
|
|
|
// advance to next relocation block |
|
relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( relocation, relocation->SizeOfBlock ); |
|
} |
|
} |
|
} |
|
|
|
static FARPROC MemoryGetProcAddress( void *module, const char *name ) |
|
{ |
|
PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((MEMORYMODULE *)module, IMAGE_DIRECTORY_ENTRY_EXPORT ); |
|
byte *codeBase = ((PMEMORYMODULE)module)->codeBase; |
|
PIMAGE_EXPORT_DIRECTORY exports; |
|
int idx = -1; |
|
DWORD i, *nameRef; |
|
WORD *ordinal; |
|
|
|
if( directory->Size == 0 ) |
|
{ |
|
// no export table found |
|
return NULL; |
|
} |
|
|
|
exports = (PIMAGE_EXPORT_DIRECTORY)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); |
|
|
|
if( exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0 ) |
|
{ |
|
// DLL doesn't export anything |
|
return NULL; |
|
} |
|
|
|
// search function name in list of exported names |
|
nameRef = (DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNames ); |
|
ordinal = (WORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNameOrdinals ); |
|
|
|
for( i = 0; i < exports->NumberOfNames; i++, nameRef++, ordinal++ ) |
|
{ |
|
// GetProcAddress case insensative ????? |
|
if( !Q_stricmp( name, (const char *)CALCULATE_ADDRESS( codeBase, *nameRef ))) |
|
{ |
|
idx = *ordinal; |
|
break; |
|
} |
|
} |
|
|
|
if( idx == -1 ) |
|
{ |
|
// exported symbol not found |
|
return NULL; |
|
} |
|
|
|
if((DWORD)idx > exports->NumberOfFunctions ) |
|
{ |
|
// name <-> ordinal number don't match |
|
return NULL; |
|
} |
|
|
|
// addressOfFunctions contains the RVAs to the "real" functions |
|
return (FARPROC)CALCULATE_ADDRESS( codeBase, *(DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfFunctions + (idx * 4))); |
|
} |
|
|
|
static int BuildImportTable( MEMORYMODULE *module ) |
|
{ |
|
PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_IMPORT ); |
|
byte *codeBase = module->codeBase; |
|
int result = 1; |
|
|
|
if( directory->Size > 0 ) |
|
{ |
|
PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); |
|
|
|
for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR )) && importDesc->Name; importDesc++ ) |
|
{ |
|
DWORD *thunkRef, *funcRef; |
|
LPCSTR libname; |
|
void *handle; |
|
|
|
libname = (LPCSTR)CALCULATE_ADDRESS( codeBase, importDesc->Name ); |
|
handle = COM_LoadLibrary( libname, false, true ); |
|
|
|
if( handle == NULL ) |
|
{ |
|
Con_Printf( S_ERROR "couldn't load library %s\n", libname ); |
|
result = 0; |
|
break; |
|
} |
|
|
|
module->modules = (void *)Mem_Realloc( host.mempool, module->modules, (module->numModules + 1) * (sizeof( void* ))); |
|
module->modules[module->numModules++] = handle; |
|
|
|
if( importDesc->OriginalFirstThunk ) |
|
{ |
|
thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->OriginalFirstThunk ); |
|
funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); |
|
} |
|
else |
|
{ |
|
// no hint table |
|
thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); |
|
funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); |
|
} |
|
|
|
for( ; *thunkRef; thunkRef++, funcRef++ ) |
|
{ |
|
LPCSTR funcName; |
|
|
|
if( IMAGE_SNAP_BY_ORDINAL( *thunkRef )) |
|
{ |
|
funcName = (LPCSTR)IMAGE_ORDINAL( *thunkRef ); |
|
*funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); |
|
} |
|
else |
|
{ |
|
PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)CALCULATE_ADDRESS( codeBase, *thunkRef ); |
|
funcName = (LPCSTR)&thunkData->Name; |
|
*funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); |
|
} |
|
|
|
if( *funcRef == 0 ) |
|
{ |
|
Con_Printf( S_ERROR "%s unable to find address: %s\n", libname, funcName ); |
|
result = 0; |
|
break; |
|
} |
|
} |
|
if( !result ) break; |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
static void MemoryFreeLibrary( void *hInstance ) |
|
{ |
|
MEMORYMODULE *module = (MEMORYMODULE *)hInstance; |
|
|
|
if( module != NULL ) |
|
{ |
|
int i; |
|
|
|
if( module->initialized != 0 ) |
|
{ |
|
// notify library about detaching from process |
|
DllEntryProc DllEntry = (DllEntryProc)CALCULATE_ADDRESS( module->codeBase, module->headers->OptionalHeader.AddressOfEntryPoint ); |
|
(*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0 ); |
|
module->initialized = 0; |
|
} |
|
|
|
if( module->modules != NULL ) |
|
{ |
|
// free previously opened libraries |
|
for( i = 0; i < module->numModules; i++ ) |
|
{ |
|
if( module->modules[i] != NULL ) |
|
COM_FreeLibrary( module->modules[i] ); |
|
} |
|
Mem_Free( module->modules ); // Mem_Realloc end |
|
} |
|
|
|
FreeSections( module->headers, module ); |
|
|
|
if( module->codeBase != NULL ) |
|
{ |
|
// release memory of library |
|
VirtualFree( module->codeBase, 0, MEM_RELEASE ); |
|
} |
|
|
|
HeapFree( GetProcessHeap(), 0, module ); |
|
} |
|
} |
|
|
|
void *MemoryLoadLibrary( const char *name ) |
|
{ |
|
MEMORYMODULE *result = NULL; |
|
PIMAGE_DOS_HEADER dos_header; |
|
PIMAGE_NT_HEADERS old_header; |
|
byte *code, *headers; |
|
DWORD locationDelta; |
|
DllEntryProc DllEntry; |
|
string errorstring; |
|
qboolean successfull; |
|
void *data = NULL; |
|
|
|
data = FS_LoadFile( name, NULL, false ); |
|
|
|
if( !data ) |
|
{ |
|
Q_sprintf( errorstring, "couldn't load %s", name ); |
|
goto library_error; |
|
} |
|
|
|
dos_header = (PIMAGE_DOS_HEADER)data; |
|
if( dos_header->e_magic != IMAGE_DOS_SIGNATURE ) |
|
{ |
|
Q_sprintf( errorstring, "%s it's not a valid executable file", name ); |
|
goto library_error; |
|
} |
|
|
|
old_header = (PIMAGE_NT_HEADERS)&((const byte *)(data))[dos_header->e_lfanew]; |
|
if( old_header->Signature != IMAGE_NT_SIGNATURE ) |
|
{ |
|
Q_sprintf( errorstring, "%s missing PE header", name ); |
|
goto library_error; |
|
} |
|
|
|
// reserve memory for image of library |
|
code = (byte *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); |
|
|
|
if( code == NULL ) |
|
{ |
|
// try to allocate memory at arbitrary position |
|
code = (byte *)VirtualAlloc( NULL, old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); |
|
} |
|
|
|
if( code == NULL ) |
|
{ |
|
Q_sprintf( errorstring, "%s can't reserve memory", name ); |
|
goto library_error; |
|
} |
|
|
|
result = (MEMORYMODULE *)HeapAlloc( GetProcessHeap(), 0, sizeof( MEMORYMODULE )); |
|
result->codeBase = code; |
|
result->numModules = 0; |
|
result->modules = NULL; |
|
result->initialized = 0; |
|
|
|
// XXX: is it correct to commit the complete memory region at once? |
|
// calling DllEntry raises an exception if we don't... |
|
VirtualAlloc( code, old_header->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE ); |
|
|
|
// commit memory for headers |
|
headers = (byte *)VirtualAlloc( code, old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE ); |
|
|
|
// copy PE header to code |
|
memcpy( headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders ); |
|
result->headers = (PIMAGE_NT_HEADERS)&((const byte *)(headers))[dos_header->e_lfanew]; |
|
|
|
// update position |
|
result->headers->OptionalHeader.ImageBase = (DWORD)code; |
|
|
|
// copy sections from DLL file block to new memory location |
|
CopySections( data, old_header, result ); |
|
|
|
// adjust base address of imported data |
|
locationDelta = (DWORD)(code - old_header->OptionalHeader.ImageBase); |
|
if( locationDelta != 0 ) PerformBaseRelocation( result, locationDelta ); |
|
|
|
// load required dlls and adjust function table of imports |
|
if( !BuildImportTable( result )) |
|
{ |
|
Q_sprintf( errorstring, "%s failed to build import table", name ); |
|
goto library_error; |
|
} |
|
|
|
// mark memory pages depending on section headers and release |
|
// sections that are marked as "discardable" |
|
FinalizeSections( result ); |
|
|
|
// get entry point of loaded library |
|
if( result->headers->OptionalHeader.AddressOfEntryPoint != 0 ) |
|
{ |
|
DllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint ); |
|
if( DllEntry == 0 ) |
|
{ |
|
Q_sprintf( errorstring, "%s has no entry point", name ); |
|
goto library_error; |
|
} |
|
|
|
// notify library about attaching to process |
|
successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 ); |
|
if( !successfull ) |
|
{ |
|
Q_sprintf( errorstring, "can't attach library %s", name ); |
|
goto library_error; |
|
} |
|
result->initialized = 1; |
|
} |
|
|
|
Mem_Free( data ); // release memory |
|
return (void *)result; |
|
library_error: |
|
// cleanup |
|
if( data ) Mem_Free( data ); |
|
MemoryFreeLibrary( result ); |
|
Con_Printf( S_ERROR "LoadLibrary: %s\n", errorstring ); |
|
|
|
return NULL; |
|
} |
|
|
|
/* |
|
--------------------------------------------------------------- |
|
|
|
Name for function stuff |
|
|
|
--------------------------------------------------------------- |
|
*/ |
|
static void FsGetString( file_t *f, char *str ) |
|
{ |
|
char ch; |
|
|
|
while(( ch = FS_Getc( f )) != EOF ) |
|
{ |
|
*str++ = ch; |
|
if( !ch ) break; |
|
} |
|
} |
|
|
|
static void FreeNameFuncGlobals( dll_user_t *hInst ) |
|
{ |
|
int i; |
|
|
|
if( !hInst ) return; |
|
|
|
if( hInst->ordinals ) Mem_Free( hInst->ordinals ); |
|
if( hInst->funcs ) Mem_Free( hInst->funcs ); |
|
|
|
for( i = 0; i < hInst->num_ordinals; i++ ) |
|
{ |
|
if( hInst->names[i] ) |
|
Mem_Free( hInst->names[i] ); |
|
} |
|
|
|
hInst->num_ordinals = 0; |
|
hInst->ordinals = NULL; |
|
hInst->funcs = NULL; |
|
} |
|
|
|
char *GetMSVCName( const char *in_name ) |
|
{ |
|
static string out_name; |
|
char *pos; |
|
|
|
if( in_name[0] == '?' ) // is this a MSVC C++ mangled name? |
|
{ |
|
if(( pos = Q_strstr( in_name, "@@" )) != NULL ) |
|
{ |
|
int len = pos - in_name; |
|
|
|
// strip off the leading '?' |
|
Q_strncpy( out_name, in_name + 1, sizeof( out_name )); |
|
out_name[len-1] = 0; // terminate string at the "@@" |
|
return out_name; |
|
} |
|
} |
|
|
|
Q_strncpy( out_name, in_name, sizeof( out_name )); |
|
|
|
return out_name; |
|
} |
|
|
|
qboolean LibraryLoadSymbols( dll_user_t *hInst ) |
|
{ |
|
file_t *f; |
|
string errorstring; |
|
DOS_HEADER dos_header; |
|
LONG nt_signature; |
|
PE_HEADER pe_header; |
|
SECTION_HEADER section_header; |
|
qboolean rdata_found; |
|
OPTIONAL_HEADER optional_header; |
|
long rdata_delta = 0; |
|
EXPORT_DIRECTORY export_directory; |
|
long name_offset; |
|
long exports_offset; |
|
long ordinal_offset; |
|
long function_offset; |
|
string function_name; |
|
dword *p_Names = NULL; |
|
int i, index; |
|
|
|
// can only be done for loaded libraries |
|
if( !hInst ) return false; |
|
|
|
for( i = 0; i < hInst->num_ordinals; i++ ) |
|
hInst->names[i] = NULL; |
|
|
|
f = FS_Open( hInst->shortPath, "rb", false ); |
|
if( !f ) |
|
{ |
|
Q_sprintf( errorstring, "couldn't load %s", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
if( FS_Read( f, &dos_header, sizeof( dos_header )) != sizeof( dos_header )) |
|
{ |
|
Q_sprintf( errorstring, "%s has corrupted EXE header", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
if( dos_header.e_magic != DOS_SIGNATURE ) |
|
{ |
|
Q_sprintf( errorstring, "%s does not have a valid dll signature", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
if( FS_Seek( f, dos_header.e_lfanew, SEEK_SET ) == -1 ) |
|
{ |
|
Q_sprintf( errorstring, "%s error seeking for new exe header", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
if( FS_Read( f, &nt_signature, sizeof( nt_signature )) != sizeof( nt_signature )) |
|
{ |
|
Q_sprintf( errorstring, "%s has corrupted NT header", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
if( nt_signature != NT_SIGNATURE ) |
|
{ |
|
Q_sprintf( errorstring, "%s does not have a valid NT signature", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
if( FS_Read( f, &pe_header, sizeof( pe_header )) != sizeof( pe_header )) |
|
{ |
|
Q_sprintf( errorstring, "%s does not have a valid PE header", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
if( !pe_header.SizeOfOptionalHeader ) |
|
{ |
|
Q_sprintf( errorstring, "%s does not have an optional header", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
if( FS_Read( f, &optional_header, sizeof( optional_header )) != sizeof( optional_header )) |
|
{ |
|
Q_sprintf( errorstring, "%s optional header probably corrupted", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
rdata_found = false; |
|
|
|
for( i = 0; i < pe_header.NumberOfSections; i++ ) |
|
{ |
|
if( FS_Read( f, §ion_header, sizeof( section_header )) != sizeof( section_header )) |
|
{ |
|
Q_sprintf( errorstring, "%s error during reading section header", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
if((( optional_header.DataDirectory[0].VirtualAddress >= section_header.VirtualAddress ) && |
|
(optional_header.DataDirectory[0].VirtualAddress < (section_header.VirtualAddress + section_header.Misc.VirtualSize)))) |
|
{ |
|
rdata_found = true; |
|
break; |
|
} |
|
} |
|
|
|
if( rdata_found ) |
|
{ |
|
rdata_delta = section_header.VirtualAddress - section_header.PointerToRawData; |
|
} |
|
|
|
exports_offset = optional_header.DataDirectory[0].VirtualAddress - rdata_delta; |
|
|
|
if( FS_Seek( f, exports_offset, SEEK_SET ) == -1 ) |
|
{ |
|
Q_sprintf( errorstring, "%s does not have a valid exports section", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
if( FS_Read( f, &export_directory, sizeof( export_directory )) != sizeof( export_directory )) |
|
{ |
|
Q_sprintf( errorstring, "%s does not have a valid optional header", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
hInst->num_ordinals = export_directory.NumberOfNames; // also number of ordinals |
|
|
|
if( hInst->num_ordinals > MAX_LIBRARY_EXPORTS ) |
|
{ |
|
Q_sprintf( errorstring, "%s too many exports %i", hInst->shortPath, hInst->num_ordinals ); |
|
hInst->num_ordinals = 0; |
|
goto table_error; |
|
} |
|
|
|
ordinal_offset = export_directory.AddressOfNameOrdinals - rdata_delta; |
|
|
|
if( FS_Seek( f, ordinal_offset, SEEK_SET ) == -1 ) |
|
{ |
|
Q_sprintf( errorstring, "%s does not have a valid ordinals section", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
hInst->ordinals = Mem_Malloc( host.mempool, hInst->num_ordinals * sizeof( word )); |
|
|
|
if( FS_Read( f, hInst->ordinals, hInst->num_ordinals * sizeof( word )) != (hInst->num_ordinals * sizeof( word ))) |
|
{ |
|
Q_sprintf( errorstring, "%s error during reading ordinals table", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
function_offset = export_directory.AddressOfFunctions - rdata_delta; |
|
|
|
if( FS_Seek( f, function_offset, SEEK_SET ) == -1 ) |
|
{ |
|
Q_sprintf( errorstring, "%s does not have a valid export address section", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
hInst->funcs = Mem_Malloc( host.mempool, hInst->num_ordinals * sizeof( dword )); |
|
|
|
if( FS_Read( f, hInst->funcs, hInst->num_ordinals * sizeof( dword )) != (hInst->num_ordinals * sizeof( dword ))) |
|
{ |
|
Q_sprintf( errorstring, "%s error during reading export address section", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
name_offset = export_directory.AddressOfNames - rdata_delta; |
|
|
|
if( FS_Seek( f, name_offset, SEEK_SET ) == -1 ) |
|
{ |
|
Q_sprintf( errorstring, "%s file does not have a valid names section", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
p_Names = Mem_Malloc( host.mempool, hInst->num_ordinals * sizeof( dword )); |
|
|
|
if( FS_Read( f, p_Names, hInst->num_ordinals * sizeof( dword )) != (hInst->num_ordinals * sizeof( dword ))) |
|
{ |
|
Q_sprintf( errorstring, "%s error during reading names table", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
|
|
for( i = 0; i < hInst->num_ordinals; i++ ) |
|
{ |
|
name_offset = p_Names[i] - rdata_delta; |
|
|
|
if( name_offset != 0 ) |
|
{ |
|
if( FS_Seek( f, name_offset, SEEK_SET ) != -1 ) |
|
{ |
|
FsGetString( f, function_name ); |
|
hInst->names[i] = copystring( GetMSVCName( function_name )); |
|
} |
|
else break; |
|
} |
|
} |
|
|
|
if( i != hInst->num_ordinals ) |
|
{ |
|
Q_sprintf( errorstring, "%s error during loading names section", hInst->shortPath ); |
|
goto table_error; |
|
} |
|
FS_Close( f ); |
|
|
|
for( i = 0; i < hInst->num_ordinals; i++ ) |
|
{ |
|
if( !Q_strcmp( "GiveFnptrsToDll", hInst->names[i] )) // main entry point for user dlls |
|
{ |
|
void *fn_offset; |
|
|
|
index = hInst->ordinals[i]; |
|
fn_offset = (void *)COM_GetProcAddress( hInst, "GiveFnptrsToDll" ); |
|
hInst->funcBase = (dword)(fn_offset) - hInst->funcs[index]; |
|
break; |
|
} |
|
} |
|
|
|
if( p_Names ) Mem_Free( p_Names ); |
|
return true; |
|
table_error: |
|
// cleanup |
|
if( f ) FS_Close( f ); |
|
if( p_Names ) Mem_Free( p_Names ); |
|
FreeNameFuncGlobals( hInst ); |
|
Con_Printf( S_ERROR "LoadLibrary: %s\n", errorstring ); |
|
|
|
return false; |
|
} |
|
|
|
qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath ) |
|
{ |
|
PIMAGE_DOS_HEADER dos_header; |
|
PIMAGE_NT_HEADERS old_header; |
|
PIMAGE_DATA_DIRECTORY directory; |
|
PIMAGE_IMPORT_DESCRIPTOR importDesc; |
|
string errorstring; |
|
void *data = NULL; |
|
dll_user_t *hInst; |
|
|
|
hInst = FS_FindLibrary( name, directpath ); |
|
if( !hInst ) |
|
{ |
|
return false; // nothing to load |
|
} |
|
|
|
data = FS_LoadFile( name, NULL, false ); |
|
if( !data ) |
|
{ |
|
Q_snprintf( errorstring, sizeof( errorstring ), "couldn't load %s", name ); |
|
goto libraryerror; |
|
} |
|
|
|
dos_header = ( PIMAGE_DOS_HEADER )data; |
|
if( dos_header->e_magic != IMAGE_DOS_SIGNATURE ) |
|
{ |
|
Q_snprintf( errorstring, sizeof( errorstring ), "%s it's not a valid executable file", name ); |
|
goto libraryerror; |
|
} |
|
|
|
old_header = ( PIMAGE_NT_HEADERS )&( ( const byte * )( data ) )[dos_header->e_lfanew]; |
|
if( old_header->Signature != IMAGE_NT_SIGNATURE ) |
|
{ |
|
Q_snprintf( errorstring, sizeof( errorstring ), "%s missing PE header", name ); |
|
goto libraryerror; |
|
} |
|
|
|
directory = &old_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; |
|
|
|
if( directory->Size <= 0 ) |
|
{ |
|
Q_snprintf( errorstring, sizeof( errorstring ), "%s has no dependencies. Is this dll valid?\n" ); |
|
goto libraryerror; |
|
} |
|
|
|
importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( data, directory->VirtualAddress ); |
|
|
|
for( ; importDesc->Name; importDesc++ ) |
|
{ |
|
const char *importName = ( const char* )CALCULATE_ADDRESS( data, importDesc->Name ); |
|
Con_Reportf( "library %s has direct dependency %s\n", name, importName ); |
|
|
|
if( !Q_stricmp( importName, depname ) ) |
|
{ |
|
Mem_Free( data ); |
|
return true; |
|
} |
|
} |
|
|
|
libraryerror: |
|
Con_Printf( errorstring ); |
|
if( data ) Mem_Free( data ); // release memory |
|
return false; |
|
} |
|
|
|
/* |
|
================ |
|
COM_LoadLibrary |
|
|
|
smart dll loader - can loading dlls from pack or wad files |
|
================ |
|
*/ |
|
void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean directpath ) |
|
{ |
|
dll_user_t *hInst; |
|
|
|
hInst = FS_FindLibrary( dllname, directpath ); |
|
if( !hInst ) return NULL; // nothing to load |
|
|
|
if( hInst->custom_loader ) |
|
{ |
|
if( hInst->encrypted ) |
|
{ |
|
Con_Printf( S_ERROR "LoadLibrary: couldn't load encrypted library %s\n", dllname ); |
|
return NULL; |
|
} |
|
|
|
hInst->hInstance = MemoryLoadLibrary( hInst->fullPath ); |
|
} |
|
else hInst->hInstance = LoadLibrary( hInst->fullPath ); |
|
|
|
if( !hInst->hInstance ) |
|
{ |
|
Con_Reportf( "LoadLibrary: Loading %s - failed\n", dllname ); |
|
COM_FreeLibrary( hInst ); |
|
return NULL; |
|
} |
|
|
|
// if not set - FunctionFromName and NameForFunction will not working |
|
if( build_ordinals_table ) |
|
{ |
|
if( !LibraryLoadSymbols( hInst )) |
|
{ |
|
Con_Reportf( "LoadLibrary: Loading %s - failed\n", dllname ); |
|
COM_FreeLibrary( hInst ); |
|
return NULL; |
|
} |
|
} |
|
|
|
Con_Reportf( "LoadLibrary: Loading %s - ok\n", dllname ); |
|
|
|
return hInst; |
|
} |
|
|
|
void *COM_GetProcAddress( void *hInstance, const char *name ) |
|
{ |
|
dll_user_t *hInst = (dll_user_t *)hInstance; |
|
|
|
if( !hInst || !hInst->hInstance ) |
|
return NULL; |
|
|
|
if( hInst->custom_loader ) |
|
return (void *)MemoryGetProcAddress( hInst->hInstance, name ); |
|
return (void *)GetProcAddress( hInst->hInstance, name ); |
|
} |
|
|
|
void COM_FreeLibrary( void *hInstance ) |
|
{ |
|
dll_user_t *hInst = (dll_user_t *)hInstance; |
|
|
|
if( !hInst || !hInst->hInstance ) |
|
return; // already freed |
|
|
|
if( host.status == HOST_CRASHED ) |
|
{ |
|
// we need to hold down all modules, while MSVC can find error |
|
Con_Reportf( "Sys_FreeLibrary: hold %s for debugging\n", hInst->dllName ); |
|
return; |
|
} |
|
else Con_Reportf( "Sys_FreeLibrary: Unloading %s\n", hInst->dllName ); |
|
|
|
if( hInst->custom_loader ) |
|
MemoryFreeLibrary( hInst->hInstance ); |
|
else FreeLibrary( hInst->hInstance ); |
|
|
|
hInst->hInstance = NULL; |
|
|
|
if( hInst->num_ordinals ) |
|
FreeNameFuncGlobals( hInst ); |
|
Mem_Free( hInst ); // done |
|
} |
|
|
|
void *COM_FunctionFromName( void *hInstance, const char *pName ) |
|
{ |
|
dll_user_t *hInst = (dll_user_t *)hInstance; |
|
int i, index; |
|
|
|
if( !hInst || !hInst->hInstance ) |
|
return 0; |
|
|
|
for( i = 0; i < hInst->num_ordinals; i++ ) |
|
{ |
|
if( !Q_strcmp( pName, hInst->names[i] )) |
|
{ |
|
index = hInst->ordinals[i]; |
|
return hInst->funcs[index] + hInst->funcBase; |
|
} |
|
} |
|
|
|
// couldn't find the function name to return address |
|
Con_Printf( "Can't find proc: %s\n", pName ); |
|
|
|
return 0; |
|
} |
|
|
|
const char *COM_NameForFunction( void *hInstance, void *function ) |
|
{ |
|
dll_user_t *hInst = (dll_user_t *)hInstance; |
|
int i, index; |
|
|
|
if( !hInst || !hInst->hInstance ) |
|
return NULL; |
|
|
|
for( i = 0; i < hInst->num_ordinals; i++ ) |
|
{ |
|
index = hInst->ordinals[i]; |
|
|
|
if(( (char*)function - (char*)hInst->funcBase ) == hInst->funcs[index] ) |
|
return hInst->names[i]; |
|
} |
|
|
|
// couldn't find the function address to return name |
|
Con_Printf( "Can't find address: %08lx\n", function ); |
|
|
|
return NULL; |
|
} |
|
#endif // XASH_64BIT |
|
#endif // _WIN32
|
|
|