diff --git a/src/BinaryParsers/ElfBinary/ElfBinary.cs b/src/BinaryParsers/ElfBinary/ElfBinary.cs index 6bd73ded8..54baf9230 100644 --- a/src/BinaryParsers/ElfBinary/ElfBinary.cs +++ b/src/BinaryParsers/ElfBinary/ElfBinary.cs @@ -2,8 +2,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using System.Text; @@ -21,6 +23,11 @@ namespace Microsoft.CodeAnalysis.BinaryParsers /// public class ElfBinary : BinaryBase, IDwarfBinary { + // ELF section flag bit for SHF_COMPRESSED. + private const ulong CompressedSectionFlag = 0x800UL; + private const uint ZlibCompressionType = 1; + private const uint ZstdCompressionType = 2; + public ElfBinary(Uri uri, string localSymbolDirectories = null, bool forceComprehensiveParsing = false) : base(uri) { try @@ -383,13 +390,76 @@ private byte[] LoadSection(string sectionName) { if (section.Name == sectionName + ".dwo" || section.Name == sectionName) { - return section.GetContents(); + return GetSectionContents(section, Is64bit); } } return Array.Empty(); } + internal static byte[] GetSectionContents(ISection section, bool is64bit) + { + byte[] contents = section.GetContents(); + + return IsCompressedSection(section) + ? DecompressSectionContents(contents, is64bit) + : contents; + } + + internal static byte[] DecompressSectionContents(byte[] contents, bool is64bit) + { + if (contents.Length == 0) + { + return contents; + } + + int headerSize = is64bit ? 24 : 12; + + if (contents.Length < headerSize) + { + throw new InvalidOperationException("Compressed ELF section header is truncated."); + } + + uint compressionType = BinaryPrimitives.ReadUInt32LittleEndian(contents.AsSpan(0, sizeof(uint))); + + if (compressionType == ZstdCompressionType) + { + throw new NotSupportedException("ELF zstd-compressed sections are not supported on this runtime target because there is no built-in Zstandard stream to decode them."); + } + + if (compressionType != ZlibCompressionType) + { + throw new NotSupportedException($"Unsupported ELF compression type: {compressionType}."); + } + + ulong uncompressedSize = is64bit + ? BinaryPrimitives.ReadUInt64LittleEndian(contents.AsSpan(8, sizeof(ulong))) + : BinaryPrimitives.ReadUInt32LittleEndian(contents.AsSpan(4, sizeof(uint))); + + using var compressedStream = new MemoryStream(contents, headerSize, contents.Length - headerSize, writable: false); + using var zlibStream = new ZLibStream(compressedStream, CompressionMode.Decompress); + using MemoryStream decompressedStream = uncompressedSize <= int.MaxValue + ? new MemoryStream((int)uncompressedSize) + : new MemoryStream(); + + zlibStream.CopyTo(decompressedStream); + + byte[] decompressedContents = decompressedStream.ToArray(); + + if ((ulong)decompressedContents.LongLength != uncompressedSize) + { + throw new InvalidOperationException("Compressed ELF section size does not match the ELF header."); + } + + return decompressedContents; + } + + private static bool IsCompressedSection(ISection section) + { + return section is Section elfSection + && (elfSection.RawFlags & CompressedSectionFlag) != 0; + } + /// /// Gets the section address after loading into memory. /// diff --git a/src/Test.UnitTests.BinaryParsers/Elf/ElfBinaryTests.cs b/src/Test.UnitTests.BinaryParsers/Elf/ElfBinaryTests.cs index 59d3e3fe9..196c2440e 100644 --- a/src/Test.UnitTests.BinaryParsers/Elf/ElfBinaryTests.cs +++ b/src/Test.UnitTests.BinaryParsers/Elf/ElfBinaryTests.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using System.Reflection; +using System.Text; using FluentAssertions; @@ -203,6 +205,67 @@ public void ValidateDwarfV5_Rust() binary.GetLanguage().Should().Be(DwarfLanguage.Rust); } + [Fact] + public void DecompressSectionContents_Elf64CompressedSection_Decompresses() + { + byte[] originalContents = Encoding.ASCII.GetBytes("dwarf-v5-go-section"); + + byte[] compressedContents; + using (var compressedStream = new MemoryStream()) + { + using (var zlibStream = new ZLibStream(compressedStream, CompressionLevel.SmallestSize, leaveOpen: true)) + { + zlibStream.Write(originalContents, 0, originalContents.Length); + } + + compressedContents = compressedStream.ToArray(); + } + + using var sectionStream = new MemoryStream(); + using (var writer = new BinaryWriter(sectionStream, Encoding.ASCII, leaveOpen: true)) + { + writer.Write((uint)1); + writer.Write((uint)0); + writer.Write((ulong)originalContents.Length); + writer.Write((ulong)1); + writer.Write(compressedContents); + } + + byte[] decompressedContents = ElfBinary.DecompressSectionContents(sectionStream.ToArray(), is64bit: true); + + decompressedContents.Should().Equal(originalContents); + } + + [Fact] + public void DecompressSectionContents_Elf32CompressedSection_Decompresses() + { + byte[] originalContents = Encoding.ASCII.GetBytes("dwarf-v5-go-section"); + + byte[] compressedContents; + using (var compressedStream = new MemoryStream()) + { + using (var zlibStream = new ZLibStream(compressedStream, CompressionLevel.SmallestSize, leaveOpen: true)) + { + zlibStream.Write(originalContents, 0, originalContents.Length); + } + + compressedContents = compressedStream.ToArray(); + } + + using var sectionStream = new MemoryStream(); + using (var writer = new BinaryWriter(sectionStream, Encoding.ASCII, leaveOpen: true)) + { + writer.Write((uint)1); + writer.Write((uint)originalContents.Length); + writer.Write((uint)1); + writer.Write(compressedContents); + } + + byte[] decompressedContents = ElfBinary.DecompressSectionContents(sectionStream.ToArray(), is64bit: false); + + decompressedContents.Should().Equal(originalContents); + } + [Fact] public void ValidateDwarfV4_WithO2_Split_DebugFileExists() {