mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
302 lines
9.3 KiB
302 lines
9.3 KiB
// Copyright (c) 2024 Siegfried Pammer |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this |
|
// software and associated documentation files (the "Software"), to deal in the Software |
|
// without restriction, including without limitation the rights to use, copy, modify, merge, |
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
|
// to whom the Software is furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be included in all copies or |
|
// substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
// DEALINGS IN THE SOFTWARE. |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Collections.Immutable; |
|
using System.Diagnostics; |
|
using System.Diagnostics.CodeAnalysis; |
|
using System.IO; |
|
using System.IO.MemoryMappedFiles; |
|
using System.Reflection.Metadata; |
|
using System.Text; |
|
|
|
using ICSharpCode.Decompiler.TypeSystem; |
|
|
|
#nullable enable |
|
|
|
namespace ICSharpCode.Decompiler.Metadata |
|
{ |
|
public class WebCilFile : MetadataFile, IDisposable, IModuleReference |
|
{ |
|
readonly MemoryMappedViewAccessor view; |
|
readonly long webcilOffset; |
|
|
|
private WebCilFile(string fileName, long webcilOffset, long metadataOffset, MemoryMappedViewAccessor view, ImmutableArray<SectionHeader> sectionHeaders, ImmutableArray<WasmSection> wasmSections, MetadataReaderProvider provider, MetadataReaderOptions metadataOptions = MetadataReaderOptions.Default) |
|
: base(MetadataFileKind.WebCIL, fileName, provider, metadataOptions, 0) |
|
{ |
|
this.webcilOffset = webcilOffset; |
|
this.MetadataOffset = (int)metadataOffset; |
|
this.view = view; |
|
this.SectionHeaders = sectionHeaders; |
|
this.WasmSections = wasmSections; |
|
} |
|
|
|
public static WebCilFile? FromFile(string fileName, MetadataReaderOptions metadataOptions = MetadataReaderOptions.Default) |
|
{ |
|
using var memoryMappedFile = TryCreateFromFile(fileName); |
|
if (memoryMappedFile == null) |
|
{ |
|
return null; |
|
} |
|
|
|
var view = memoryMappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); |
|
try |
|
{ |
|
// read magic "\0asm" |
|
if (view.ReadUInt32(0) != WASM_MAGIC) |
|
return null; |
|
|
|
// read version |
|
if (view.ReadUInt32(4) != 1) |
|
return null; |
|
|
|
using var stream = view.AsStream(); |
|
using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true); |
|
|
|
stream.Position += 8; |
|
|
|
long metadataOffset = -1; |
|
List<WasmSection> sections = new List<WasmSection>(); |
|
|
|
while (stream.Position < stream.Length) |
|
{ |
|
WasmSectionId id = (WasmSectionId)reader.ReadByte(); |
|
uint size = reader.ReadULEB128(); |
|
sections.Add(new WasmSection(id, stream.Position, size, view)); |
|
|
|
if (id == WasmSectionId.Custom && size == 0) |
|
{ |
|
break; |
|
} |
|
stream.Seek(size, SeekOrigin.Current); |
|
} |
|
|
|
foreach (var section in sections) |
|
{ |
|
if (section.Id != WasmSectionId.Data || metadataOffset > -1) |
|
continue; |
|
|
|
stream.Seek(section.Offset, SeekOrigin.Begin); |
|
|
|
uint numSegments = reader.ReadULEB128(); |
|
if (numSegments != 2) |
|
continue; |
|
|
|
// skip the first segment |
|
if (reader.ReadByte() != 1) |
|
continue; |
|
|
|
long segmentLength = reader.ReadULEB128(); |
|
long segmentStart = reader.BaseStream.Position; |
|
|
|
reader.BaseStream.Seek(segmentLength, SeekOrigin.Current); |
|
|
|
if (reader.ReadByte() != 1) |
|
continue; |
|
|
|
segmentLength = reader.ReadULEB128(); |
|
if (TryReadWebCilSegment(reader, out var header, out metadataOffset, out var webcilOffset, out var sectionHeaders)) |
|
{ |
|
stream.Seek(metadataOffset, SeekOrigin.Begin); |
|
var metadata = MetadataReaderProvider.FromMetadataStream(stream, MetadataStreamOptions.LeaveOpen | MetadataStreamOptions.PrefetchMetadata); |
|
|
|
var result = new WebCilFile(fileName, webcilOffset, metadataOffset, view, ImmutableArray.Create(sectionHeaders), sections.ToImmutableArray(), metadata, metadataOptions); |
|
|
|
view = null; // don't dispose the view, we're still using it in the sections |
|
return result; |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
finally |
|
{ |
|
view?.Dispose(); |
|
} |
|
|
|
static MemoryMappedFile? TryCreateFromFile(string fileName) |
|
{ |
|
try |
|
{ |
|
return MemoryMappedFile.CreateFromFile(fileName, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); |
|
} |
|
catch (IOException) |
|
{ |
|
return null; |
|
} |
|
} |
|
} |
|
|
|
static unsafe bool TryReadWebCilSegment(BinaryReader reader, out WebcilHeader webcilHeader, out long metadataOffset, out long webcilOffset, [NotNullWhen(true)] out SectionHeader[]? sectionHeaders) |
|
{ |
|
webcilHeader = default; |
|
metadataOffset = -1; |
|
sectionHeaders = null; |
|
|
|
webcilOffset = reader.BaseStream.Position; |
|
|
|
if (reader.ReadUInt32() != WEBCIL_MAGIC) |
|
return false; |
|
|
|
webcilHeader.VersionMajor = reader.ReadUInt16(); |
|
webcilHeader.VersionMinor = reader.ReadUInt16(); |
|
webcilHeader.CoffSections = reader.ReadUInt16(); |
|
_ = reader.ReadUInt16(); // reserved0 |
|
webcilHeader.PECliHeaderRVA = reader.ReadUInt32(); |
|
webcilHeader.PECliHeaderSize = reader.ReadUInt32(); |
|
webcilHeader.PEDebugRVA = reader.ReadUInt32(); |
|
webcilHeader.PEDebugSize = reader.ReadUInt32(); |
|
|
|
sectionHeaders = new SectionHeader[webcilHeader.CoffSections]; |
|
for (int i = 0; i < webcilHeader.CoffSections; i++) |
|
{ |
|
sectionHeaders[i].VirtualSize = reader.ReadUInt32(); |
|
sectionHeaders[i].VirtualAddress = reader.ReadUInt32(); |
|
sectionHeaders[i].RawDataSize = reader.ReadUInt32(); |
|
sectionHeaders[i].RawDataPtr = reader.ReadUInt32(); |
|
} |
|
|
|
long corHeaderStart = TranslateRVA(sectionHeaders, webcilOffset, webcilHeader.PECliHeaderRVA); |
|
if (reader.BaseStream.Seek(corHeaderStart, SeekOrigin.Begin) != corHeaderStart) |
|
return false; |
|
int byteCount = reader.ReadInt32(); |
|
int majorVersion = reader.ReadUInt16(); |
|
int minorVersion = reader.ReadUInt16(); |
|
metadataOffset = TranslateRVA(sectionHeaders, webcilOffset, (uint)reader.ReadInt32()); |
|
return reader.BaseStream.Seek(metadataOffset, SeekOrigin.Begin) == metadataOffset; |
|
} |
|
|
|
public override int MetadataOffset { get; } |
|
public override bool IsMetadataOnly => false; |
|
|
|
private static int GetContainingSectionIndex(IEnumerable<SectionHeader> sections, int rva) |
|
{ |
|
int i = 0; |
|
foreach (var section in sections) |
|
{ |
|
if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize) |
|
{ |
|
return i; |
|
} |
|
i++; |
|
} |
|
return -1; |
|
} |
|
|
|
private static long TranslateRVA(IEnumerable<SectionHeader> sections, long webcilOffset, uint rva) |
|
{ |
|
foreach (var section in sections) |
|
{ |
|
if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize) |
|
{ |
|
return section.RawDataPtr + (rva - section.VirtualAddress) + webcilOffset; |
|
} |
|
} |
|
throw new BadImageFormatException("RVA not found in any section"); |
|
} |
|
|
|
public override MethodBodyBlock GetMethodBody(int rva) |
|
{ |
|
var reader = GetSectionData(rva).GetReader(); |
|
return MethodBodyBlock.Create(reader); |
|
} |
|
|
|
public override int GetContainingSectionIndex(int rva) |
|
{ |
|
return GetContainingSectionIndex(SectionHeaders, rva); |
|
} |
|
|
|
public override unsafe SectionData GetSectionData(int rva) |
|
{ |
|
foreach (var section in SectionHeaders) |
|
{ |
|
if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize) |
|
{ |
|
byte* ptr = (byte*)0; |
|
view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); |
|
return new SectionData(ptr + section.RawDataPtr + webcilOffset + (rva - section.VirtualAddress), (int)section.RawDataSize); |
|
} |
|
} |
|
throw new BadImageFormatException("RVA not found in any section"); |
|
} |
|
|
|
public override ImmutableArray<SectionHeader> SectionHeaders { get; } |
|
|
|
public ImmutableArray<WasmSection> WasmSections { get; } |
|
|
|
IModule? IModuleReference.Resolve(ITypeResolveContext context) |
|
{ |
|
return new MetadataModule(context.Compilation, this, TypeSystemOptions.Default); |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
view.Dispose(); |
|
} |
|
|
|
public struct WebcilHeader |
|
{ |
|
public ushort VersionMajor; |
|
public ushort VersionMinor; |
|
public ushort CoffSections; |
|
public uint PECliHeaderRVA; |
|
public uint PECliHeaderSize; |
|
public uint PEDebugRVA; |
|
public uint PEDebugSize; |
|
} |
|
|
|
const uint WASM_MAGIC = 0x6d736100u; // "\0asm" |
|
const uint WEBCIL_MAGIC = 0x4c496257u; // "WbIL" |
|
|
|
[DebuggerDisplay("WasmSection {Id}: {Offset} {Size}")] |
|
public class WasmSection |
|
{ |
|
public WasmSectionId Id; |
|
public long Offset; |
|
public uint Size; |
|
private MemoryMappedViewAccessor view; |
|
|
|
public WasmSection(WasmSectionId id, long offset, uint size, MemoryMappedViewAccessor view) |
|
{ |
|
this.Id = id; |
|
this.Size = size; |
|
this.Offset = offset; |
|
this.view = view; |
|
} |
|
} |
|
|
|
public enum WasmSectionId : byte |
|
{ |
|
// order matters: enum values must match the WebAssembly spec |
|
Custom = 0, |
|
Type = 1, |
|
Import = 2, |
|
Function = 3, |
|
Table = 4, |
|
Memory = 5, |
|
Global = 6, |
|
Export = 7, |
|
Start = 8, |
|
Element = 9, |
|
Code = 10, |
|
Data = 11, |
|
DataCount = 12, |
|
} |
|
} |
|
}
|
|
|