Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] Don't skip symbol-less AOT libs (#6630)
Browse files Browse the repository at this point in the history
Fixes: #6615

Context: db161ae

Commit db161ae added a way to analyze and skip native shared libraries
produced by the Mono AOT compiler and skip packaging for those which
contain no executable code (a common case for Profiled AOT builds).
However, it turns out that by adding the following property to the
project configuration:

	<AndroidAotAdditionalArguments>no-write-symbols,nodebug</AndroidAotAdditionalArguments>

can make the `ELFHelper` treat the shared library in question as
empty, even though it does contain executable, AOT-d, code.

The reason for this is the way Mono generates code when the above
options are used.  It will output a blob of executable code without any
labels (which are turned to symbols when compiling and linking) and
then add just a single non-function symbol whose name ends with the
`_plt` suffix.  This symbol is essentially a jump table, a collection
of tiny snippets of code which jump into the executable code blob
mentioned above.  Since the `*_plt` symbol is **not** a function (that
is, its symbol type is not `FUNC`), it was skipped by ELFHelper:

	27: 0000000000078620 22480 NOTYPE  LOCAL  DEFAULT     6 mono_aot_Mono_Android_plt

Furthermore, since shared libraries built with the above options do not
contain any `FUNC` symbols, they were consequently rejected as empty
and not packaged, leading to Issue #6615.

Fix the problem by looking for non-empty symbols in executable sections
of the shared library with symbol names ending in `_plt`.
  • Loading branch information
grendello authored and jonpryor committed Jan 25, 2022
1 parent e9c7503 commit d3cd463
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 3 deletions.
Expand Up @@ -447,7 +447,7 @@ public void HybridAOT ([Values ("armeabi-v7a;arm64-v8a", "armeabi-v7a", "arm64-v

[Test]
[Category ("LLVM")]
public void NoSymbolsArgShouldReduceAppSize ([Values ("", "Hybrid")] string androidAotMode)
public void NoSymbolsArgShouldReduceAppSize ([Values ("", "Hybrid")] string androidAotMode, [Values (false, true)] bool skipDebugSymbols)
{
if (Builder.UseDotNet) {
Assert.Ignore ("https://github.com/dotnet/runtime/issues/57800");
Expand Down Expand Up @@ -476,7 +476,12 @@ public void NoSymbolsArgShouldReduceAppSize ([Values ("", "Hybrid")] string andr
using (var apk = ZipHelper.OpenZip (apkPath)) {
xaAssemblySize = ZipHelper.ReadFileFromZip (apk, $"lib/{supportedAbi}/libaot-Mono.Android.dll.so").Length;
}
proj.SetProperty ("AndroidAotAdditionalArguments", "no-write-symbols");

string additionalArgs = "no-write-symbols";
if (skipDebugSymbols) {
additionalArgs += ",nodebug";
}
proj.SetProperty ("AndroidAotAdditionalArguments", additionalArgs);
Assert.IsTrue (b.Build (proj), "Second build should have succeeded.");
FileAssert.Exists (apkPath);
using (var apk = ZipHelper.OpenZip (apkPath)) {
Expand Down
31 changes: 30 additions & 1 deletion src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs
Expand Up @@ -56,13 +56,42 @@ static bool IsEmptyAOTLibrary (TaskLoggingHelper log, string path, IELF elf)
return false;
}

bool isElf64 = elf.Class == Class.Bit64;
foreach (var entry in symtab.Entries) {
if (entry.Type == SymbolType.Function) {
return false;
}
}

if (!(isElf64 ? IsNonEmptyCodeSymbol (entry as SymbolEntry<ulong>) : IsNonEmptyCodeSymbol (entry as SymbolEntry<uint>))) {
continue;
}

// We have an entry that's in (some) executable section and has some code in it.
// Mono creates symbols which are essentially jump tables into executable code
// inside the DSO that is not accessible via any other symbol, merely a blob of
// executable code. The jump table symbols are named with the `_plt` prefix.
if (entry.Name.EndsWith ("_plt")) {
return false;
}
}
return true;

bool IsNonEmptyCodeSymbol<T> (SymbolEntry<T>? symbolEntry) where T : struct
{
if (symbolEntry == null) {
return true; // Err on the side of caution
}

Type t = typeof(T);
ulong size = 0;
if (t == typeof(System.UInt64)) {
size = (ulong)(object)symbolEntry.Size;
} else if (t == typeof(System.UInt32)) {
size = (uint)(object)symbolEntry.Size;
}

return size != 0 && symbolEntry.PointedSection.Type == SectionType.ProgBits;
}
}

static ISymbolTable? GetSymbolTable (IELF elf, string sectionName)
Expand Down

0 comments on commit d3cd463

Please sign in to comment.