Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embedded assemblies blob #6311

Merged
merged 54 commits into from
Oct 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
cfaeb6c
[WIP] Blob generation + infrastructure
grendello Sep 2, 2021
1cee121
[WIP] Blob generation works
grendello Sep 13, 2021
8044c9f
[WIP] Simplify runtime structures + packaging
grendello Sep 14, 2021
e9b2ba2
[WIP] Check arch-specific assembly list identity and add a manifest
grendello Sep 14, 2021
c270f8d
[WIP] Flush some work to GH
grendello Sep 16, 2021
94f422e
[WIP] Doesn't build, saving work
grendello Sep 16, 2021
4cc4efc
[WIP] assembly mapping progress
grendello Sep 16, 2021
83df401
[WIP] It's alive!
grendello Sep 17, 2021
43a54cd
[WIP] Towards split config detection
grendello Sep 17, 2021
07d84f9
[WIP] Optimize code when dealing with split APKs
grendello Sep 20, 2021
9030cf3
[WIP] Fix DebugRuntime.init signature
grendello Sep 20, 2021
b2d5325
[WIP] Beginnings of the blob reader
grendello Sep 21, 2021
28611c9
[WIP] Blob reader console app
grendello Sep 23, 2021
4e7ac66
[WIP] Support reading from apk/aab archives
grendello Sep 24, 2021
bd5ea4c
[WIP] Test helper progress
grendello Sep 27, 2021
117c8f7
[WIP] Fix a handful of tests
grendello Sep 28, 2021
88f95bf
[WIP] More test fixes + some debug logging
grendello Sep 28, 2021
f81a467
[WIP] More test fixes
grendello Sep 29, 2021
bdcfc89
[WIP] Another set of test fixes
grendello Sep 29, 2021
15b9377
[WIP] More test fixes
grendello Sep 30, 2021
4f2a1d3
[WIP] Even more test (and logic) fixes
grendello Sep 30, 2021
e9bcd9d
[WIP] Don't overwrite logs
grendello Sep 30, 2021
a7f8460
[WIP] Disable assembly blobs when fastdev is active
grendello Sep 30, 2021
284a8f9
[WIP] Add some debug info to see why some tests using F# fail
grendello Oct 4, 2021
09cf605
[WIP] Don't add the `Abi` metadata to satellite assemblies
grendello Oct 5, 2021
0cc93b1
[WIP] Don't use blobs in tests which run apkdiff
grendello Oct 5, 2021
05bd0b4
[WIP] Update BuildReleaseArm64XFormsLegacy.apkdesc
grendello Oct 5, 2021
ffdab97
[WIP] Properly register .dll.config files
grendello Oct 5, 2021
5a4b536
[WIP] Implement some TODOs
grendello Oct 6, 2021
8117dc0
[WIP] update another apkdesc
grendello Oct 6, 2021
25cf170
[WIP] A handful of updates
grendello Oct 6, 2021
33aa5c7
[WIP] Update assembly decompressor to read blobs
grendello Oct 6, 2021
c111001
[WIP] Add blob format documentation
grendello Oct 7, 2021
025be5a
[WIP] This should be a warning
grendello Oct 7, 2021
9316f4d
[WIP] Update docs + fix an issue detected by the Roslyn analyzer
grendello Oct 11, 2021
0ee96ef
[WIP] Code cleanup
grendello Oct 11, 2021
c8673b9
[WIP] Replace all instances of 'blob' with 'store'
grendello Oct 12, 2021
3759790
[WIP] The blob -> store renaming changed file extensions for blobs,
grendello Oct 13, 2021
3e58097
[WIP] Mind your extensions + reuse constants more
grendello Oct 18, 2021
d57ba72
[WIP] Design blobs for Design Time Build
grendello Oct 18, 2021
2bc8d38
[WIP] Try to fix designer tests again
grendello Oct 18, 2021
4de3779
[WIP] Add a trailing comma
grendello Oct 18, 2021
fd9a54a
[WIP] Force Linux builds to use g++/gcc 10, for full C++20 support
grendello Oct 18, 2021
d930fef
[WIP] Attempt to work around a mingw bug on the CI bots
grendello Oct 19, 2021
df09449
[WIP] Another attempt to work around a mingw issue
grendello Oct 19, 2021
f12f7aa
[WIP] And another attempt to make Linux CI bots mingw work
grendello Oct 19, 2021
f0f8293
[WIP] Bundled apps should not use blobs
grendello Oct 19, 2021
03be452
[WIP] Fix single-rid builds
grendello Oct 20, 2021
a6bac6a
[WIP] Do not overwrite binlogs when using `dotnet run` in tests
grendello Oct 20, 2021
ed879e0
[WIP] Ignore duplicate assemblies when adding to sets
grendello Oct 20, 2021
d667f81
[WIP] Fix blob assembly synthesis when only one rid is used
grendello Oct 20, 2021
03124dd
[WIP] More test fixes
grendello Oct 20, 2021
fd883c7
[WIP] And another test fix
grendello Oct 20, 2021
dcbaa06
[WIP] Duplicate assemblies are treated as arch-specific ones
grendello Oct 21, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Configuration.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<!-- Should correspond to the first value from `$(API_LEVELS)` in `build-tools/api-xml-adjuster/Makefile` -->
<AndroidFirstFrameworkVersion Condition="'$(AndroidFirstFrameworkVersion)' == ''">v4.4</AndroidFirstFrameworkVersion>
<AndroidFirstApiLevel Condition="'$(AndroidFirstApiLevel)' == ''">19</AndroidFirstApiLevel>
<AndroidJavaRuntimeApiLevel Condition="'$(AndroidJavaRuntimeApiLevel)' == ''">21</AndroidJavaRuntimeApiLevel>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't "Classic"/"Xammie" Xamarin.Android still support API-19? Shouldn't this value likewise by 19, not 21?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also use a comment mentioning that this controls the android.jar used when building src/java-runtime.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary to build Java code that accesses properties added in 21. Access to those properties is protected with an API level check, so it will run fine on 19

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in src/java-runtime/java-runtime.targes like so:

<_AndroidJar>"$(AndroidToolchainDirectory)\sdk\platforms\android-$(AndroidJavaRuntimeApiLevel)\android.jar"</_AndroidJar>

<!-- The min API level supported by Microsoft.Android.Sdk, should refactor/remove when this value is the same as $(AndroidFirstApiLevel) -->
<AndroidMinimumDotNetApiLevel Condition="'$(AndroidMinimumDotNetApiLevel)' == ''">21</AndroidMinimumDotNetApiLevel>
<AndroidFirstPlatformId Condition="'$(AndroidFirstPlatformId)' == ''">$(AndroidFirstApiLevel)</AndroidFirstPlatformId>
Expand Down
1 change: 1 addition & 0 deletions Documentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

* [Submitting Bugs, Feature Requests, and Pull Requests][bugs]
* [Directory Structure](project-docs/ExploringSources.md)
* [Assembly store format](project-docs/AssemblyStores.md)

[bugs]: https://github.com/xamarin/xamarin-android/wiki/Submitting-Bugs,-Feature-Requests,-and-Pull-Requests

Expand Down
224 changes: 224 additions & 0 deletions Documentation/project-docs/AssemblyStores.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
**Table of Contents**

- [Assembly Store format and purpose](#assembly-store-format-and-purpose)
- [Rationale](#rationale)
- [Store kinds and locations](#store-kinds-and-locations)
- [Store format](#store-format)
- [Common header](#common-header)
- [Assembly descriptor table](#assembly-descriptor-table)
- [Index store](#index-store)
- [Hash table format](#hash-table-format)

<!-- markdown-toc end -->

# Assembly Store format and purpose

Assembly stores are binary files which contain the managed
assemblies, their debug data (optionally) and the associated config
file (optionally). They are placed inside the Android APK/AAB
archives, replacing individual assemblies/pdb/config files.

Assembly stores are an optional form of assembly storage in the
archive, they can be used in all build configurations **except** when
Fast Deployment is in effect (in which case assemblies aren't placed
in the archives at all, they are instead synchronized from the host to
the device/emulator filesystem)

## Rationale

During native startup, the Xamarin.Android runtime looks inside the
application APK file for the managed assemblies (and their associated
pdb and config files, if applicable) in order to map them (using the
`mmap(2)` call) into memory so that they can be given to the Mono
runtime when it requests a given assembly is loaded. The reason for
the memory mapping is that, as far as Android is concerned, managed
assembly files are just data/resources and, thus, aren't extracted to
the filesystem. As a result, Mono wouldn't be able to find the
assemblies by scanning the filesystem - the host application
(Xamarin.Android) must give it a hand in finding them.

Applications can contain hundreds of assemblies (for instance a Hello
World MAUI application currently contains over 120 assemblies) and
each of them would have to be mmapped at startup, together with its
pdb and config files, if found. This not only costs time (each `mmap`
invocation is a system call) but it also makes the assembly discovery
an O(n) algorithm, which takes more time as more assemblies are added
to the APK/AAB archive.

An assembly store, however, needs to be mapped only once and any
further operations are merely pointer arithmetic, making the process
not only faster but also reducing the algorithm complexity to O(1).

# Store kinds and locations

Each application will contain at least a single assembly store, with
assemblies that are architecture-agnostics and any number of
architecture-specific stores. dotnet ships with a handful of
assemblies that **are** architecture-specific - those assemblies are
placed in an architecture specific store, one per architecture
supported by and enabled for the application. On the execution time,
the Xamarin.Android runtime will always map the architecture-agnostic
store and one, and **only** one, of the architecture-specific stores.

Stores are placed in the same location in the APK/AAB archive where the
individual assemblies traditionally live, the `assemblies/` (for APK)
and `base/root/assemblies/` (for AAB) folders.

The architecture agnostic store is always named `assemblies.blob` while
the architecture-specific one is called `assemblies.[ARCH].blob`.

Each APK in the application (e.g. the future Feature APKs) **may**
contain the above two assembly store files (some APKs may contain only
resources, other may contain only native libraries etc)

Currently, Xamarin.Android applications will produce only one set of
stores but when Xamarin.Android adds support for Android Features, each
feature APK will contain its own set of stores. All of the APKs will
follow the location, format and naming conventions described above.

# Store format

Each store is a structured binary file, using little-endian byte order
and aligned to a byte boundary. Each store consists of a header, an
assembly descriptor table and, optionally (see below), two tables with
assembly name hashes. All the stores are assigned a unique ID, with
the store having ID equal to `0` being the [Index store](#index-store)

Assemblies are stored as adjacent byte streams:

- **Image data**
Required to be present for all assemblies, contains the actual
assembly PE image.
- **Debug data**
Optional. Contains the assembly's PDB or MDB debug data.
- **Config data**
Optional. Contains the assembly's .config file. Config data
**must** be terminated with a `NUL` character (`0`), this is to
make runtime code slightly more efficient.

All the structures described here are defined in the
[`xamarin-app.hh`](../../src/monodroid/jni/xamarin-app.hh) file.
Should there be any difference between this document and the
structures in the header file, the information from the header is the
one that should be trusted.

## Common header

All kinds of stores share the following header format:

struct AssemblyStoreHeader
{
uint32_t magic;
uint32_t version;
uint32_t local_entry_count;
uint32_t global_entry_count;
uint32_t store_id;
;

Individual fields have the following meanings:

- `magic`: has the value of 0x41424158 (`XABA`)
- `version`: a value increased every time assembly store format changes.
- `local_entry_count`: number of assemblies stored in this assembly
store (also the number of entries in the assembly descriptor
table, see below)
- `global_entry_count`: number of entries in the index store's (see
below) hash tables and, thus, the number of assemblies stored in
**all** of the assembly stores across **all** of the application's
APK files, all the other assembly stores have `0` in this field
since they do **not** have the hash tables.
- `store_id`: a unique ID of this store.

## Assembly descriptor table

Each store header is followed by a table of
`AssemblyStoreHeader.local_entry_count` entries, each entry
defined by the following structure:

struct AssemblyStoreAssemblyDescriptor
{
uint32_t data_offset;
uint32_t data_size;
uint32_t debug_data_offset;
uint32_t debug_data_size;
uint32_t config_data_offset;
uint32_t config_data_size;
};

Only the `data_offset` and `data_size` fields must have a non-zero
value, other fields describe optional data and can be set to `0`.

Individual fields have the following meanings:

- `data_offset`: offset of the assembly image data from the
beginning of the store file
- `data_size`: number of bytes of the image data
- `debug_data_offset`: offset of the assembly's debug data from the
beginning of the store file. A value of `0` indicates there's no
debug data for this assembly.
- `debug_data_size`: number of bytes of debug data. Can be `0` only
if `debug_data_offset` is `0`
- `config_data_offset`: offset of the assembly's config file data
from the beginning of the store file. A value of `0` indicates
there's no config file data for this assembly.
- `config_data_size`: number of bytes of config file data. Can be
`0` only if `config_data_offset` is `0`

## Index store

Each application will contain exactly one store with a global index -
two tables with assembly name hashes. All the other stores **do not**
contain these tables. Two hash tables are necessary because hashes
for 32-bit and 64-bit devices are different.

The hash tables follow the [Assembly descriptor
table](#assembly-descriptor-table) and precede the individual assembly
streams.

Placing the hash tables in a single index store, while "wasting" a
certain amount of memory (since 32-bit devices won't use the 64-bit
table and vice versa), makes for simpler and faster runtime
implementation and the amount of memory wasted isn't big (1000
two tables which are 8kb long each, this being the amount of memory
wasted)

### Hash table format

Both tables share the same format, despite the hashes themselves being
of different sizes. This is done to make handling of the tables
easier on the runtime.

Each entry contains, among other fields, the assembly name hash. In
case of satellite assemblies, the assembly culture (e.g. `en/` or
`fr/`) is treated as part of the assembly name, thus resulting in a
unique hash. The hash value is obtained using the
[xxHash](https://cyan4973.github.io/xxHash/) algorithm and is
calculated **without** including the `.dll` extension. This is done
for runtime efficiency as the vast majority of Mono requests to load
an assembly does not include the `.dll` suffix, thus saving us time of
appending it in order to generate the hash for index lookup.

Each entry is represented by the following structure:

struct AssemblyStoreHashEntry
{
union {
uint64_t hash64;
uint32_t hash32;
};
uint32_t mapping_index;
uint32_t local_store_index;
uint32_t store_id;
};

Individual fields have the following meanings:

- `hash64`/`hash32`: the 32-bit or 64-bit hash of the assembly's name
**without** the `.dll` suffix
- `mapping_index`: index into a compile-time generated array of
assembly data pointers. This is a global index, unique across
**all** the APK files comprising the application.
- `local_store_index`: index into assembly store [Assembly descriptor table](#assembly-descriptor-table)
describing the assembly.
- `store_id`: ID of the assembly store containing the assembly
7 changes: 7 additions & 0 deletions Xamarin.Android.sln
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "decompress-assemblies", "to
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tmt", "tools\tmt\tmt.csproj", "{1A273ED2-AE84-48E9-9C23-E978C2D0CB34}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "assembly-store-reader", "tools\assembly-store-reader\assembly-store-reader.csproj", "{DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Xamarin.Android.NamingCustomAttributes\Xamarin.Android.NamingCustomAttributes.projitems*{3f1f2f50-af1a-4a5a-bedb-193372f068d7}*SharedItemsImports = 5
Expand Down Expand Up @@ -408,6 +410,10 @@ Global
{1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Debug|AnyCPU.Build.0 = Debug|Any CPU
{1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Release|AnyCPU.ActiveCfg = Release|Any CPU
{1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Release|AnyCPU.Build.0 = Release|Any CPU
{DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|AnyCPU.ActiveCfg = Debug|anycpu
{DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|AnyCPU.Build.0 = Debug|anycpu
{DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|AnyCPU.ActiveCfg = Release|anycpu
{DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|AnyCPU.Build.0 = Release|anycpu
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -474,6 +480,7 @@ Global
{37FCD325-1077-4603-98E7-4509CAD648D6} = {864062D3-A415-4A6F-9324-5820237BA058}
{88B746FF-8D6E-464D-9D66-FF2ECCF148E0} = {864062D3-A415-4A6F-9324-5820237BA058}
{1A273ED2-AE84-48E9-9C23-E978C2D0CB34} = {864062D3-A415-4A6F-9324-5820237BA058}
{DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51} = {864062D3-A415-4A6F-9324-5820237BA058}
{1FED3F23-1175-42AA-BE87-EF1E8DB52F8B} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
3 changes: 3 additions & 0 deletions build-tools/automation/azure-pipelines.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ stages:
cancelTimeoutInMinutes: 2
workspace:
clean: all
variables:
CXX: g++-10
CC: gcc-10
steps:
- checkout: self
clean: true
Expand Down
1 change: 1 addition & 0 deletions build-tools/installers/create-installers.targets
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.SourceWriter.dll" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.SourceWriter.pdb" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\K4os.Compression.LZ4.dll" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\K4os.Hash.xxHash.dll" />
</ItemGroup>
<ItemGroup>
<_MSBuildTargetsSrcFiles Include="$(MSBuildTargetsSrcDir)\Xamarin.Android.AvailableItems.targets" />
Expand Down
Loading