Skip to content

Commit fba6db9

Browse files
committed
Verify alignment of shared libraries
1 parent f281ef8 commit fba6db9

File tree

3 files changed

+100
-7
lines changed

3 files changed

+100
-7
lines changed

src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ public class BuildApk : AndroidTask
107107

108108
public string ZipFlushSizeLimit { get; set; }
109109

110+
public int ZipAlignmentPages { get; set; } = AndroidZipAlign.DefaultZipAlignment64Bit;
111+
110112
[Required]
111113
public string ProjectFullPath { get; set; }
112114

@@ -683,14 +685,15 @@ sealed class LibInfo
683685
public string Link;
684686
public string Abi;
685687
public string ArchiveFileName;
688+
public ITaskItem Item;
686689
}
687690

688691
CompressionMethod GetCompressionMethod (string fileName)
689692
{
690693
return uncompressedFileExtensions.Contains (Path.GetExtension (fileName)) ? UncompressedMethod : CompressionMethod.Default;
691694
}
692695

693-
void AddNativeLibraryToArchive (ZipArchiveEx apk, string abi, string filesystemPath, string inArchiveFileName)
696+
void AddNativeLibraryToArchive (ZipArchiveEx apk, string abi, string filesystemPath, string inArchiveFileName, ITaskItem taskItem)
694697
{
695698
string archivePath = MakeArchiveLibPath (abi, inArchiveFileName);
696699
existingEntries.Remove (archivePath);
@@ -700,6 +703,7 @@ void AddNativeLibraryToArchive (ZipArchiveEx apk, string abi, string filesystemP
700703
return;
701704
}
702705
Log.LogDebugMessage ($"Adding native library: {filesystemPath} (APK path: {archivePath})");
706+
ELFHelper.AssertValidLibraryAlignment (Log, ZipAlignmentPages, filesystemPath, taskItem);
703707
apk.AddEntryAndFlush (archivePath, File.OpenRead (filesystemPath), compressionMethod);
704708
}
705709

@@ -709,7 +713,7 @@ void AddRuntimeLibraries (ZipArchiveEx apk, string [] supportedAbis)
709713
foreach (ITaskItem item in ApplicationSharedLibraries) {
710714
if (String.Compare (abi, item.GetMetadata ("abi"), StringComparison.Ordinal) != 0)
711715
continue;
712-
AddNativeLibraryToArchive (apk, abi, item.ItemSpec, Path.GetFileName (item.ItemSpec));
716+
AddNativeLibraryToArchive (apk, abi, item.ItemSpec, Path.GetFileName (item.ItemSpec), item);
713717
}
714718
}
715719
}
@@ -762,7 +766,8 @@ private void AddNativeLibraries (ArchiveFileList files, string [] supportedAbis)
762766
Path = v.ItemSpec,
763767
Link = v.GetMetadata ("Link"),
764768
Abi = GetNativeLibraryAbi (v),
765-
ArchiveFileName = GetArchiveFileName (v)
769+
ArchiveFileName = GetArchiveFileName (v),
770+
Item = v,
766771
});
767772

768773
AddNativeLibraries (files, supportedAbis, frameworkLibs);
@@ -773,7 +778,8 @@ private void AddNativeLibraries (ArchiveFileList files, string [] supportedAbis)
773778
Path = v.ItemSpec,
774779
Link = v.GetMetadata ("Link"),
775780
Abi = GetNativeLibraryAbi (v),
776-
ArchiveFileName = GetArchiveFileName (v)
781+
ArchiveFileName = GetArchiveFileName (v),
782+
Item = v,
777783
}
778784
);
779785

@@ -854,8 +860,9 @@ void AddNativeLibraries (ArchiveFileList files, string [] supportedAbis, System.
854860
string.Join (", ", libs.Where (lib => lib.Abi == null).Select (lib => lib.Path)));
855861
libs = libs.Where (lib => lib.Abi != null);
856862
libs = libs.Where (lib => supportedAbis.Contains (lib.Abi));
857-
foreach (var info in libs)
858-
AddNativeLibrary (files, info.Path, info.Abi, info.ArchiveFileName);
863+
foreach (var info in libs) {
864+
AddNativeLibrary (files, info.Path, info.Abi, info.ArchiveFileName, info.Item);
865+
}
859866
}
860867

861868
private void AddAdditionalNativeLibraries (ArchiveFileList files, string [] supportedAbis)
@@ -868,12 +875,13 @@ private void AddAdditionalNativeLibraries (ArchiveFileList files, string [] supp
868875
Path = l.ItemSpec,
869876
Abi = AndroidRidAbiHelper.GetNativeLibraryAbi (l),
870877
ArchiveFileName = l.GetMetadata ("ArchiveFileName"),
878+
Item = l,
871879
});
872880

873881
AddNativeLibraries (files, supportedAbis, libs);
874882
}
875883

876-
void AddNativeLibrary (ArchiveFileList files, string path, string abi, string archiveFileName)
884+
void AddNativeLibrary (ArchiveFileList files, string path, string abi, string archiveFileName, ITaskItem? taskItem = null)
877885
{
878886
string fileName = string.IsNullOrEmpty (archiveFileName) ? Path.GetFileName (path) : archiveFileName;
879887
var item = (filePath: path, archivePath: MakeArchiveLibPath (abi, fileName));
@@ -882,6 +890,7 @@ void AddNativeLibrary (ArchiveFileList files, string path, string abi, string ar
882890
return;
883891
}
884892

893+
ELFHelper.AssertValidLibraryAlignment (Log, ZipAlignmentPages, path, taskItem);
885894
if (!ELFHelper.IsEmptyAOTLibrary (Log, item.filePath)) {
886895
files.Add (item);
887896
} else {

src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.IO;
4+
using System.Text;
45

56
using ELFSharp;
67
using ELFSharp.ELF;
78
using ELFSharp.ELF.Sections;
9+
using ELFSharp.ELF.Segments;
810
using Microsoft.Android.Build.Tasks;
11+
using Microsoft.Build.Framework;
912
using Microsoft.Build.Utilities;
1013

1114
using ELFSymbolType = global::ELFSharp.ELF.Sections.SymbolType;
@@ -15,6 +18,85 @@ namespace Xamarin.Android.Tasks
1518
{
1619
static class ELFHelper
1720
{
21+
public static void AssertValidLibraryAlignment (TaskLoggingHelper log, int alignmentInPages, string path, ITaskItem? item)
22+
{
23+
if (String.IsNullOrEmpty (path) || !File.Exists (path)) {
24+
return;
25+
}
26+
27+
log.LogDebugMessage ($"Checking alignment to {alignmentInPages}k page boundary in shared library {path}");
28+
try {
29+
AssertValidLibraryAlignment (log, MonoAndroidHelper.ZipAlignmentToPageSize (alignmentInPages), path, ELFReader.Load (path), item);
30+
} catch (Exception ex) {
31+
log.LogWarning ($"Attempt to check whether '{path}' is a correctly aligned ELF file failed with exception, ignoring alignment check for the file.");
32+
log.LogWarningFromException (ex, showStackTrace: true);
33+
}
34+
}
35+
36+
static void AssertValidLibraryAlignment (TaskLoggingHelper log, uint pageSize, string path, IELF elf, ITaskItem? item)
37+
{
38+
if (elf.Class == Class.Bit32 || elf.Class == Class.NotELF) {
39+
log.LogDebugMessage ($" Not a 64-bit ELF image. Ignored.");
40+
return;
41+
}
42+
43+
var elf64 = elf as ELF<ulong>;
44+
if (elf64 == null) {
45+
throw new InvalidOperationException ($"Internal error: {elf} is not ELF<ulong>");
46+
}
47+
48+
// We need to find all segments of Load type and make sure their alignment is as expected.
49+
foreach (ISegment segment in elf64.Segments) {
50+
if (segment.Type != SegmentType.Load) {
51+
continue;
52+
}
53+
54+
var segment64 = segment as Segment<ulong>;
55+
if (segment64 == null) {
56+
throw new InvalidOperationException ($"Internal error: {segment} is not Segment<ulong>");
57+
}
58+
59+
// TODO: what happens if the library is aligned at, say, 64k while 16k is required? Should we erorr out?
60+
// We will need more info about that, have to wait till Google formally announce the requirement.
61+
// At this moment the script https://developer.android.com/guide/practices/page-sizes#test they
62+
// provide suggests it's a strict requirement, so we test for equality below.
63+
if (segment64.Alignment == pageSize) {
64+
continue;
65+
}
66+
log.LogDebugMessage ($" expected segment alignment of 0x{pageSize:x}, found 0x{segment64.Alignment:x}");
67+
68+
// TODO: turn into a coded warning and, eventually, error. Need better wording.
69+
// Until dotnet runtime produces properly aligned libraries, this should be a plain message as a warning
70+
// would break all the tests that require no warnings to be produced during build.
71+
log.LogMessage ($"Native {elf64.Machine} shared library '{Path.GetFileName (path)}', from NuGet package {GetNugetPackageInfo ()} isn't properly aligned.");
72+
break;
73+
}
74+
75+
string GetNugetPackageInfo ()
76+
{
77+
const string Unknown = "<unknown>";
78+
79+
if (item == null) {
80+
return Unknown;
81+
}
82+
83+
var sb = new StringBuilder ();
84+
string? metaValue = item.GetMetadata ("NuGetPackageId");
85+
if (String.IsNullOrEmpty (metaValue)) {
86+
return Unknown;
87+
}
88+
89+
sb.Append (metaValue);
90+
metaValue = item.GetMetadata ("NuGetPackageVersion");
91+
if (!String.IsNullOrEmpty (metaValue)) {
92+
sb.Append (" version ");
93+
sb.Append (metaValue);
94+
}
95+
96+
return sb.ToString ();
97+
}
98+
}
99+
18100
public static bool IsEmptyAOTLibrary (TaskLoggingHelper log, string path)
19101
{
20102
if (String.IsNullOrEmpty (path) || !File.Exists (path)) {

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2092,6 +2092,7 @@ because xbuild doesn't support framework reference assemblies.
20922092
IncludeFiles="@(AndroidPackagingOptionsInclude)"
20932093
ZipFlushFilesLimit="$(_ZipFlushFilesLimit)"
20942094
ZipFlushSizeLimit="$(_ZipFlushSizeLimit)"
2095+
ZipAlignmentPages="$(AndroidZipAlignment)"
20952096
UseAssemblyStore="$(AndroidUseAssemblyStore)">
20962097
<Output TaskParameter="OutputFiles" ItemName="ApkFiles" />
20972098
</BuildApk>
@@ -2129,6 +2130,7 @@ because xbuild doesn't support framework reference assemblies.
21292130
IncludeFiles="@(AndroidPackagingOptionsInclude)"
21302131
ZipFlushFilesLimit="$(_ZipFlushFilesLimit)"
21312132
ZipFlushSizeLimit="$(_ZipFlushSizeLimit)"
2133+
ZipAlignmentPages="$(AndroidZipAlignment)"
21322134
UseAssemblyStore="$(AndroidUseAssemblyStore)">
21332135
<Output TaskParameter="OutputFiles" ItemName="BaseZipFile" />
21342136
</BuildBaseAppBundle>

0 commit comments

Comments
 (0)