Skip to content

Commit

Permalink
Added ELF shared library support to LinkImportsTransformation.
Browse files Browse the repository at this point in the history
Relates to #108
  • Loading branch information
PathogenDavid committed Jul 31, 2021
1 parent c2ce051 commit 2796111
Show file tree
Hide file tree
Showing 9 changed files with 938 additions and 146 deletions.
1 change: 1 addition & 0 deletions Biohazrd.Transformation/Biohazrd.Transformation.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<ItemGroup>
<PackageReference Include="Kaisa" Version="0.0.1" />
<PackageReference Include="LibObjectFile" Version="0.3.5" />
</ItemGroup>

<ItemGroup>
Expand Down
142 changes: 125 additions & 17 deletions Biohazrd.Transformation/Common/LinkImportsTransformation.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Biohazrd.Transformation.Infrastructure;
using Kaisa;
using LibObjectFile.Elf;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;

namespace Biohazrd.Transformation.Common
Expand Down Expand Up @@ -46,10 +48,117 @@ public bool TrackVerboseImportInformation
/// <remarks>You generally do not want to enable this option unless you have advanced needs for virtual methods to be exported.</remarks>
public bool ErrorOnMissingVirtualMethods { get; set; }

private static ReadOnlySpan<byte> ElfFileSignature => new byte[] { 0x7F, 0x45, 0x4C, 0x46 }; // 0x7F "ELF"
private static ReadOnlySpan<byte> WindowsArchiveSignature => new byte[] { 0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0xA };// "!<arch>\n"
private static int LongestSignatureLength => WindowsArchiveSignature.Length;
public void AddLibrary(string filePath)
{
using FileStream stream = new(filePath, FileMode.Open, FileAccess.Read);
Archive library = new(stream);

// Determine if the library is an ELF shared library or a Windows archive file
Span<byte> header = stackalloc byte[LongestSignatureLength];
if (stream.Read(header) != header.Length)
{ throw new ArgumentException("The specified file is too small to be a library.", nameof(filePath)); }

stream.Position = 0;

if (header.StartsWith(WindowsArchiveSignature))
{
Archive library = new(stream);

// Enumerate all import and export symbols from the package
foreach (ArchiveMember member in library.ObjectFiles)
{
if (member is ImportArchiveMember importMember)
{
SymbolImportExportInfo info = new(importMember);
GetOrCreateSymbolEntry(importMember.Symbol).AddImport(filePath, info);
}
else if (member is CoffArchiveMember coffMember)
{
foreach (CoffSymbol coffSymbol in coffMember.Symbols)
{ GetOrCreateSymbolEntry(coffSymbol.Name).AddExport(filePath); }
}
}
}
else if (header.StartsWith(ElfFileSignature))
{
ElfReaderOptions options = new() { ReadOnly = true };
ElfObjectFile elf = ElfObjectFile.Read(stream, options);

// Determine the name to use for the import
// In theory we could strip off the lib prefix and .so suffix here, however that will remove information required for loading the library via NativeLibrary.Load(string)
// If we want to strip these we should handle it in the output stage instead (that way it's consistent across the entire library too.)
string libraryFileName = Path.GetFileName(filePath);

// Find the .dynsym table
// (We do not bother with the symbol hash table because we'll most likely end up needing nearly every symbol as it is and it simplifies our architecture.)
const string sectionName = ".dynsym";
ElfSymbolTable? symbolTable = elf.Sections.OfType<ElfSymbolTable>().FirstOrDefault(s => s.Name == sectionName);

if (symbolTable is null)
{ throw new ArgumentException($"The specified ELF file does not contain a '{sectionName}' section."); }

// See https://refspecs.linuxbase.org/elf/gabi4+/ch4.symtab.html for details on what the symbol table entires mean
// Note that the spec is not very specific about what is and isn't allowed in the dynamic symbol table, so we're probably overly defensive here
// In general we ignore symbols which have OS and architecture-specific types or binding since we can't safely understand what they're for.
foreach (ElfSymbol symbol in symbolTable.Entries)
{
// Ignore unnamed symbols
if (String.IsNullOrEmpty(symbol.Name))
{ continue; }

// Ignore symbols which are not publically accessible
// (Default effectively means public)
if (symbol.Visibility != ElfSymbolVisibility.Default && symbol.Visibility != ElfSymbolVisibility.Protected)
{ continue; }

// Ignore symbols which are undefined
if (symbol.Section.IsSpecial && symbol.Section.SpecialIndex == ElfNative.SHN_UNDEF)
{ continue; }

// Ignore symbols which are not functions or objects (variables)
if (symbol.Type != ElfSymbolType.Function && symbol.Type != ElfSymbolType.Object)
{ continue; }

// Ignore symbols which are not global or weak
// Note that we do not actually differentiate between global and weak symbols, the distinction only matters for static linking
// See https://www.bottomupcs.com/libraries_and_the_linker.xhtml#d0e10440
if (symbol.Bind != ElfSymbolBind.Global && symbol.Bind != ElfSymbolBind.Weak)
{ continue; }

// Determine the import type of the symbol based on its section
ImportType importType;
switch (symbol.Section.Section?.Name)
{
case ".text":
importType = ImportType.Code;
break;
case ".rodata":
importType = ImportType.Const;
break;
case ".data":
case ".bss":
importType = ImportType.Data;
break;
case null when symbol.Section.IsSpecial:
Debug.Fail($"ELF symbol from special section '{symbol.Section}'");
continue;
default:
if (symbol.Section.Section?.Name is not null)
{ Debug.Fail($"ELF symbol from unrecognized section '{symbol.Section.Section.Name}'"); }
else
{ Debug.Fail($"ELF symbol from unnamed/null section."); }
continue;
}

// Add the symbol to our lookup
SymbolImportExportInfo info = new(libraryFileName, symbol, importType);
GetOrCreateSymbolEntry(symbol.Name).AddImport(filePath, info);
}
}
else
{ throw new ArgumentException("The specified file does not appear to be in a compatible format.", nameof(filePath)); }

SymbolEntry GetOrCreateSymbolEntry(string symbol)
{
Expand All @@ -61,21 +170,6 @@ SymbolEntry GetOrCreateSymbolEntry(string symbol)
}
return symbolEntry;
}

// Enumerate all import and export symbols from the package
foreach (ArchiveMember member in library.ObjectFiles)
{
if (member is ImportArchiveMember importMember)
{
SymbolImportExportInfo info = new(importMember);
GetOrCreateSymbolEntry(importMember.Symbol).AddImport(filePath, info);
}
else if (member is CoffArchiveMember coffMember)
{
foreach (CoffSymbol coffSymbol in coffMember.Symbols)
{ GetOrCreateSymbolEntry(coffSymbol.Name).AddExport(filePath); }
}
}
}

private bool Resolve(string symbolName, [NotNullWhen(true)] out string? dllFileName, [NotNullWhen(true)] out string? mangledName, ref DiagnosticAccumulator diagnosticsAccumulator, bool isFunction, bool isVirtualMethod)
Expand Down Expand Up @@ -106,7 +200,9 @@ static string MakeErrorMessage(string message, SymbolEntry symbolEntry)

foreach ((string library, SymbolImportExportInfo info) in symbolEntry.Sources)
{
if (info.IsImport)
if (info.IsFromElf)
{ builder.Append($"\n '{library}'"); } // The library and the DllFileName are the same for ELF sources, avoid printing the redundant info.
else if (info.IsImport)
{ builder.Append($"\n '{library}': Import from '{info.DllFileName}'"); }
else
{ builder.Append($"\n '{library}': Statically-linked export'"); }
Expand Down Expand Up @@ -273,6 +369,7 @@ private readonly struct SymbolImportExportInfo
public ImportNameType ImportNameType { get; }
public string? DllFileName { get; }
public ushort OrdinalOrHint { get; }
public bool IsFromElf { get; }

public SymbolImportExportInfo(ImportArchiveMember importMember)
{
Expand All @@ -281,6 +378,17 @@ public SymbolImportExportInfo(ImportArchiveMember importMember)
ImportNameType = importMember.ImportHeader.NameType;
DllFileName = importMember.Dll;
OrdinalOrHint = importMember.ImportHeader.OrdinalOrHint;
IsFromElf = false;
}

public SymbolImportExportInfo(string libraryFileName, ElfSymbol symbol, ImportType importType)
{
IsImport = true;
ImportNameType = ImportNameType.Name; // ELFs do not have ordinal members
DllFileName = libraryFileName;
OrdinalOrHint = 0;
ImportType = importType;
IsFromElf = true;
}

public bool IsEquivalentTo(SymbolImportExportInfo other)
Expand Down
29 changes: 29 additions & 0 deletions THIRD-PARTY-NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Biohazrd incorporates third-party libraries licensed as follows.
- [ClangSharp](#clangsharp)
- [ClangSharp.Pathogen](#clangsharppathogen)
- [Kaisa the Sharp Librarian](#kaisa-the-sharp-librarian)
- [LibObjectFile](#libobjectfile)
- [The LLVM Project](#the-llvm-project)

<!-- /TOC -->
Expand Down Expand Up @@ -82,6 +83,34 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

# LibObjectFile

```
Copyright (c) 2019, Alexandre Mutel
All rights reserved.
Redistribution and use in source and binary forms, with or without modification
, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```

# The LLVM Project

```
Expand Down
103 changes: 103 additions & 0 deletions Tests/Biohazrd.Tests.Common/LlvmTools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;

namespace Biohazrd.Tests.Common
{
public static partial class LlvmTools
{
private static string? ClangPath = null;
private static Exception? LlvmToolchainRootLocationFailureException = null;

private static string? TryFindClang(out Exception? exception)
{
if (ClangPath is not null)
{
exception = null;
return ClangPath;
}

if (LlvmToolchainRootLocationFailureException is not null)
{
exception = LlvmToolchainRootLocationFailureException;
return null;
}

// It's not super clear if Win32Exception.NativeErrorCode is actually errno on Unix-like systems when Process.Start fails due to a missing executable,
// but it doesn't actually matter since ERROR_FILE_NOT_FOUND and ENOENT are both 2.
const int ERROR_FILE_NOT_FOUND = 2;

// Check if Clang is present on the system PATH
try
{
using Process clang = Process.Start("clang", "--version");
clang.WaitForExit();

if (clang.ExitCode == 0)
{
exception = null;
return ClangPath = "clang";
}
else
{
exception = new Exception("The Clang install found on the system PATH appears to be non-functional.");
LlvmToolchainRootLocationFailureException = exception;
return null;
}
}
catch (Win32Exception ex) when (ex.NativeErrorCode == ERROR_FILE_NOT_FOUND)
{ exception = new FileNotFoundException("Clang was not found on the system PATH."); }
catch (Exception ex)
{
exception = new Exception($"The Clang install found on the system PATH appears to be unusable: {ex.Message}.", ex);
LlvmToolchainRootLocationFailureException = exception;
return null;
}

// Find Clang from Visual Studio if the appropriate component is installed
if (OperatingSystem.IsWindows())
{
try
{
// The other LLVM-related component (Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset) is only for using clang-cl for building C++ MSBuild projects.
VisualStudioLocator locator = new("Microsoft.VisualStudio.Component.VC.Llvm.Clang");
string visualStudioRoot = locator.LocateVisualStudio();
string visualStudioClangPath = Path.Combine(visualStudioRoot, "VC", "Tools", "Llvm", "bin", "clang.exe");

if (!File.Exists(visualStudioClangPath))
{ throw new FileNotFoundException("Visual Studio install claims to have LLVM toolchain but clang.exe was not found.", visualStudioClangPath); }

return ClangPath = visualStudioClangPath;
}
catch (Exception ex)
{ exception = new AggregateException(exception, ex); }
}

// Clang is not installed
LlvmToolchainRootLocationFailureException = exception;
return null;
}

public static Exception? IsClangAvailable()
{
string? clangPath = TryFindClang(out Exception? exception);
Debug.Assert(exception is not null || clangPath is not null);
return exception;
}

public static string GetClangPath()
{
string? clangPath = TryFindClang(out Exception? exception);

if (exception is not null)
{
Debug.Assert(clangPath is null);
throw exception;
}

Debug.Assert(clangPath is not null);
return clangPath;
}
}
}
Loading

0 comments on commit 2796111

Please sign in to comment.