Support section alignments that are smaller than the memory page size (fixes #20/#21).

This commit is contained in:
Joachim Bauch
2015-02-28 23:01:44 +01:00
parent 927bd2a9f0
commit aa6b93bab4
+113 -33
View File
@@ -60,9 +60,19 @@ typedef struct {
CustomFreeLibraryFunc freeLibrary; CustomFreeLibraryFunc freeLibrary;
void *userdata; void *userdata;
ExeEntryProc exeEntry; ExeEntryProc exeEntry;
DWORD pageSize;
} MEMORYMODULE, *PMEMORYMODULE; } MEMORYMODULE, *PMEMORYMODULE;
typedef struct {
LPVOID address;
LPVOID alignedAddress;
DWORD size;
DWORD characteristics;
BOOL last;
} SECTIONFINALIZEDATA, *PSECTIONFINALIZEDATA;
#define GET_HEADER_DICTIONARY(module, idx) &(module)->headers->OptionalHeader.DataDirectory[idx] #define GET_HEADER_DICTIONARY(module, idx) &(module)->headers->OptionalHeader.DataDirectory[idx]
#define ALIGN_DOWN(address, alignment) (LPVOID)((uintptr_t)(address) & ~((alignment) - 1))
#ifdef DEBUG_OUTPUT #ifdef DEBUG_OUTPUT
static void static void
@@ -98,6 +108,9 @@ CopySections(const unsigned char *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMO
MEM_COMMIT, MEM_COMMIT,
PAGE_READWRITE); PAGE_READWRITE);
// Always use position from file to support alignments smaller
// than page size.
dest = codeBase + section->VirtualAddress;
section->Misc.PhysicalAddress = (DWORD) (uintptr_t) dest; section->Misc.PhysicalAddress = (DWORD) (uintptr_t) dest;
memset(dest, 0, size); memset(dest, 0, size);
} }
@@ -111,6 +124,10 @@ CopySections(const unsigned char *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMO
section->SizeOfRawData, section->SizeOfRawData,
MEM_COMMIT, MEM_COMMIT,
PAGE_READWRITE); PAGE_READWRITE);
// Always use position from file to support alignments smaller
// than page size.
dest = codeBase + section->VirtualAddress;
memcpy(dest, data + section->PointerToRawData, section->SizeOfRawData); memcpy(dest, data + section->PointerToRawData, section->SizeOfRawData);
section->Misc.PhysicalAddress = (DWORD) (uintptr_t) dest; section->Misc.PhysicalAddress = (DWORD) (uintptr_t) dest;
} }
@@ -129,6 +146,63 @@ static int ProtectionFlags[2][2][2] = {
}, },
}; };
static DWORD
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;
}
static BOOL
FinalizeSection(PMEMORYMODULE module, PSECTIONFINALIZEDATA sectionData) {
DWORD protect, oldProtect;
int executable;
int readable;
int 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
VirtualFree(sectionData->address, sectionData->size, MEM_DECOMMIT);
}
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) {
#ifdef DEBUG_OUTPUT
OutputLastError("Error protecting memory page")
#endif
return FALSE;
}
return TRUE;
}
static void static void
FinalizeSections(PMEMORYMODULE module) FinalizeSections(PMEMORYMODULE module)
{ {
@@ -139,45 +213,41 @@ FinalizeSections(PMEMORYMODULE module)
#else #else
#define imageOffset 0 #define imageOffset 0
#endif #endif
SECTIONFINALIZEDATA sectionData;
sectionData.address = (LPVOID)((uintptr_t)section->Misc.PhysicalAddress | imageOffset);
sectionData.alignedAddress = ALIGN_DOWN(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 // loop through all sections and change access flags
for (i=0; i<module->headers->FileHeader.NumberOfSections; i++, section++) { for (i=1; i<module->headers->FileHeader.NumberOfSections; i++, section++) {
DWORD protect, oldProtect, size; LPVOID sectionAddress = (LPVOID)((uintptr_t)section->Misc.PhysicalAddress | imageOffset);
int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; LPVOID alignedAddress = ALIGN_DOWN(sectionAddress, module->pageSize);
int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; DWORD sectionSize = GetRealSectionSize(module, section);
int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; // Combine access flags of all sections that share a page
// TODO(fancycode): We currently share flags of a trailing large section
if (section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) { // with the page of a first small section. This should be optimized.
// section is not needed any more and can safely be freed if (sectionData.alignedAddress == alignedAddress || (uintptr_t) sectionData.address + sectionData.size > (uintptr_t) alignedAddress) {
VirtualFree((LPVOID)((uintptr_t)section->Misc.PhysicalAddress | imageOffset), section->SizeOfRawData, MEM_DECOMMIT); // 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) + sectionSize) - (uintptr_t) sectionData.address;
continue; continue;
} }
// determine protection flags based on characteristics FinalizeSection(module, &sectionData);
protect = ProtectionFlags[executable][readable][writeable]; sectionData.address = sectionAddress;
if (section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED) { sectionData.alignedAddress = alignedAddress;
protect |= PAGE_NOCACHE; sectionData.size = sectionSize;
} sectionData.characteristics = section->Characteristics;
// 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)((uintptr_t)section->Misc.PhysicalAddress | imageOffset), size, protect, &oldProtect) == 0)
#ifdef DEBUG_OUTPUT
OutputLastError("Error protecting memory page")
#endif
;
}
} }
sectionData.last = TRUE;
FinalizeSection(module, &sectionData);
#ifndef _WIN64 #ifndef _WIN64
#undef imageOffset #undef imageOffset
#endif #endif
@@ -358,6 +428,7 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data,
unsigned char *code, *headers; unsigned char *code, *headers;
SIZE_T locationDelta; SIZE_T locationDelta;
BOOL successfull; BOOL successfull;
SYSTEM_INFO sysInfo;
dos_header = (PIMAGE_DOS_HEADER)data; dos_header = (PIMAGE_DOS_HEADER)data;
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) { if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
@@ -380,6 +451,12 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data,
return NULL; 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;
}
// reserve memory for image of library // reserve memory for image of library
// XXX: is it correct to commit the complete memory region at once? // XXX: is it correct to commit the complete memory region at once?
// calling DllEntry raises an exception if we don't... // calling DllEntry raises an exception if we don't...
@@ -417,6 +494,9 @@ HMEMORYMODULE MemoryLoadLibraryEx(const void *data,
result->freeLibrary = freeLibrary; result->freeLibrary = freeLibrary;
result->userdata = userdata; result->userdata = userdata;
GetNativeSystemInfo(&sysInfo);
result->pageSize = sysInfo.dwPageSize;
// commit memory for headers // commit memory for headers
headers = (unsigned char *)VirtualAlloc(code, headers = (unsigned char *)VirtualAlloc(code,
old_header->OptionalHeader.SizeOfHeaders, old_header->OptionalHeader.SizeOfHeaders,