diff --git a/src/coreclr/tools/Common/Wasm/Webcil.cs b/src/coreclr/tools/Common/Wasm/Webcil.cs index 4fdbfdecbe53ad..56bcfba4322889 100644 --- a/src/coreclr/tools/Common/Wasm/Webcil.cs +++ b/src/coreclr/tools/Common/Wasm/Webcil.cs @@ -40,6 +40,8 @@ public struct WebcilHeader public uint PeDebugRva; public uint PeDebugSize; // 28 bytes + public uint TableBase; + // 32 bytes } /// diff --git a/src/mono/mono/metadata/webcil-loader.c b/src/mono/mono/metadata/webcil-loader.c index 3aaeb96823053e..c6c43e3f01dcff 100644 --- a/src/mono/mono/metadata/webcil-loader.c +++ b/src/mono/mono/metadata/webcil-loader.c @@ -13,7 +13,8 @@ /* keep in sync with webcil-writer */ enum { - MONO_WEBCIL_VERSION_MAJOR = 0, + MONO_WEBCIL_VERSION_MAJOR_0 = 0, + MONO_WEBCIL_VERSION_MAJOR_1 = 1, MONO_WEBCIL_VERSION_MINOR = 0, }; @@ -36,6 +37,13 @@ typedef struct MonoWebcilHeader { // 28 bytes } MonoWebcilHeader; +// Version 1 adds a TableBase field after the V0 header +typedef struct MonoWebcilHeader_1 { + MonoWebcilHeader base; + uint32_t table_base; + // 32 bytes +} MonoWebcilHeader_1; + static gboolean find_webcil_in_wasm (const uint8_t *ptr, const uint8_t *boundp, const uint8_t **webcil_payload_start); @@ -84,7 +92,8 @@ do_load_header (const char *raw_data, uint32_t raw_data_len, int32_t offset, Mon memcpy (&wcheader, raw_data + offset, sizeof (wcheader)); if (!(wcheader.id [0] == 'W' && wcheader.id [1] == 'b' && wcheader.id[2] == 'I' && wcheader.id[3] == 'L' && - GUINT16_FROM_LE (wcheader.version_major) == MONO_WEBCIL_VERSION_MAJOR && GUINT16_FROM_LE (wcheader.version_minor) == MONO_WEBCIL_VERSION_MINOR)) + (GUINT16_FROM_LE (wcheader.version_major) == MONO_WEBCIL_VERSION_MAJOR_0 || GUINT16_FROM_LE (wcheader.version_major) == MONO_WEBCIL_VERSION_MAJOR_1) && + GUINT16_FROM_LE (wcheader.version_minor) == MONO_WEBCIL_VERSION_MINOR)) return -1; memset (header, 0, sizeof(MonoDotNetHeader)); @@ -94,7 +103,13 @@ do_load_header (const char *raw_data, uint32_t raw_data_len, int32_t offset, Mon header->datadir.pe_debug.rva = GUINT32_FROM_LE (wcheader.pe_debug_rva); header->datadir.pe_debug.size = GUINT32_FROM_LE (wcheader.pe_debug_size); - offset += sizeof (wcheader); + // V1 header is larger (32 bytes vs 28) + if (GUINT16_FROM_LE (wcheader.version_major) >= MONO_WEBCIL_VERSION_MAJOR_1) { + if (offset + sizeof (MonoWebcilHeader_1) > raw_data_len) + return -1; + offset += sizeof (MonoWebcilHeader_1); + } else + offset += sizeof (wcheader); return offset; } diff --git a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilConverter.cs b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilConverter.cs index b43484c9534d2a..1b14dc17528dc6 100644 --- a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilConverter.cs +++ b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilConverter.cs @@ -39,22 +39,28 @@ FilePosition SectionStart ); private const int SectionAlignment = 16; + private const int V0HeaderSize = 28; + private const int V1HeaderSize = 32; private readonly string _inputPath; private readonly string _outputPath; + private readonly int _webcilVersion; private string InputPath => _inputPath; public bool WrapInWebAssembly { get; set; } = true; - private WebcilConverter(string inputPath, string outputPath) + private WebcilConverter(string inputPath, string outputPath, int webcilVersion) { + if (webcilVersion != 0 && webcilVersion != 1) + throw new ArgumentOutOfRangeException(nameof(webcilVersion), webcilVersion, "Only webcil version 0 and 1 are supported"); _inputPath = inputPath; _outputPath = outputPath; + _webcilVersion = webcilVersion; } - public static WebcilConverter FromPortableExecutable(string inputPath, string outputPath) - => new WebcilConverter(inputPath, outputPath); + public static WebcilConverter FromPortableExecutable(string inputPath, string outputPath, int webcilVersion = 0) + => new WebcilConverter(inputPath, outputPath, webcilVersion); public void ConvertToWebcil() { @@ -104,9 +110,9 @@ public record struct FilePosition(int Position) public static FilePosition operator +(FilePosition left, int right) => new(left.Position + right); } - private static unsafe int SizeOfHeader() + private int SizeOfHeader() { - return sizeof(WebcilHeader); + return _webcilVersion >= 1 ? V1HeaderSize : V0HeaderSize; } public unsafe void GatherInfo(PEReader peReader, out WCFileInfo wcInfo, out PEFileInfo peInfo) @@ -117,7 +123,7 @@ public unsafe void GatherInfo(PEReader peReader, out WCFileInfo wcInfo, out PEFi var sections = headers.SectionHeaders; WebcilHeader header = default; header.Id = WebcilConstants.WEBCIL_MAGIC; - header.VersionMajor = WebcilConstants.WC_VERSION_MAJOR; + header.VersionMajor = (ushort)_webcilVersion; header.VersionMinor = WebcilConstants.WC_VERSION_MINOR; header.CoffSections = (ushort)coffHeader.NumberOfSections; header.Reserved0 = 0; @@ -125,6 +131,10 @@ public unsafe void GatherInfo(PEReader peReader, out WCFileInfo wcInfo, out PEFi header.PeCliHeaderSize = (uint)peHeader.CorHeaderTableDirectory.Size; header.PeDebugRva = (uint)peHeader.DebugTableDirectory.RelativeVirtualAddress; header.PeDebugSize = (uint)peHeader.DebugTableDirectory.Size; + if (_webcilVersion >= 1) + { + header.TableBase = uint.MaxValue; + } // current logical position in the output file FilePosition pos = SizeOfHeader(); @@ -174,7 +184,7 @@ public unsafe void GatherInfo(PEReader peReader, out WCFileInfo wcInfo, out PEFi SectionStart: firstWCSection); } - private static void WriteHeader(Stream s, WebcilHeader webcilHeader) + private void WriteHeader(Stream s, WebcilHeader webcilHeader) { if (!BitConverter.IsLittleEndian) { @@ -186,8 +196,12 @@ private static void WriteHeader(Stream s, WebcilHeader webcilHeader) webcilHeader.PeCliHeaderSize = BinaryPrimitives.ReverseEndianness(webcilHeader.PeCliHeaderSize); webcilHeader.PeDebugRva = BinaryPrimitives.ReverseEndianness(webcilHeader.PeDebugRva); webcilHeader.PeDebugSize = BinaryPrimitives.ReverseEndianness(webcilHeader.PeDebugSize); + if (_webcilVersion >= 1) + { + webcilHeader.TableBase = BinaryPrimitives.ReverseEndianness(webcilHeader.TableBase); + } } - WriteStructure(s, webcilHeader); + WriteStructure(s, webcilHeader, SizeOfHeader()); } private static void WriteSectionHeaders(Stream s, ImmutableArray sectionsHeaders) @@ -223,6 +237,18 @@ private static void WriteStructure(Stream s, T structure) s.Write(new ReadOnlySpan(p, sizeof(T))); } } + + private static void WriteStructure(Stream s, T structure, int size) + where T : unmanaged + { + unsafe + { + if (size > sizeof(T)) + throw new ArgumentOutOfRangeException(nameof(size), size, $"size exceeds struct size {sizeof(T)}"); + byte* p = (byte*)&structure; + s.Write(new ReadOnlySpan(p, size)); + } + } #else private static void WriteStructure(Stream s, T structure) where T : unmanaged @@ -242,6 +268,27 @@ private static void WriteStructure(Stream s, T structure) } s.Write(buffer, 0, size); } + + private static void WriteStructure(Stream s, T structure, int size) + where T : unmanaged + { + int fullSize = Marshal.SizeOf(); + if (size > fullSize) + throw new ArgumentOutOfRangeException(nameof(size), size, $"size exceeds struct size {fullSize}"); + byte[] buffer = new byte[fullSize]; + IntPtr ptr = IntPtr.Zero; + try + { + ptr = Marshal.AllocHGlobal(fullSize); + Marshal.StructureToPtr(structure, ptr, false); + Marshal.Copy(ptr, buffer, 0, fullSize); + } + finally + { + Marshal.FreeHGlobal(ptr); + } + s.Write(buffer, 0, size); + } #endif private static void CopySections(Stream outStream, FileStream inputStream, ImmutableArray peSections, ImmutableArray wcSections) diff --git a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs index c81beb4e8d53ad..fb52e9de15969a 100644 --- a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs +++ b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs @@ -55,17 +55,23 @@ public WebcilReader (Stream stream, string inputPath) : this(stream) InputPath = inputPath; } + // V0 header is 28 bytes, V1 adds a 4-byte TableBase field (32 bytes total) + private const int V0HeaderSize = 28; + private const int V1HeaderSize = 32; + private unsafe bool ReadHeader() { - WebcilHeader header; - var buffer = new byte[Marshal.SizeOf()]; + // Read the V0 portion of the header first (28 bytes) + WebcilHeader header = default; + var buffer = new byte[V0HeaderSize]; if (_stream.Read(buffer, 0, buffer.Length) != buffer.Length) { return false; } fixed (byte* p = buffer) { - header = *(WebcilHeader*)p; + // Copy V0 fields only (28 bytes) into the 32-byte struct + Buffer.MemoryCopy(p, &header, Marshal.SizeOf(), V0HeaderSize); } if (!BitConverter.IsLittleEndian) { @@ -79,11 +85,29 @@ private unsafe bool ReadHeader() header.PeDebugSize = BinaryPrimitives.ReverseEndianness(header.PeDebugSize); } if (header.Id != WebcilConstants.WEBCIL_MAGIC - || header.VersionMajor != WebcilConstants.WC_VERSION_MAJOR + || (header.VersionMajor != 0 && header.VersionMajor != 1) || header.VersionMinor != WebcilConstants.WC_VERSION_MINOR) { return false; } + if (header.VersionMajor >= 1) + { + // V1 has an additional TableBase field + var extra = new byte[V1HeaderSize - V0HeaderSize]; + if (_stream.Read(extra, 0, extra.Length) != extra.Length) + { + return false; + } + header.TableBase = BitConverter.ToUInt32(extra, 0); + if (!BitConverter.IsLittleEndian) + { + header.TableBase = BinaryPrimitives.ReverseEndianness(header.TableBase); + } + } + else + { + header.TableBase = uint.MaxValue; + } _header = header; return true; } @@ -353,7 +377,10 @@ private long TranslateRVA(uint rva) throw new BadImageFormatException("RVA not found in any section", nameof(_stream)); } - private static long SectionDirectoryOffset => Marshal.SizeOf(); + // V0 header is 28 bytes (no TableBase), V1 is 32 bytes + private long SectionDirectoryOffset => _header.VersionMajor >= 1 + ? V1HeaderSize + : V0HeaderSize; private unsafe ImmutableArray ReadSections() { diff --git a/src/tasks/WasmAppBuilder/WebcilConverter.cs b/src/tasks/WasmAppBuilder/WebcilConverter.cs index 526aa62460bf62..8012fa6d5e388c 100644 --- a/src/tasks/WasmAppBuilder/WebcilConverter.cs +++ b/src/tasks/WasmAppBuilder/WebcilConverter.cs @@ -31,9 +31,9 @@ private WebcilConverter(NET.WebAssembly.Webcil.WebcilConverter converter, string Log = logger; } - public static WebcilConverter FromPortableExecutable(string inputPath, string outputPath, LogAdapter logger) + public static WebcilConverter FromPortableExecutable(string inputPath, string outputPath, LogAdapter logger, int webcilVersion = 0) { - var converter = NET.WebAssembly.Webcil.WebcilConverter.FromPortableExecutable(inputPath, outputPath); + var converter = NET.WebAssembly.Webcil.WebcilConverter.FromPortableExecutable(inputPath, outputPath, webcilVersion); return new WebcilConverter(converter, inputPath, outputPath, logger); }