-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PE loader/image cleanups. No-copy mapping of R2R PEs on Windows. #61938
Changes from 11 commits
515a1b3
0178294
49c759f
519fe5c
00ae715
230e9ef
3e2c617
23945fb
be5f1fe
2bf609d
1e0a933
5cc040e
30be65f
473f734
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -683,17 +683,25 @@ public static PEHeaderBuilder Create(Subsystem subsystem, TargetDetails target) | |
ulong imageBase = is64BitTarget ? PE64HeaderConstants.DllImageBase : PE32HeaderConstants.ImageBase; | ||
|
||
int fileAlignment = 0x200; | ||
if (!target.IsWindows && !is64BitTarget) | ||
if (target.IsWindows || !is64BitTarget) | ||
{ | ||
// To minimize wasted VA space on 32-bit systems, align file to page boundaries (presumed to be 4K) | ||
// | ||
// On Windows we use 4K file alignment, per requirements of memory mapping API (MapViewOfFile3, et al). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit - according to the above condition this section is used both on Windows and on 32-bit Unix (most notably Linux on arm I guess); please consider making the comment more precise. |
||
// We also want files always accepted by the native loader, thus we can't use the same approach as on Unix. | ||
fileAlignment = 0x1000; | ||
} | ||
|
||
int sectionAlignment = 0x1000; | ||
if (!target.IsWindows && is64BitTarget) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The condition |
||
{ | ||
// On Linux, we must match the bottom 12 bits of section RVA's to their file offsets. For this reason | ||
// On 64bit Linux, we must match the bottom 12 bits of section RVA's to their file offsets. For this reason | ||
// we need the same alignment for both. | ||
// | ||
// In addition to that we specify section RVAs to be at least 64K apart, which is > page on most systems. | ||
// It ensures that the sections will not overlap when mapped from a singlefile bundle, which introduces a sub-page skew. | ||
// | ||
// Such format would not be accepted by OS loader on Windows, but it is not a problem on Unix. | ||
sectionAlignment = fileAlignment; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -372,7 +372,10 @@ CHECK PEDecoder::CheckSection(COUNT_T previousAddressEnd, COUNT_T addressStart, | |
CHECK(alignedSize >= VAL32(pNT->OptionalHeader.SizeOfImage)); | ||
|
||
// Check expected alignments | ||
#if TARGET_WINDOWS | ||
// R2R files on Unix do not align section RVAs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo - should this be 'R2R files on Windows'? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In fact, in a way the comment does hold on Unix but it's strange that it's in a TARGET_WINDOWS conditional compilation block. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On Windows we make section RVAs always a multiple of That is not the case on Unix where we keep lower 16bit of the RVA intact. I do not know why this asserts is not triggered on Unix. It should. When I tried using the same approach on Windows as on Unix, this assert was triggered. Perhaps this style of assert does not run on Unix? (that would be mildly disturbing). Anyways - I think the assert should be only enabled on Windows, where the condition is expected. At least as a matter of documenting the expectations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps I should use a better terminology to make it more clear. I.E. on Windows section start RVAs are multiples of SectionAlignment. On Unix this is not true. Any suggestions? (same applies to having gaps between sections when loaded - Windows PEs are not expected to have gaps, Unix PEs may have gaps) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I guess that probably the only cause of my confusion was the wording - having a comment about Unix behavior under a conditional compilation section |
||
CHECK(CheckAligned(addressStart, VAL32(pNT->OptionalHeader.SectionAlignment))); | ||
#endif | ||
CHECK(CheckAligned(offsetStart, VAL32(pNT->OptionalHeader.FileAlignment))); | ||
CHECK(CheckAligned(offsetSize, VAL32(pNT->OptionalHeader.FileAlignment))); | ||
|
||
|
@@ -1659,86 +1662,6 @@ CHECK PEDecoder::CheckILOnlyEntryPoint() const | |
} | ||
#endif // TARGET_X86 | ||
|
||
#ifndef DACCESS_COMPILE | ||
|
||
void PEDecoder::LayoutILOnly(void *base, bool enableExecution) const | ||
{ | ||
CONTRACT_VOID | ||
{ | ||
INSTANCE_CHECK; | ||
PRECONDITION(CheckZeroedMemory(base, VAL32(FindNTHeaders()->OptionalHeader.SizeOfImage))); | ||
// Ideally we would require the layout address to honor the section alignment constraints. | ||
// However, we do have 8K aligned IL only images which we load on 32 bit platforms. In this | ||
// case, we can only guarantee OS page alignment (which after all, is good enough.) | ||
PRECONDITION(CheckAligned((SIZE_T)base, GetOsPageSize())); | ||
THROWS; | ||
GC_NOTRIGGER; | ||
} | ||
CONTRACT_END; | ||
|
||
// We're going to copy everything first, and write protect what we need to later. | ||
|
||
// First, copy headers | ||
CopyMemory(base, (void *)m_base, VAL32(FindNTHeaders()->OptionalHeader.SizeOfHeaders)); | ||
|
||
// Now, copy all sections to appropriate virtual address | ||
|
||
IMAGE_SECTION_HEADER *sectionStart = IMAGE_FIRST_SECTION(FindNTHeaders()); | ||
IMAGE_SECTION_HEADER *sectionEnd = sectionStart + VAL16(FindNTHeaders()->FileHeader.NumberOfSections); | ||
|
||
IMAGE_SECTION_HEADER *section = sectionStart; | ||
while (section < sectionEnd) | ||
{ | ||
// Raw data may be less than section size if tail is zero, but may be more since VirtualSize is | ||
// not padded. | ||
DWORD size = min(VAL32(section->SizeOfRawData), VAL32(section->Misc.VirtualSize)); | ||
|
||
CopyMemory((BYTE *) base + VAL32(section->VirtualAddress), (BYTE *) m_base + VAL32(section->PointerToRawData), size); | ||
|
||
// Note that our memory is zeroed already, so no need to initialize any tail. | ||
|
||
section++; | ||
} | ||
|
||
// Apply write protection to copied headers | ||
DWORD oldProtection; | ||
if (!ClrVirtualProtect((void *) base, VAL32(FindNTHeaders()->OptionalHeader.SizeOfHeaders), | ||
PAGE_READONLY, &oldProtection)) | ||
ThrowLastError(); | ||
|
||
// Finally, apply proper protection to copied sections | ||
for (section = sectionStart; section < sectionEnd; section++) | ||
{ | ||
// Add appropriate page protection. | ||
DWORD newProtection; | ||
if (!enableExecution) | ||
{ | ||
if (section->Characteristics & IMAGE_SCN_MEM_WRITE) | ||
continue; | ||
|
||
newProtection = PAGE_READONLY; | ||
} | ||
else | ||
{ | ||
newProtection = section->Characteristics & IMAGE_SCN_MEM_EXECUTE ? | ||
PAGE_EXECUTE_READ : | ||
section->Characteristics & IMAGE_SCN_MEM_WRITE ? | ||
PAGE_READWRITE : | ||
PAGE_READONLY; | ||
} | ||
|
||
if (!ClrVirtualProtect((void*)((BYTE*)base + VAL32(section->VirtualAddress)), | ||
VAL32(section->Misc.VirtualSize), | ||
newProtection, &oldProtection)) | ||
{ | ||
ThrowLastError(); | ||
} | ||
} | ||
|
||
RETURN; | ||
} | ||
|
||
#endif // #ifndef DACCESS_COMPILE | ||
|
||
bool ReadResourceDirectoryHeader(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, DWORD rva, IMAGE_RESOURCE_DIRECTORY_ENTRY** ppDirectoryEntries, IMAGE_RESOURCE_DIRECTORY **ppResourceDirectory) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -189,11 +189,17 @@ void Assembly::Init(AllocMemTracker *pamTracker, LoaderAllocator *pLoaderAllocat | |
m_pClassLoader = new ClassLoader(this); | ||
m_pClassLoader->Init(pamTracker); | ||
|
||
if (GetManifestFile()->IsDynamic()) | ||
PEAssembly* pPEAssembly = GetManifestFile(); | ||
|
||
// "Module::Create" will initialize R2R support, if there is an R2R header. | ||
// make sure the PE is loaded or R2R will be disabled. | ||
pPEAssembly->EnsureLoaded(); | ||
|
||
if (pPEAssembly->IsDynamic()) | ||
// manifest modules of dynamic assemblies are always transient | ||
m_pManifest = ReflectionModule::Create(this, GetManifestFile(), pamTracker, REFEMIT_MANIFEST_MODULE_NAME); | ||
m_pManifest = ReflectionModule::Create(this, pPEAssembly, pamTracker, REFEMIT_MANIFEST_MODULE_NAME); | ||
else | ||
m_pManifest = Module::Create(this, mdFileNil, GetManifestFile(), pamTracker); | ||
m_pManifest = Module::Create(this, mdFileNil, pPEAssembly, pamTracker); | ||
|
||
FastInterlockIncrement((LONG*)&g_cAssemblies); | ||
|
||
|
@@ -208,15 +214,16 @@ void Assembly::Init(AllocMemTracker *pamTracker, LoaderAllocator *pLoaderAllocat | |
// loading it entirely. | ||
//CacheFriendAssemblyInfo(); | ||
|
||
if (IsCollectible()) | ||
if (IsCollectible() && !pPEAssembly->IsDynamic()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are dynamic assemblies excluded here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dynamic assemblies do not have PE layouts. The |
||
{ | ||
COUNT_T size; | ||
BYTE *start = (BYTE*)m_pManifest->GetPEAssembly()->GetLoadedImageContents(&size); | ||
if (start != NULL) | ||
{ | ||
GCX_COOP(); | ||
LoaderAllocator::AssociateMemoryWithLoaderAllocator(start, start + size, m_pLoaderAllocator); | ||
} | ||
BYTE* start = (BYTE*)pPEAssembly->GetLoadedImageContents(&size); | ||
|
||
// We should have the content loaded at this time. There will be no other attempt to associate memory. | ||
_ASSERTE(start != NULL); | ||
|
||
GCX_COOP(); | ||
LoaderAllocator::AssociateMemoryWithLoaderAllocator(start, start + size, m_pLoaderAllocator); | ||
} | ||
|
||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just curious - why was this change necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without this change if
prot
is executable, theprot | PROT_WRITE
becomes WX.mprotect
will accept that, butpread
will fail and we would bail from this method. This would always happen in an R2R PE, since.text
is executable.We then would go on the path of doing layout via copying, which would work, but often end up not in the desired location and later fail when applying relocations, where we had similar WX issue. Failed relocations would result in R2R disabled. The code would still run - in pure IL/JIT mode.
Ultimately we would have R2R enabled on OSX only when we are very lucky.