Skip to content

Commit

Permalink
[release/5.0-rc2] Bundle assemblies at 4K for linux arm64 (#41907)
Browse files Browse the repository at this point in the history
Backport of #41809 to release/5.0-rc2

This fixes single-file deployments on Linux ARM64, which were segfaulting due to an incorrect computed address in R2R code. See #41832 for details about the issue.
  • Loading branch information
github-actions[bot] authored Sep 8, 2020
1 parent a695f86 commit 98dd097
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 17 deletions.
10 changes: 4 additions & 6 deletions src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,11 @@ public class Bundler
readonly TargetInfo Target;
readonly BundleOptions Options;

// Assemblies are 16 bytes aligned, so that their sections can be memory-mapped cache aligned.
public const int AssemblyAlignment = 16;

public Bundler(string hostName,
string outputDir,
BundleOptions options = BundleOptions.None,
OSPlatform? targetOS = null,
Architecture? targetArch = null,
Version targetFrameworkVersion = null,
bool diagnosticOutput = false,
string appAssemblyName = null)
Expand All @@ -44,7 +42,7 @@ public Bundler(string hostName,

HostName = hostName;
OutputDir = Path.GetFullPath(string.IsNullOrEmpty(outputDir) ? Environment.CurrentDirectory : outputDir);
Target = new TargetInfo(targetOS, targetFrameworkVersion);
Target = new TargetInfo(targetOS, targetArch, targetFrameworkVersion);

appAssemblyName ??= Target.GetAssemblyName(hostName);
DepsJson = appAssemblyName + ".deps.json";
Expand All @@ -64,11 +62,11 @@ long AddToBundle(Stream bundle, Stream file, FileType type)
{
if (type == FileType.Assembly)
{
long misalignment = (bundle.Position % AssemblyAlignment);
long misalignment = (bundle.Position % Target.AssemblyAlignment);

if (misalignment != 0)
{
long padding = AssemblyAlignment - misalignment;
long padding = Target.AssemblyAlignment - misalignment;
bundle.Position += padding;
}
}
Expand Down
27 changes: 23 additions & 4 deletions src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,25 @@ namespace Microsoft.NET.HostModel.Bundle
///
/// Currently the TargetInfo only tracks:
/// - the target operating system
/// - The target framework
/// If necessary, the target architecture may be tracked in future.
/// - the target architecture
/// - the target framework
/// - the default options for this target
/// - the assembly alignment for this target
/// </summary>

public class TargetInfo
{
public readonly OSPlatform OS;
public readonly Architecture Arch;
public readonly Version FrameworkVersion;
public readonly uint BundleVersion;
public readonly BundleOptions DefaultOptions;
public readonly int AssemblyAlignment;

public TargetInfo(OSPlatform? os, Version targetFrameworkVersion)
public TargetInfo(OSPlatform? os, Architecture? arch, Version targetFrameworkVersion)
{
OS = os ?? HostOS;
Arch = arch ?? RuntimeInformation.OSArchitecture;
FrameworkVersion = targetFrameworkVersion ?? net50;

Debug.Assert(IsLinux || IsOSX || IsWindows);
Expand All @@ -46,6 +51,19 @@ public TargetInfo(OSPlatform? os, Version targetFrameworkVersion)
{
throw new ArgumentException($"Invalid input: Unsupported Target Framework Version {targetFrameworkVersion}");
}

if (IsLinux && Arch == Architecture.Arm64)
{
// We align assemblies in the bundle at 4K so that we can use mmap on Linux without changing the page alignment of ARM64 R2R code.
// This is only necessary for R2R assemblies, but we do it for all assemblies for simplicity.
// See https://github.com/dotnet/runtime/issues/41832.
AssemblyAlignment = 4096;
}
else
{
// Otherwise, assemblies are 16 bytes aligned, so that their sections can be memory-mapped cache aligned.
AssemblyAlignment = 16;
}
}

public bool IsNativeBinary(string filePath)
Expand All @@ -63,7 +81,8 @@ public string GetAssemblyName(string hostName)
public override string ToString()
{
string os = IsWindows ? "win" : IsLinux ? "linux" : "osx";
return $"OS: {os} FrameworkVersion: {FrameworkVersion}";
string arch = Arch.ToString().ToLowerInvariant();
return $"OS: {os} Arch: {arch} FrameworkVersion: {FrameworkVersion}";
}

static OSPlatform HostOS => RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? OSPlatform.Linux :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Xml.Linq;

namespace BundleTests.Helpers
Expand Down Expand Up @@ -90,11 +91,31 @@ public static string GetExtractionPath(TestProjectFixture fixture, Bundler bundl
return Path.Combine(GetExtractionRootPath(fixture), GetAppBaseName(fixture), bundler.BundleManifest.BundleID);

}

public static DirectoryInfo GetExtractionDir(TestProjectFixture fixture, Bundler bundler)
{
return new DirectoryInfo(GetExtractionPath(fixture, bundler));
}

public static OSPlatform GetTargetOS(string runtimeIdentifier)
{
return runtimeIdentifier.Split('-')[0] switch {
"win" => OSPlatform.Windows,
"osx" => OSPlatform.OSX,
"linux" => OSPlatform.Linux,
_ => throw new ArgumentException(nameof(runtimeIdentifier))
};
}

public static Architecture GetTargetArch(string runtimeIdentifier)
{
return runtimeIdentifier.EndsWith("-x64") || runtimeIdentifier.Contains("-x64-") ? Architecture.X64 :
runtimeIdentifier.EndsWith("-x86") || runtimeIdentifier.Contains("-x86-") ? Architecture.X86 :
runtimeIdentifier.EndsWith("-arm64") || runtimeIdentifier.Contains("-arm64-") ? Architecture.Arm64 :
runtimeIdentifier.EndsWith("-arm") || runtimeIdentifier.Contains("-arm-") ? Architecture.Arm :
throw new ArgumentException(nameof (runtimeIdentifier));
}

/// Generate a bundle containind the (embeddable) files in sourceDir
public static string GenerateBundle(Bundler bundler, string sourceDir, string outputDir, bool copyExludedFiles=true)
{
Expand Down Expand Up @@ -145,8 +166,10 @@ public static Bundler BundleApp(TestProjectFixture fixture,
var hostName = GetHostName(fixture);
string publishPath = GetPublishPath(fixture);
var bundleDir = GetBundleDir(fixture);
var targetOS = GetTargetOS(fixture.CurrentRid);
var targetArch = GetTargetArch(fixture.CurrentRid);

var bundler = new Bundler(hostName, bundleDir.FullName, options, targetFrameworkVersion: targetFrameworkVersion);
var bundler = new Bundler(hostName, bundleDir.FullName, options, targetOS, targetArch, targetFrameworkVersion);
singleFile = GenerateBundle(bundler, publishPath, bundleDir.FullName, copyExcludedFiles);

return bundler;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ public void TestWithEmptySpecFails()

var hostName = BundleHelper.GetHostName(fixture);
var bundleDir = BundleHelper.GetBundleDir(fixture);
Bundler bundler = new Bundler(hostName, bundleDir.FullName);
var targetOS = BundleHelper.GetTargetOS(fixture.CurrentRid);
var targetArch = BundleHelper.GetTargetArch(fixture.CurrentRid);
Bundler bundler = new Bundler(hostName, bundleDir.FullName, targetOS: targetOS, targetArch: targetArch);

FileSpec[][] invalidSpecs =
{
Expand All @@ -55,13 +57,15 @@ public void TestWithoutSpecifyingHostFails()
var hostName = BundleHelper.GetHostName(fixture);
var appName = Path.GetFileNameWithoutExtension(hostName);
var bundleDir = BundleHelper.GetBundleDir(fixture);
var targetOS = BundleHelper.GetTargetOS(fixture.CurrentRid);
var targetArch = BundleHelper.GetTargetArch(fixture.CurrentRid);

// Generate a file specification without the apphost
var fileSpecs = new List<FileSpec>();
string[] files = { $"{appName}.dll", $"{appName}.deps.json", $"{appName}.runtimeconfig.json" };
Array.ForEach(files, x => fileSpecs.Add(new FileSpec(x, x)));

Bundler bundler = new Bundler(hostName, bundleDir.FullName);
Bundler bundler = new Bundler(hostName, bundleDir.FullName, targetOS: targetOS, targetArch: targetArch);

Assert.Throws<ArgumentException>(() => bundler.GenerateBundle(fileSpecs));
}
Expand All @@ -73,14 +77,16 @@ public void TestWithDuplicateEntriesFails()

var hostName = BundleHelper.GetHostName(fixture);
var bundleDir = BundleHelper.GetBundleDir(fixture);
var targetOS = BundleHelper.GetTargetOS(fixture.CurrentRid);
var targetArch = BundleHelper.GetTargetArch(fixture.CurrentRid);

// Generate a file specification with duplicate entries
var fileSpecs = new List<FileSpec>();
fileSpecs.Add(new FileSpec(BundleHelper.GetHostPath(fixture), BundleHelper.GetHostName(fixture)));
fileSpecs.Add(new FileSpec(BundleHelper.GetAppPath(fixture), "app.repeat"));
fileSpecs.Add(new FileSpec(BundleHelper.GetAppPath(fixture), "app.repeat"));

Bundler bundler = new Bundler(hostName, bundleDir.FullName);
Bundler bundler = new Bundler(hostName, bundleDir.FullName, targetOS: targetOS, targetArch: targetArch);
Assert.Throws<ArgumentException>(() => bundler.GenerateBundle(fileSpecs));
}

Expand All @@ -90,6 +96,8 @@ public void TestBaseNameComputation()
var fixture = sharedTestState.TestFixture.Copy();
var publishPath = BundleHelper.GetPublishPath(fixture);
var bundleDir = BundleHelper.GetBundleDir(fixture);
var targetOS = BundleHelper.GetTargetOS(fixture.CurrentRid);
var targetArch = BundleHelper.GetTargetArch(fixture.CurrentRid);

// Rename the host from "StandaloneApp" to "Stand.Alone.App" to check that baseName computation
// (and consequently deps.json and runtimeconfig.json name computations) in the bundler
Expand All @@ -110,7 +118,7 @@ void rename(string extension)
var depsJson = newBaseName + ".deps.json";
var runtimeconfigJson = newBaseName + ".runtimeconfig.json";

var bundler = new Bundler(hostName, bundleDir.FullName);
var bundler = new Bundler(hostName, bundleDir.FullName, targetOS: targetOS, targetArch: targetArch);
BundleHelper.GenerateBundle(bundler, publishPath, bundleDir.FullName);

string[] jsonFiles = { depsJson, runtimeconfigJson };
Expand Down Expand Up @@ -196,9 +204,11 @@ public void TestAssemblyAlignment()
{
var fixture = sharedTestState.TestFixture.Copy();
var bundler = BundleHelper.Bundle(fixture);

var targetOS = BundleHelper.GetTargetOS(fixture.CurrentRid);
var targetArch = BundleHelper.GetTargetArch(fixture.CurrentRid);
var alignment = (targetOS == OSPlatform.Linux && targetArch == Architecture.Arm64) ? 4096 : 16;
bundler.BundleManifest.Files.ForEach(file =>
Assert.True((file.Type != FileType.Assembly) || (file.Offset % Bundler.AssemblyAlignment == 0)));
Assert.True((file.Type != FileType.Assembly) || (file.Offset % alignment == 0)));
}

[Fact]
Expand Down

0 comments on commit 98dd097

Please sign in to comment.