Skip to content

Commit

Permalink
Verify alignment of shared libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
grendello committed Jul 5, 2024
1 parent 77445ec commit a79f4e1
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 7 deletions.
23 changes: 16 additions & 7 deletions src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ public class BuildApk : AndroidTask

public string ZipFlushSizeLimit { get; set; }

public int ZipAlignmentPages { get; set; } = AndroidZipAlign.DefaultZipAlignment64Bit;

[Required]
public string ProjectFullPath { get; set; }

Expand Down Expand Up @@ -683,14 +685,15 @@ sealed class LibInfo
public string Link;
public string Abi;
public string ArchiveFileName;
public ITaskItem Item;
}

CompressionMethod GetCompressionMethod (string fileName)
{
return uncompressedFileExtensions.Contains (Path.GetExtension (fileName)) ? UncompressedMethod : CompressionMethod.Default;
}

void AddNativeLibraryToArchive (ZipArchiveEx apk, string abi, string filesystemPath, string inArchiveFileName)
void AddNativeLibraryToArchive (ZipArchiveEx apk, string abi, string filesystemPath, string inArchiveFileName, ITaskItem taskItem)
{
string archivePath = MakeArchiveLibPath (abi, inArchiveFileName);
existingEntries.Remove (archivePath);
Expand All @@ -700,6 +703,7 @@ void AddNativeLibraryToArchive (ZipArchiveEx apk, string abi, string filesystemP
return;
}
Log.LogDebugMessage ($"Adding native library: {filesystemPath} (APK path: {archivePath})");
ELFHelper.AssertValidLibraryAlignment (Log, ZipAlignmentPages, filesystemPath, taskItem);
apk.AddEntryAndFlush (archivePath, File.OpenRead (filesystemPath), compressionMethod);
}

Expand All @@ -709,7 +713,7 @@ void AddRuntimeLibraries (ZipArchiveEx apk, string [] supportedAbis)
foreach (ITaskItem item in ApplicationSharedLibraries) {
if (String.Compare (abi, item.GetMetadata ("abi"), StringComparison.Ordinal) != 0)
continue;
AddNativeLibraryToArchive (apk, abi, item.ItemSpec, Path.GetFileName (item.ItemSpec));
AddNativeLibraryToArchive (apk, abi, item.ItemSpec, Path.GetFileName (item.ItemSpec), item);
}
}
}
Expand Down Expand Up @@ -762,7 +766,8 @@ private void AddNativeLibraries (ArchiveFileList files, string [] supportedAbis)
Path = v.ItemSpec,
Link = v.GetMetadata ("Link"),
Abi = GetNativeLibraryAbi (v),
ArchiveFileName = GetArchiveFileName (v)
ArchiveFileName = GetArchiveFileName (v),
Item = v,
});

AddNativeLibraries (files, supportedAbis, frameworkLibs);
Expand All @@ -773,7 +778,8 @@ private void AddNativeLibraries (ArchiveFileList files, string [] supportedAbis)
Path = v.ItemSpec,
Link = v.GetMetadata ("Link"),
Abi = GetNativeLibraryAbi (v),
ArchiveFileName = GetArchiveFileName (v)
ArchiveFileName = GetArchiveFileName (v),
Item = v,
}
);

Expand Down Expand Up @@ -854,8 +860,9 @@ void AddNativeLibraries (ArchiveFileList files, string [] supportedAbis, System.
string.Join (", ", libs.Where (lib => lib.Abi == null).Select (lib => lib.Path)));
libs = libs.Where (lib => lib.Abi != null);
libs = libs.Where (lib => supportedAbis.Contains (lib.Abi));
foreach (var info in libs)
AddNativeLibrary (files, info.Path, info.Abi, info.ArchiveFileName);
foreach (var info in libs) {
AddNativeLibrary (files, info.Path, info.Abi, info.ArchiveFileName, info.Item);
}
}

private void AddAdditionalNativeLibraries (ArchiveFileList files, string [] supportedAbis)
Expand All @@ -868,12 +875,13 @@ private void AddAdditionalNativeLibraries (ArchiveFileList files, string [] supp
Path = l.ItemSpec,
Abi = AndroidRidAbiHelper.GetNativeLibraryAbi (l),
ArchiveFileName = l.GetMetadata ("ArchiveFileName"),
Item = l,
});

AddNativeLibraries (files, supportedAbis, libs);
}

void AddNativeLibrary (ArchiveFileList files, string path, string abi, string archiveFileName)
void AddNativeLibrary (ArchiveFileList files, string path, string abi, string archiveFileName, ITaskItem? taskItem = null)
{
string fileName = string.IsNullOrEmpty (archiveFileName) ? Path.GetFileName (path) : archiveFileName;
var item = (filePath: path, archivePath: MakeArchiveLibPath (abi, fileName));
Expand All @@ -882,6 +890,7 @@ void AddNativeLibrary (ArchiveFileList files, string path, string abi, string ar
return;
}

ELFHelper.AssertValidLibraryAlignment (Log, ZipAlignmentPages, path, taskItem);
if (!ELFHelper.IsEmptyAOTLibrary (Log, item.filePath)) {
files.Add (item);
} else {
Expand Down
82 changes: 82 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;

using ELFSharp;
using ELFSharp.ELF;
using ELFSharp.ELF.Sections;
using ELFSharp.ELF.Segments;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

using ELFSymbolType = global::ELFSharp.ELF.Sections.SymbolType;
Expand All @@ -15,6 +18,85 @@ namespace Xamarin.Android.Tasks
{
static class ELFHelper
{
public static void AssertValidLibraryAlignment (TaskLoggingHelper log, int alignmentInPages, string path, ITaskItem? item)
{
if (String.IsNullOrEmpty (path) || !File.Exists (path)) {
return;
}

log.LogDebugMessage ($"Checking alignment to {alignmentInPages}k page boundary in shared library {path}");
try {
AssertValidLibraryAlignment (log, MonoAndroidHelper.ZipAlignmentToPageSize (alignmentInPages), path, ELFReader.Load (path), item);
} catch (Exception ex) {
log.LogWarning ($"Attempt to check whether '{path}' is a correctly aligned ELF file failed with exception, ignoring alignment check for the file.");
log.LogWarningFromException (ex, showStackTrace: true);
}
}

static void AssertValidLibraryAlignment (TaskLoggingHelper log, uint pageSize, string path, IELF elf, ITaskItem? item)
{
if (elf.Class == Class.Bit32 || elf.Class == Class.NotELF) {
log.LogDebugMessage ($" Not a 64-bit ELF image. Ignored.");
return;
}

var elf64 = elf as ELF<ulong>;
if (elf64 == null) {
throw new InvalidOperationException ($"Internal error: {elf} is not ELF<ulong>");
}

// We need to find all segments of Load type and make sure their alignment is as expected.
foreach (ISegment segment in elf64.Segments) {
if (segment.Type != SegmentType.Load) {
continue;
}

var segment64 = segment as Segment<ulong>;
if (segment64 == null) {
throw new InvalidOperationException ($"Internal error: {segment} is not Segment<ulong>");
}

// TODO: what happens if the library is aligned at, say, 64k while 16k is required? Should we erorr out?
// We will need more info about that, have to wait till Google formally announce the requirement.
// At this moment the script https://developer.android.com/guide/practices/page-sizes#test they
// provide suggests it's a strict requirement, so we test for equality below.
if (segment64.Alignment == pageSize) {
continue;
}
log.LogDebugMessage ($" expected segment alignment of 0x{pageSize:x}, found 0x{segment64.Alignment:x}");

// TODO: turn into a coded warning and, eventually, error. Need better wording.
// Until dotnet runtime produces properly aligned libraries, this should be a plain message as a warning
// would break all the tests that require no warnings to be produced during build.
log.LogMessage ($"Native {elf64.Machine} shared library '{Path.GetFileName (path)}', from NuGet package {GetNugetPackageInfo ()} isn't properly aligned.");
break;
}

string GetNugetPackageInfo ()
{
const string Unknown = "<unknown>";

if (item == null) {
return Unknown;
}

var sb = new StringBuilder ();
string? metaValue = item.GetMetadata ("NuGetPackageId");
if (String.IsNullOrEmpty (metaValue)) {
return Unknown;
}

sb.Append (metaValue);
metaValue = item.GetMetadata ("NuGetPackageVersion");
if (!String.IsNullOrEmpty (metaValue)) {
sb.Append (" version ");
sb.Append (metaValue);
}

return sb.ToString ();
}
}

public static bool IsEmptyAOTLibrary (TaskLoggingHelper log, string path)
{
if (String.IsNullOrEmpty (path) || !File.Exists (path)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2092,6 +2092,7 @@ because xbuild doesn't support framework reference assemblies.
IncludeFiles="@(AndroidPackagingOptionsInclude)"
ZipFlushFilesLimit="$(_ZipFlushFilesLimit)"
ZipFlushSizeLimit="$(_ZipFlushSizeLimit)"
ZipAlignmentPages="$(AndroidZipAlignment)"
UseAssemblyStore="$(AndroidUseAssemblyStore)">
<Output TaskParameter="OutputFiles" ItemName="ApkFiles" />
</BuildApk>
Expand Down Expand Up @@ -2129,6 +2130,7 @@ because xbuild doesn't support framework reference assemblies.
IncludeFiles="@(AndroidPackagingOptionsInclude)"
ZipFlushFilesLimit="$(_ZipFlushFilesLimit)"
ZipFlushSizeLimit="$(_ZipFlushSizeLimit)"
ZipAlignmentPages="$(AndroidZipAlignment)"
UseAssemblyStore="$(AndroidUseAssemblyStore)">
<Output TaskParameter="OutputFiles" ItemName="BaseZipFile" />
</BuildBaseAppBundle>
Expand Down

0 comments on commit a79f4e1

Please sign in to comment.