From 4d820df4437139275b7c05330dd98631db708802 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 1 May 2019 15:29:34 -0700 Subject: [PATCH] Copy all win32 resources (#24308) - Add crossgen test to verify file version is preserved - Add support for general win32 resource copying to ReadyToRun - Copy all resources --- src/inc/pedecoder.h | 10 +- src/pal/inc/rt/palrt.h | 3 +- src/utilcode/pedecoder.cpp | 326 +++++++++++++++++++-- src/zap/zapheaders.cpp | 261 +++++++++++++++-- src/zap/zapheaders.h | 114 ++++++- src/zap/zapimage.cpp | 2 +- src/zap/zapimage.h | 2 +- src/zap/zapnodetype.h | 2 +- .../Properties/AssemblyInfo.cs | 5 + .../fileversionpreservation.cs | 39 +++ .../fileversionpreservation.csproj | 46 +++ 11 files changed, 729 insertions(+), 81 deletions(-) create mode 100644 tests/src/readytorun/tests/fileversionpreservation/Properties/AssemblyInfo.cs create mode 100644 tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.cs create mode 100644 tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.csproj diff --git a/src/inc/pedecoder.h b/src/inc/pedecoder.h index 37051d9..5dff335 100644 --- a/src/inc/pedecoder.h +++ b/src/inc/pedecoder.h @@ -106,6 +106,10 @@ inline CHECK CheckOverflow(RVA value1, COUNT_T value2) typedef DPTR(class PEDecoder) PTR_PEDecoder; +typedef bool (*PEDecoder_ResourceTypesCallbackFunction)(LPCWSTR lpType, void* context); +typedef bool (*PEDecoder_ResourceNamesCallbackFunction)(LPCWSTR lpName, LPCWSTR lpType, void* context); +typedef bool (*PEDecoder_ResourceCallbackFunction)(LPCWSTR lpName, LPCWSTR lpType, DWORD langid, BYTE* data, COUNT_T cbData, void* context); + class PEDecoder { public: @@ -253,9 +257,9 @@ class PEDecoder // Win32 resources void *GetWin32Resource(LPCWSTR lpName, LPCWSTR lpType, COUNT_T *pSize = NULL) const; - private: - DWORD ReadResourceDictionary(DWORD rvaOfResourceSection, DWORD rva, LPCWSTR name, BOOL *pIsDictionary) const; - DWORD ReadResourceDataEntry(DWORD rva, COUNT_T *pSize) const; + bool EnumerateWin32ResourceTypes(PEDecoder_ResourceTypesCallbackFunction callback, void* context) const; + bool EnumerateWin32ResourceNames(LPCWSTR lpType, PEDecoder_ResourceNamesCallbackFunction callback, void* context) const; + bool EnumerateWin32Resources(LPCWSTR lpName, LPCWSTR lpType, PEDecoder_ResourceCallbackFunction callback, void* context) const; public: // COR header fields diff --git a/src/pal/inc/rt/palrt.h b/src/pal/inc/rt/palrt.h index 89f28e7..6502cf5 100644 --- a/src/pal/inc/rt/palrt.h +++ b/src/pal/inc/rt/palrt.h @@ -1133,8 +1133,9 @@ typedef JIT_DEBUG_INFO JIT_DEBUG_INFO32, *LPJIT_DEBUG_INFO32; typedef JIT_DEBUG_INFO JIT_DEBUG_INFO64, *LPJIT_DEBUG_INFO64; /******************* resources ***************************************/ - +#define IS_INTRESOURCE(_r) ((((ULONG_PTR)(_r)) >> 16) == 0) #define MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i)))) +#define MAKEINTRESOURCE(i) ((LPWSTR)((ULONG_PTR)((WORD)(i)))) #define RT_RCDATA MAKEINTRESOURCE(10) #define RT_VERSION MAKEINTRESOURCE(16) diff --git a/src/utilcode/pedecoder.cpp b/src/utilcode/pedecoder.cpp index 24cde95..411b462 100644 --- a/src/utilcode/pedecoder.cpp +++ b/src/utilcode/pedecoder.cpp @@ -1790,26 +1790,73 @@ void PEDecoder::LayoutILOnly(void *base, BOOL allowFullPE) const #endif // #ifndef DACCESS_COMPILE -DWORD PEDecoder::ReadResourceDictionary(DWORD rvaOfResourceSection, DWORD rva, LPCWSTR name, BOOL *pIsDictionary) const +bool ReadResourceDirectoryHeader(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, DWORD rva, IMAGE_RESOURCE_DIRECTORY_ENTRY** ppDirectoryEntries, IMAGE_RESOURCE_DIRECTORY **ppResourceDirectory) { - *pIsDictionary = FALSE; + if (!pDecoder->CheckRva(rva, sizeof(IMAGE_RESOURCE_DIRECTORY))) + { + return false; + } - if (!CheckRva(rva, sizeof(IMAGE_RESOURCE_DIRECTORY))) + *ppResourceDirectory = (IMAGE_RESOURCE_DIRECTORY *)pDecoder->GetRvaData(rva); + + // Check to see if entire resource directory is accessible + if (!pDecoder->CheckRva(rva + sizeof(IMAGE_RESOURCE_DIRECTORY), + (sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY) * (*ppResourceDirectory)->NumberOfNamedEntries) + + (sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY) * (*ppResourceDirectory)->NumberOfIdEntries))) { - return 0; + return false; } - IMAGE_RESOURCE_DIRECTORY *pResourceDirectory = (IMAGE_RESOURCE_DIRECTORY *)GetRvaData(rva); + *ppDirectoryEntries = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)pDecoder->GetRvaData(rva + sizeof(IMAGE_RESOURCE_DIRECTORY)); + return true; +} + +bool ReadNameFromResourceDirectoryEntry(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, IMAGE_RESOURCE_DIRECTORY_ENTRY* pDirectoryEntries, DWORD iEntry, DWORD *pNameUInt, WCHAR **pNameStr) +{ + *pNameStr = NULL; + *pNameUInt = 0; - // Check to see if entire resource dictionary is accessible - if (!CheckRva(rva + sizeof(IMAGE_RESOURCE_DIRECTORY), - (sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY) * pResourceDirectory->NumberOfNamedEntries) + - (sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY) * pResourceDirectory->NumberOfIdEntries))) + if (!IS_INTRESOURCE(pDirectoryEntries[iEntry].Name)) { - return 0; + DWORD entryName = pDirectoryEntries[iEntry].Name; + if (!(entryName & IMAGE_RESOURCE_NAME_IS_STRING)) + return false; + DWORD entryNameRva = (entryName & ~IMAGE_RESOURCE_NAME_IS_STRING) + rvaOfResourceSection; + + if (!pDecoder->CheckRva(entryNameRva, sizeof(WORD))) + return false; + + size_t entryNameLen = *(WORD*)pDecoder->GetRvaData(entryNameRva); + if (!pDecoder->CheckRva(entryNameRva, (COUNT_T)(sizeof(WORD) * (1 + entryNameLen)))) + return false; + *pNameStr = new(nothrow) WCHAR[entryNameLen + 1]; + if ((*pNameStr) == NULL) + return false; + memcpy((*pNameStr), (WCHAR*)pDecoder->GetRvaData(entryNameRva + sizeof(WORD)), entryNameLen * sizeof(WCHAR)); + (*pNameStr)[entryNameLen] = 0; + } + else + { + DWORD name = pDirectoryEntries[iEntry].Name; + if (!IS_INTRESOURCE(name)) + return false; + + *pNameUInt = name; } - IMAGE_RESOURCE_DIRECTORY_ENTRY* pDirectoryEntries = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)GetRvaData(rva + sizeof(IMAGE_RESOURCE_DIRECTORY)); + return true; +} + +DWORD ReadResourceDirectory(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, DWORD rva, LPCWSTR name, BOOL *pisDirectory) +{ + *pisDirectory = FALSE; + + IMAGE_RESOURCE_DIRECTORY* pResourceDirectory; + IMAGE_RESOURCE_DIRECTORY_ENTRY* pDirectoryEntries; + if (!ReadResourceDirectoryHeader(pDecoder, rvaOfResourceSection, rva, &pDirectoryEntries, &pResourceDirectory)) + { + return 0; + } // A fast implementation of resource lookup uses a binary search, but our needs are simple, and a linear search // is easier to prove correct, so do that instead. @@ -1819,7 +1866,7 @@ DWORD PEDecoder::ReadResourceDictionary(DWORD rvaOfResourceSection, DWORD rva, L { BOOL foundEntry = FALSE; - if (((UINT_PTR)name) <= 0xFFFF) + if (IS_INTRESOURCE(name)) { // name is id if (pDirectoryEntries[iEntry].Name == (DWORD)(SIZE_T)name) @@ -1834,24 +1881,24 @@ DWORD PEDecoder::ReadResourceDictionary(DWORD rvaOfResourceSection, DWORD rva, L DWORD entryNameRva = (entryName & ~IMAGE_RESOURCE_NAME_IS_STRING) + rvaOfResourceSection; - if (!CheckRva(entryNameRva, sizeof(WORD))) + if (!pDecoder->CheckRva(entryNameRva, sizeof(WORD))) return 0; - size_t entryNameLen = *(WORD*)GetRvaData(entryNameRva); + size_t entryNameLen = *(WORD*)pDecoder->GetRvaData(entryNameRva); if (wcslen(name) != entryNameLen) continue; - if (!CheckRva(entryNameRva, (COUNT_T)(sizeof(WORD) * (1 + entryNameLen)))) + if (!pDecoder->CheckRva(entryNameRva, (COUNT_T)(sizeof(WORD) * (1 + entryNameLen)))) return 0; - if (memcmp((WCHAR*)GetRvaData(entryNameRva + sizeof(WORD)), name, entryNameLen * sizeof(WCHAR)) == 0) + if (memcmp((WCHAR*)pDecoder->GetRvaData(entryNameRva + sizeof(WORD)), name, entryNameLen * sizeof(WCHAR)) == 0) foundEntry = TRUE; } if (!foundEntry) continue; - *pIsDictionary = !!(pDirectoryEntries[iEntry].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY); + *pisDirectory = !!(pDirectoryEntries[iEntry].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY); DWORD offsetToData = pDirectoryEntries[iEntry].OffsetToData & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY; DWORD dataRva = rvaOfResourceSection + offsetToData; return dataRva; @@ -1860,16 +1907,16 @@ DWORD PEDecoder::ReadResourceDictionary(DWORD rvaOfResourceSection, DWORD rva, L return 0; } -DWORD PEDecoder::ReadResourceDataEntry(DWORD rva, COUNT_T *pSize) const +DWORD ReadResourceDataEntry(const PEDecoder *pDecoder, DWORD rva, COUNT_T *pSize) { *pSize = 0; - if (!CheckRva(rva, sizeof(IMAGE_RESOURCE_DATA_ENTRY))) + if (!pDecoder->CheckRva(rva, sizeof(IMAGE_RESOURCE_DATA_ENTRY))) { return 0; } - IMAGE_RESOURCE_DATA_ENTRY *pDataEntry = (IMAGE_RESOURCE_DATA_ENTRY *)GetRvaData(rva); + IMAGE_RESOURCE_DATA_ENTRY *pDataEntry = (IMAGE_RESOURCE_DATA_ENTRY *)pDecoder->GetRvaData(rva); *pSize = pDataEntry->Size; return pDataEntry->OffsetToData; } @@ -1898,17 +1945,17 @@ void * PEDecoder::GetWin32Resource(LPCWSTR lpName, LPCWSTR lpType, COUNT_T *pSiz if (pDir->VirtualAddress == 0) return NULL; - BOOL isDictionary = FALSE; - DWORD nameTableRva = ReadResourceDictionary(pDir->VirtualAddress, pDir->VirtualAddress, lpType, &isDictionary); + BOOL isDirectory = FALSE; + DWORD nameTableRva = ReadResourceDirectory(this, pDir->VirtualAddress, pDir->VirtualAddress, lpType, &isDirectory); - if (!isDictionary) + if (!isDirectory) return NULL; if (nameTableRva == 0) return NULL; - DWORD languageTableRva = ReadResourceDictionary(pDir->VirtualAddress, nameTableRva, lpName, &isDictionary); - if (!isDictionary) + DWORD languageTableRva = ReadResourceDirectory(this, pDir->VirtualAddress, nameTableRva, lpName, &isDirectory); + if (!isDirectory) return NULL; if (languageTableRva == 0) @@ -1918,14 +1965,14 @@ void * PEDecoder::GetWin32Resource(LPCWSTR lpName, LPCWSTR lpType, COUNT_T *pSiz // This translates to LANGID 0 as the initial lookup point, which is sufficient for the needs of this api at this time // (FindResource in the Windows api implements a large number of fallback paths which this api does not implement) - DWORD resourceDataEntryRva = ReadResourceDictionary(pDir->VirtualAddress, languageTableRva, 0, &isDictionary); - if (isDictionary) // This must not be a resource dictionary itself + DWORD resourceDataEntryRva = ReadResourceDirectory(this, pDir->VirtualAddress, languageTableRva, 0, &isDirectory); + if (isDirectory) // This must not be a resource directory itself return NULL; if (resourceDataEntryRva == 0) return NULL; - DWORD resourceDataRva = ReadResourceDataEntry(resourceDataEntryRva, pSize); + DWORD resourceDataRva = ReadResourceDataEntry(this, resourceDataEntryRva, pSize); if (!CheckRva(resourceDataRva, *pSize)) { *pSize = 0; @@ -1935,6 +1982,229 @@ void * PEDecoder::GetWin32Resource(LPCWSTR lpName, LPCWSTR lpType, COUNT_T *pSiz return (void*)GetRvaData(resourceDataRva); } +typedef bool (*PEDecoder_EnumerateResourceTableFunction)(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context); + +struct ResourceEnumerateNamesState +{ + PEDecoder_ResourceNamesCallbackFunction namesCallback; + PEDecoder_ResourceCallbackFunction langIDcallback; + void *context; + LPCWSTR nameType; + LPCWSTR nameName; + PEDecoder_EnumerateResourceTableFunction callbackPerName; + PEDecoder_EnumerateResourceTableFunction callbackPerLangID; +}; + +struct ResourceEnumerateTypesState +{ + PEDecoder_ResourceTypesCallbackFunction callback; + void *context; + LPCWSTR nameType; +}; + +bool EnumerateWin32ResourceTable(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, DWORD rvaOfResourceTable, PEDecoder_EnumerateResourceTableFunction resourceTableEnumerator, void *context) +{ + IMAGE_RESOURCE_DIRECTORY* pResourceDirectory; + IMAGE_RESOURCE_DIRECTORY_ENTRY* pDirectoryEntries; + if (!ReadResourceDirectoryHeader(pDecoder, rvaOfResourceSection, rvaOfResourceTable, &pDirectoryEntries, &pResourceDirectory)) + { + return false; + } + + DWORD iEntryCount = (DWORD)pResourceDirectory->NumberOfNamedEntries + (DWORD)pResourceDirectory->NumberOfIdEntries; + + for (DWORD iEntry = 0; iEntry < iEntryCount; iEntry++) + { + DWORD nameUInt; + NewArrayHolder nameString; + if (!ReadNameFromResourceDirectoryEntry(pDecoder, rvaOfResourceSection, pDirectoryEntries, iEntry, &nameUInt, &nameString)) + return false; + + LPCWSTR name = MAKEINTRESOURCEW(nameUInt); + if (nameString != NULL) + name = &nameString[0]; + + bool isDirectory = !!(pDirectoryEntries[iEntry].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY); + DWORD offsetToData = pDirectoryEntries[iEntry].OffsetToData & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY; + DWORD dataRva = rvaOfResourceSection + offsetToData; + + if (!resourceTableEnumerator(pDecoder, rvaOfResourceSection, isDirectory, name, dataRva, context)) + return false; + } + + return true; +} + +bool DoesResourceNameMatch(LPCWSTR nameA, LPCWSTR nameB) +{ + bool foundEntry = false; + + if (IS_INTRESOURCE(nameA)) + { + // name is id + if (nameA == nameB) + foundEntry = true; + } + else + { + // name is a string. + + // Check for name enumerated is an id. If so, it doesn't match, skip to next. + if (IS_INTRESOURCE(nameB)) + return false; + else + foundEntry = !wcscmp(nameB, nameA); + } + + return foundEntry; +} + +bool EnumerateLangIDs(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context) +{ + ResourceEnumerateNamesState *state = (ResourceEnumerateNamesState*)context; + if (isDirectory) + return false; + + // Only LangIDs are permitted here + if (!IS_INTRESOURCE(name)) + return false; + + if (dataRVA == 0) + return false; + + COUNT_T cbData; + DWORD resourceDataRva = ReadResourceDataEntry(pDecoder, dataRVA, &cbData); + if (!pDecoder->CheckRva(resourceDataRva, cbData)) + { + return false; + } + + BYTE *pData = (BYTE*)pDecoder->GetRvaData(resourceDataRva); + + return state->langIDcallback(state->nameName, state->nameType, (DWORD)name, pData, cbData, state->context); +} + + +bool EnumerateNames(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context) +{ + ResourceEnumerateNamesState *state = (ResourceEnumerateNamesState*)context; + if (!isDirectory) + return false; + + state->nameName = name; + return state->namesCallback(state->nameName, state->nameType, state->context); +} + +bool EnumerateNamesForLangID(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context) +{ + ResourceEnumerateNamesState *state = (ResourceEnumerateNamesState*)context; + if (!isDirectory) + return false; + + bool foundEntry = DoesResourceNameMatch(state->nameName, name); + + if (foundEntry) + return EnumerateWin32ResourceTable(pDecoder, rvaOfResourceSection, dataRVA, state->callbackPerLangID, context); + else + return true; // Keep scanning +} + + +bool EnumerateTypes(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context) +{ + ResourceEnumerateTypesState *state = (ResourceEnumerateTypesState*)context; + if (!isDirectory) + return false; + + state->nameType = name; + return state->callback(name, state->context); +} + +bool EnumerateTypesForNames(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context) +{ + ResourceEnumerateNamesState *state = (ResourceEnumerateNamesState*)context; + if (!isDirectory) + return false; + + bool foundEntry = DoesResourceNameMatch(state->nameType, name); + + if (foundEntry) + return EnumerateWin32ResourceTable(pDecoder, rvaOfResourceSection, dataRVA, state->callbackPerName, context); + else + return true; // Keep scanning +} + + +bool PEDecoder::EnumerateWin32ResourceTypes(PEDecoder_ResourceTypesCallbackFunction callback, void* context) const +{ + if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE)) + return true; + + COUNT_T resourceDataSize = 0; + IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE); + + if (pDir->VirtualAddress == 0) + return true; + + DWORD rvaOfResourceSection = pDir->VirtualAddress; + + ResourceEnumerateTypesState state; + state.context = context; + state.callback = callback; + + return EnumerateWin32ResourceTable(this, rvaOfResourceSection, rvaOfResourceSection, EnumerateTypes, &state); +} + +bool PEDecoder::EnumerateWin32ResourceNames(LPCWSTR lpType, PEDecoder_ResourceNamesCallbackFunction callback, void* context) const +{ + if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE)) + return true; + + COUNT_T resourceDataSize = 0; + IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE); + + if (pDir->VirtualAddress == 0) + return true; + + DWORD rvaOfResourceSection = pDir->VirtualAddress; + + ResourceEnumerateNamesState state; + state.context = context; + state.namesCallback = callback; + state.langIDcallback = NULL; + state.nameType = lpType; + state.nameName = NULL; + state.callbackPerName = EnumerateNames; + state.callbackPerLangID = NULL; + + return EnumerateWin32ResourceTable(this, rvaOfResourceSection, rvaOfResourceSection, EnumerateTypesForNames, &state); +} + +bool PEDecoder::EnumerateWin32Resources(LPCWSTR lpName, LPCWSTR lpType, PEDecoder_ResourceCallbackFunction callback, void* context) const +{ + if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE)) + return true; + + COUNT_T resourceDataSize = 0; + IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE); + + if (pDir->VirtualAddress == 0) + return true; + + DWORD rvaOfResourceSection = pDir->VirtualAddress; + + ResourceEnumerateNamesState state; + state.context = context; + state.namesCallback = NULL; + state.langIDcallback = callback; + state.nameType = lpType; + state.nameName = lpName; + state.callbackPerName = EnumerateNamesForLangID; + state.callbackPerLangID = EnumerateLangIDs; + + return EnumerateWin32ResourceTable(this, rvaOfResourceSection, rvaOfResourceSection, EnumerateTypesForNames, &state); +} + BOOL PEDecoder::HasNativeHeader() const { CONTRACT(BOOL) diff --git a/src/zap/zapheaders.cpp b/src/zap/zapheaders.cpp index c931d3b..cdf511b 100644 --- a/src/zap/zapheaders.cpp +++ b/src/zap/zapheaders.cpp @@ -19,6 +19,9 @@ #include "zapmetadata.h" #include "zapimport.h" +#include +#include + // // IMAGE_COR20_HEADER // @@ -190,54 +193,252 @@ void ZapImage::SaveCodeManagerEntry() // Needed for RT_VERSION. #define MAKEINTRESOURCE(v) MAKEINTRESOURCEW(v) -void ZapVersionResource::Save(ZapWriter * pZapWriter) +void ZapWin32ResourceDirectory::Save(ZapWriter * pZapWriter) { - // Resources are binary-sorted tree structure. Since we are saving just one resource - // the binary structure is degenerated link list. By convention, Windows uses three levels - // for resources: Type, Name, Language. + // + // The IMAGE_RESOURCE_DIRECTORY resource data structure is followed by a number of IMAGE_RESOURCE_DIRECTORY_ENTRY entries, which can either + // point to other resource directories (RVAs to other ZapWin32ResourceDirectory nodes), or point to actual resource data (RVAs to a number + // of IMAGE_RESOURCE_DATA_ENTRY entries that immediately follow the IMAGE_RESOURCE_DIRECTORY_ENTRY entries). + // - // See vctools\link\doc\pecoff.doc for detailed documentation of PE format. + // + // Sorting for resources is done in the following way accoring to the PE format specifications: + // 1) First, all the IMAGE_RESOURCE_DIRECTORY_ENTRY entries where the ID is a name string, sorted by names + // 2) Second, all the IMAGE_RESOURCE_DIRECTORY_ENTRY entries with non-string IDs, sorted by IDs. + // + struct ResourceSorter + { + bool operator() (DataOrSubDirectoryEntry& a, DataOrSubDirectoryEntry& b) + { + if (a.m_nameOrIdIsString && !b.m_nameOrIdIsString) + return true; + if (!a.m_nameOrIdIsString && b.m_nameOrIdIsString) + return false; + if (a.m_nameOrIdIsString) + return wcscmp(((ZapWin32ResourceString*)(a.m_pNameOrId))->GetString(), ((ZapWin32ResourceString*)(b.m_pNameOrId))->GetString()) < 0; + else + return a.m_pNameOrId < b.m_pNameOrId; + } + } resourceSorter; + std::sort(m_entries.begin(), m_entries.end(), resourceSorter); - VersionResourceHeader header; - ZeroMemory(&header, sizeof(header)); + IMAGE_RESOURCE_DIRECTORY directory; + ZeroMemory(&directory, sizeof(IMAGE_RESOURCE_DIRECTORY)); + + for (auto& entry : m_entries) + { + if (entry.m_nameOrIdIsString) + directory.NumberOfNamedEntries++; + else + directory.NumberOfIdEntries++; + } + pZapWriter->Write(&directory, sizeof(IMAGE_RESOURCE_DIRECTORY)); - header.TypeDir.NumberOfIdEntries = 1; - header.TypeEntry.Id = (USHORT)((ULONG_PTR)RT_VERSION); - header.TypeEntry.OffsetToDirectory = offsetof(VersionResourceHeader, NameDir); - header.TypeEntry.DataIsDirectory = 1; + // Offsets are based from the begining of the resources blob (see PE format documentation) + DWORD dataEntryRVA = this->GetRVA() - m_pWin32ResourceSection->GetRVA() + + sizeof(IMAGE_RESOURCE_DIRECTORY) + + (DWORD)m_entries.size() * sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY); - header.NameDir.NumberOfIdEntries = 1; - header.NameEntry.Id = 1; - header.NameEntry.OffsetToDirectory = offsetof(VersionResourceHeader, LangDir); - header.NameEntry.DataIsDirectory = 1; + for (auto& entry : m_entries) + { + IMAGE_RESOURCE_DIRECTORY_ENTRY dirEntry; + ZeroMemory(&dirEntry, sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY)); - header.LangDir.NumberOfIdEntries = 1; - header.LangEntry.OffsetToDirectory = offsetof(VersionResourceHeader, DataEntry); + if (entry.m_nameOrIdIsString) + { + // Offsets are based from the begining of the resources blob (see PE format documentation) + dirEntry.NameOffset = ((ZapWin32ResourceString*)(entry.m_pNameOrId))->GetRVA() - m_pWin32ResourceSection->GetRVA(); + dirEntry.NameIsString = true; + } + else + { + _ASSERT(IS_INTRESOURCE(entry.m_pNameOrId)); + dirEntry.Id = (WORD)((ULONG_PTR)entry.m_pNameOrId & 0xffff); + } - header.DataEntry.OffsetToData = m_pVersionData->GetRVA(); - header.DataEntry.Size = m_pVersionData->GetSize(); + if (entry.m_dataIsSubDirectory) + { + // Offsets are based from the begining of the resources blob (see PE format documentation) + dirEntry.OffsetToDirectory = entry.m_pDataOrSubDirectory->GetRVA() - m_pWin32ResourceSection->GetRVA(); + dirEntry.DataIsDirectory = true; + } + else + { + dirEntry.OffsetToData = dataEntryRVA; + dataEntryRVA += sizeof(IMAGE_RESOURCE_DATA_ENTRY); + } - pZapWriter->Write(&header, sizeof(header)); + pZapWriter->Write(&dirEntry, sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY)); + } + + for (auto& entry : m_entries) + { + if (entry.m_dataIsSubDirectory) + continue; + + IMAGE_RESOURCE_DATA_ENTRY dataEntry; + ZeroMemory(&dataEntry, sizeof(IMAGE_RESOURCE_DATA_ENTRY)); + + dataEntry.OffsetToData = entry.m_pDataOrSubDirectory->GetRVA(); + dataEntry.Size = entry.m_pDataOrSubDirectory->GetSize(); + + pZapWriter->Write(&dataEntry, sizeof(IMAGE_RESOURCE_DATA_ENTRY)); + } } -void ZapImage::CopyWin32VersionResource() +void ZapImage::CopyWin32Resources() { - // Copy the version resource over so it is easy to see in the dumps where the ngened module came from - COUNT_T cbResourceData; - PVOID pResourceData = m_ModuleDecoder.GetWin32Resource(MAKEINTRESOURCE(1), RT_VERSION, &cbResourceData); +#ifdef FEATURE_PREJIT + if (!IsReadyToRunCompilation()) + { + // When compiling a fragile NGEN image, in order to avoid the risk of regression, only copy the RT_VERSION resource over so it + // is easy to see in the dumps where the ngened module came from. For R2R, we copy all resources (new behavior). + COUNT_T cbResourceData; + PVOID pResourceData = m_ModuleDecoder.GetWin32Resource(MAKEINTRESOURCE(1), RT_VERSION, &cbResourceData); + + if (!pResourceData || !cbResourceData) + return; + + ZapBlob * pVersionData = new (GetHeap()) ZapBlobPtr(pResourceData, cbResourceData); + + ZapWin32ResourceDirectory* pTypeDirectory = new (GetHeap()) ZapWin32ResourceDirectory(m_pWin32ResourceSection); + ZapWin32ResourceDirectory* pNameDirectory = new (GetHeap()) ZapWin32ResourceDirectory(m_pWin32ResourceSection); + ZapWin32ResourceDirectory* pLanguageDirectory = new (GetHeap()) ZapWin32ResourceDirectory(m_pWin32ResourceSection); + + pTypeDirectory->AddEntry(RT_VERSION, false, pNameDirectory, true); + pNameDirectory->AddEntry(MAKEINTRESOURCE(1), false, pLanguageDirectory, true); + pLanguageDirectory->AddEntry(MAKEINTRESOURCE(0), false, pVersionData, false); + + pTypeDirectory->PlaceNodeAndDependencies(m_pWin32ResourceSection); + + m_pWin32ResourceSection->Place(pVersionData); + + SetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE, m_pWin32ResourceSection); - if (!pResourceData || !cbResourceData) return; + } +#endif + + class ResourceEnumerationCallback + { + ZapImage* m_pZapImage; + PEDecoder* m_pModuleDecoder; + + std::vector m_dataEntries; + std::vector m_stringEntries; + + ZapWin32ResourceDirectory* m_pRootDirectory; + ZapWin32ResourceDirectory* m_pCurrentTypesDirectory; + ZapWin32ResourceDirectory* m_pCurrentNamesDirectory; + + bool AddResource(LPCWSTR lpszResourceName, LPCWSTR lpszResourceType, DWORD langID, BYTE* pResourceData, COUNT_T cbResourceData) + { + ZapBlob* pDataBlob = new (m_pZapImage->GetHeap()) ZapBlobPtr(pResourceData, cbResourceData); + m_dataEntries.push_back(pDataBlob); + + m_pCurrentNamesDirectory->AddEntry((PVOID)(ULONG_PTR)langID, false, pDataBlob, false); + + return true; + } + + ZapWin32ResourceDirectory* CreateResourceSubDirectory(ZapWin32ResourceDirectory* pRootDir, LPCWSTR pNameOrId) + { + bool nameIsString = !IS_INTRESOURCE(pNameOrId); + + PVOID pIdOrNameZapNode = (PVOID)pNameOrId; + if (nameIsString) + { + pIdOrNameZapNode = new (m_pZapImage->GetHeap()) ZapWin32ResourceString(pNameOrId); + m_stringEntries.push_back((ZapBlob*)pIdOrNameZapNode); + } + + ZapWin32ResourceDirectory* pResult = new (m_pZapImage->GetHeap()) ZapWin32ResourceDirectory(m_pZapImage->m_pWin32ResourceSection); + pRootDir->AddEntry(pIdOrNameZapNode, nameIsString, pResult, true); + + return pResult; + } + + public: + ResourceEnumerationCallback(PEDecoder* pModuleDecoder, ZapImage* pZapImage) + : m_pZapImage(pZapImage), m_pModuleDecoder(pModuleDecoder) + { + m_pRootDirectory = new (pZapImage->GetHeap()) ZapWin32ResourceDirectory(pZapImage->m_pWin32ResourceSection); + m_pCurrentTypesDirectory = m_pCurrentNamesDirectory = NULL; + } - ZapBlob * pVersionData = new (GetHeap()) ZapBlobPtr(pResourceData, cbResourceData); + static bool EnumResourcesCallback(LPCWSTR lpszResourceName, LPCWSTR lpszResourceType, DWORD langID, BYTE* data, COUNT_T cbData, void *context) + { + ResourceEnumerationCallback* pCallback = (ResourceEnumerationCallback*)context; + // Third level in the enumeration: resources by langid for each name/type. - ZapVersionResource * pVersionResource = new (GetHeap()) ZapVersionResource(pVersionData); + // Note that this callback is not equivalent to the Windows enumeration apis as this api provides the resource data + // itself, and the resources are guaranteed to be present directly in the associated binary. This does not exactly + // match the Windows api, but it is exactly what we want when copying all resource data. - m_pWin32ResourceSection->Place(pVersionResource); - m_pWin32ResourceSection->Place(pVersionData); + return pCallback->AddResource(lpszResourceName, lpszResourceType, langID, data, cbData); + } + + static bool EnumResourceNamesCallback(LPCWSTR lpszResourceName, LPCWSTR lpszResourceType, void *context) + { + // Second level in the enumeration: resources by names for each resource type + + ResourceEnumerationCallback* pCallback = (ResourceEnumerationCallback*)context; + pCallback->m_pCurrentNamesDirectory = pCallback->CreateResourceSubDirectory(pCallback->m_pCurrentTypesDirectory, lpszResourceName); + + return pCallback->m_pModuleDecoder->EnumerateWin32Resources(lpszResourceName, lpszResourceType, ResourceEnumerationCallback::EnumResourcesCallback, context); + } - SetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE, m_pWin32ResourceSection); + static bool EnumResourceTypesCallback(LPCWSTR lpszType, void *context) + { + // First level in the enumeration: resources by types + + // Skip IBC resources + if (!IS_INTRESOURCE(lpszType) && (wcscmp(lpszType, W("IBC")) == 0)) + return true; + + ResourceEnumerationCallback* pCallback = (ResourceEnumerationCallback*)context; + pCallback->m_pCurrentTypesDirectory = pCallback->CreateResourceSubDirectory(pCallback->m_pRootDirectory, lpszType); + + return pCallback->m_pModuleDecoder->EnumerateWin32ResourceNames(lpszType, ResourceEnumerationCallback::EnumResourceNamesCallback, context); + } + + void PlaceResourceNodes(ZapVirtualSection* pWin32ResourceSection) + { + m_pRootDirectory->PlaceNodeAndDependencies(pWin32ResourceSection); + + // + // These strings are stored together after the last Resource Directory entry and before the first Resource Data entry. This + // minimizes the impact of these variable-length strings on the alignment of the fixed-size directory entries + // + for (auto& entry : m_stringEntries) + pWin32ResourceSection->Place(entry); + + for (auto& entry : m_dataEntries) + pWin32ResourceSection->Place(entry); + } + }; + + ResourceEnumerationCallback callbacks(&m_ModuleDecoder, this); + + HMODULE hModule = (HMODULE)dac_cast(m_ModuleDecoder.GetBase()); + + // + // Resources are binary-sorted tree structure. By convention, Windows uses three levels + // for resources: Type, Name, Language. To reduces the overall complexity, we'll copy and store resources in the + // "neutral" language only. + // + + if (!m_ModuleDecoder.EnumerateWin32ResourceTypes(ResourceEnumerationCallback::EnumResourceTypesCallback, &callbacks)) + { + ThrowHR(E_FAIL); + } + else + { + callbacks.PlaceResourceNodes(m_pWin32ResourceSection); + + SetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE, m_pWin32ResourceSection); + } } #undef MAKEINTRESOURCE diff --git a/src/zap/zapheaders.h b/src/zap/zapheaders.h index f966ced..415ea4e 100644 --- a/src/zap/zapheaders.h +++ b/src/zap/zapheaders.h @@ -13,6 +13,8 @@ #ifndef __ZAPHEADERS_H__ #define __ZAPHEADERS_H__ +#include + // // IMAGE_COR20_HEADER // @@ -184,33 +186,99 @@ public: }; // -// Version Resource +// Win32 Resources // -class ZapVersionResource : public ZapNode +class ZapWin32ResourceString : public ZapNode { - ZapNode * m_pVersionData; + // + // This ZapNode maps the IMAGE_RESOURCE_DIR_STRING_U resource data structure for storing strings. + // + + LPWSTR m_pString; public: - ZapVersionResource(ZapNode * pVersionData) - : m_pVersionData(pVersionData) + ZapWin32ResourceString(LPCWSTR pString) + { + size_t strLen = wcslen(pString); + _ASSERT(pString != NULL && strLen < 0xffff); + + m_pString = new WCHAR[strLen + 1]; + wcscpy(m_pString, pString); + m_pString[strLen] = L'\0'; + } + + LPCWSTR GetString() { return m_pString; } + + virtual DWORD GetSize() + { + return sizeof(WORD) + sizeof(WCHAR) * (DWORD)wcslen(m_pString); + } + + virtual UINT GetAlignment() + { + return sizeof(WORD); + } + + virtual ZapNodeType GetType() { + return ZapNodeType_Blob; } - struct VersionResourceHeader { - IMAGE_RESOURCE_DIRECTORY TypeDir; - IMAGE_RESOURCE_DIRECTORY_ENTRY TypeEntry; - IMAGE_RESOURCE_DIRECTORY NameDir; - IMAGE_RESOURCE_DIRECTORY_ENTRY NameEntry; - IMAGE_RESOURCE_DIRECTORY LangDir; - IMAGE_RESOURCE_DIRECTORY_ENTRY LangEntry; - IMAGE_RESOURCE_DATA_ENTRY DataEntry; - CHAR Data[0]; + virtual void Save(ZapWriter * pZapWriter) + { + WORD size = (WORD)wcslen(m_pString); + pZapWriter->Write(&size, sizeof(WORD)); + pZapWriter->Write((PVOID)m_pString, sizeof(WCHAR) * size); + } +}; + +class ZapWin32ResourceDirectory : public ZapNode +{ + // + // This ZapNode maps the IMAGE_RESOURCE_DIRECTORY resource data structure for storing a resource directory. Each directory + // is then followed by a number of IMAGE_RESOURCE_DIRECTORY_ENTRY entries, which can either point to other resource directories (RVAs + // to other ZapWin32ResourceDirectory nodes), or point to actual resource data (RVAs to a number of IMAGE_RESOURCE_DATA_ENTRY entries + // that immediately follow the IMAGE_RESOURCE_DIRECTORY_ENTRY entries). + // + // Refer to the PE resources format for more information (https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#the-rsrc-section) + // + + struct DataOrSubDirectoryEntry + { + PVOID m_pNameOrId; + bool m_nameOrIdIsString; + ZapNode* m_pDataOrSubDirectory; + bool m_dataIsSubDirectory; }; + std::vector m_entries; + ZapVirtualSection* m_pWin32ResourceSection; + +public: + ZapWin32ResourceDirectory(ZapVirtualSection* pWin32ResourceSection) + : m_pWin32ResourceSection(pWin32ResourceSection) + { } + + void AddEntry(PVOID pNameOrId, bool nameOrIdIsString, ZapNode* pDataOrSubDirectory, bool dataIsSubDirectory) + { + DataOrSubDirectoryEntry entry; + entry.m_pDataOrSubDirectory = pDataOrSubDirectory; + entry.m_dataIsSubDirectory = dataIsSubDirectory; + entry.m_pNameOrId = pNameOrId; + entry.m_nameOrIdIsString = nameOrIdIsString; + + m_entries.push_back(entry); + } virtual DWORD GetSize() { - return sizeof(VersionResourceHeader); + DWORD size = sizeof(IMAGE_RESOURCE_DIRECTORY) + (DWORD)m_entries.size() * sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY); + for (auto& entry : m_entries) + { + if (!entry.m_dataIsSubDirectory) + size += sizeof(IMAGE_RESOURCE_DATA_ENTRY); + } + return size; } virtual UINT GetAlignment() @@ -220,7 +288,21 @@ public: virtual ZapNodeType GetType() { - return ZapNodeType_VersionResource; + return ZapNodeType_Win32Resources; + } + + void PlaceNodeAndDependencies(ZapVirtualSection* pWin32ResourceSection) + { + pWin32ResourceSection->Place(this); + + for (auto& entry : m_entries) + { + if (entry.m_dataIsSubDirectory) + { + ZapWin32ResourceDirectory* pSubDirNode = (ZapWin32ResourceDirectory*)entry.m_pDataOrSubDirectory; + pSubDirNode->PlaceNodeAndDependencies(pWin32ResourceSection); + } + } } virtual void Save(ZapWriter * pZapWriter); diff --git a/src/zap/zapimage.cpp b/src/zap/zapimage.cpp index aa661f5..50df05f 100644 --- a/src/zap/zapimage.cpp +++ b/src/zap/zapimage.cpp @@ -1496,7 +1496,7 @@ void ZapImage::OutputTables() } CopyDebugDirEntry(); - CopyWin32VersionResource(); + CopyWin32Resources(); if (m_pILMetaData != NULL) { diff --git a/src/zap/zapimage.h b/src/zap/zapimage.h index 832f2b1..d5317a3 100644 --- a/src/zap/zapimage.h +++ b/src/zap/zapimage.h @@ -599,7 +599,7 @@ private: void OutputManifestMetadataForReadyToRun(); void CopyDebugDirEntry(); - void CopyWin32VersionResource(); + void CopyWin32Resources(); void OutputManifestMetadata(); void OutputTables(); diff --git a/src/zap/zapnodetype.h b/src/zap/zapnodetype.h index ae7d58a..82bad5b 100644 --- a/src/zap/zapnodetype.h +++ b/src/zap/zapnodetype.h @@ -35,7 +35,7 @@ enum ZapNodeType { ZapNodeType_CodeManagerEntry, ZapNodeType_MetaData, ZapNodeType_DebugDirectory, - ZapNodeType_VersionResource, + ZapNodeType_Win32Resources, // PlaceHolders diff --git a/tests/src/readytorun/tests/fileversionpreservation/Properties/AssemblyInfo.cs b/tests/src/readytorun/tests/fileversionpreservation/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2cef16e --- /dev/null +++ b/tests/src/readytorun/tests/fileversionpreservation/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System.Reflection; + +[assembly: AssemblyFileVersion("2.0.1.9")] +[assembly: AssemblyVersion("2.0.1.10")] + diff --git a/tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.cs b/tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.cs new file mode 100644 index 0000000..5839087 --- /dev/null +++ b/tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Diagnostics; + +public class Program +{ + public static int Main() + { + return RunTest(); + } + + public static bool CheckVersionResourceUsingApi(string filename) + { + FileVersionInfo fileVer = FileVersionInfo.GetVersionInfo(filename); + + bool success = fileVer.FileVersion == "2.0.1.9"; + if (!success) + Console.WriteLine($"{filename} has version \"{fileVer.FileVersion}\""); + + return success; + } + + public static int RunTest() + { + string ilfilename = typeof(Program).Assembly.Location; + string nifilename = ilfilename.Replace(".exe", ".ni.exe"); + + bool success = true; + + if (!CheckVersionResourceUsingApi(ilfilename)) + success = false; + + if (!CheckVersionResourceUsingApi(nifilename)) + success = false; + + return success ? 100 : -1; + } +} diff --git a/tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.csproj b/tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.csproj new file mode 100644 index 0000000..48a10ed --- /dev/null +++ b/tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.csproj @@ -0,0 +1,46 @@ + + + + + Debug + AnyCPU + 2.0 + {7DECC55A-B584-4456-83BA-6C42A5B3B3CB} + exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + BuildAndRun + $(DefineConstants);STATIC;CORECLR + 0 + PdbOnly + True + + + + + + + + + False + + + + + + + + + + + + + + + \ No newline at end of file -- 2.7.4