Skip to content

Commit

Permalink
Optimize loading of Relocation/Symbol tables
Browse files Browse the repository at this point in the history
  • Loading branch information
xoofx committed Oct 15, 2024
1 parent dcfc2fb commit 0c6bcb3
Show file tree
Hide file tree
Showing 9 changed files with 237 additions and 95 deletions.
14 changes: 14 additions & 0 deletions src/LibObjectFile.Bench/LibObjectFile.Bench.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\LibObjectFile\LibObjectFile.csproj" />
</ItemGroup>

</Project>
58 changes: 58 additions & 0 deletions src/LibObjectFile.Bench/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.

using System.Diagnostics;
using LibObjectFile.Elf;

namespace LibObjectFile.Bench;

internal class Program
{
static void Main(string[] args)
{
var clock = Stopwatch.StartNew();
//var memoryStream = new MemoryStream();
foreach (var file in GetLinuxBins())
{
//memoryStream.SetLength(0);
using var stream = File.OpenRead((string)file[0]);
//stream.CopyTo(memoryStream);

if (ElfFile.IsElf(stream))
{
ElfFile.Read(stream);
}
}
clock.Stop();
Console.WriteLine($"{clock.Elapsed.TotalMilliseconds}ms");
}

public static IEnumerable<object[]> GetLinuxBins()
{
var wslDirectory = @"\\wsl$\Ubuntu\usr\bin";
if (OperatingSystem.IsLinux())
{
foreach (var file in Directory.EnumerateFiles(@"/usr/bin"))
{
yield return new object[] { file };
}
}
else if (OperatingSystem.IsWindows() && Directory.Exists(wslDirectory))
{
foreach (var file in Directory.EnumerateFiles(wslDirectory))
{
var fileInfo = new FileInfo(file);
// Skip symbolic links as loading them will fail
if ((fileInfo.Attributes & FileAttributes.ReparsePoint) == 0)
{
yield return new object[] { file };
}
}
}
else
{
yield return new object[] { string.Empty };
}
}
}
6 changes: 6 additions & 0 deletions src/LibObjectFile.sln
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{BD580DD4-4E2
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "objdasm", "objdasm\objdasm.csproj", "{056AA737-6B5F-47A6-8426-E7918D930C5C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibObjectFile.Bench", "LibObjectFile.Bench\LibObjectFile.Bench.csproj", "{34AD50B2-FAE3-42C9-8117-B5DE1CEEF0EA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -52,6 +54,10 @@ Global
{056AA737-6B5F-47A6-8426-E7918D930C5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{056AA737-6B5F-47A6-8426-E7918D930C5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{056AA737-6B5F-47A6-8426-E7918D930C5C}.Release|Any CPU.Build.0 = Release|Any CPU
{34AD50B2-FAE3-42C9-8117-B5DE1CEEF0EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{34AD50B2-FAE3-42C9-8117-B5DE1CEEF0EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34AD50B2-FAE3-42C9-8117-B5DE1CEEF0EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{34AD50B2-FAE3-42C9-8117-B5DE1CEEF0EA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
2 changes: 1 addition & 1 deletion src/LibObjectFile/Elf/Sections/ElfRelocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace LibObjectFile.Elf;
/// A relocation entry in the <see cref="ElfRelocationTable"/>
/// This is the value seen in <see cref="ElfNative.Elf32_Rel"/> or <see cref="ElfNative.Elf64_Rel"/>
/// </summary>
public record ElfRelocation
public record struct ElfRelocation
{
public ElfRelocation()
{
Expand Down
109 changes: 47 additions & 62 deletions src/LibObjectFile/Elf/Sections/ElfRelocationTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibObjectFile.Diagnostics;
using LibObjectFile.IO;

namespace LibObjectFile.Elf;

Expand Down Expand Up @@ -63,95 +66,77 @@ public override void Write(ElfWriter writer)
private unsafe void Read32(ElfReader reader)
{
var numberOfEntries = base.Size / base.TableEntrySize;
_entries.Capacity = (int)numberOfEntries;
var entries = _entries;
CollectionsMarshal.SetCount(entries, (int)numberOfEntries);
var span = CollectionsMarshal.AsSpan(entries);

if (IsRelocationWithAddends)
{
for (ulong i = 0; i < numberOfEntries; i++)
using var batch = new BatchDataReader<ElfNative.Elf32_Rela>(reader.Stream, (int)numberOfEntries);
ref var entry = ref MemoryMarshal.GetReference(span);
while (batch.HasNext())
{
ElfNative.Elf32_Rela rel;
ulong streamOffset = (ulong)reader.Stream.Position;
if (!reader.TryReadData(sizeof(ElfNative.Elf32_Rela), out rel))
{
reader.Diagnostics.Error(DiagnosticId.ELF_ERR_IncompleteRelocationAddendsEntry32Size, $"Unable to read entirely the relocation entry [{i}] from {Type} section [{Index}]. Not enough data (size: {base.TableEntrySize}) read at offset {streamOffset} from the stream");
}

var offset = reader.Decode(rel.r_offset);
ref var rel = ref batch.ReadNext();
entry.Offset = reader.Decode(rel.r_offset);
var r_info = reader.Decode(rel.r_info);
var type = new ElfRelocationType(Parent!.Arch, r_info & 0xFF);
var symbolIndex = r_info >> 8;
var addend = reader.Decode(rel.r_addend);

var entry = new ElfRelocation(offset, type, symbolIndex, addend);
_entries.Add(entry);
entry.Type = new ElfRelocationType(Parent!.Arch, r_info & 0xFF);
entry.SymbolIndex = r_info >> 8;
entry.Addend = reader.Decode(rel.r_addend);
entry = ref Unsafe.Add(ref entry, 1);
}
}
else
{
for (ulong i = 0; i < numberOfEntries; i++)
using var batch = new BatchDataReader<ElfNative.Elf32_Rel>(reader.Stream, (int)numberOfEntries);
ref var entry = ref MemoryMarshal.GetReference(span);
while (batch.HasNext())
{
ElfNative.Elf32_Rel rel;
ulong streamOffset = (ulong)reader.Stream.Position;
if (!reader.TryReadData(sizeof(ElfNative.Elf32_Rel), out rel))
{
reader.Diagnostics.Error(DiagnosticId.ELF_ERR_IncompleteRelocationEntry32Size, $"Unable to read entirely the relocation entry [{i}] from {Type} section [{Index}]. Not enough data (size: {base.TableEntrySize}) read at offset {streamOffset} from the stream");
}

var offset = reader.Decode(rel.r_offset);

ref var rel = ref batch.ReadNext();
entry.Offset = reader.Decode(rel.r_offset);
var r_info = reader.Decode(rel.r_info);
var type = new ElfRelocationType(Parent!.Arch, r_info & 0xFF);
var symbolIndex = r_info >> 8;

var entry = new ElfRelocation(offset, type, symbolIndex, 0);
_entries.Add(entry);
entry.Type = new ElfRelocationType(Parent!.Arch, r_info & 0xFF);
entry.SymbolIndex = r_info >> 8;
entry.Addend = 0;
entry = ref Unsafe.Add(ref entry, 1);
}
}
}

private unsafe void Read64(ElfReader reader)
{
var numberOfEntries = base.Size / base.TableEntrySize;
var entries = _entries;
CollectionsMarshal.SetCount(entries, (int)numberOfEntries);
var span = CollectionsMarshal.AsSpan(entries);

if (IsRelocationWithAddends)
{
for (ulong i = 0; i < numberOfEntries; i++)
using var batch = new BatchDataReader<ElfNative.Elf64_Rela>(reader.Stream, (int)numberOfEntries);
ref var entry = ref MemoryMarshal.GetReference(span);
while (batch.HasNext())
{
ElfNative.Elf64_Rela rel;
ulong streamOffset = (ulong)reader.Stream.Position;
if (!reader.TryReadData(sizeof(ElfNative.Elf64_Rela), out rel))
{
reader.Diagnostics.Error(DiagnosticId.ELF_ERR_IncompleteRelocationAddendsEntry64Size, $"Unable to read entirely the relocation entry [{i}] from {Type} section [{Index}]. Not enough data (size: {base.TableEntrySize}) read at offset {streamOffset} from the stream");
}

var offset = reader.Decode(rel.r_offset);

ref var rel = ref batch.ReadNext();
entry.Offset = reader.Decode(rel.r_offset);
var r_info = reader.Decode(rel.r_info);
var type = new ElfRelocationType(Parent!.Arch, (uint)(r_info & 0xFFFFFFFF));
var symbolIndex = (uint)(r_info >> 32);
var addend = reader.Decode(rel.r_addend);

var entry = new ElfRelocation(offset, type, symbolIndex, addend);
_entries.Add(entry);
entry.Type = new ElfRelocationType(Parent!.Arch, (uint)(r_info & 0xFFFFFFFF));
entry.SymbolIndex = (uint)(r_info >> 32);
entry.Addend = reader.Decode(rel.r_addend);
entry = ref Unsafe.Add(ref entry, 1);
}
}
else
{
for (ulong i = 0; i < numberOfEntries; i++)
using var batch = new BatchDataReader<ElfNative.Elf64_Rel>(reader.Stream, (int)numberOfEntries);
ref var entry = ref MemoryMarshal.GetReference(span);
while (batch.HasNext())
{
ElfNative.Elf64_Rel rel;
ulong streamOffset = (ulong)reader.Stream.Position;
if (!reader.TryReadData(sizeof(ElfNative.Elf64_Rel), out rel))
{
reader.Diagnostics.Error(DiagnosticId.ELF_ERR_IncompleteRelocationEntry64Size, $"Unable to read entirely the relocation entry [{i}] from {Type} section [{Index}]. Not enough data (size: {base.TableEntrySize}) read at offset {streamOffset} from the stream");
}

var offset = reader.Decode(rel.r_offset);

ref var rel = ref batch.ReadNext();
entry.Offset = reader.Decode(rel.r_offset);
var r_info = reader.Decode(rel.r_info);
var type = new ElfRelocationType(Parent!.Arch, (uint)(r_info & 0xFFFFFFFF));
var symbolIndex = (uint)(r_info >> 32);

var entry = new ElfRelocation(offset, type, symbolIndex, 0);
_entries.Add(entry);
entry.Type = new ElfRelocationType(Parent!.Arch, (uint)(r_info & 0xFFFFFFFF));
entry.SymbolIndex = (uint)(r_info >> 32);
entry.Addend = 0;
entry = ref Unsafe.Add(ref entry, 1);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/LibObjectFile/Elf/Sections/ElfSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace LibObjectFile.Elf;
/// A symbol entry in the <see cref="ElfSymbolTable"/>
/// This is the value seen in <see cref="ElfNative.Elf32_Sym"/> or <see cref="ElfNative.Elf64_Sym"/>
/// </summary>
public record ElfSymbol
public record struct ElfSymbol
{
/// <summary>
/// Gets or sets the value associated to this symbol.
Expand Down
54 changes: 24 additions & 30 deletions src/LibObjectFile/Elf/Sections/ElfSymbolTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibObjectFile.Diagnostics;
using LibObjectFile.IO;

namespace LibObjectFile.Elf;

Expand Down Expand Up @@ -36,13 +39,17 @@ public override void Read(ElfReader reader)
reader.Position = Position;
Entries.Clear();

var numberOfEntries = (int)(base.Size / base.TableEntrySize);
var entries = Entries;
CollectionsMarshal.SetCount(entries, numberOfEntries);

if (_is32)
{
Read32(reader);
Read32(reader, numberOfEntries);
}
else
{
Read64(reader);
Read64(reader, numberOfEntries);
}
}

Expand All @@ -58,20 +65,15 @@ public override void Write(ElfWriter writer)
}
}

private void Read32(ElfReader reader)
private void Read32(ElfReader reader, int numberOfEntries)
{
var numberOfEntries = base.Size / base.TableEntrySize;
Entries.Capacity = (int)numberOfEntries;
for (ulong i = 0; i < numberOfEntries; i++)
using var batch = new BatchDataReader<ElfNative.Elf32_Sym>(reader.Stream, numberOfEntries);
var span = CollectionsMarshal.AsSpan(Entries);
ref var entry = ref MemoryMarshal.GetReference(span);
while (batch.HasNext())
{
ElfNative.Elf32_Sym sym;
ulong streamOffset = (ulong)reader.Stream.Position;
if (!reader.TryReadData((int)base.TableEntrySize, out sym))
{
reader.Diagnostics.Error(DiagnosticId.ELF_ERR_IncompleteSymbolEntry32Size, $"Unable to read entirely the symbol entry [{i}] from {Type} section [{Index}]. Not enough data (size: {base.TableEntrySize}) read at offset {streamOffset} from the stream");
}
ref var sym = ref batch.ReadNext();

var entry = new ElfSymbol();
entry.Name = new ElfString(reader.Decode(sym.st_name));
entry.Value = reader.Decode(sym.st_value);
entry.Size = reader.Decode(sym.st_size);
Expand All @@ -81,25 +83,19 @@ private void Read32(ElfReader reader)
entry.Bind = (ElfSymbolBind)(st_info >> 4);
entry.Visibility = (ElfSymbolVisibility) sym.st_other;
entry.SectionLink = new ElfSectionLink(reader.Decode(sym.st_shndx));

Entries.Add(entry);
entry = ref Unsafe.Add(ref entry, 1);
}
}

private void Read64(ElfReader reader)
private void Read64(ElfReader reader, int numberOfEntries)
{
var numberOfEntries = base.Size / base.TableEntrySize;
Entries.Capacity = (int)numberOfEntries;
for (ulong i = 0; i < numberOfEntries; i++)
using var batch = new BatchDataReader<ElfNative.Elf64_Sym>(reader.Stream, numberOfEntries);
var span = CollectionsMarshal.AsSpan(Entries);
ref var entry = ref MemoryMarshal.GetReference(span);
while (batch.HasNext())
{
ElfNative.Elf64_Sym sym;
ulong streamOffset = (ulong)reader.Stream.Position;
if (!reader.TryReadData((int)base.TableEntrySize, out sym))
{
reader.Diagnostics.Error(DiagnosticId.ELF_ERR_IncompleteSymbolEntry64Size, $"Unable to read entirely the symbol entry [{i}] from {Type} section [{Index}]. Not enough data (size: {base.TableEntrySize}) read at offset {streamOffset} from the stream");
}
ref var sym = ref batch.ReadNext();

var entry = new ElfSymbol();
entry.Name = new ElfString(reader.Decode(sym.st_name));
entry.Value = reader.Decode(sym.st_value);
entry.Size = reader.Decode(sym.st_size);
Expand All @@ -109,12 +105,10 @@ private void Read64(ElfReader reader)
entry.Bind = (ElfSymbolBind)(st_info >> 4);
entry.Visibility = (ElfSymbolVisibility)sym.st_other;
entry.SectionLink = new ElfSectionLink(reader.Decode(sym.st_shndx));

Entries.Add(entry);
entry = ref Unsafe.Add(ref entry, 1);
}
}



private void Write32(ElfWriter writer)
{
var stringTable = (ElfStringTable)Link.Section!;
Expand Down
Loading

0 comments on commit 0c6bcb3

Please sign in to comment.