Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion src/BinaryParsers/ElfBinary/ElfBinary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -21,6 +23,11 @@ namespace Microsoft.CodeAnalysis.BinaryParsers
/// </summary>
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
Expand Down Expand Up @@ -383,13 +390,76 @@ private byte[] LoadSection(string sectionName)
{
if (section.Name == sectionName + ".dwo" || section.Name == sectionName)
{
return section.GetContents();
return GetSectionContents(section, Is64bit);
Comment thread
qmuntal marked this conversation as resolved.
}
}

return Array.Empty<byte>();
}

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<ulong> elfSection
&& (elfSection.RawFlags & CompressedSectionFlag) != 0;
}

/// <summary>
/// Gets the section address after loading into memory.
/// </summary>
Expand Down
63 changes: 63 additions & 0 deletions src/Test.UnitTests.BinaryParsers/Elf/ElfBinaryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()
{
Expand Down
Loading