diff --git a/docs/design/mono/webcil.md b/docs/design/mono/webcil.md index 346d7633f6a94c..37d3c4968e4c59 100644 --- a/docs/design/mono/webcil.md +++ b/docs/design/mono/webcil.md @@ -1,4 +1,4 @@ -# WebCIL assembly format +# Webcil assembly format ## Version @@ -12,7 +12,7 @@ customers that certain users are unable to use their apps because firewalls and may prevent browsers from downloading or caching assemblies with a .DLL extension and PE contents. This document defines a new container format for ECMA-335 assemblies that uses the `.wasm` extension -and uses a new WebCIL metadata payload format wrapped in a WebAssembly module. +and uses a new Webcil metadata payload format wrapped in a WebAssembly module. ## Specification @@ -111,21 +111,21 @@ The Webcil headers consist of a Webcil header followed by a sequence of section ``` c struct WebcilHeader { - uint8_t id[4]; // 'W' 'b' 'I' 'L' - // 4 bytes - uint16_t version_major; // 0 - uint16_t version_minor; // 0 - // 8 bytes - uint16_t coff_sections; - uint16_t reserved0; // 0 - // 12 bytes - - uint32_t pe_cli_header_rva; - uint32_t pe_cli_header_size; - // 20 bytes - - uint32_t pe_debug_rva; - uint32_t pe_debug_size; + uint8_t Id[4]; // 'W' 'b' 'I' 'L' + // 4 bytes + uint16_t VersionMajor; // 0 + uint16_t VersionMinor; // 0 + // 8 bytes + uint16_t CoffSections; + uint16_t Reserved0; // 0 + // 12 bytes + + uint32_t PeCliHeaderRva; + uint32_t PeCliHeaderSize; + // 20 bytes + + uint32_t PeDebugRva; + uint32_t PeDebugSize; // 28 bytes }; ``` @@ -139,7 +139,7 @@ of the CLI header, as well as the directory entry for the PE debug directory. #### Section header table -Immediately following the Webcil header is a sequence (whose length is given by `coff_sections` +Immediately following the Webcil header is a sequence (whose length is given by `CoffSections` above) of section headers giving their virtual address and virtual size, as well as the offset in the Webcil payload and the size in the file. This is a subset of the PE section header that includes enough information to correctly interpret the RVAs from the webcil header and from the .NET @@ -147,18 +147,29 @@ metadata. Other information (such as the section names) are not included. ``` c struct SectionHeader { - uint32_t st_virtual_size; - uint32_t st_virtual_address; - uint32_t st_raw_data_size; - uint32_t st_raw_data_ptr; + uint32_t VirtualSize; + uint32_t VirtualAddress; + uint32_t SizeOfRawData; + uint32_t PointerToRawData; }; ``` -(**Note**: the `st_raw_data_ptr` member is an offset from the beginning of the Webcil payload, not from the beginning of the WebAssembly wrapper module.) +(**Note**: the `PointerToRawData` member is an offset from the beginning of the Webcil payload, not from the beginning of the WebAssembly wrapper module.) #### Sections -Immediately following the section table are the sections. These are copied verbatim from the PE file. +The section data starts at the first 16-byte-aligned offset after the end of the +section header table. Any gap between the last section header and the first section's +raw data is filled with zero-valued padding bytes. Each subsequent section likewise +begins at a 16-byte-aligned offset. This alignment guarantees that RVA static fields +(such as those backing `ReadOnlySpan` over types up to `Vector128`) retain +their natural alignment when the payload is loaded into memory at a 16-byte-aligned +base address. + +Because PE `SizeOfRawData` is normally a multiple of the PE `FileAlignment` (≥ 512), +the inter-section padding is almost always zero bytes. In the worst case a single +assembly may gain up to ~30 bytes of padding total (header-to-first-section plus +one boundary per additional section). ### Rationale diff --git a/src/mono/browser/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/browser/debugger/BrowserDebugProxy/DebugStore.cs index 1408f21cfed8c0..2569b7d5ee3982 100644 --- a/src/mono/browser/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/browser/debugger/BrowserDebugProxy/DebugStore.cs @@ -894,7 +894,7 @@ public void LoadInfoFromBytes(MonoProxy monoProxy, SessionId sessionId, Assembly { try { - // First try to read it as a PE file, otherwise try it as a WebCIL file + // First try to read it as a PE file, otherwise try it as a Webcil file var peReader = new PEReader(asmStream); if (!peReader.HasMetadata) throw new BadImageFormatException(); @@ -970,6 +970,7 @@ private void FromPEReader(MonoProxy monoProxy, SessionId sessionId, PEReader peR LoadAssemblyInfo(peReader, name, asmMetadataReader, summary, logger); } + private void FromWebcilReader(MonoProxy monoProxy, SessionId sessionId, WebcilReader wcReader, byte[] pdb, ILogger logger, CancellationToken token) { var debugProvider = new WebcilDebugMetadataProvider(wcReader); diff --git a/src/mono/cmake/config.h.in b/src/mono/cmake/config.h.in index c887a617527d68..ad5ac8564baf11 100644 --- a/src/mono/cmake/config.h.in +++ b/src/mono/cmake/config.h.in @@ -742,7 +742,7 @@ /* Enable System.WeakAttribute support */ #cmakedefine ENABLE_WEAK_ATTR 1 -/* Enable WebCIL image loader */ +/* Enable Webcil image loader */ #cmakedefine ENABLE_WEBCIL 1 /* define if clockgettime exists */ diff --git a/src/mono/cmake/options.cmake b/src/mono/cmake/options.cmake index de513c32012390..4cbe0e7fd41aa8 100644 --- a/src/mono/cmake/options.cmake +++ b/src/mono/cmake/options.cmake @@ -48,7 +48,7 @@ option (ENABLE_OVERRIDABLE_ALLOCATORS "Enable overridable allocator support") option (ENABLE_SIGALTSTACK "Enable support for using sigaltstack for SIGSEGV and stack overflow handling, this doesn't work on some platforms") option (USE_MALLOC_FOR_MEMPOOLS "Use malloc for each single mempool allocation, so tools like Valgrind can run better") option (STATIC_COMPONENTS "Compile mono runtime components as static (not dynamic) libraries") -option (ENABLE_WEBCIL "Enable the WebCIL loader") +option (ENABLE_WEBCIL "Enable the Webcil loader") set (MONO_GC "sgen" CACHE STRING "Garbage collector implementation (sgen or boehm). Default: sgen") set (GC_SUSPEND "default" CACHE STRING "GC suspend method (default, preemptive, coop, hybrid)") diff --git a/src/mono/mono/metadata/webcil-loader.c b/src/mono/mono/metadata/webcil-loader.c index c96523af293734..3aaeb96823053e 100644 --- a/src/mono/mono/metadata/webcil-loader.c +++ b/src/mono/mono/metadata/webcil-loader.c @@ -17,7 +17,7 @@ enum { MONO_WEBCIL_VERSION_MINOR = 0, }; -typedef struct MonoWebCilHeader { +typedef struct MonoWebcilHeader { uint8_t id[4]; // 'W' 'b' 'I' 'L' // 4 bytes uint16_t version_major; // 0 @@ -34,7 +34,7 @@ typedef struct MonoWebCilHeader { uint32_t pe_debug_rva; uint32_t pe_debug_size; // 28 bytes -} MonoWebCilHeader; +} MonoWebcilHeader; static gboolean find_webcil_in_wasm (const uint8_t *ptr, const uint8_t *boundp, const uint8_t **webcil_payload_start); @@ -43,7 +43,7 @@ static gboolean webcil_image_match (MonoImage *image) { gboolean success = FALSE; - if (image->raw_data_len >= sizeof (MonoWebCilHeader)) { + if (image->raw_data_len >= sizeof (MonoWebcilHeader)) { success = image->raw_data[0] == 'W' && image->raw_data[1] == 'b' && image->raw_data[2] == 'I' && image->raw_data[3] == 'L'; if (!success && mono_wasm_module_is_wasm ((const uint8_t*)image->raw_data, (const uint8_t*)image->raw_data + image->raw_data_len)) { @@ -64,7 +64,7 @@ webcil_image_match (MonoImage *image) static int32_t do_load_header (const char *raw_data, uint32_t raw_data_len, int32_t offset, MonoDotNetHeader *header, int32_t *raw_data_rva_map_wasm_bump) { - MonoWebCilHeader wcheader; + MonoWebcilHeader wcheader; const uint8_t *raw_data_bound = (const uint8_t*)raw_data + raw_data_len; *raw_data_rva_map_wasm_bump = 0; if (mono_wasm_module_is_wasm ((const uint8_t*)raw_data, raw_data_bound)) { @@ -79,7 +79,7 @@ do_load_header (const char *raw_data, uint32_t raw_data_len, int32_t offset, Mon offset += offset_adjustment; } - if (offset + sizeof (MonoWebCilHeader) > raw_data_len) + if (offset + sizeof (MonoWebcilHeader) > raw_data_len) return -1; memcpy (&wcheader, raw_data + offset, sizeof (wcheader)); diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index 74353513b2bac3..ccb3c368bb051b 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -68,7 +68,7 @@ Copyright (c) .NET Foundation. All rights reserved. - + true @@ -348,15 +348,15 @@ Copyright (c) .NET Foundation. All rights reserved. - <_WasmBuildWebCilPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'webcil')) - <_WasmBuildTmpWebCilPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'tmp-webcil')) + <_WasmBuildWebcilPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'webcil')) + <_WasmBuildTmpWebcilPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'tmp-webcil')) <_WasmBuildOuputPath>$([MSBuild]::NormalizeDirectory('$(OutputPath)', 'wwwroot')) - - + + - + <_WasmFingerprintPatterns Include="WasmFiles" Pattern="*.wasm" Expression="#[.{fingerprint}]!" /> @@ -367,7 +367,7 @@ Copyright (c) .NET Foundation. All rights reserved. @@ -716,40 +716,40 @@ Copyright (c) .NET Foundation. All rights reserved. - <_WasmPublishWebCilPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'webcil', 'publish')) - <_WasmPublishTmpWebCilPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'tmp-webcil', 'publish')) + <_WasmPublishWebcilPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'webcil', 'publish')) + <_WasmPublishTmpWebcilPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'tmp-webcil', 'publish')) - - + + - + - - <_NewWebCilPublishStaticWebAssetsCandidatesNoMetadata - Include="@(_NewWebCilPublishStaticWebAssetsCandidates)" + <_NewWebcilPublishStaticWebAssetsCandidatesNoMetadata + Include="@(_NewWebcilPublishStaticWebAssetsCandidates)" RemoveMetadata="Integrity;Fingerprint" /> - - + + - + - - + + ../ - `dotnet.boot.js` - contains list of all other assets and their integrity hash and also various configuration flags. - `dotnet.native.wasm` - is the compiled binary of the dotnet (Mono) runtime. - `System.Private.CoreLib.*` - is NET assembly with the core implementation of dotnet runtime and class library -- `*.wasm` - are .NET assemblies stored in `WebCIL` format (for better compatibility with firewalls and virus scanners). +- `*.wasm` - are .NET assemblies stored in `Webcil` format (for better compatibility with firewalls and virus scanners). - `*.dll` - are .NET assemblies stored in Portable Executable format (only used when you use `false`). - `dotnet.js.map` - is a source map file, for easier debugging of the runtime code. It's not included in the published applications. - `dotnet.native.js.symbols` - are debug symbols which help to put `C` runtime method names back to the `.wasm` stack traces. To enable generating it, use `true`. diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs index bdf13fd5662fb7..ab3cef3e812dab 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs @@ -60,7 +60,7 @@ public class ComputeWasmPublishAssets : Task public bool EmitSymbolMap { get; set; } - public bool IsWebCilEnabled { get; set; } + public bool IsWebcilEnabled { get; set; } public bool FingerprintAssets { get; set; } @@ -454,7 +454,7 @@ private void ComputeUpdatedAssemblies( var asset = kvp.Value; var fileName = Path.GetFileName(GetItemSpecWithoutFingerprint(asset)); var assetToUpdateItemSpec = FingerprintAssets ? GetNonFingerprintedAssetItemSpec(asset) : asset.ItemSpec; - if (IsWebCilEnabled) + if (IsWebcilEnabled) fileName = Path.ChangeExtension(fileName, ".dll"); if (resolvedAssembliesToPublish.TryGetValue(fileName, out var existing)) @@ -484,7 +484,7 @@ private void ComputeUpdatedAssemblies( assetsToUpdate.Add(satelliteAssembly.ItemSpec, satelliteAssembly); var culture = satelliteAssembly.GetMetadata("AssetTraitValue"); var fileName = Path.GetFileName(GetItemSpecWithoutFingerprint(satelliteAssembly)); - if (IsWebCilEnabled) + if (IsWebcilEnabled) fileName = Path.ChangeExtension(fileName, ".dll"); if (satelliteAssemblies.TryGetValue((culture, fileName), out var existing)) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs index 4c148adc972b5e..9ae242de37af0c 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs @@ -10,7 +10,7 @@ namespace Microsoft.NET.Sdk.WebAssembly; -public class ConvertDllsToWebCil : Task +public class ConvertDllsToWebcil : Task { [Required] public ITaskItem[] Candidates { get; set; } @@ -25,7 +25,7 @@ public class ConvertDllsToWebCil : Task public bool IsEnabled { get; set; } [Output] - public ITaskItem[] WebCilCandidates { get; set; } + public ITaskItem[] WebcilCandidates { get; set; } protected readonly List _fileWrites = new(); @@ -34,11 +34,11 @@ public class ConvertDllsToWebCil : Task public override bool Execute() { - var webCilCandidates = new List(); + var webcilCandidates = new List(); if (!IsEnabled) { - WebCilCandidates = Candidates; + WebcilCandidates = Candidates; return true; } @@ -56,14 +56,14 @@ public override bool Execute() if (extension != ".dll") { - webCilCandidates.Add(candidate); + webcilCandidates.Add(candidate); continue; } try { TaskItem webcilItem = ConvertDll(tmpDir, candidate); - webCilCandidates.Add(webcilItem); + webcilCandidates.Add(webcilItem); } catch (Exception ex) { @@ -74,7 +74,7 @@ public override bool Execute() Directory.Delete(tmpDir, true); - WebCilCandidates = webCilCandidates.ToArray(); + WebcilCandidates = webcilCandidates.ToArray(); return true; } diff --git a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilConverter.cs b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilConverter.cs index 0a0495f72fef4e..820d38631706e0 100644 --- a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilConverter.cs +++ b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilConverter.cs @@ -28,16 +28,18 @@ public record PEFileInfo( ImmutableArray DebugDirectoryEntries ); - // Intersting stuff we know about the webcil file we're writing + // Interesting stuff we know about the Webcil file we're writing public record WCFileInfo( - // The header of the webcil file + // The header of the Webcil file WebcilHeader Header, - // The section directory of the webcil file + // The section directory of the Webcil file ImmutableArray SectionHeaders, // The file offset of the sections, following the section directory FilePosition SectionStart ); + private const int SectionAlignment = 16; + private readonly string _inputPath; private readonly string _outputPath; @@ -71,9 +73,9 @@ public void ConvertToWebcil() } else { - // if wrapping in WASM, write the webcil payload to memory because we need to discover the length + // if wrapping in WASM, write the Webcil payload to memory because we need to discover the length - // webcil is about the same size as the PE file + // Webcil is about the same size as the PE file using var memoryStream = new MemoryStream(checked((int)inputStream.Length)); WriteConversionTo(memoryStream, inputStream, peInfo, wcInfo); memoryStream.Flush(); @@ -87,8 +89,8 @@ public void WriteConversionTo(Stream outputStream, FileStream inputStream, PEFil { WriteHeader(outputStream, wcInfo.Header); WriteSectionHeaders(outputStream, wcInfo.SectionHeaders); - CopySections(outputStream, inputStream, peInfo.SectionHeaders); - if (wcInfo.Header.pe_debug_size != 0 && wcInfo.Header.pe_debug_rva != 0) + CopySections(outputStream, inputStream, peInfo.SectionHeaders, wcInfo.SectionHeaders); + if (wcInfo.Header.PeDebugSize != 0 && wcInfo.Header.PeDebugRva != 0) { var wcDebugDirectoryEntries = FixupDebugDirectoryEntries(peInfo, wcInfo); OverwriteDebugDirectoryEntries(outputStream, wcInfo, wcDebugDirectoryEntries); @@ -114,25 +116,25 @@ public unsafe void GatherInfo(PEReader peReader, out WCFileInfo wcInfo, out PEFi var coffHeader = headers.CoffHeader!; var sections = headers.SectionHeaders; WebcilHeader header; - header.id[0] = (byte)'W'; - header.id[1] = (byte)'b'; - header.id[2] = (byte)'I'; - header.id[3] = (byte)'L'; - header.version_major = Internal.Constants.WC_VERSION_MAJOR; - header.version_minor = Internal.Constants.WC_VERSION_MINOR; - header.coff_sections = (ushort)coffHeader.NumberOfSections; - header.reserved0 = 0; - header.pe_cli_header_rva = (uint)peHeader.CorHeaderTableDirectory.RelativeVirtualAddress; - header.pe_cli_header_size = (uint)peHeader.CorHeaderTableDirectory.Size; - header.pe_debug_rva = (uint)peHeader.DebugTableDirectory.RelativeVirtualAddress; - header.pe_debug_size = (uint)peHeader.DebugTableDirectory.Size; + header.Id[0] = (byte)'W'; + header.Id[1] = (byte)'b'; + header.Id[2] = (byte)'I'; + header.Id[3] = (byte)'L'; + header.VersionMajor = Internal.Constants.WC_VERSION_MAJOR; + header.VersionMinor = Internal.Constants.WC_VERSION_MINOR; + header.CoffSections = (ushort)coffHeader.NumberOfSections; + header.Reserved0 = 0; + header.PeCliHeaderRva = (uint)peHeader.CorHeaderTableDirectory.RelativeVirtualAddress; + header.PeCliHeaderSize = (uint)peHeader.CorHeaderTableDirectory.Size; + header.PeDebugRva = (uint)peHeader.DebugTableDirectory.RelativeVirtualAddress; + header.PeDebugSize = (uint)peHeader.DebugTableDirectory.Size; // current logical position in the output file FilePosition pos = SizeOfHeader(); // position of the current section in the output file - // initially it's after all the section headers - FilePosition curSectionPos = pos + sizeof(WebcilSectionHeader) * coffHeader.NumberOfSections; - // The first WC section is immediately after the section directory + // initially it's after all the section headers, aligned to 16-byte boundary + FilePosition curSectionPos = AlignTo((pos + sizeof(WebcilSectionHeader) * coffHeader.NumberOfSections).Position, SectionAlignment); + // The first WC section is at the aligned position after the section directory FilePosition firstWCSection = curSectionPos; FilePosition firstPESection = 0; @@ -159,7 +161,7 @@ public unsafe void GatherInfo(PEReader peReader, out WCFileInfo wcInfo, out PEFi ); pos += sizeof(WebcilSectionHeader); - curSectionPos += sectionHeader.SizeOfRawData; + curSectionPos = AlignTo((curSectionPos + sectionHeader.SizeOfRawData).Position, SectionAlignment); headerBuilder.Add(newHeader); } @@ -179,13 +181,13 @@ private static void WriteHeader(Stream s, WebcilHeader webcilHeader) { if (!BitConverter.IsLittleEndian) { - webcilHeader.version_major = BinaryPrimitives.ReverseEndianness(webcilHeader.version_major); - webcilHeader.version_minor = BinaryPrimitives.ReverseEndianness(webcilHeader.version_minor); - webcilHeader.coff_sections = BinaryPrimitives.ReverseEndianness(webcilHeader.coff_sections); - webcilHeader.pe_cli_header_rva = BinaryPrimitives.ReverseEndianness(webcilHeader.pe_cli_header_rva); - webcilHeader.pe_cli_header_size = BinaryPrimitives.ReverseEndianness(webcilHeader.pe_cli_header_size); - webcilHeader.pe_debug_rva = BinaryPrimitives.ReverseEndianness(webcilHeader.pe_debug_rva); - webcilHeader.pe_debug_size = BinaryPrimitives.ReverseEndianness(webcilHeader.pe_debug_size); + webcilHeader.VersionMajor = BinaryPrimitives.ReverseEndianness(webcilHeader.VersionMajor); + webcilHeader.VersionMinor = BinaryPrimitives.ReverseEndianness(webcilHeader.VersionMinor); + webcilHeader.CoffSections = BinaryPrimitives.ReverseEndianness(webcilHeader.CoffSections); + webcilHeader.PeCliHeaderRva = BinaryPrimitives.ReverseEndianness(webcilHeader.PeCliHeaderRva); + webcilHeader.PeCliHeaderSize = BinaryPrimitives.ReverseEndianness(webcilHeader.PeCliHeaderSize); + webcilHeader.PeDebugRva = BinaryPrimitives.ReverseEndianness(webcilHeader.PeDebugRva); + webcilHeader.PeDebugSize = BinaryPrimitives.ReverseEndianness(webcilHeader.PeDebugSize); } WriteStructure(s, webcilHeader); } @@ -244,13 +246,19 @@ private static void WriteStructure(Stream s, T structure) } #endif - private static void CopySections(Stream outStream, FileStream inputStream, ImmutableArray peSections) + private static void CopySections(Stream outStream, FileStream inputStream, ImmutableArray peSections, ImmutableArray wcSections) { // endianness: ok, we're just copying from one stream to another - foreach (var peHeader in peSections) + for (int i = 0; i < peSections.Length; i++) { - var buffer = new byte[peHeader.SizeOfRawData]; - inputStream.Seek(peHeader.PointerToRawData, SeekOrigin.Begin); + // Write zero padding to reach the aligned section position + int paddingNeeded = wcSections[i].PointerToRawData - (int)outStream.Position; + if (paddingNeeded > 0) + { + outStream.Write(new byte[paddingNeeded], 0, paddingNeeded); + } + var buffer = new byte[peSections[i].SizeOfRawData]; + inputStream.Seek(peSections[i].PointerToRawData, SeekOrigin.Begin); ReadExactly(inputStream, buffer); outStream.Write(buffer, 0, buffer.Length); } @@ -281,7 +289,13 @@ private static FilePosition GetPositionOfRelativeVirtualAddress(ImmutableArray= section.VirtualAddress && relativeVirtualAddress < section.VirtualAddress + section.VirtualSize) { - FilePosition pos = section.PointerToRawData + ((int)relativeVirtualAddress - section.VirtualAddress); + uint offsetInSection = relativeVirtualAddress - (uint)section.VirtualAddress; + if (offsetInSection >= (uint)section.SizeOfRawData) + { + throw new InvalidOperationException( + $"relative virtual address 0x{relativeVirtualAddress:X} is in virtual tail of section (offset {offsetInSection} >= SizeOfRawData {section.SizeOfRawData})"); + } + FilePosition pos = section.PointerToRawData + (int)offsetInSection; return pos; } } @@ -289,40 +303,12 @@ private static FilePosition GetPositionOfRelativeVirtualAddress(ImmutableArray peSections, FilePosition fileOffset) - { - foreach (var section in peSections) - { - if (fileOffset.Position >= section.PointerToRawData && fileOffset.Position < section.PointerToRawData + section.SizeOfRawData) - { - return (section, fileOffset.Position - section.PointerToRawData); - } - } - - throw new InvalidOperationException($"file offset not in any section (Webcil) for {InputPath}"); - } - - private void GetSectionFromFileOffset(ImmutableArray sections, FilePosition fileOffset) - { - foreach (var section in sections) - { - if (fileOffset.Position >= section.PointerToRawData && fileOffset.Position < section.PointerToRawData + section.SizeOfRawData) - { - return; - } - } - - throw new InvalidOperationException($"file offset {fileOffset.Position} not in any section (PE) for {InputPath}"); - } - // Make a new set of debug directory entries that - // have their data pointers adjusted to be relative to the start of the webcil file. - // This is necessary because the debug directory entires in the PE file are relative to the start of the PE file, - // and a PE header is bigger than a webcil header. - private ImmutableArray FixupDebugDirectoryEntries(PEFileInfo peInfo, WCFileInfo wcInfo) + // have their data pointers adjusted to be relative to the start of the Webcil file. + // This is necessary because the debug directory entries in the PE file are relative to the start of the PE file, + // and section offsets may differ between PE and Webcil due to header sizes and alignment. + private static ImmutableArray FixupDebugDirectoryEntries(PEFileInfo peInfo, WCFileInfo wcInfo) { - int dataPointerAdjustment = peInfo.SectionStart.Position - wcInfo.SectionStart.Position; ImmutableArray entries = peInfo.DebugDirectoryEntries; ImmutableArray.Builder newEntries = ImmutableArray.CreateBuilder(entries.Length); foreach (var entry in entries) @@ -335,28 +321,48 @@ private ImmutableArray FixupDebugDirectoryEntries(PEFileInf } else { - // the "DataPointer" field is a file offset in the PE file, adjust the entry wit the corresponding offset in the Webcil file - var newDataPointer = entry.DataPointer - dataPointerAdjustment; + // the "DataPointer" field is a file offset in the PE file, translate it to the corresponding offset in the Webcil file + var newDataPointer = TranslateFileOffset(peInfo.SectionHeaders, wcInfo.SectionHeaders, entry.DataPointer); newEntry = new DebugDirectoryEntry(entry.Stamp, entry.MajorVersion, entry.MinorVersion, entry.Type, entry.DataSize, entry.DataRelativeVirtualAddress, newDataPointer); - GetSectionFromFileOffset(peInfo.SectionHeaders, entry.DataPointer); - // validate that the new entry is in some section - GetSectionFromFileOffset(wcInfo.SectionHeaders, newDataPointer); } newEntries.Add(newEntry); } return newEntries.MoveToImmutable(); } + private static int TranslateFileOffset(ImmutableArray peSections, ImmutableArray wcSections, int peFileOffset) + { + for (int i = 0; i < peSections.Length; i++) + { + var peSection = peSections[i]; + if (peFileOffset >= peSection.PointerToRawData && peFileOffset < peSection.PointerToRawData + peSection.SizeOfRawData) + { + int offsetInSection = peFileOffset - peSection.PointerToRawData; + return wcSections[i].PointerToRawData + offsetInSection; + } + } + + throw new InvalidOperationException($"file offset {peFileOffset} not in any PE section"); + } + + private static int AlignTo(int value, int alignment) => (value + (alignment - 1)) & ~(alignment - 1); + private static void OverwriteDebugDirectoryEntries(Stream s, WCFileInfo wcInfo, ImmutableArray entries) { - FilePosition debugDirectoryPos = GetPositionOfRelativeVirtualAddress(wcInfo.SectionHeaders, wcInfo.Header.pe_debug_rva); + FilePosition debugDirectoryPos = GetPositionOfRelativeVirtualAddress(wcInfo.SectionHeaders, wcInfo.Header.PeDebugRva); using var writer = new BinaryWriter(s, System.Text.Encoding.UTF8, leaveOpen: true); writer.Seek(debugDirectoryPos.Position, SeekOrigin.Begin); foreach (var entry in entries) { WriteDebugDirectoryEntry(writer, entry); } - // TODO check that we overwrite with the same size as the original + writer.Flush(); + long bytesWritten = s.Position - debugDirectoryPos.Position; + if (bytesWritten != wcInfo.Header.PeDebugSize) + { + throw new InvalidOperationException( + $"Debug directory size mismatch: wrote {bytesWritten} bytes, expected {wcInfo.Header.PeDebugSize}"); + } // restore the stream position writer.Seek(0, SeekOrigin.End); diff --git a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebciHeader.cs b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilHeader.cs similarity index 61% rename from src/tasks/Microsoft.NET.WebAssembly.Webcil/WebciHeader.cs rename to src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilHeader.cs index 33dcc85791fff4..419e058bd180f2 100644 --- a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebciHeader.cs +++ b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilHeader.cs @@ -6,7 +6,7 @@ namespace Microsoft.NET.WebAssembly.Webcil; /// -/// The header of a WebCIL file. +/// The header of a Webcil file. /// /// /// @@ -15,19 +15,19 @@ namespace Microsoft.NET.WebAssembly.Webcil; [StructLayout(LayoutKind.Sequential, Pack = 1)] public unsafe struct WebcilHeader { - public fixed byte id[4]; // 'W' 'b' 'I' 'L' + public fixed byte Id[4]; // 4 bytes - public ushort version_major; // 0 - public ushort version_minor; // 0 + public ushort VersionMajor; + public ushort VersionMinor; // 8 bytes - public ushort coff_sections; - public ushort reserved0; // 0 + public ushort CoffSections; + public ushort Reserved0; // 12 bytes - public uint pe_cli_header_rva; - public uint pe_cli_header_size; + public uint PeCliHeaderRva; + public uint PeCliHeaderSize; // 20 bytes - public uint pe_debug_rva; - public uint pe_debug_size; + public uint PeDebugRva; + public uint PeDebugSize; // 28 bytes } diff --git a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs index 6e079b58c32599..379313c0a939db 100644 --- a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs +++ b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs @@ -69,18 +69,18 @@ private unsafe bool ReadHeader() } if (!BitConverter.IsLittleEndian) { - header.version_major = BinaryPrimitives.ReverseEndianness(header.version_major); - header.version_minor = BinaryPrimitives.ReverseEndianness(header.version_minor); - header.coff_sections = BinaryPrimitives.ReverseEndianness(header.coff_sections); - header.pe_cli_header_rva = BinaryPrimitives.ReverseEndianness(header.pe_cli_header_rva); - header.pe_cli_header_size = BinaryPrimitives.ReverseEndianness(header.pe_cli_header_size); - header.pe_debug_rva = BinaryPrimitives.ReverseEndianness(header.pe_debug_rva); - header.pe_debug_rva = BinaryPrimitives.ReverseEndianness(header.pe_debug_size); - } - if (header.id[0] != 'W' || header.id[1] != 'b' - || header.id[2] != 'I' || header.id[3] != 'L' - || header.version_major != Internal.Constants.WC_VERSION_MAJOR - || header.version_minor != Internal.Constants.WC_VERSION_MINOR) + header.VersionMajor = BinaryPrimitives.ReverseEndianness(header.VersionMajor); + header.VersionMinor = BinaryPrimitives.ReverseEndianness(header.VersionMinor); + header.CoffSections = BinaryPrimitives.ReverseEndianness(header.CoffSections); + header.PeCliHeaderRva = BinaryPrimitives.ReverseEndianness(header.PeCliHeaderRva); + header.PeCliHeaderSize = BinaryPrimitives.ReverseEndianness(header.PeCliHeaderSize); + header.PeDebugRva = BinaryPrimitives.ReverseEndianness(header.PeDebugRva); + header.PeDebugSize = BinaryPrimitives.ReverseEndianness(header.PeDebugSize); + } + if (header.Id[0] != 'W' || header.Id[1] != 'b' + || header.Id[2] != 'I' || header.Id[3] != 'L' + || header.VersionMajor != Internal.Constants.WC_VERSION_MAJOR + || header.VersionMinor != Internal.Constants.WC_VERSION_MINOR) { return false; } @@ -92,7 +92,7 @@ private unsafe bool ReadCorHeader() { // we can't construct CorHeader because it's constructor is internal // but we don't care, really, we only want the metadata directory entry - var pos = TranslateRVA(_header.pe_cli_header_rva); + var pos = TranslateRVA(_header.PeCliHeaderRva); if (_stream.Seek(pos, SeekOrigin.Begin) != pos) { return false; @@ -124,12 +124,12 @@ public MetadataReaderProvider GetMetadataReaderProvider() public ImmutableArray ReadDebugDirectory() { - var debugRVA = _header.pe_debug_rva; + var debugRVA = _header.PeDebugRva; if (debugRVA == 0) { return ImmutableArray.Empty; } - var debugSize = _header.pe_debug_size; + var debugSize = _header.PeDebugSize; if (debugSize == 0) { return ImmutableArray.Empty; @@ -342,7 +342,12 @@ private long TranslateRVA(uint rva) { if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize) { - return section.PointerToRawData + (rva - section.VirtualAddress) + _webcilInWasmOffset; + uint offset = (uint)(rva - section.VirtualAddress); + if (offset >= section.SizeOfRawData) + { + throw new BadImageFormatException("RVA maps to an offset beyond the section's raw data", nameof(_stream)); + } + return section.PointerToRawData + offset + _webcilInWasmOffset; } } throw new BadImageFormatException("RVA not found in any section", nameof(_stream)); @@ -352,11 +357,11 @@ private long TranslateRVA(uint rva) private unsafe ImmutableArray ReadSections() { - WebcilSectionHeader secheader; - var sections = ImmutableArray.CreateBuilder(_header.coff_sections); + WebcilSectionHeader sectionHeader; + var sections = ImmutableArray.CreateBuilder(_header.CoffSections); var buffer = new byte[Marshal.SizeOf()]; _stream.Seek(SectionDirectoryOffset + _webcilInWasmOffset, SeekOrigin.Begin); - for (int i = 0; i < _header.coff_sections; i++) + for (int i = 0; i < _header.CoffSections; i++) { if (_stream.Read(buffer, 0, buffer.Length) != buffer.Length) { @@ -364,7 +369,7 @@ private unsafe ImmutableArray ReadSections() } fixed (byte* p = buffer) { - secheader = (*(WebcilSectionHeader*)p); + sectionHeader = (*(WebcilSectionHeader*)p); } if (!BitConverter.IsLittleEndian) { @@ -372,16 +377,16 @@ private unsafe ImmutableArray ReadSections() ( new WebcilSectionHeader ( - virtualSize: BinaryPrimitives.ReverseEndianness(secheader.VirtualSize), - virtualAddress: BinaryPrimitives.ReverseEndianness(secheader.VirtualAddress), - sizeOfRawData: BinaryPrimitives.ReverseEndianness(secheader.SizeOfRawData), - pointerToRawData: BinaryPrimitives.ReverseEndianness(secheader.PointerToRawData) + virtualSize: BinaryPrimitives.ReverseEndianness(sectionHeader.VirtualSize), + virtualAddress: BinaryPrimitives.ReverseEndianness(sectionHeader.VirtualAddress), + sizeOfRawData: BinaryPrimitives.ReverseEndianness(sectionHeader.SizeOfRawData), + pointerToRawData: BinaryPrimitives.ReverseEndianness(sectionHeader.PointerToRawData) ) ); } else { - sections.Add(secheader); + sections.Add(sectionHeader); } } return sections.MoveToImmutable(); diff --git a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilWasmWrapper.cs b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilWasmWrapper.cs index f47a1247474784..d78fbfd1bc01a3 100644 --- a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilWasmWrapper.cs +++ b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilWasmWrapper.cs @@ -185,7 +185,7 @@ private void WriteDataSection(BinaryWriter writer) writer.Write((byte)1); // passive segment writer.Write(ulebWebcilPayloadSize, 0, ulebWebcilPayloadSize.Length); // segment size: _webcilPayloadSize if (writer.BaseStream.Position % WebcilPayloadInternalAlignment != 0) { - throw new Exception ($"predited offset {payloadOffset}, actual position {writer.BaseStream.Position}"); + throw new Exception ($"Expected offset {payloadOffset}, actual position {writer.BaseStream.Position}"); } _webcilPayloadStream.CopyTo(writer.BaseStream); // payload is the entire webcil content }