diff --git a/docs/design/mono/webcil.md b/docs/design/mono/webcil.md index 37d3c4968e4c59..ca41a31f680147 100644 --- a/docs/design/mono/webcil.md +++ b/docs/design/mono/webcil.md @@ -130,6 +130,13 @@ struct WebcilHeader { }; ``` +#### Webcil Header (V1 Changes) +For Webcil V1, the Reserved0 field may be used to store a 1-based index which corresponds to a +base reloc section. +``` + uint16_t Reserved0; // 0, or 1-based index of .reloc webcil section +``` + The Webcil header starts with the magic characters 'W' 'b' 'I' 'L' followed by the version in major minor format (must be 0 and 0). Then a count of the section headers and two reserved bytes. @@ -184,3 +191,18 @@ Lossless conversion from Webcil back to PE is not intended to be supported. The documented in order to support diagnostic tooling and utilities such as decompilers, disassemblers, file identification utilities, dependency analyzers, etc. + +### Webcil V1 (Base Relocations) +It is possible to specify base relocations in the standard PE base relocation format in Webcil V1. +Relocations are grouped by page (default 4kb), and each relocation entry is a `uint16_t` containing: + +- Relocation Type (upper 4 bits) + - IMAGE_REL_BASED_DIR64 (wasm64) + - IMAGE_REL_BASED_HIGHLOW (wasm32) + - IMAGE_REL_BASED_WASM32_TABLE (wasm32) + - IMAGE_REL_BASED_WASM64_TABLE (wasm64) +- Offset (lower 12 bits) + +`IMAGE_REL_BASED_WASM{32, 64}_TABLE` relocations represent a "table base offset" fixup; They should be used to indicate places +where function pointer table indices need to be offset after the Webcil payload has been loaded by the runtime. The offset will +be dependent on the state of the table when an implementation's loader loads a Webcil module. diff --git a/src/coreclr/inc/webcildecoder.h b/src/coreclr/inc/webcildecoder.h index c47d91267a8631..9173e9efc4ae9b 100644 --- a/src/coreclr/inc/webcildecoder.h +++ b/src/coreclr/inc/webcildecoder.h @@ -48,7 +48,7 @@ struct WebcilHeader uint16_t VersionMajor; // 0 uint16_t VersionMinor; // 0 uint16_t CoffSections; - uint16_t Reserved0; + uint16_t Reserved0; // 1-based index of the base relocations section, or 0 if none uint32_t PeCliHeaderRva; uint32_t PeCliHeaderSize; uint32_t PeDebugRva; @@ -137,7 +137,8 @@ class WebcilDecoder // Webcil is always flat, never mapped in the PE sense BOOL IsMapped() const { return FALSE; } - BOOL IsRelocated() const { return FALSE; } + BOOL IsRelocated() const { return m_relocated; } + void SetRelocated() { m_relocated = TRUE; } BOOL IsFlat() const { return HasContents(); } // ------------------------------------------------------------ @@ -151,7 +152,7 @@ class WebcilDecoder BOOL HasNTHeaders() const { return FALSE; } CHECK CheckNTHeaders() const; BOOL Has32BitNTHeaders() const { return FALSE; } - BOOL HasBaseRelocations() const { return FALSE; } + BOOL HasBaseRelocations() const; BOOL HasWriteableSections() const { return FALSE; } BOOL HasTls() const { return FALSE; } @@ -258,6 +259,7 @@ class WebcilDecoder TADDR m_base; COUNT_T m_size; BOOL m_hasContents; + BOOL m_relocated = FALSE; const WebcilHeader *m_pHeader; mutable IMAGE_COR20_HEADER *m_pCorHeader; }; diff --git a/src/coreclr/pal/inc/rt/ntimage.h b/src/coreclr/pal/inc/rt/ntimage.h index 8f88791ef51cf6..9acfdd1f924348 100644 --- a/src/coreclr/pal/inc/rt/ntimage.h +++ b/src/coreclr/pal/inc/rt/ntimage.h @@ -1163,6 +1163,15 @@ typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION; #define IMAGE_REL_BASED_ARM_MOV32 5 #define IMAGE_REL_BASED_THUMB_MOV32 7 +#ifdef FEATURE_WEBCIL +// +// Webcil-specific based relocation types. +// The fixup adds a WASM table base offset to the value at the relocation address. +// +#define IMAGE_REL_BASED_WASM32_TABLE 12 +#define IMAGE_REL_BASED_WASM64_TABLE 13 +#endif // FEATURE_WEBCIL + // // Archive format. // diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs index 70011a82acca64..e42de9d295a037 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs @@ -18,6 +18,10 @@ public enum RelocType // COFF relocation types IMAGE_REL_BASED_ADDR32NB = 0x0B, // The 32-bit address without an image base (RVA) + // Webcil Relocation Types + IMAGE_REL_BASED_WASM32_TABLE = 0x0C, + IMAGE_REL_BASED_WASM64_TABLE = 0x0D, + // General relocation types IMAGE_REL_BASED_REL32 = 0x10, // 32-bit relative address from byte following reloc IMAGE_REL_BASED_THUMB_BRANCH24 = 0x13, // Thumb2: based B, BL @@ -807,6 +811,12 @@ public static RelocType GetFileRelocationType(RelocType relocationType) case RelocType.IMAGE_REL_BASED_THUMB_MOV32: return relocationType; + case RelocType.WASM_TABLE_INDEX_I32: + return RelocType.IMAGE_REL_BASED_WASM32_TABLE; + + case RelocType.WASM_TABLE_INDEX_I64: + return RelocType.IMAGE_REL_BASED_WASM64_TABLE; + default: return RelocType.IMAGE_REL_BASED_ABSOLUTE; } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 91aa2abedd1c09..1528223e128659 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -294,20 +294,7 @@ public int GetFlatMappedSize() return size; } - public long ResolveSymbolRVA(SymbolDefinition definition) - { - for (int i = 0; i < Sections.Length; i++) - { - WebcilSection section = Sections[i]; - if (definition.SectionIndex == section.Index) - { - return section.Header.VirtualAddress + definition.Value; - } - } - - return 0; - } - } + } static WasmFunctionBody GetWebcilSize = new WasmFunctionBody( new WasmFuncType(new([WasmValueType.I32]), new([])), // (func (destPtr i32) (result)) @@ -365,13 +352,32 @@ private void InsertWasmStub(Utf8String name, WasmFunctionBody body) _methodCount++; RegisterStubIndexAndSignature(body); + + } + private long ResolveSymbolRVA(WebcilSection[] sections, SymbolDefinition definition) + { + for (int i = 0; i < sections.Length; i++) + { + WebcilSection section = sections[i]; + if (definition.SectionIndex == section.Index) + { + return section.Header.VirtualAddress + definition.Value; + } + } + + return 0; } public const int WebcilSectionAlignment = 16; - private WebcilSegment BuildWebcilDataSegment() - { - WebcilSection[] webcilSections = _sections.OfType().ToArray(); + /// + /// Assigns VirtualAddresses and related header fields to each webcil section based on the + /// total section count and each section's stream length. This can be called before all + /// sections have their final content as long as the section count is finalized, though + /// sections whose size changes later must come last they don't invalidate earlier VAs. + /// + private static void AssignWebcilSectionVirtualAddresses(WebcilSection[] webcilSections) + { uint sizeOfHeaders = (uint)WebcilEncoder.HeaderEncodeSize() + (uint)(webcilSections.Length * WebcilEncoder.SectionHeaderEncodeSize()); uint pointerToRawData = (uint)AlignmentHelper.AlignUp((int)sizeOfHeaders, (int)WebcilSectionAlignment); uint virtualAddress = pointerToRawData; @@ -400,6 +406,36 @@ private WebcilSegment BuildWebcilDataSegment() pointerToRawData += alignedSectionSize; virtualAddress += virtualSize; } + } + + private WebcilSegment BuildWebcilDataSegment() + { + WebcilSection[] webcilSections = _sections.OfType().ToArray(); + + AssignWebcilSectionVirtualAddresses(webcilSections); + + // Populate the RVAs for the Cor header/size and debug directory/size, which are required for the runtime + // to be able to load this segment. + Utf8String corHeaderDefName = _wellKnownSymbols[SortableDependencyNode.ObjectNodeOrder.CorHeaderNode]; + SymbolDefinition corHeaderNode = _definedSymbols[corHeaderDefName]; + uint peCliHeaderRva = (uint)ResolveSymbolRVA(webcilSections, corHeaderNode); + Debug.Assert(peCliHeaderRva != 0); + uint peCliHeaderSize = (uint)corHeaderNode.Size; + + Utf8String debugDirectoryDefName = _wellKnownSymbols[SortableDependencyNode.ObjectNodeOrder.DebugDirectoryNode]; + SymbolDefinition debugDirectoryDef = _definedSymbols[debugDirectoryDefName]; + uint peDebugRva = (uint)ResolveSymbolRVA(webcilSections, debugDirectoryDef); + Debug.Assert(peDebugRva != 0); + uint peDebugSize = (uint)debugDirectoryDef.Size; + + // The index of the reloc section is either: 0 (if no reloc section) OR + // the 1-based index of the section, which in our case is assumed to be the last section + if (_baseRelocMap.Count > 0) + { + Debug.Assert(webcilSections.Length > 0); + Debug.Assert(webcilSections[webcilSections.Length - 1].Name.ToString() == "reloc"); + } + ushort relocSectionIdx = _baseRelocMap.Count > 0 ? checked((ushort)webcilSections.Length) : (ushort)0; WebcilHeader header = new WebcilHeader { @@ -407,13 +443,14 @@ private WebcilSegment BuildWebcilDataSegment() VersionMajor = WebcilConstants.WC_VERSION_MAJOR, VersionMinor = WebcilConstants.WC_VERSION_MINOR, CoffSections = (ushort)webcilSections.Length, - PeCliHeaderRva = 0, // This RVA will be resolved later - PeCliHeaderSize = 0, // Resolved along with RVA - PeDebugRva = 0, // This RVA will be resolved later - PeDebugSize = 0 // Resolved along with RVA + // In Webcil v1.0, Reserved0 is used for the index of the image base reloc section + Reserved0 = relocSectionIdx, + PeCliHeaderRva = peCliHeaderRva, + PeCliHeaderSize = peCliHeaderSize, + PeDebugRva = peDebugRva, + PeDebugSize = peDebugSize }; - return new WebcilSegment(header, webcilSections.ToArray()); } #endif @@ -444,6 +481,7 @@ private protected override SectionWriter.Params WriterParams(ObjectNodeSection s }; } + private protected override void CreateSection(ObjectNodeSection section, Utf8String comdatName, Utf8String symbolName, int sectionIndex, Stream sectionStream) { WasmSectionType sectionType = GetWasmSectionType(section); @@ -477,9 +515,6 @@ private void WriteDataCountSection() private WebcilSegment _webcilSegment = null; private protected override void EmitSectionsAndLayout() { - - _webcilSegment = BuildWebcilDataSegment(); - InsertWasmStub(new Utf8String("getWebcilSize"), GetWebcilSize); InsertWasmStub(new Utf8String("getWebcilPayload"), GetWebcilPayload); @@ -581,12 +616,74 @@ private int[] SectionEmitOrder } } + private static readonly ObjectNodeSection WebcilRelocSection = new ObjectNodeSection("reloc", SectionType.ReadOnly); + private void EmitRelocSectionData() + { + var writer = GetOrCreateSection(WebcilRelocSection); + Debug.Assert(writer.SectionIndex == _sections.Count - 1, "The .reloc section must be the last section we emit."); + + foreach (var kv in _baseRelocMap) + { + uint pageRva = kv.Key; + List entries = kv.Value; + entries.Sort(); + + int entriesSize = entries.Count * 2; + int sizeOfBlock = 8 + entriesSize; + sizeOfBlock = AlignmentHelper.AlignUp(sizeOfBlock, 4); + + writer.WriteLittleEndian(pageRva); + writer.WriteLittleEndian((uint)sizeOfBlock); + + // Emit entries + foreach (ushort e in entries) + { + writer.WriteLittleEndian(e); + } + + // Ensure block is 4-byte aligned + writer.EmitAlignment(4); + } + } + private PaddingHelper _paddingHelper = new PaddingHelper(WebcilSectionAlignment); private protected override void EmitObjectFile(Stream outputFileStream) { Debug.Assert(outputFileStream.CanSeek, $"EmitObjectFile requires seekable output stream"); + if (_pendingBaseRelocs.Count > 0) + { + GetOrCreateSection(WebcilRelocSection); + } + + WebcilSection[] webcilSections = _sections.OfType().ToArray(); + // At this point, our count of sections is final since we've determined if we have base relocs. + // This allows us to do an initial assignment of virtual addresses to our webcil sections, + // which is required for resolving file-level relocations whose RVA depends on the section VAs. + AssignWebcilSectionVirtualAddresses(webcilSections); + + // We can now build our base relocs with the correct addresses + BuildBaseRelocMap(); + + if (_baseRelocMap.Count > 0) + { + EmitRelocSectionData(); + } + + // Build the final webcil segment (re-assigns VAs with reloc section's real size). This must come last, + // since we must know if we have a reloc section as well as its final size to determine the segment layout. + _webcilSegment = BuildWebcilDataSegment(); + + // Writing our memory import <- size of the webcil segment (for an accurate minimum size) + WriteMemoryImport((ulong)_webcilSegment.GetFlatMappedSize()); + // Writing element counts <- imports being finalized. + EmitSectionElementCounts(); + + /********************************************************************* + * Write Wasm Sections, Excluding Data + *********************************************************************/ + EmitWasmHeader(outputFileStream); foreach (int index in SectionEmitOrder) { @@ -609,30 +706,10 @@ private protected override void EmitObjectFile(Stream outputFileStream) } #if READYTORUN - /***************************************************************** - Emit Webcil segment at end of file to support ReadyToRun - *****************************************************************/ - - Debug.Assert(_webcilSegment != null); // This should have been built in EmitSectionsAndLayout() - - // Populate the RVAs for the Cor header/size and debug directory/size, which are required for the runtime - // to be able to load this segment. - bool exists = _wellKnownSymbols.TryGetValue(SortableDependencyNode.ObjectNodeOrder.CorHeaderNode, out Utf8String corHeaderDefName); - Debug.Assert(exists, $"Cor header symbol definition {SortableDependencyNode.ObjectNodeOrder.CorHeaderNode} not found"); - - SymbolDefinition corHeaderNode = _definedSymbols[corHeaderDefName]; - _webcilSegment.Header.PeCliHeaderRva = (uint)_webcilSegment.ResolveSymbolRVA(corHeaderNode); - Debug.Assert(_webcilSegment.Header.PeCliHeaderRva != 0); - _webcilSegment.Header.PeCliHeaderSize = (uint)corHeaderNode.Size; + * Emit Webcil segment at end of file to support ReadyToRun + ****************************************************************/ - exists = _wellKnownSymbols.TryGetValue(SortableDependencyNode.ObjectNodeOrder.DebugDirectoryNode, out Utf8String debugDirectoryDefName); - Debug.Assert(exists, $"Debug directory symbol definition {SortableDependencyNode.ObjectNodeOrder.DebugDirectoryNode} not found"); - - SymbolDefinition debugDirectoryDef = _definedSymbols[debugDirectoryDefName]; - _webcilSegment.Header.PeDebugRva = (uint)_webcilSegment.ResolveSymbolRVA(debugDirectoryDef); - Debug.Assert(_webcilSegment.Header.PeDebugRva != 0); - _webcilSegment.Header.PeDebugSize = (uint)debugDirectoryDef.Size; MemoryStream webcilStream = new(_webcilSegment.GetFlatMappedSize()); WebcilEncoder.EmitHeader(_webcilSegment.Header, webcilStream); @@ -687,6 +764,14 @@ Emit Webcil segment at end of file to support ReadyToRun } Dictionary> _resolvableRelocations = new(); + SortedDictionary> _baseRelocMap = new(); + // We group webcil relocs into 4kb blocks, similar to PE + const uint WebcilRelocPageSize = 0x1000; + + // File-level relocations whose RVA computation is deferred until webcil section + // VirtualAddresses have been assigned. + private readonly record struct PendingBaseReloc(int SectionIndex, long Offset, RelocType FileRelocType); + private readonly List _pendingBaseRelocs = new(); private protected override void EmitRelocations(int sectionIndex, List relocationList) { @@ -696,9 +781,51 @@ private protected override void EmitRelocations(int sectionIndex, List(); } - // Unconditionally add the reloc to our resolvable list; all relocs must be resolvable for Wasm - // since we do not emit any relocations in the output object file. + // Unconditionally add the reloc to our resolvable list; we do some amount of relocation resolution + // for all relocation types. resolvable.Add(reloc); + + // A few relocation types (table indices and IMAGE_REL type relocs in Webcil) need + // an additional runtime reloc as well to add a base address. + // We defer the actual RVA computation to EmitObjectFile, where webcil section + // VirtualAddresses will have been assigned. Here we just record the raw info. + RelocType fileRelocType = Relocation.GetFileRelocationType(reloc.Type); + if (fileRelocType is not RelocType.IMAGE_REL_BASED_ABSOLUTE) + { + Debug.Assert(_sections[sectionIndex] is WebcilSection); + _pendingBaseRelocs.Add(new PendingBaseReloc(sectionIndex, reloc.Offset, fileRelocType)); + } + } + } + + /// + /// Processes the deferred file-level relocations after webcil section VirtualAddresses + /// have been assigned. Populates with page-grouped base reloc + /// entries, mirroring the PE base relocation format. + /// + private void BuildBaseRelocMap() + { + foreach (PendingBaseReloc pending in _pendingBaseRelocs) + { + Debug.Assert(_sections[pending.SectionIndex] is WebcilSection); + WebcilSection webcilSection = (WebcilSection)_sections[pending.SectionIndex]; + Debug.Assert(pending.Offset >= 0, "Pending base relocation has a negative offset."); + // Gather file-level relocations that need to go into the webcil .reloc + // section. We collect entries grouped by 4KB page into a map of + // (page RVA -> list of (type<<12 | offsetInPage) WORD entries). + // Note that this handling is logically the same as the implementation in the PE Object Writer. + uint targetRva = webcilSection.Header.VirtualAddress + (uint)pending.Offset; + Debug.Assert(targetRva != 0); // this section should have been assigned a non-zero VirtualAddress at this point. + uint pageRva = targetRva & ~(WebcilRelocPageSize - 1); + ushort offsetInPage = (ushort)(targetRva & (WebcilRelocPageSize - 1)); + ushort entry = (ushort)(((ushort)pending.FileRelocType << 12) | offsetInPage); + + if (!_baseRelocMap.TryGetValue(pageRva, out List list)) + { + list = new List(); + _baseRelocMap.Add(pageRva, list); + } + list.Add(entry); } } @@ -880,26 +1007,18 @@ void WriteRelocFromDataSpan(SymbolicRelocation reloc, byte* pData, long sectionS const int ImageBaseGlobalIndex = 1; const int ImageFunctionPointerBaseGlobalIndex = 2; - private WasmImport[] _defaultImports = new[] + private WasmImport[] _defaultGlobalImports = new[] { - null, // placeholder for memory, which is set up dynamically in WriteImports() - new WasmImport("env", "__stack_pointer", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Mut), index: StackPointerGlobalIndex), - new WasmImport("env", "__image_base", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: ImageBaseGlobalIndex), - new WasmImport("env", "__image_function_pointer_base", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: ImageFunctionPointerBaseGlobalIndex), + new WasmImport("webcil", "__stack_pointer", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Mut), index: StackPointerGlobalIndex), + new WasmImport("webcil", "__image_base", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: ImageBaseGlobalIndex), + new WasmImport("webcil", "__image_function_pointer_base", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: ImageFunctionPointerBaseGlobalIndex), }; private void WriteImports() { - // Calculate the minimum required memory size based on the Webcil Segment size - ulong contentSize = (ulong)_webcilSegment.GetFlatMappedSize(); - uint dataPages = checked((uint)((contentSize + (1 << 16) - 1) >> 16)); - uint numPages = Math.Max(dataPages, 1); // Ensure at least one page is allocated for the minimum - - // TODO-Wasm: decide on convention here; webcil spec states this should be "webcil" - _defaultImports[0] = new WasmImport("env", "memory", import: new WasmMemoryImportType(WasmLimitType.HasMin, numPages)); // memory limits: flags (0 = only minimum) int[] assignedImportIndices = new int[(int)WasmExternalKind.Count]; - foreach (WasmImport import in _defaultImports) + foreach (WasmImport import in _defaultGlobalImports) { if (import.Index.HasValue) { @@ -913,6 +1032,15 @@ private void WriteImports() _numImportedGlobals = assignedImportIndices[(int)WasmExternalKind.Global]; } + private void WriteMemoryImport(ulong contentSize) + { + uint dataPages = checked((uint)((contentSize + (1 << 16) - 1) >> 16)); + uint numPages = Math.Max(dataPages, 1); // Ensure at least one page is allocated for the minimum + + WasmImport memoryImport = new WasmImport("webcil", "memory", import: new WasmMemoryImportType(WasmLimitType.HasMin, numPages)); // memory limits: flags (0 = only minimum) + WriteImport(memoryImport); + } + private void WriteExports() { WriteTableExport("table", 0); @@ -954,6 +1082,12 @@ private protected override void EmitSymbolTable(IDictionary(definedSymbols); + } + + private void EmitSectionElementCounts() + { int funcIdx = _sectionNameToIndex[WasmObjectNodeSection.FunctionSection.Name]; PrependCount(_sections[funcIdx], _methodCount); @@ -971,12 +1105,10 @@ private protected override void EmitSymbolTable(IDictionary(definedSymbols); } } + internal class WasmSection { public WasmSectionType Type { get; } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WebcilEncoder.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WebcilEncoder.cs index a2559ec2141c92..8e2cb7e35cd190 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WebcilEncoder.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WebcilEncoder.cs @@ -49,10 +49,10 @@ public static int EmitHeader(in WebcilHeader header, Stream outputStream) public static int SectionHeaderEncodeSize() { - return sizeof(uint) + // VirtualSize: 4 - sizeof(uint) + // VirtualAddress: 8 - sizeof(uint) + // SizeOfRawData: 12 - sizeof(uint); // PointerToRawData: 16 + return sizeof(uint) + // VirtualSize: 4 + sizeof(uint) + // VirtualAddress: 8 + sizeof(uint) + // SizeOfRawData: 12 + sizeof(uint); // PointerToRawData: 16 } public static int EncodeSectionHeader(in WebcilSectionHeader sectionHeader, Stream outputStream) diff --git a/src/coreclr/utilcode/webcildecoder.cpp b/src/coreclr/utilcode/webcildecoder.cpp index 516a85366da85c..efe6f3769018c6 100644 --- a/src/coreclr/utilcode/webcildecoder.cpp +++ b/src/coreclr/utilcode/webcildecoder.cpp @@ -57,6 +57,7 @@ void WebcilDecoder::Init(void *flatBase, COUNT_T size) m_hasContents = (size > 0); m_pHeader = (m_hasContents && size >= sizeof(WebcilHeader)) ? (const WebcilHeader *)flatBase : NULL; m_pCorHeader = NULL; + m_relocated = FALSE; } void WebcilDecoder::Reset() @@ -67,6 +68,7 @@ void WebcilDecoder::Reset() m_hasContents = FALSE; m_pHeader = NULL; m_pCorHeader = NULL; + m_relocated = FALSE; } // ------------------------------------------------------------ @@ -134,6 +136,22 @@ BOOL WebcilDecoder::HasWebcilHeaders() const RETURN TRUE; } +BOOL WebcilDecoder::HasBaseRelocations() const +{ + LIMITED_METHOD_CONTRACT; + if (!HasWebcilHeaders()) + return FALSE; + + const WebcilHeader *pHeader = (const WebcilHeader *)m_base; + uint32_t relocSectionIndex = pHeader->Reserved0; + uint16_t numSections = pHeader->CoffSections; + + if (relocSectionIndex == 0 || relocSectionIndex > numSections) + return FALSE; + + return TRUE; +} + CHECK WebcilDecoder::CheckWebcilHeaders() const { CONTRACT_CHECK @@ -741,13 +759,18 @@ BOOL WebcilDecoder::HasDirectoryEntry(int entry) const LIMITED_METHOD_CONTRACT; // Webcil has no PE IMAGE_DATA_DIRECTORY array. - // Only the debug directory is stored in the WebcilHeader. + // Only the debug directory and base relocation directory are supported. if (entry == IMAGE_DIRECTORY_ENTRY_DEBUG && HasWebcilHeaders()) { const WebcilHeader *pHeader = (const WebcilHeader *)m_base; return pHeader->PeDebugRva != 0 && pHeader->PeDebugSize != 0; } + if (entry == IMAGE_DIRECTORY_ENTRY_BASERELOC && HasWebcilHeaders()) + { + return HasBaseRelocations(); + } + return FALSE; } @@ -783,6 +806,36 @@ TADDR WebcilDecoder::GetDirectoryEntryData(int entry, COUNT_T *pSize) const return GetRvaData(debugRva); } + // Base relocations: Reserved0 is the 1-based index of the relocations section + if (entry == IMAGE_DIRECTORY_ENTRY_BASERELOC && HasWebcilHeaders()) + { + const WebcilHeader *pHeader = (const WebcilHeader *)m_base; + uint16_t relocSectionIndex = pHeader->Reserved0; + if (relocSectionIndex == 0) + { + if (pSize != NULL) + *pSize = 0; + return (TADDR)0; + } + + // Convert from 1-based to 0-based index and validate + uint16_t sectionIndex = relocSectionIndex - 1; + if (sectionIndex >= pHeader->CoffSections) + { + if (pSize != NULL) + *pSize = 0; + return (TADDR)0; + } + + const WebcilSectionHeader *sections = (const WebcilSectionHeader *)(m_base + sizeof(WebcilHeader)); + const WebcilSectionHeader *relocSection = §ions[sectionIndex]; + + if (pSize != NULL) + *pSize = relocSection->SizeOfRawData; + + return (TADDR)(m_base + relocSection->PointerToRawData); + } + if (pSize != NULL) *pSize = 0; return (TADDR)0; diff --git a/src/coreclr/vm/peimagelayout.cpp b/src/coreclr/vm/peimagelayout.cpp index 3cdc899d6ad67c..5442b4d17e9cfe 100644 --- a/src/coreclr/vm/peimagelayout.cpp +++ b/src/coreclr/vm/peimagelayout.cpp @@ -233,7 +233,8 @@ void PEImageLayout::ApplyBaseRelocations(bool relocationMustWriteCopy) { STANDARD_VM_CONTRACT; - _ASSERTE(IsPEFormat()); + _ASSERTE(IsPEFormat() || IsWebcilFormat()); + SetRelocated(); // @@ -243,9 +244,19 @@ void PEImageLayout::ApplyBaseRelocations(bool relocationMustWriteCopy) SSIZE_T delta = (SIZE_T) GetBase() - (SIZE_T) GetPreferredBase(); - // Nothing to do - image is loaded at preferred base - if (delta == 0) +#ifdef FEATURE_WEBCIL + SSIZE_T tableBaseDelta = m_tableBaseOffset; +#endif // FEATURE_WEBCIL + + // Nothing to do - image is loaded at preferred base and no table base offset + if (delta == 0 +#ifdef FEATURE_WEBCIL + && tableBaseDelta == 0 +#endif // FEATURE_WEBCIL + ) + { return; + } LOG((LF_LOADER, LL_INFO100, "PEImage: Applying base relocations (preferred: %x, actual: %x)\n", GetPreferredBase(), GetBase())); @@ -283,8 +294,9 @@ void PEImageLayout::ApplyBaseRelocations(bool relocationMustWriteCopy) BYTE * pageAddress = (BYTE *)GetBase() + rva; - // Check whether the page is outside the unprotected region - if ((SIZE_T)(pageAddress - pWriteableRegion) >= cbWriteableRegion) + // Check whether the page is outside the unprotected region. + // Webcil data is already writable, so skip memory protection management. + if (IsPEFormat() && (SIZE_T)(pageAddress - pWriteableRegion) >= cbWriteableRegion) { // Restore the protection if (dwOldProtection != 0) @@ -361,6 +373,16 @@ void PEImageLayout::ApplyBaseRelocations(bool relocationMustWriteCopy) break; #endif +#ifdef FEATURE_WEBCIL + case IMAGE_REL_BASED_WASM32_TABLE: + *(uint32_t *)address += (uint32_t)tableBaseDelta; + break; + + case IMAGE_REL_BASED_WASM64_TABLE: + *(uint64_t *)address += (uint64_t)tableBaseDelta; + break; +#endif // FEATURE_WEBCIL + case IMAGE_REL_BASED_ABSOLUTE: //no adjustment break; @@ -370,22 +392,25 @@ void PEImageLayout::ApplyBaseRelocations(bool relocationMustWriteCopy) } } - BOOL bExecRegion = (dwOldProtection & (PAGE_EXECUTE | PAGE_EXECUTE_READ | - PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) != 0; - - if (bExecRegion && pEndAddressToFlush != NULL) + if (IsPEFormat()) { - // If the current page is not next to the pending region to flush, flush the current pending region and start a new one - if (pageAddress >= pFlushRegion + cbFlushRegion + cbPageSize || pageAddress < pFlushRegion) + BOOL bExecRegion = (dwOldProtection & (PAGE_EXECUTE | PAGE_EXECUTE_READ | + PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) != 0; + + if (bExecRegion && pEndAddressToFlush != NULL) { - if (pFlushRegion != NULL) + // If the current page is not next to the pending region to flush, flush the current pending region and start a new one + if (pageAddress >= pFlushRegion + cbFlushRegion + cbPageSize || pageAddress < pFlushRegion) { - ClrFlushInstructionCache(pFlushRegion, cbFlushRegion); + if (pFlushRegion != NULL) + { + ClrFlushInstructionCache(pFlushRegion, cbFlushRegion); + } + pFlushRegion = pageAddress; } - pFlushRegion = pageAddress; - } - cbFlushRegion = pEndAddressToFlush - pFlushRegion; + cbFlushRegion = pEndAddressToFlush - pFlushRegion; + } } dirPos += fixupsSize; @@ -408,9 +433,9 @@ void PEImageLayout::ApplyBaseRelocations(bool relocationMustWriteCopy) ThrowLastError(); #endif // __APPLE__ && HOST_ARM64 } -#ifdef TARGET_UNIX +#if defined(TARGET_UNIX) && !defined(TARGET_WASM) PAL_LOADMarkSectionAsNotNeeded((void*)dir); -#endif // TARGET_UNIX +#endif // TARGET_UNIX && !TARGET_WASM if (pFlushRegion != NULL) { diff --git a/src/coreclr/vm/peimagelayout.h b/src/coreclr/vm/peimagelayout.h index 76a3b1ec71ab2b..542ec86216569b 100644 --- a/src/coreclr/vm/peimagelayout.h +++ b/src/coreclr/vm/peimagelayout.h @@ -76,6 +76,12 @@ class PEImageLayout void ApplyBaseRelocations(bool relocationMustWriteCopy); +#ifdef FEATURE_WEBCIL +// TODO-WASM: These can be removed very soon, and we can fetch this from the webcil header itself + void SetTableBaseOffset(SSIZE_T tableBaseOffset) { m_tableBaseOffset = tableBaseOffset; } + SSIZE_T GetTableBaseOffset() const { return m_tableBaseOffset; } +#endif + // ------------------------------------------------------------ // Format query // ------------------------------------------------------------ @@ -227,7 +233,7 @@ class PEImageLayout // Protected forwarding helpers for subclass access to PEDecoder protected members IMAGE_NT_HEADERS* FindNTHeaders() const { return m_peDecoder.FindNTHeaders(); } IMAGE_SECTION_HEADER* RvaToSection(RVA rva) const { return m_peDecoder.RvaToSection(rva); } - void SetRelocated() { m_peDecoder.SetRelocated(); } + void SetRelocated(); private: Volatile m_refCount; @@ -238,6 +244,7 @@ class PEImageLayout PEDecoder m_peDecoder; #ifdef FEATURE_WEBCIL WebcilDecoder m_webcilDecoder; + SSIZE_T m_tableBaseOffset; #endif public: diff --git a/src/coreclr/vm/peimagelayout.inl b/src/coreclr/vm/peimagelayout.inl index 562940a9b6004a..bd0d1a8c2b5f28 100644 --- a/src/coreclr/vm/peimagelayout.inl +++ b/src/coreclr/vm/peimagelayout.inl @@ -65,6 +65,9 @@ inline PEImageLayout::~PEImageLayout() inline PEImageLayout::PEImageLayout() : m_refCount(1) , m_format(FORMAT_PE) +#ifdef FEATURE_WEBCIL + , m_tableBaseOffset(0) +#endif , m_pOwner(NULL) { LIMITED_METHOD_CONTRACT; @@ -139,7 +142,13 @@ inline BOOL PEImageLayout::IsMapped() const inline BOOL PEImageLayout::IsRelocated() const { LIMITED_METHOD_DAC_CONTRACT; - PE_OR_WEBCIL(IsRelocated(), FALSE) + DECODER_DISPATCH(IsRelocated()) +} + +inline void PEImageLayout::SetRelocated() +{ + LIMITED_METHOD_DAC_CONTRACT; + DECODER_DISPATCH(SetRelocated()) } inline BOOL PEImageLayout::IsFlat() const @@ -267,7 +276,7 @@ inline BOOL PEImageLayout::IsDll() const inline BOOL PEImageLayout::HasBaseRelocations() const { LIMITED_METHOD_DAC_CONTRACT; - PE_OR_WEBCIL(HasBaseRelocations(), FALSE) + DECODER_DISPATCH(HasBaseRelocations()) } inline const void *PEImageLayout::GetPreferredBase() const