diff --git a/docs/design/mono/webcil.md b/docs/design/mono/webcil.md index 346d7633f6a94c..e168340adbf766 100644 --- a/docs/design/mono/webcil.md +++ b/docs/design/mono/webcil.md @@ -2,7 +2,7 @@ ## Version -This is version 0.0 of the Webcil payload format. +This is version 1.0 of the Webcil payload format. This is version 0 of the WebAssembly module Webcil wrapper. ## Motivation @@ -111,27 +111,27 @@ 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; - // 28 bytes + uint8_t id[4]; // 'W' 'b' 'I' 'L' + // 4 bytes + uint16_t version_major; // 1 + 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; + // 28 bytes }; ``` 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. +minor format (must be 1 and 0). Then a count of the section headers and two reserved bytes. The next pairs of integers are a subset of the PE Header data directory specifying the RVA and size of the CLI header, as well as the directory entry for the PE debug directory. @@ -140,25 +140,47 @@ 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` -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 -metadata. Other information (such as the section names) are not included. +above) of PE/COFF `IMAGE_SECTION_HEADER` structures (40 bytes each). Each header contains the +section name, virtual address and size, file offset and size, and other PE section attributes. +Unused fields (such as `PointerToRelocations`, `NumberOfRelocations`, `PointerToLinenumbers`, +`NumberOfLinenumbers`, and `Characteristics`) are set to zero. The `Name` field is copied from +the original PE section header. + +Using the standard `IMAGE_SECTION_HEADER` layout allows runtimes to reference the section headers +directly from the loaded file image without converting from a compact representation at load time. ``` c +// IMAGE_SECTION_HEADER — 40 bytes, same layout as PE/COFF struct SectionHeader { - uint32_t st_virtual_size; - uint32_t st_virtual_address; - uint32_t st_raw_data_size; - uint32_t st_raw_data_ptr; + char Name[8]; + uint32_t VirtualSize; + uint32_t VirtualAddress; + uint32_t SizeOfRawData; + uint32_t PointerToRawData; + uint32_t PointerToRelocations; // 0 + uint32_t PointerToLinenumbers; // 0 + uint16_t NumberOfRelocations; // 0 + uint16_t NumberOfLinenumbers; // 0 + uint32_t Characteristics; // 0 }; ``` -(**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/eng/testing/tests.browser.targets b/eng/testing/tests.browser.targets index 1c90efeb8d0bae..8724486f79dcef 100644 --- a/eng/testing/tests.browser.targets +++ b/eng/testing/tests.browser.targets @@ -59,10 +59,9 @@ true - + false - false diff --git a/src/coreclr/inc/pedecoder.h b/src/coreclr/inc/pedecoder.h index 7d4982a742b770..322f82d5c6f987 100644 --- a/src/coreclr/inc/pedecoder.h +++ b/src/coreclr/inc/pedecoder.h @@ -46,6 +46,10 @@ typedef DPTR(struct READYTORUN_SECTION) PTR_READYTORUN_SECTION; typedef DPTR(IMAGE_COR20_HEADER) PTR_IMAGE_COR20_HEADER; +#ifdef TARGET_BROWSER +#include "webcil.h" +#endif // TARGET_BROWSER + // -------------------------------------------------------------------------------- // Forward declared types // -------------------------------------------------------------------------------- @@ -167,6 +171,17 @@ class PEDecoder BOOL HasNTHeaders() const; CHECK CheckNTHeaders() const; +#ifdef TARGET_BROWSER + BOOL HasWebcilHeaders() const; + CHECK CheckWebcilHeaders() const; + inline BOOL HasHeaders() const { return HasWebcilHeaders() || HasNTHeaders(); } +#else + inline BOOL HasWebcilHeaders() const { return FALSE; } + inline BOOL HasHeaders() const { return HasNTHeaders(); } +#endif + + CHECK CheckHeaders() const; + IMAGE_NT_HEADERS32 *GetNTHeaders32() const; IMAGE_NT_HEADERS64 *GetNTHeaders64() const; BOOL Has32BitNTHeaders() const; @@ -189,7 +204,6 @@ class PEDecoder SIZE_T GetSizeOfHeapCommit() const; UINT32 GetLoaderFlags() const; UINT32 GetWin32VersionValue() const; - COUNT_T GetNumberOfRvaAndSizes() const; COUNT_T GetNumberOfSections() const; PTR_IMAGE_SECTION_HEADER FindFirstSection() const; IMAGE_SECTION_HEADER *FindSection(LPCSTR sectionName) const; @@ -365,6 +379,8 @@ class PEDecoder IMAGE_DATA_DIRECTORY *GetMetaDataHelper(METADATA_SECTION_TYPE type) const; + COUNT_T GetNumberOfRvaAndSizes() const; + static PTR_IMAGE_SECTION_HEADER FindFirstSection(IMAGE_NT_HEADERS * pNTHeaders); IMAGE_COR20_HEADER *FindCorHeader() const; @@ -399,6 +415,8 @@ class PEDecoder FLAG_NATIVE_CHECKED = 0x80, FLAG_HAS_NO_READYTORUN_HEADER = 0x100, + FLAG_WEBCIL = 0x200, + FLAG_WEBCIL_CHECKED = 0x400, }; TADDR m_base; diff --git a/src/coreclr/inc/pedecoder.inl b/src/coreclr/inc/pedecoder.inl index 485d7661c68d84..fcda883fe5e2ff 100644 --- a/src/coreclr/inc/pedecoder.inl +++ b/src/coreclr/inc/pedecoder.inl @@ -502,13 +502,25 @@ inline BOOL PEDecoder::HasDirectoryEntry(int entry) const CONTRACTL { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); + PRECONDITION(HasHeaders()); NOTHROW; GC_NOTRIGGER; SUPPORTS_DAC; } CONTRACTL_END; +#ifdef TARGET_BROWSER + if (HasWebcilHeaders()) + { + const WebcilHeader* pWC = (const WebcilHeader*)(TADDR)m_base; + if (entry == IMAGE_DIRECTORY_ENTRY_COMHEADER) + return VAL32(pWC->pe_cli_header_rva) != 0; + if (entry == IMAGE_DIRECTORY_ENTRY_DEBUG) + return VAL32(pWC->pe_debug_rva) != 0; + return FALSE; + } +#endif + if (Has32BitNTHeaders()) return (GetNTHeaders32()->OptionalHeader.DataDirectory[entry].VirtualAddress != 0); else @@ -546,8 +558,8 @@ inline TADDR PEDecoder::GetDirectoryEntryData(int entry, COUNT_T *pSize) const CONTRACT(TADDR) { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); - PRECONDITION(CheckDirectoryEntry(entry, 0, NULL_OK)); + PRECONDITION(CheckHeaders()); + PRECONDITION((HasWebcilHeaders() || CheckDirectoryEntry(entry, 0, NULL_OK))); PRECONDITION(CheckPointer(pSize, NULL_OK)); NOTHROW; GC_NOTRIGGER; @@ -557,6 +569,71 @@ inline TADDR PEDecoder::GetDirectoryEntryData(int entry, COUNT_T *pSize) const } CONTRACT_END; +#ifdef TARGET_BROWSER + if (HasWebcilHeaders()) + { + const WebcilHeader* pWC = (const WebcilHeader*)(TADDR)m_base; + RVA rva = 0; + COUNT_T size = 0; + if (entry == IMAGE_DIRECTORY_ENTRY_COMHEADER) + { + rva = VAL32(pWC->pe_cli_header_rva); + size = VAL32(pWC->pe_cli_header_size); + } + else if (entry == IMAGE_DIRECTORY_ENTRY_DEBUG) + { + rva = VAL32(pWC->pe_debug_rva); + size = VAL32(pWC->pe_debug_size); + } + else + { + // WebCIL only supports COMHEADER and DEBUG directory entries. + _ASSERTE(!"Unsupported directory entry for WebCIL"); + if (pSize != NULL) + *pSize = 0; + RETURN (TADDR)NULL; + } + + // Validate that the {rva, size} range falls within a section's + // backed data. The normal PE path does this via CheckDirectoryEntry + // / CheckRva; for WebCIL we must check explicitly. + if (rva == 0) + { + if (pSize != NULL) + *pSize = 0; + RETURN (TADDR)NULL; + } + + IMAGE_SECTION_HEADER *section = RvaToSection(rva); + if (section == NULL) + { + if (pSize != NULL) + *pSize = 0; + RETURN (TADDR)NULL; + } + + // RvaToSection guarantees rva >= sectionBase, so the subtraction + // is safe. Verify the full [rva, rva+size) range fits within the + // section's raw data (for flat/unmapped images) or virtual extent. + RVA sectionBase = VAL32(section->VirtualAddress); + _ASSERTE(rva >= sectionBase); + COUNT_T sectionLimit = IsMapped() + ? VAL32(section->Misc.VirtualSize) + : VAL32(section->SizeOfRawData); + COUNT_T offsetInSection = rva - sectionBase; + if (size > sectionLimit - offsetInSection) + { + if (pSize != NULL) + *pSize = 0; + RETURN (TADDR)NULL; + } + + if (pSize != NULL) + *pSize = size; + RETURN GetRvaData(rva); + } +#endif + IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(entry); if (pSize != NULL) @@ -570,8 +647,8 @@ inline TADDR PEDecoder::GetDirectoryData(IMAGE_DATA_DIRECTORY *pDir) const CONTRACT(TADDR) { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); - PRECONDITION(CheckDirectory(pDir, 0, NULL_OK)); + PRECONDITION(CheckHeaders()); + PRECONDITION((HasWebcilHeaders() || CheckDirectory(pDir, 0, NULL_OK))); NOTHROW; GC_NOTRIGGER; SUPPORTS_DAC; @@ -588,8 +665,8 @@ inline TADDR PEDecoder::GetDirectoryData(IMAGE_DATA_DIRECTORY *pDir, COUNT_T *pS CONTRACT(TADDR) { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); - PRECONDITION(CheckDirectory(pDir, 0, NULL_OK)); + PRECONDITION(CheckHeaders()); + PRECONDITION((HasWebcilHeaders() || CheckDirectory(pDir, 0, NULL_OK))); PRECONDITION(CheckPointer(pSize)); NOTHROW; GC_NOTRIGGER; @@ -625,7 +702,7 @@ inline BOOL PEDecoder::HasCorHeader() const CONTRACTL { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); + PRECONDITION(HasHeaders()); NOTHROW; SUPPORTS_DAC; GC_NOTRIGGER; @@ -656,7 +733,7 @@ inline COUNT_T PEDecoder::RvaToOffset(RVA rva) const CONTRACTL { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckHeaders()); PRECONDITION(CheckRva(rva,NULL_OK)); NOTHROW; CANNOT_TAKE_LOCK; @@ -680,7 +757,7 @@ inline RVA PEDecoder::OffsetToRva(COUNT_T fileOffset) const CONTRACTL { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckHeaders()); PRECONDITION(CheckOffset(fileOffset,NULL_OK)); NOTHROW; GC_NOTRIGGER; @@ -734,7 +811,7 @@ inline CHECK PEDecoder::CheckStrongNameSignature() const CONTRACT_CHECK { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckHeaders()); PRECONDITION(HasCorHeader()); PRECONDITION(HasStrongNameSignature()); NOTHROW; @@ -852,7 +929,7 @@ inline IMAGE_COR20_HEADER *PEDecoder::GetCorHeader() const CONTRACT(IMAGE_COR20_HEADER *) { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckHeaders()); PRECONDITION(HasCorHeader()); NOTHROW; GC_NOTRIGGER; @@ -871,7 +948,9 @@ inline IMAGE_COR20_HEADER *PEDecoder::GetCorHeader() const inline BOOL PEDecoder::IsNativeMachineFormat() const { - if (!HasContents() || !HasNTHeaders() ) + if (!HasContents() || !HasHeaders() ) + return FALSE; + if (HasWebcilHeaders()) return FALSE; _ASSERTE(m_pNTHeaders); WORD expectedFormat = HasCorHeader() && HasReadyToRunHeader() ? @@ -883,7 +962,9 @@ inline BOOL PEDecoder::IsNativeMachineFormat() const inline BOOL PEDecoder::IsI386() const { - if (!HasContents() || !HasNTHeaders() ) + if (!HasContents() || !HasHeaders() ) + return FALSE; + if (HasWebcilHeaders()) return FALSE; _ASSERTE(m_pNTHeaders); //do not call GetNTHeaders as we do not want to bother with PE32->PE32+ conversion @@ -907,13 +988,21 @@ inline COUNT_T PEDecoder::GetNumberOfSections() const CONTRACTL { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); + PRECONDITION(HasHeaders()); NOTHROW; GC_NOTRIGGER; SUPPORTS_DAC; } CONTRACTL_END; +#ifdef TARGET_BROWSER + if (HasWebcilHeaders()) + { + const WebcilHeader* pWC = (const WebcilHeader*)(TADDR)m_base; + return VAL16(pWC->coff_sections); + } +#endif + return VAL16(FindNTHeaders()->FileHeader.NumberOfSections); } @@ -930,7 +1019,7 @@ inline PTR_IMAGE_SECTION_HEADER PEDecoder::FindFirstSection() const CONTRACT(IMAGE_SECTION_HEADER *) { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); + PRECONDITION(HasHeaders()); NOTHROW; GC_NOTRIGGER; POSTCONDITION(CheckPointer(RETVAL)); @@ -938,6 +1027,11 @@ inline PTR_IMAGE_SECTION_HEADER PEDecoder::FindFirstSection() const } CONTRACT_END; +#ifdef TARGET_BROWSER + if (HasWebcilHeaders()) + RETURN dac_cast(m_base + sizeof(WebcilHeader)); +#endif + RETURN FindFirstSection(FindNTHeaders()); } @@ -1027,65 +1121,73 @@ inline void PEDecoder::GetPEKindAndMachine(DWORD * pdwPEKind, DWORD *pdwMachine) CONTRACTL_END; DWORD dwKind=0,dwMachine=0; - if(HasContents() && HasNTHeaders()) + if(HasContents() && HasHeaders()) { - dwMachine = GetMachine(); + if (HasWebcilHeaders()) + { + dwKind = peILonly; + dwMachine = IMAGE_FILE_MACHINE_I386; + } + else + { + dwMachine = GetMachine(); - BOOL fIsPE32Plus = !Has32BitNTHeaders(); + BOOL fIsPE32Plus = !Has32BitNTHeaders(); - if (fIsPE32Plus) - dwKind |= (DWORD)pe32Plus; + if (fIsPE32Plus) + dwKind |= (DWORD)pe32Plus; - if (HasCorHeader()) - { - IMAGE_COR20_HEADER * pCorHdr = GetCorHeader(); - if(pCorHdr != NULL) + if (HasCorHeader()) { - DWORD dwCorFlags = pCorHdr->Flags; - - if (dwCorFlags & VAL32(COMIMAGE_FLAGS_ILONLY)) + IMAGE_COR20_HEADER * pCorHdr = GetCorHeader(); + if(pCorHdr != NULL) { - dwKind |= (DWORD)peILonly; + DWORD dwCorFlags = pCorHdr->Flags; + + if (dwCorFlags & VAL32(COMIMAGE_FLAGS_ILONLY)) + { + dwKind |= (DWORD)peILonly; #ifdef HOST_64BIT - // compensate for shim promotion of PE32/ILONLY headers to PE32+ on WIN64 - if (fIsPE32Plus && (GetMachine() == IMAGE_FILE_MACHINE_I386)) - dwKind &= ~((DWORD)pe32Plus); + // compensate for shim promotion of PE32/ILONLY headers to PE32+ on WIN64 + if (fIsPE32Plus && (GetMachine() == IMAGE_FILE_MACHINE_I386)) + dwKind &= ~((DWORD)pe32Plus); #endif - } + } - if (COR_IS_32BIT_REQUIRED(dwCorFlags)) - dwKind |= (DWORD)pe32BitRequired; - else if (COR_IS_32BIT_PREFERRED(dwCorFlags)) - dwKind |= (DWORD)pe32BitPreferred; + if (COR_IS_32BIT_REQUIRED(dwCorFlags)) + dwKind |= (DWORD)pe32BitRequired; + else if (COR_IS_32BIT_PREFERRED(dwCorFlags)) + dwKind |= (DWORD)pe32BitPreferred; - // compensate for MC++ peculiarity - if(dwKind == 0) - dwKind = (DWORD)pe32BitRequired; - } - else - { - dwKind |= (DWORD)pe32Unmanaged; - } - - if (HasReadyToRunHeader()) - { - if (dwMachine == IMAGE_FILE_MACHINE_NATIVE_NI) + // compensate for MC++ peculiarity + if(dwKind == 0) + dwKind = (DWORD)pe32BitRequired; + } + else { - // Supply the original machine type to the assembly binder - dwMachine = IMAGE_FILE_MACHINE_NATIVE; + dwKind |= (DWORD)pe32Unmanaged; } - if ((GetReadyToRunHeader()->CoreHeader.Flags & READYTORUN_FLAG_PLATFORM_NEUTRAL_SOURCE) != 0) + if (HasReadyToRunHeader()) { - // Supply the original PEKind/Machine to the assembly binder to make the full assembly name look like the original - dwKind = peILonly; - dwMachine = IMAGE_FILE_MACHINE_I386; + if (dwMachine == IMAGE_FILE_MACHINE_NATIVE_NI) + { + // Supply the original machine type to the assembly binder + dwMachine = IMAGE_FILE_MACHINE_NATIVE; + } + + if ((GetReadyToRunHeader()->CoreHeader.Flags & READYTORUN_FLAG_PLATFORM_NEUTRAL_SOURCE) != 0) + { + // Supply the original PEKind/Machine to the assembly binder to make the full assembly name look like the original + dwKind = peILonly; + dwMachine = IMAGE_FILE_MACHINE_I386; + } } } - } - else - { - dwKind |= (DWORD)pe32Unmanaged; + else + { + dwKind |= (DWORD)pe32Unmanaged; + } } } diff --git a/src/coreclr/inc/webcil.h b/src/coreclr/inc/webcil.h new file mode 100644 index 00000000000000..f3504b64a46f9c --- /dev/null +++ b/src/coreclr/inc/webcil.h @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef WEBCIL_H_ +#define WEBCIL_H_ + +#include + +#define WEBCIL_MAGIC_W 'W' +#define WEBCIL_MAGIC_B 'b' +#define WEBCIL_MAGIC_I 'I' +#define WEBCIL_MAGIC_L 'L' +#define WEBCIL_VERSION_MAJOR 1 +#define WEBCIL_VERSION_MINOR 0 + +#pragma pack(push, 1) + +struct WebcilHeader { + uint8_t id[4]; + uint16_t version_major; + uint16_t version_minor; + uint16_t coff_sections; + uint16_t reserved0; + uint32_t pe_cli_header_rva; + uint32_t pe_cli_header_size; + uint32_t pe_debug_rva; + uint32_t pe_debug_size; +}; // 28 bytes + +#pragma pack(pop) + +static_assert(sizeof(WebcilHeader) == 28, "WebcilHeader must be 28 bytes"); +// Section headers following WebcilHeader are standard IMAGE_SECTION_HEADER (40 bytes each). + +#endif // WEBCIL_H_ diff --git a/src/coreclr/utilcode/pedecoder.cpp b/src/coreclr/utilcode/pedecoder.cpp index c71e19d1b7a7f7..fd06763ffbf2a7 100644 --- a/src/coreclr/utilcode/pedecoder.cpp +++ b/src/coreclr/utilcode/pedecoder.cpp @@ -13,6 +13,10 @@ #include "mdcommon.h" #include "nibblemapmacros.h" +#ifdef TARGET_BROWSER +#include "webcil.h" +#endif + CHECK PEDecoder::CheckFormat() const { CONTRACT_CHECK @@ -25,6 +29,21 @@ CHECK PEDecoder::CheckFormat() const CHECK(HasContents()); +#ifdef TARGET_BROWSER + if (HasWebcilHeaders()) + { + CHECK(CheckWebcilHeaders()); + + if (HasCorHeader()) + { + CHECK(CheckCorHeader()); + + if (IsILOnly()) + CHECK(CheckILOnly()); + } + } + else +#endif // TARGET_BROWSER if (HasNTHeaders()) { CHECK(CheckNTHeaders()); @@ -70,7 +89,7 @@ CHECK PEDecoder::CheckCORFormat() const CONTRACT_CHECK_END; CHECK(CheckFormat()); - CHECK(HasNTHeaders()); + CHECK(HasHeaders()); CHECK(HasCorHeader()); CHECK_OK; @@ -89,7 +108,7 @@ CHECK PEDecoder::CheckILFormat() const CONTRACT_CHECK_END; CHECK(CheckFormat()); - CHECK(HasNTHeaders()); + CHECK(HasHeaders()); CHECK(HasCorHeader()); CHECK_OK; @@ -108,7 +127,7 @@ CHECK PEDecoder::CheckILOnlyFormat() const CONTRACT_CHECK_END; CHECK(CheckFormat()); - CHECK(HasNTHeaders()); + CHECK(HasHeaders()); CHECK(HasCorHeader()); CHECK(IsILOnly()); @@ -197,6 +216,120 @@ BOOL PEDecoder::HasNTHeaders() const RETURN TRUE; } +#ifdef TARGET_BROWSER +BOOL PEDecoder::HasWebcilHeaders() const +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + PRECONDITION(HasContents()); + } + CONTRACT_END; + + if (m_flags & FLAG_WEBCIL) + RETURN TRUE; + + if (m_size < sizeof(WebcilHeader)) + RETURN FALSE; + + const WebcilHeader* pWC = (const WebcilHeader*)(TADDR)m_base; + + if (pWC->id[0] != WEBCIL_MAGIC_W + || pWC->id[1] != WEBCIL_MAGIC_B + || pWC->id[2] != WEBCIL_MAGIC_I + || pWC->id[3] != WEBCIL_MAGIC_L) + { + RETURN FALSE; + } + + if (VAL16(pWC->version_major) != WEBCIL_VERSION_MAJOR + || VAL16(pWC->version_minor) != WEBCIL_VERSION_MINOR) + { + RETURN FALSE; + } + + if (VAL16(pWC->coff_sections) == 0) + RETURN FALSE; + + S_SIZE_T cbHeaderAndSections(S_SIZE_T(sizeof(WebcilHeader)) + + S_SIZE_T(static_cast(VAL16(pWC->coff_sections))) * S_SIZE_T(sizeof(IMAGE_SECTION_HEADER))); + if (cbHeaderAndSections.IsOverflow()) + RETURN FALSE; + + if (m_size < cbHeaderAndSections.Value()) + RETURN FALSE; + + const_cast(this)->m_flags |= FLAG_WEBCIL; + + RETURN TRUE; +} + +CHECK PEDecoder::CheckWebcilHeaders() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + PRECONDITION(HasContents()); + } + CONTRACT_CHECK_END; + + if (m_flags & FLAG_WEBCIL_CHECKED) + CHECK_OK; + + CHECK(HasWebcilHeaders()); + + const WebcilHeader* pWC = (const WebcilHeader*)(TADDR)m_base; + uint16_t nSections = VAL16(pWC->coff_sections); + const IMAGE_SECTION_HEADER* pSections = (const IMAGE_SECTION_HEADER*)(m_base + sizeof(WebcilHeader)); + + // Validate every section header. + // This mirrors the spirit of CheckNTHeaders() for PE files: + // - raw data must lie within the file + // - no arithmetic overflow on ptr + size + // - sections must be ordered by VirtualAddress and must not overlap + UINT32 previousVAEnd = 0; + UINT32 previousOffsetEnd = 0; + for (uint16_t i = 0; i < nSections; i++) + { + UINT32 vaStart = VAL32(pSections[i].VirtualAddress); + UINT32 vaSize = VAL32(pSections[i].Misc.VirtualSize); + UINT32 rawOffset = VAL32(pSections[i].PointerToRawData); + UINT32 rawSize = VAL32(pSections[i].SizeOfRawData); + + // Overflow checks + S_SIZE_T endInFile(S_SIZE_T(rawOffset) + S_SIZE_T(rawSize)); + CHECK(!endInFile.IsOverflow()); + + // Raw data must fit within the file + CHECK(endInFile.Value() <= m_size); + + // VA overflow check + S_SIZE_T vaEnd(S_SIZE_T(vaStart) + S_SIZE_T(vaSize)); + CHECK(!vaEnd.IsOverflow()); + + // Sections must be ordered by VA and must not overlap + CHECK(vaStart >= previousVAEnd); + + // Raw data regions must not overlap (allow rawSize == 0 gaps) + if (rawSize != 0) + CHECK(rawOffset >= previousOffsetEnd); + + previousVAEnd = (UINT32)vaEnd.Value(); + previousOffsetEnd = (UINT32)endInFile.Value(); + } + + const_cast(this)->m_flags |= FLAG_WEBCIL_CHECKED; + + CHECK_OK; +} +#endif // TARGET_BROWSER + CHECK PEDecoder::CheckNTHeaders() const { CONTRACT_CHECK @@ -344,6 +477,31 @@ CHECK PEDecoder::CheckNTHeaders() const CHECK_OK; } +CHECK PEDecoder::CheckHeaders() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + PRECONDITION(HasContents()); + } + CONTRACT_CHECK_END; + +#ifdef TARGET_BROWSER + if (HasWebcilHeaders()) + { + CHECK(CheckWebcilHeaders()); + CHECK_OK; + } +#endif + + CHECK(CheckNTHeaders()); + + CHECK_OK; +} + CHECK PEDecoder::CheckSection(COUNT_T previousAddressEnd, COUNT_T addressStart, COUNT_T addressSize, COUNT_T previousOffsetEnd, COUNT_T offsetStart, COUNT_T offsetSize) const { @@ -423,7 +581,7 @@ BOOL PEDecoder::HasWriteableSections() const PTR_IMAGE_SECTION_HEADER pSection = FindFirstSection(); _ASSERTE(pSection != NULL); - PTR_IMAGE_SECTION_HEADER pSectionEnd = pSection + VAL16(FindNTHeaders()->FileHeader.NumberOfSections); + PTR_IMAGE_SECTION_HEADER pSectionEnd = pSection + GetNumberOfSections(); while (pSection < pSectionEnd) { @@ -461,7 +619,7 @@ CHECK PEDecoder::CheckDirectory(IMAGE_DATA_DIRECTORY *pDir, int forbiddenFlags, CONTRACT_CHECK { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckHeaders()); PRECONDITION(CheckPointer(pDir)); NOTHROW; GC_NOTRIGGER; @@ -770,8 +928,8 @@ IMAGE_SECTION_HEADER *PEDecoder::RvaToSection(RVA rva) const } CONTRACT_END; - PTR_IMAGE_SECTION_HEADER section = dac_cast(FindFirstSection(FindNTHeaders())); - PTR_IMAGE_SECTION_HEADER sectionEnd = section + VAL16(FindNTHeaders()->FileHeader.NumberOfSections); + PTR_IMAGE_SECTION_HEADER section = FindFirstSection(); + PTR_IMAGE_SECTION_HEADER sectionEnd = section + GetNumberOfSections(); while (section < sectionEnd) { @@ -805,7 +963,7 @@ IMAGE_SECTION_HEADER *PEDecoder::OffsetToSection(COUNT_T fileOffset) const CONTRACT(IMAGE_SECTION_HEADER *) { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckHeaders()); NOTHROW; GC_NOTRIGGER; POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); @@ -813,8 +971,8 @@ IMAGE_SECTION_HEADER *PEDecoder::OffsetToSection(COUNT_T fileOffset) const } CONTRACT_END; - PTR_IMAGE_SECTION_HEADER section = dac_cast(FindFirstSection(FindNTHeaders())); - PTR_IMAGE_SECTION_HEADER sectionEnd = section + VAL16(FindNTHeaders()->FileHeader.NumberOfSections); + PTR_IMAGE_SECTION_HEADER section = FindFirstSection(); + PTR_IMAGE_SECTION_HEADER sectionEnd = section + GetNumberOfSections(); while (section < sectionEnd) { @@ -837,7 +995,7 @@ TADDR PEDecoder::GetRvaData(RVA rva, IsNullOK ok /*= NULL_NOT_OK*/) const CONTRACT(TADDR) { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckHeaders()); PRECONDITION(CheckRva(rva, NULL_OK)); NOTHROW; GC_NOTRIGGER; @@ -997,21 +1155,34 @@ CHECK PEDecoder::CheckCorHeader() const if (m_flags & FLAG_COR_CHECKED) CHECK_OK; - CHECK(CheckNTHeaders()); +#ifdef TARGET_BROWSER + if (HasWebcilHeaders()) + { + CHECK(HasCorHeader()); - CHECK(HasCorHeader()); + const WebcilHeader* pWC = (const WebcilHeader*)(TADDR)m_base; + CHECK(VAL32(pWC->pe_cli_header_size) >= sizeof(IMAGE_COR20_HEADER)); + CHECK(CheckRva(VAL32(pWC->pe_cli_header_rva), sizeof(IMAGE_COR20_HEADER))); + } + else +#endif + { + CHECK(CheckNTHeaders()); - IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_COMHEADER); + CHECK(HasCorHeader()); - CHECK(CheckDirectory(pDir, IMAGE_SCN_MEM_WRITE, NULL_NOT_OK)); + IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_COMHEADER); - CHECK(VAL32(pDir->Size) >= sizeof(IMAGE_COR20_HEADER)); + CHECK(CheckDirectory(pDir, IMAGE_SCN_MEM_WRITE, NULL_NOT_OK)); - IMAGE_SECTION_HEADER *section = RvaToSection(VAL32(pDir->VirtualAddress)); - CHECK(section != NULL); - CHECK((section->Characteristics & VAL32(IMAGE_SCN_MEM_READ))!=0); + CHECK(VAL32(pDir->Size) >= sizeof(IMAGE_COR20_HEADER)); - CHECK(CheckRva(VAL32(pDir->VirtualAddress), sizeof(IMAGE_COR20_HEADER))); + IMAGE_SECTION_HEADER *section = RvaToSection(VAL32(pDir->VirtualAddress)); + CHECK(section != NULL); + CHECK((section->Characteristics & VAL32(IMAGE_SCN_MEM_READ))!=0); + + CHECK(CheckRva(VAL32(pDir->VirtualAddress), sizeof(IMAGE_COR20_HEADER))); + } IMAGE_COR20_HEADER *pCor = GetCorHeader(); @@ -1349,6 +1520,15 @@ CHECK PEDecoder::CheckILOnly() const CHECK(CheckCorHeader()); +#ifdef TARGET_BROWSER + if (HasWebcilHeaders()) + { + // WebCIL images are always IL-only by definition. + const_cast(this)->m_flags |= FLAG_IL_ONLY_CHECKED; + CHECK_OK; + } +#endif + if (HasReadyToRunHeader()) { // Pretend R2R images are IL-only @@ -2345,7 +2525,7 @@ PTR_IMAGE_DEBUG_DIRECTORY PEDecoder::GetDebugDirectoryEntry(UINT index) const CONTRACT(PTR_IMAGE_DEBUG_DIRECTORY) { INSTANCE_CHECK; - PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckHeaders()); NOTHROW; GC_NOTRIGGER; SUPPORTS_DAC; diff --git a/src/coreclr/vm/peassembly.cpp b/src/coreclr/vm/peassembly.cpp index 6551714eab7817..b8519f7e49714c 100644 --- a/src/coreclr/vm/peassembly.cpp +++ b/src/coreclr/vm/peassembly.cpp @@ -84,7 +84,9 @@ void PEAssembly::EnsureLoaded() ValidatePEFileMachineType(this); #if !defined(TARGET_64BIT) - if (!GetPEImage()->Has32BitNTHeaders()) + // WebCIL images don't have NT headers (HasNTHeaders() returns FALSE), + // so this guard correctly skips the check for WebCIL on 32-bit platforms. + if (GetPEImage()->HasNTHeaders() && !GetPEImage()->Has32BitNTHeaders()) { // Tried to load 64-bit assembly on 32-bit platform. EEFileLoadException::Throw(this, COR_E_BADIMAGEFORMAT, NULL); @@ -205,7 +207,7 @@ PTR_CVOID PEAssembly::GetMetadata(COUNT_T *pSize) CONTRACT_END; if (IsReflectionEmit() - || !GetPEImage()->HasNTHeaders() + || !GetPEImage()->HasHeaders() || !GetPEImage()->HasCorHeader()) { if (pSize != NULL) @@ -234,7 +236,7 @@ PTR_CVOID PEAssembly::GetLoadedMetadata(COUNT_T *pSize) CONTRACT_END; if (!HasLoadedPEImage() - || !GetLoadedLayout()->HasNTHeaders() + || !GetLoadedLayout()->HasHeaders() || !GetLoadedLayout()->HasCorHeader()) { if (pSize != NULL) @@ -386,7 +388,7 @@ void PEAssembly::OpenMDImport() if (m_pMDImport != NULL) return; if (!IsReflectionEmit() - && GetPEImage()->HasNTHeaders() + && GetPEImage()->HasHeaders() && GetPEImage()->HasCorHeader()) { m_pMDImport=GetPEImage()->GetMDImport(); diff --git a/src/coreclr/vm/peimage.cpp b/src/coreclr/vm/peimage.cpp index 4362b3b571d28e..b1e0ee971d3277 100644 --- a/src/coreclr/vm/peimage.cpp +++ b/src/coreclr/vm/peimage.cpp @@ -305,7 +305,7 @@ void PEImage::OpenMDImport() IMDInternalImport* m_pNewImport; const void* pMeta=NULL; COUNT_T cMeta=0; - if(HasNTHeaders() && HasCorHeader()) + if(HasHeaders() && HasCorHeader()) pMeta=GetMetadata(&cMeta); if(pMeta==NULL) diff --git a/src/coreclr/vm/peimage.h b/src/coreclr/vm/peimage.h index 76e579a527960a..bf00b9075bdfef 100644 --- a/src/coreclr/vm/peimage.h +++ b/src/coreclr/vm/peimage.h @@ -158,6 +158,7 @@ class PEImage final BOOL IsPtrInImage(PTR_CVOID data); BOOL HasNTHeaders(); + BOOL HasHeaders(); BOOL HasCorHeader(); BOOL HasReadyToRunHeader(); BOOL HasDirectoryEntry(int entry); diff --git a/src/coreclr/vm/peimage.inl b/src/coreclr/vm/peimage.inl index b59895f49b6964..a5a5882a7c4256 100644 --- a/src/coreclr/vm/peimage.inl +++ b/src/coreclr/vm/peimage.inl @@ -228,6 +228,12 @@ inline BOOL PEImage::HasNTHeaders() return GetOrCreateLayout(PEImageLayout::LAYOUT_ANY)->HasNTHeaders(); } +inline BOOL PEImage::HasHeaders() +{ + WRAPPER_NO_CONTRACT; + return GetOrCreateLayout(PEImageLayout::LAYOUT_ANY)->HasHeaders(); +} + inline BOOL PEImage::HasCorHeader() { WRAPPER_NO_CONTRACT; diff --git a/src/mono/browser/build/WasmApp.InTree.props b/src/mono/browser/build/WasmApp.InTree.props index b83e9994dc24f8..85bc987c3abe0a 100644 --- a/src/mono/browser/build/WasmApp.InTree.props +++ b/src/mono/browser/build/WasmApp.InTree.props @@ -14,10 +14,9 @@ - + false - false diff --git a/src/mono/mono/metadata/webcil-loader.c b/src/mono/mono/metadata/webcil-loader.c index c96523af293734..eaed638beef655 100644 --- a/src/mono/mono/metadata/webcil-loader.c +++ b/src/mono/mono/metadata/webcil-loader.c @@ -13,14 +13,14 @@ /* keep in sync with webcil-writer */ enum { - MONO_WEBCIL_VERSION_MAJOR = 0, + MONO_WEBCIL_VERSION_MAJOR = 1, 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 + uint16_t version_major; // 1 uint16_t version_minor; // 0 // 8 bytes uint16_t coff_sections; @@ -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,12 +79,13 @@ 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)); 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 && + GUINT16_FROM_LE (wcheader.version_minor) == MONO_WEBCIL_VERSION_MINOR)) return -1; memset (header, 0, sizeof(MonoDotNetHeader)); @@ -101,21 +102,29 @@ do_load_header (const char *raw_data, uint32_t raw_data_len, int32_t offset, Mon int32_t mono_webcil_load_section_table (const char *raw_data, uint32_t raw_data_len, int32_t offset, int32_t webcil_section_adjustment, MonoSectionTable *t) { - /* WebCIL section table entries are a subset of a PE section - * header. Initialize just the parts we have. + /* WebCIL v1.0 section headers are standard IMAGE_SECTION_HEADER (40 bytes). + * Layout: Name[8], VirtualSize, VirtualAddress, SizeOfRawData, + * PointerToRawData, plus unused trailing fields. */ - uint32_t st [4]; + #define IMAGE_SECTION_HEADER_SIZE 40 if (G_UNLIKELY (offset < 0)) return offset; - if ((uint32_t)offset > raw_data_len) + if ((uint32_t)(offset + IMAGE_SECTION_HEADER_SIZE) > raw_data_len) return -1; - memcpy (st, raw_data + offset, sizeof (st)); - t->st_virtual_size = GUINT32_FROM_LE (st [0]); - t->st_virtual_address = GUINT32_FROM_LE (st [1]); - t->st_raw_data_size = GUINT32_FROM_LE (st [2]); - t->st_raw_data_ptr = GUINT32_FROM_LE (st [3]) + (uint32_t)webcil_section_adjustment; - offset += sizeof(st); + + const uint8_t *p = (const uint8_t *)(raw_data + offset); + uint32_t virtual_size, virtual_address, raw_data_size, raw_data_ptr; + memcpy (&virtual_size, p + 8, sizeof (uint32_t)); + memcpy (&virtual_address, p + 12, sizeof (uint32_t)); + memcpy (&raw_data_size, p + 16, sizeof (uint32_t)); + memcpy (&raw_data_ptr, p + 20, sizeof (uint32_t)); + + t->st_virtual_size = GUINT32_FROM_LE (virtual_size); + t->st_virtual_address = GUINT32_FROM_LE (virtual_address); + t->st_raw_data_size = GUINT32_FROM_LE (raw_data_size); + t->st_raw_data_ptr = GUINT32_FROM_LE (raw_data_ptr) + (uint32_t)webcil_section_adjustment; + offset += IMAGE_SECTION_HEADER_SIZE; return offset; } 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 707ce66956448e..047c9f8e28ea1d 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 @@ -347,15 +347,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}]!" /> @@ -365,7 +365,7 @@ Copyright (c) .NET Foundation. All rights reserved. @@ -713,40 +713,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" /> - - + + - + - - + + = new Map(); -// eslint-disable-next-line @typescript-eslint/no-unused-vars -let wasmMemory: WebAssembly.Memory = undefined as any; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -let wasmMainTable: WebAssembly.Table = undefined as any; // eslint-disable-next-line @typescript-eslint/no-unused-vars export function registerPdbBytes(bytes: Uint8Array, virtualPath: string) { @@ -21,7 +17,6 @@ export function registerPdbBytes(bytes: Uint8Array, virtualPath: string) { export function registerDllBytes(bytes: Uint8Array, virtualPath: string) { const sp = _ems_.stackSave(); try { - const sizeOfPtr = 4; const ptrPtr = _ems_.stackAlloc(sizeOfPtr); if (_ems_._posix_memalign(ptrPtr as any, 16, bytes.length)) { throw new Error("posix_memalign failed"); @@ -29,8 +24,8 @@ export function registerDllBytes(bytes: Uint8Array, virtualPath: string) { const ptr = _ems_.HEAPU32[ptrPtr as any >>> 2]; _ems_.HEAPU8.set(bytes, ptr >>> 0); - const name = virtualPath.substring(virtualPath.lastIndexOf("/") + 1); + const name = virtualPath.substring(virtualPath.lastIndexOf("/") + 1); _ems_.dotnetLogger.debug(`Registered assembly '${virtualPath}' (name: '${name}') at ${ptr.toString(16)} length ${bytes.length}`); loadedAssemblies.set(virtualPath, { ptr, length: bytes.length }); loadedAssemblies.set(name, { ptr, length: bytes.length }); @@ -39,6 +34,50 @@ export function registerDllBytes(bytes: Uint8Array, virtualPath: string) { } } +export async function instantiateWebCILModule(webCILPromise: Promise, memory: WebAssembly.Memory, virtualPath: string): Promise { + + const imports: WebAssembly.Imports = { + webcil: { + memory, + } + }; + + const { instance } = await instantiateWasm(webCILPromise, imports, false); + const webcilVersion = (instance.exports.webcilVersion as WebAssembly.Global).value; + if (webcilVersion !== 0) { + throw new Error(`Unsupported WebCIL version: ${webcilVersion}`); + } + + const sp = _ems_.stackSave(); + try { + const sizePtr = _ems_.stackAlloc(sizeOfPtr); + const getWebcilSize = instance.exports.getWebcilSize as (destPtr: number) => void; + getWebcilSize(sizePtr as any); + const payloadSize = _ems_.HEAPU32[sizePtr as any >>> 2]; + + if (payloadSize === 0) { + throw new Error("WebCIL payload size is 0"); + } + + const ptrPtr = _ems_.stackAlloc(sizeOfPtr); + if (_ems_._posix_memalign(ptrPtr as any, 16, payloadSize)) { + throw new Error("posix_memalign failed for WebCIL payload"); + } + + const payloadPtr = _ems_.HEAPU32[ptrPtr as any >>> 2]; + + const getWebcilPayload = instance.exports.getWebcilPayload as (ptr: number, size: number) => void; + getWebcilPayload(payloadPtr, payloadSize); + + const name = virtualPath.substring(virtualPath.lastIndexOf("/") + 1); + _ems_.dotnetLogger.debug(`Registered WebCIL assembly '${virtualPath}' (name: '${name}') at ${payloadPtr.toString(16)} length ${payloadSize}`); + loadedAssemblies.set(virtualPath, { ptr: payloadPtr, length: payloadSize }); + loadedAssemblies.set(name, { ptr: payloadPtr, length: payloadSize }); + } finally { + _ems_.stackRestore(sp); + } +} + export function BrowserHost_ExternalAssemblyProbe(pathPtr: CharPtr, outDataStartPtr: VoidPtrPtr, outSize: VoidPtr) { const path = _ems_.UTF8ToString(pathPtr); const assembly = loadedAssemblies.get(path); @@ -59,7 +98,6 @@ export function BrowserHost_ExternalAssemblyProbe(pathPtr: CharPtr, outDataStart export function loadIcuData(bytes: Uint8Array) { const sp = _ems_.stackSave(); try { - const sizeOfPtr = 4; const ptrPtr = _ems_.stackAlloc(sizeOfPtr); if (_ems_._posix_memalign(ptrPtr as any, 16, bytes.length)) { throw new Error("posix_memalign failed for ICU data"); @@ -100,11 +138,11 @@ export function installVfsFile(bytes: Uint8Array, asset: VfsAsset) { _ems_.FS.createDataFile(parentDirectory, fileName, bytes, true /* canRead */, true /* canWrite */, true /* canOwn */); } -export async function instantiateWasm(wasmPromise: Promise, imports: WebAssembly.Imports, isStreaming: boolean, isMainModule: boolean): Promise<{ instance: WebAssembly.Instance; module: WebAssembly.Module; }> { +export async function instantiateWasm(wasmPromise: Promise, imports: WebAssembly.Imports, isMainModule: boolean): Promise<{ instance: WebAssembly.Instance; module: WebAssembly.Module; }> { let instance: WebAssembly.Instance; let module: WebAssembly.Module; const res = await checkResponseOk(wasmPromise); - if (!hasInstantiateStreaming || !isStreaming || !res.isStreamingOk) { + if (!hasInstantiateStreaming || !res.isStreamingOk) { const data = await res.arrayBuffer(); module = await WebAssembly.compile(data); instance = await WebAssembly.instantiate(module, imports); @@ -113,10 +151,6 @@ export async function instantiateWasm(wasmPromise: Promise, imports: W instance = instantiated.instance; module = instantiated.module; } - if (isMainModule) { - wasmMemory = instance.exports.memory as WebAssembly.Memory; - wasmMainTable = instance.exports.__indirect_function_table as WebAssembly.Table; - } return { instance, module }; async function checkResponseOk(wasmPromise: Promise | undefined): Promise { diff --git a/src/native/corehost/browserhost/host/host.ts b/src/native/corehost/browserhost/host/host.ts index a8107f16ee36e4..c5f60d99b3aa33 100644 --- a/src/native/corehost/browserhost/host/host.ts +++ b/src/native/corehost/browserhost/host/host.ts @@ -3,7 +3,7 @@ import type { CharPtrPtr, VoidPtr } from "./types"; import { _ems_ } from "../../../libs/Common/JavaScript/ems-ambient"; -import { browserVirtualAppBase } from "./per-module"; +import { browserVirtualAppBase, sizeOfPtr } from "./per-module"; const HOST_PROPERTY_RUNTIME_CONTRACT = "HOST_RUNTIME_CONTRACT"; const HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES = "TRUSTED_PLATFORM_ASSEMBLIES"; @@ -23,8 +23,8 @@ export function initializeCoreCLR(): number { } } - const assemblyPaths = loaderConfig.resources!.assembly.map(asset => asset.virtualPath); - const coreAssemblyPaths = loaderConfig.resources!.coreAssembly.map(asset => asset.virtualPath); + const assemblyPaths = loaderConfig.resources!.assembly.map(asset => asset.virtualPath.replace(/\.wasm$/, ".dll")); + const coreAssemblyPaths = loaderConfig.resources!.coreAssembly.map(asset => asset.virtualPath.replace(/\.wasm$/, ".dll")); const tpa = [...coreAssemblyPaths, ...assemblyPaths].join(":"); runtimeConfigProperties.set(HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES, tpa); runtimeConfigProperties.set(HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES, loaderConfig.virtualWorkingDirectory!); @@ -35,8 +35,8 @@ export function initializeCoreCLR(): number { runtimeConfigProperties.set(HOST_PROPERTY_RUNTIME_CONTRACT, `0x${(hostContractPtr as unknown as number).toString(16)}`); const buffers: VoidPtr[] = []; - const appctx_keys = _ems_._malloc(4 * runtimeConfigProperties.size) as any as CharPtrPtr; - const appctx_values = _ems_._malloc(4 * runtimeConfigProperties.size) as any as CharPtrPtr; + const appctx_keys = _ems_._malloc(sizeOfPtr * runtimeConfigProperties.size) as any as CharPtrPtr; + const appctx_values = _ems_._malloc(sizeOfPtr * runtimeConfigProperties.size) as any as CharPtrPtr; buffers.push(appctx_keys as any); buffers.push(appctx_values as any); @@ -44,8 +44,8 @@ export function initializeCoreCLR(): number { for (const [key, value] of runtimeConfigProperties.entries()) { const keyPtr = _ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(key); const valuePtr = _ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(value); - _ems_.dotnetApi.setHeapU32((appctx_keys as any) + (propertyCount * 4), keyPtr); - _ems_.dotnetApi.setHeapU32((appctx_values as any) + (propertyCount * 4), valuePtr); + _ems_.dotnetApi.setHeapU32((appctx_keys as any) + (propertyCount * sizeOfPtr), keyPtr); + _ems_.dotnetApi.setHeapU32((appctx_values as any) + (propertyCount * sizeOfPtr), valuePtr); propertyCount++; buffers.push(keyPtr as any); buffers.push(valuePtr as any); @@ -72,7 +72,7 @@ export async function runMain(mainAssemblyName?: string, args?: string[]): Promi args ??= []; const sp = _ems_.stackSave(); - const argsvPtr: number = _ems_.stackAlloc((args.length + 1) * 4) as any; + const argsvPtr: number = _ems_.stackAlloc((args.length + 1) * sizeOfPtr) as any; const ptrs: VoidPtr[] = []; try { diff --git a/src/native/corehost/browserhost/host/index.ts b/src/native/corehost/browserhost/host/index.ts index 04e131fb67782c..e13bbc1003abeb 100644 --- a/src/native/corehost/browserhost/host/index.ts +++ b/src/native/corehost/browserhost/host/index.ts @@ -8,7 +8,7 @@ import { _ems_ } from "../../../libs/Common/JavaScript/ems-ambient"; import GitHash from "consts:gitHash"; import { runMain, runMainAndExit, initializeCoreCLR } from "./host"; -import { registerPdbBytes, registerDllBytes, installVfsFile, loadIcuData, instantiateWasm, } from "./assets"; +import { registerPdbBytes, instantiateWebCILModule, registerDllBytes, installVfsFile, loadIcuData, instantiateWasm, } from "./assets"; export function dotnetInitializeModule(internals: InternalExchange): void { if (!Array.isArray(internals)) throw new Error("Expected internals to be an array"); @@ -30,6 +30,7 @@ export function dotnetInitializeModule(internals: InternalExchange): void { initializeCoreCLR, registerPdbBytes, instantiateWasm, + instantiateWebCILModule, }); _ems_.dotnetUpdateInternals(internals, _ems_.dotnetUpdateInternalsSubscriber); @@ -44,6 +45,7 @@ export function dotnetInitializeModule(internals: InternalExchange): void { map.initializeCoreCLR, map.registerPdbBytes, map.instantiateWasm, + map.instantiateWebCILModule, ]; } } diff --git a/src/native/corehost/browserhost/loader/assets.ts b/src/native/corehost/browserhost/loader/assets.ts index 90352ff8a9cf5a..ce5190bbc457e0 100644 --- a/src/native/corehost/browserhost/loader/assets.ts +++ b/src/native/corehost/browserhost/loader/assets.ts @@ -3,7 +3,7 @@ import type { JsModuleExports, JsAsset, AssemblyAsset, WasmAsset, IcuAsset, EmscriptenModuleInternal, WebAssemblyBootResourceType, AssetEntryInternal, PromiseCompletionSource, LoadBootResourceCallback, InstantiateWasmSuccessCallback } from "./types"; -import { dotnetAssert, dotnetLogger, dotnetInternals, dotnetBrowserHostExports, dotnetUpdateInternals, Module } from "./cross-module"; +import { dotnetAssert, dotnetLogger, dotnetInternals, dotnetBrowserHostExports, dotnetUpdateInternals, Module, dotnetNativeBrowserExports } from "./cross-module"; import { ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_NODE, browserVirtualAppBase } from "./per-module"; import { createPromiseCompletionSource, delay } from "./promise-completion-source"; import { locateFile, makeURLAbsoluteWithApplicationBase } from "./bootstrap"; @@ -20,7 +20,7 @@ export function setLoadBootResourceCallback(callback: LoadBootResourceCallback | loadBootResourceCallback = callback; } export let wasmBinaryPromise: Promise | undefined = undefined; -export const mainModulePromiseController = createPromiseCompletionSource(); +export const wasmMemoryPromiseController = createPromiseCompletionSource(); export const nativeModulePromiseController = createPromiseCompletionSource(() => { dotnetUpdateInternals(dotnetInternals); }); @@ -66,10 +66,11 @@ export function fetchWasm(asset: WasmAsset): Promise { } export async function instantiateMainWasm(imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback): Promise { - const { instance, module } = await dotnetBrowserHostExports.instantiateWasm(wasmBinaryPromise!, imports, true, true); + const { instance, module } = await dotnetBrowserHostExports.instantiateWasm(wasmBinaryPromise!, imports, true); onDownloadedAsset(); successCallback(instance, module); - mainModulePromiseController.resolve(instance); + const memory = dotnetNativeBrowserExports.getWasmMemory(); + wasmMemoryPromiseController.resolve(memory); } export async function fetchIcu(asset: IcuAsset): Promise { @@ -94,17 +95,26 @@ export async function fetchDll(asset: AssemblyAsset): Promise { if (assetInternal.name && !asset.resolvedUrl) { asset.resolvedUrl = locateFile(assetInternal.name); } - assetInternal.behavior = "assembly"; assetInternal.virtualPath = assetInternal.virtualPath.startsWith("/") ? assetInternal.virtualPath : browserVirtualAppBase + assetInternal.virtualPath; + if (assetInternal.virtualPath.endsWith(".wasm")) { + assetInternal.behavior = "webcil01"; + const webCILPromise = loadResource(assetInternal); - const bytes = await fetchBytes(assetInternal); - await nativeModulePromiseController.promise; + const memory = await wasmMemoryPromiseController.promise; + const virtualPath = assetInternal.virtualPath.replace(/\.wasm$/, ".dll"); + await dotnetBrowserHostExports.instantiateWebCILModule(webCILPromise, memory, virtualPath); + onDownloadedAsset(); + } else { + assetInternal.behavior = "assembly"; + const bytes = await fetchBytes(assetInternal); + onDownloadedAsset(); - onDownloadedAsset(); - if (bytes) { - dotnetBrowserHostExports.registerDllBytes(bytes, asset.virtualPath); + await nativeModulePromiseController.promise; + if (bytes) { + dotnetBrowserHostExports.registerDllBytes(bytes, assetInternal.virtualPath); + } } } @@ -125,7 +135,7 @@ export async function fetchPdb(asset: AssemblyAsset): Promise { onDownloadedAsset(); if (bytes) { - dotnetBrowserHostExports.registerPdbBytes(bytes, asset.virtualPath); + dotnetBrowserHostExports.registerPdbBytes(bytes, assetInternal.virtualPath); } } @@ -159,7 +169,7 @@ async function fetchBytes(asset: AssetEntryInternal): Promise } function loadResource(asset: AssetEntryInternal): Promise { - if ("dotnetwasm" === asset.behavior) { + if ("dotnetwasm" === asset.behavior || "webcil01" === asset.behavior) { // `response.arrayBuffer()` can't be called twice. return loadResourceFetch(asset); } @@ -317,6 +327,7 @@ const behaviorToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType "vfs": "configuration", "manifest": "manifest", "dotnetwasm": "dotnetwasm", + "webcil01": "dotnetwasm", "js-module-dotnet": "dotnetjs", "js-module-native": "dotnetjs", "js-module-runtime": "dotnetjs", @@ -331,4 +342,5 @@ const behaviorToContentTypeMap: { [key: string]: string | undefined } = { "vfs": "application/octet-stream", "manifest": "application/json", "dotnetwasm": "application/wasm", + "webcil01": "application/wasm", }; diff --git a/src/native/corehost/browserhost/loader/dotnet.d.ts b/src/native/corehost/browserhost/loader/dotnet.d.ts index dbcf0beeeb83e5..a32a656b20d376 100644 --- a/src/native/corehost/browserhost/loader/dotnet.d.ts +++ b/src/native/corehost/browserhost/loader/dotnet.d.ts @@ -441,7 +441,11 @@ type AssetBehaviors = SingleAssetBehaviors | /** * The javascript module that came from nuget package . */ - | "js-module-library-initializer"; + | "js-module-library-initializer" +/** + * Managed assembly packaged as WebCIL v 1.0 + */ + | "webcil01"; declare const enum GlobalizationMode { /** * Load sharded ICU data. diff --git a/src/native/corehost/browserhost/loader/run.ts b/src/native/corehost/browserhost/loader/run.ts index 5d6838b5ef7969..6f4fa00a8806d9 100644 --- a/src/native/corehost/browserhost/loader/run.ts +++ b/src/native/corehost/browserhost/loader/run.ts @@ -14,7 +14,6 @@ import { validateWasmFeatures } from "./bootstrap"; const runMainPromiseController = createPromiseCompletionSource(); -// WASM-TODO: webCIL // WASM-TODO: downloadOnly - blazor render mode auto pre-download. Really no start. // WASM-TODO: loadAllSatelliteResources // WASM-TODO: debugLevel diff --git a/src/native/libs/Common/JavaScript/cross-module/index.ts b/src/native/libs/Common/JavaScript/cross-module/index.ts index e2a5f86213910f..c69c4d48d5ed77 100644 --- a/src/native/libs/Common/JavaScript/cross-module/index.ts +++ b/src/native/libs/Common/JavaScript/cross-module/index.ts @@ -151,6 +151,7 @@ export function dotnetUpdateInternalsSubscriber() { initializeCoreCLR: table[3], registerPdbBytes: table[4], instantiateWasm: table[5], + instantiateWebCILModule: table[6], }; Object.assign(native, nativeLocal); } @@ -171,6 +172,8 @@ export function dotnetUpdateInternalsSubscriber() { // keep in sync with nativeBrowserExportsToTable() function nativeBrowserExportsFromTable(table: NativeBrowserExportsTable, interop: NativeBrowserExports): void { const interopLocal: NativeBrowserExports = { + getWasmMemory: table[0], + getWasmTable: table[1], }; Object.assign(interop, interopLocal); } diff --git a/src/native/libs/Common/JavaScript/per-module/index.ts b/src/native/libs/Common/JavaScript/per-module/index.ts index 577503db58402f..73dd39d2448dab 100644 --- a/src/native/libs/Common/JavaScript/per-module/index.ts +++ b/src/native/libs/Common/JavaScript/per-module/index.ts @@ -14,3 +14,4 @@ export const VoidPtrNull: VoidPtr = 0; export const CharPtrNull: CharPtr = 0; export const NativePointerNull: NativePointer = 0; export const browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase +export const sizeOfPtr = 4; diff --git a/src/native/libs/Common/JavaScript/types/ems-ambient.ts b/src/native/libs/Common/JavaScript/types/ems-ambient.ts index 15b4f9dcd8611b..1cd33a7ca6afae 100644 --- a/src/native/libs/Common/JavaScript/types/ems-ambient.ts +++ b/src/native/libs/Common/JavaScript/types/ems-ambient.ts @@ -82,4 +82,7 @@ export type EmsAmbientSymbolsType = EmscriptenModuleInternal & { writeI53ToI64(ptr: MemOffset, value: number): void; readI53FromI64(ptr: MemOffset): number; readI53FromU64(ptr: MemOffset): number; + + wasmMemory: WebAssembly.Memory; + wasmTable: WebAssembly.Table; } diff --git a/src/native/libs/Common/JavaScript/types/exchange.ts b/src/native/libs/Common/JavaScript/types/exchange.ts index fe634c54feff20..99c8c24990e417 100644 --- a/src/native/libs/Common/JavaScript/types/exchange.ts +++ b/src/native/libs/Common/JavaScript/types/exchange.ts @@ -6,7 +6,7 @@ import type { resolveRunMainPromise, rejectRunMainPromise, getRunMainPromise, ab import type { addOnExitListener, isExited, isRuntimeRunning, quitNow } from "../../../../corehost/browserhost/loader/exit"; import type { initializeCoreCLR } from "../../../../corehost/browserhost/host/host"; -import type { instantiateWasm, installVfsFile, registerDllBytes, loadIcuData, registerPdbBytes } from "../../../../corehost/browserhost/host/assets"; +import type { instantiateWasm, installVfsFile, registerDllBytes, loadIcuData, registerPdbBytes, instantiateWebCILModule } from "../../../../corehost/browserhost/host/assets"; import type { createPromiseCompletionSource, getPromiseCompletionSource, isControllablePromise } from "../../../../corehost/browserhost/loader/promise-completion-source"; import type { isSharedArrayBuffer, zeroRegion } from "../../../System.Native.Browser/utils/memory"; @@ -21,6 +21,10 @@ import type { abortInteropTimers } from "../../../System.Runtime.InteropServices import type { symbolicateStackTrace } from "../../../System.Native.Browser/diagnostics/symbolicate"; import type { EmsAmbientSymbolsType } from "../types"; + +type getWasmMemoryType = () => WebAssembly.Memory; +type getWasmTableType = () => WebAssembly.Table; + export type RuntimeExports = { bindJSImportST: typeof bindJSImportST, invokeJSImportST: typeof invokeJSImportST, @@ -96,6 +100,7 @@ export type BrowserHostExports = { initializeCoreCLR: typeof initializeCoreCLR registerPdbBytes: typeof registerPdbBytes instantiateWasm: typeof instantiateWasm + instantiateWebCILModule: typeof instantiateWebCILModule } export type BrowserHostExportsTable = [ @@ -105,6 +110,7 @@ export type BrowserHostExportsTable = [ typeof initializeCoreCLR, typeof registerPdbBytes, typeof instantiateWasm, + typeof instantiateWebCILModule, ] export type InteropJavaScriptExports = { @@ -126,9 +132,13 @@ export type InteropJavaScriptExportsTable = [ ] export type NativeBrowserExports = { + getWasmMemory: getWasmMemoryType, + getWasmTable: getWasmTableType, } export type NativeBrowserExportsTable = [ + getWasmMemoryType, + getWasmTableType, ] export type BrowserUtilsExports = { diff --git a/src/native/libs/Common/JavaScript/types/public-api.ts b/src/native/libs/Common/JavaScript/types/public-api.ts index 254e0acaec4f50..592ba9ee20d951 100644 --- a/src/native/libs/Common/JavaScript/types/public-api.ts +++ b/src/native/libs/Common/JavaScript/types/public-api.ts @@ -405,7 +405,12 @@ export type AssetBehaviors = SingleAssetBehaviors | /** * The javascript module that came from nuget package . */ - | "js-module-library-initializer"; + | "js-module-library-initializer" + /** + * Managed assembly packaged as WebCIL v 1.0 + */ + | "webcil01" + ; export declare const enum GlobalizationMode { /** * Load sharded ICU data. diff --git a/src/native/libs/System.Native.Browser/native/index.ts b/src/native/libs/System.Native.Browser/native/index.ts index 302b6ea425504b..29b3d61c484ecb 100644 --- a/src/native/libs/System.Native.Browser/native/index.ts +++ b/src/native/libs/System.Native.Browser/native/index.ts @@ -24,6 +24,8 @@ export function dotnetInitializeModule(internals: InternalExchange): void { } internals[InternalExchangeIndex.NativeBrowserExportsTable] = nativeBrowserExportsToTable({ + getWasmMemory, + getWasmTable, }); _ems_.dotnetUpdateInternals(internals, _ems_.dotnetUpdateInternalsSubscriber); @@ -31,6 +33,16 @@ export function dotnetInitializeModule(internals: InternalExchange): void { function nativeBrowserExportsToTable(map: NativeBrowserExports): NativeBrowserExportsTable { // keep in sync with nativeBrowserExportsFromTable() return [ + map.getWasmMemory, + map.getWasmTable, ]; } + + function getWasmMemory(): WebAssembly.Memory { + return _ems_.wasmMemory; + } + + function getWasmTable(): WebAssembly.Table { + return _ems_.wasmTable; + } } 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 69205e6f3b982f..32c2f630607edb 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs @@ -58,7 +58,7 @@ public class ComputeWasmPublishAssets : Task public bool EmitSourceMap { get; set; } - public bool IsWebCilEnabled { get; set; } + public bool IsWebCILEnabled { get; set; } public bool FingerprintAssets { get; set; } @@ -452,7 +452,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)) @@ -482,7 +482,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..97c6acff15953b 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(); @@ -38,7 +38,7 @@ public override bool Execute() if (!IsEnabled) { - WebCilCandidates = Candidates; + WebCILCandidates = Candidates; return true; } @@ -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/Internal/Constants.cs b/src/tasks/Microsoft.NET.WebAssembly.Webcil/Internal/Constants.cs index 2d486645d23b66..a355954bfe3988 100644 --- a/src/tasks/Microsoft.NET.WebAssembly.Webcil/Internal/Constants.cs +++ b/src/tasks/Microsoft.NET.WebAssembly.Webcil/Internal/Constants.cs @@ -5,6 +5,6 @@ namespace Microsoft.NET.WebAssembly.Webcil.Internal; internal static unsafe class Constants { - public const int WC_VERSION_MAJOR = 0; + public const int WC_VERSION_MAJOR = 1; public const int WC_VERSION_MINOR = 0; } diff --git a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebciHeader.cs b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebciHeader.cs index 33dcc85791fff4..de13540be591df 100644 --- a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebciHeader.cs +++ b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebciHeader.cs @@ -15,14 +15,14 @@ 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 version_major; + public ushort version_minor; // 8 bytes public ushort coff_sections; - public ushort reserved0; // 0 + public ushort reserved0; // 12 bytes public uint pe_cli_header_rva; public uint pe_cli_header_size; diff --git a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilConverter.cs b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilConverter.cs index 0a0495f72fef4e..d8a363ff5c6ac1 100644 --- a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilConverter.cs +++ b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilConverter.cs @@ -22,8 +22,6 @@ public record PEFileInfo( ImmutableArray SectionHeaders, // The location of the debug directory entries DirectoryEntry DebugTableDirectory, - // The file offset of the sections, following the section directory - FilePosition SectionStart, // The debug directory entries ImmutableArray DebugDirectoryEntries ); @@ -41,6 +39,13 @@ FilePosition SectionStart private readonly string _inputPath; private readonly string _outputPath; + // Section data is aligned to this boundary so that RVA static fields maintain + // their natural alignment (required by CoreCLR's GetSpanDataFrom). 16 bytes + // covers Vector128, Int128/UInt128, and all smaller primitive types. + private const int SectionAlignment = 16; + + private static readonly byte[] s_paddingBytes = new byte[SectionAlignment - 1]; + private string InputPath => _inputPath; public bool WrapInWebAssembly { get; set; } = true; @@ -87,6 +92,12 @@ public void WriteConversionTo(Stream outputStream, FileStream inputStream, PEFil { WriteHeader(outputStream, wcInfo.Header); WriteSectionHeaders(outputStream, wcInfo.SectionHeaders); + // Pad to reach the aligned section start position + int headerPadding = (int)(wcInfo.SectionStart.Position - outputStream.Position); + if (headerPadding > 0) + { + outputStream.Write(s_paddingBytes, 0, headerPadding); + } CopySections(outputStream, inputStream, peInfo.SectionHeaders); if (wcInfo.Header.pe_debug_size != 0 && wcInfo.Header.pe_debug_rva != 0) { @@ -132,26 +143,17 @@ public unsafe void GatherInfo(PEReader peReader, out WCFileInfo wcInfo, out PEFi // 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 + // Align section data so that RVA static fields maintain their natural alignment. + curSectionPos += (SectionAlignment - (curSectionPos.Position % SectionAlignment)) % SectionAlignment; + // The first WC section is immediately after the section directory (plus alignment padding) FilePosition firstWCSection = curSectionPos; - FilePosition firstPESection = 0; - ImmutableArray.Builder headerBuilder = ImmutableArray.CreateBuilder(coffHeader.NumberOfSections); foreach (var sectionHeader in sections) { - // The first section is the one with the lowest file offset - if (firstPESection.Position == 0) - { - firstPESection = sectionHeader.PointerToRawData; - } - else - { - firstPESection = Math.Min(firstPESection.Position, sectionHeader.PointerToRawData); - } - var newHeader = new WebcilSectionHeader ( + name: sectionHeader.Name, virtualSize: sectionHeader.VirtualSize, virtualAddress: sectionHeader.VirtualAddress, sizeOfRawData: sectionHeader.SizeOfRawData, @@ -160,6 +162,8 @@ public unsafe void GatherInfo(PEReader peReader, out WCFileInfo wcInfo, out PEFi pos += sizeof(WebcilSectionHeader); curSectionPos += sectionHeader.SizeOfRawData; + // Align next section so that RVA static fields maintain their natural alignment. + curSectionPos += (SectionAlignment - (curSectionPos.Position % SectionAlignment)) % SectionAlignment; headerBuilder.Add(newHeader); } @@ -167,7 +171,6 @@ public unsafe void GatherInfo(PEReader peReader, out WCFileInfo wcInfo, out PEFi peInfo = new PEFileInfo(SectionHeaders: sections, DebugTableDirectory: peHeader.DebugTableDirectory, - SectionStart: firstPESection, DebugDirectoryEntries: debugDirectoryEntries); wcInfo = new WCFileInfo(Header: header, @@ -202,13 +205,15 @@ private static void WriteSectionHeader(Stream s, WebcilSectionHeader sectionHead { if (!BitConverter.IsLittleEndian) { - sectionHeader = new WebcilSectionHeader - ( - virtualSize: BinaryPrimitives.ReverseEndianness(sectionHeader.VirtualSize), - virtualAddress: BinaryPrimitives.ReverseEndianness(sectionHeader.VirtualAddress), - sizeOfRawData: BinaryPrimitives.ReverseEndianness(sectionHeader.SizeOfRawData), - pointerToRawData: BinaryPrimitives.ReverseEndianness(sectionHeader.PointerToRawData) - ); + unsafe + { + // Name is a byte array, no endian swap needed + sectionHeader.VirtualSize = BinaryPrimitives.ReverseEndianness(sectionHeader.VirtualSize); + sectionHeader.VirtualAddress = BinaryPrimitives.ReverseEndianness(sectionHeader.VirtualAddress); + sectionHeader.SizeOfRawData = BinaryPrimitives.ReverseEndianness(sectionHeader.SizeOfRawData); + sectionHeader.PointerToRawData = BinaryPrimitives.ReverseEndianness(sectionHeader.PointerToRawData); + // Remaining fields are zero, no swap needed + } } WriteStructure(s, sectionHeader); } @@ -253,6 +258,12 @@ private static void CopySections(Stream outStream, FileStream inputStream, Immut inputStream.Seek(peHeader.PointerToRawData, SeekOrigin.Begin); ReadExactly(inputStream, buffer); outStream.Write(buffer, 0, buffer.Length); + // Align next section so that RVA static fields maintain their natural alignment. + int padding = (int)((SectionAlignment - (outStream.Position % SectionAlignment)) % SectionAlignment); + if (padding > 0) + { + outStream.Write(s_paddingBytes, 0, padding); + } } } @@ -289,40 +300,16 @@ private static FilePosition GetPositionOfRelativeVirtualAddress(ImmutableArray peSections, FilePosition fileOffset) + // Make a new set of debug directory entries whose DataPointer fields + // are correct for the webcil layout. DataPointer is a file offset, and + // the mapping from RVA→file-offset differs between PE and WebCIL because + // inter-section alignment padding may differ (PE uses FileAlignment, e.g. + // 512 bytes; WebCIL uses SectionAlignment, e.g. 16 bytes). We therefore + // derive each entry's new DataPointer from its DataRelativeVirtualAddress + // (which is the same in both formats) rather than applying a single constant + // offset that is only correct when all per-section padding happens to match. + private static ImmutableArray FixupDebugDirectoryEntries(PEFileInfo peInfo, WCFileInfo wcInfo) { - 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) - { - 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,12 +322,10 @@ 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; + // Map the entry's RVA to the corresponding file offset in the WebCIL layout. + int newDataPointer = GetPositionOfRelativeVirtualAddress( + wcInfo.SectionHeaders, (uint)entry.DataRelativeVirtualAddress).Position; 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); } @@ -356,7 +341,13 @@ private static void OverwriteDebugDirectoryEntries(Stream s, WCFileInfo wcInfo, { 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.pe_debug_size) + { + throw new InvalidOperationException( + $"Debug directory size mismatch: wrote {bytesWritten} bytes, expected {wcInfo.Header.pe_debug_size}"); + } // restore the stream position writer.Seek(0, SeekOrigin.End); diff --git a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs index 6e079b58c32599..c4315db33898fb 100644 --- a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs +++ b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs @@ -354,7 +354,7 @@ private unsafe ImmutableArray ReadSections() { WebcilSectionHeader secheader; var sections = ImmutableArray.CreateBuilder(_header.coff_sections); - var buffer = new byte[Marshal.SizeOf()]; + var buffer = new byte[sizeof(WebcilSectionHeader)]; _stream.Seek(SectionDirectoryOffset + _webcilInWasmOffset, SeekOrigin.Begin); for (int i = 0; i < _header.coff_sections; i++) { @@ -368,21 +368,13 @@ private unsafe ImmutableArray ReadSections() } if (!BitConverter.IsLittleEndian) { - sections.Add - ( - new WebcilSectionHeader - ( - virtualSize: BinaryPrimitives.ReverseEndianness(secheader.VirtualSize), - virtualAddress: BinaryPrimitives.ReverseEndianness(secheader.VirtualAddress), - sizeOfRawData: BinaryPrimitives.ReverseEndianness(secheader.SizeOfRawData), - pointerToRawData: BinaryPrimitives.ReverseEndianness(secheader.PointerToRawData) - ) - ); - } - else - { - sections.Add(secheader); + // Name is a byte array, no endian swap needed + secheader.VirtualSize = BinaryPrimitives.ReverseEndianness(secheader.VirtualSize); + secheader.VirtualAddress = BinaryPrimitives.ReverseEndianness(secheader.VirtualAddress); + secheader.SizeOfRawData = BinaryPrimitives.ReverseEndianness(secheader.SizeOfRawData); + secheader.PointerToRawData = BinaryPrimitives.ReverseEndianness(secheader.PointerToRawData); } + sections.Add(secheader); } return sections.MoveToImmutable(); } diff --git a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilSectionHeader.cs b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilSectionHeader.cs index 8571fb8ac325c4..35c9a230befa3b 100644 --- a/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilSectionHeader.cs +++ b/src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilSectionHeader.cs @@ -1,26 +1,40 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Runtime.InteropServices; +using System.Text; namespace Microsoft.NET.WebAssembly.Webcil; /// -/// This is the Webcil analog of System.Reflection.PortableExecutable.SectionHeader, but with fewer fields +/// WebCIL v1.0 section header — binary-compatible with IMAGE_SECTION_HEADER (40 bytes). /// [StructLayout(LayoutKind.Sequential, Pack = 1)] -public readonly struct WebcilSectionHeader +public unsafe struct WebcilSectionHeader { - public readonly int VirtualSize; - public readonly int VirtualAddress; - public readonly int SizeOfRawData; - public readonly int PointerToRawData; + public fixed byte Name[8]; + public int VirtualSize; + public int VirtualAddress; + public int SizeOfRawData; + public int PointerToRawData; + public int PointerToRelocations; + public int PointerToLinenumbers; + public ushort NumberOfRelocations; + public ushort NumberOfLinenumbers; + public int Characteristics; - public WebcilSectionHeader(int virtualSize, int virtualAddress, int sizeOfRawData, int pointerToRawData) + public WebcilSectionHeader(string name, int virtualSize, int virtualAddress, int sizeOfRawData, int pointerToRawData) { + this = default; VirtualSize = virtualSize; VirtualAddress = virtualAddress; SizeOfRawData = sizeOfRawData; PointerToRawData = pointerToRawData; + + ReadOnlySpan nameBytes = Encoding.ASCII.GetBytes(name); + int len = Math.Min(nameBytes.Length, 8); + for (int i = 0; i < len; i++) + Name[i] = nameBytes[i]; } }