-
Notifications
You must be signed in to change notification settings - Fork 537
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
Embedded assemblies blob #6311
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 1cee121
[WIP] Blob generation works
grendello 8044c9f
[WIP] Simplify runtime structures + packaging
grendello e9b2ba2
[WIP] Check arch-specific assembly list identity and add a manifest
grendello c270f8d
[WIP] Flush some work to GH
grendello 94f422e
[WIP] Doesn't build, saving work
grendello 4cc4efc
[WIP] assembly mapping progress
grendello 83df401
[WIP] It's alive!
grendello 43a54cd
[WIP] Towards split config detection
grendello 07d84f9
[WIP] Optimize code when dealing with split APKs
grendello 9030cf3
[WIP] Fix DebugRuntime.init signature
grendello b2d5325
[WIP] Beginnings of the blob reader
grendello 28611c9
[WIP] Blob reader console app
grendello 4e7ac66
[WIP] Support reading from apk/aab archives
grendello bd5ea4c
[WIP] Test helper progress
grendello 117c8f7
[WIP] Fix a handful of tests
grendello 88f95bf
[WIP] More test fixes + some debug logging
grendello f81a467
[WIP] More test fixes
grendello bdcfc89
[WIP] Another set of test fixes
grendello 15b9377
[WIP] More test fixes
grendello 4f2a1d3
[WIP] Even more test (and logic) fixes
grendello e9bcd9d
[WIP] Don't overwrite logs
grendello a7f8460
[WIP] Disable assembly blobs when fastdev is active
grendello 284a8f9
[WIP] Add some debug info to see why some tests using F# fail
grendello 09cf605
[WIP] Don't add the `Abi` metadata to satellite assemblies
grendello 0cc93b1
[WIP] Don't use blobs in tests which run apkdiff
grendello 05bd0b4
[WIP] Update BuildReleaseArm64XFormsLegacy.apkdesc
grendello ffdab97
[WIP] Properly register .dll.config files
grendello 5a4b536
[WIP] Implement some TODOs
grendello 8117dc0
[WIP] update another apkdesc
grendello 25cf170
[WIP] A handful of updates
grendello 33aa5c7
[WIP] Update assembly decompressor to read blobs
grendello c111001
[WIP] Add blob format documentation
grendello 025be5a
[WIP] This should be a warning
grendello 9316f4d
[WIP] Update docs + fix an issue detected by the Roslyn analyzer
grendello 0ee96ef
[WIP] Code cleanup
grendello c8673b9
[WIP] Replace all instances of 'blob' with 'store'
grendello 3759790
[WIP] The blob -> store renaming changed file extensions for blobs,
grendello 3e58097
[WIP] Mind your extensions + reuse constants more
grendello d57ba72
[WIP] Design blobs for Design Time Build
grendello 2bc8d38
[WIP] Try to fix designer tests again
grendello 4de3779
[WIP] Add a trailing comma
grendello fd9a54a
[WIP] Force Linux builds to use g++/gcc 10, for full C++20 support
grendello d930fef
[WIP] Attempt to work around a mingw bug on the CI bots
grendello df09449
[WIP] Another attempt to work around a mingw issue
grendello f12f7aa
[WIP] And another attempt to make Linux CI bots mingw work
grendello f0f8293
[WIP] Bundled apps should not use blobs
grendello 03be452
[WIP] Fix single-rid builds
grendello a6bac6a
[WIP] Do not overwrite binlogs when using `dotnet run` in tests
grendello ed879e0
[WIP] Ignore duplicate assemblies when adding to sets
grendello d667f81
[WIP] Fix blob assembly synthesis when only one rid is used
grendello 03124dd
[WIP] More test fixes
grendello fd883c7
[WIP] And another test fix
grendello dcbaa06
[WIP] Duplicate assemblies are treated as arch-specific ones
grendello File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
, not21
?There was a problem hiding this comment.
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 buildingsrc/java-runtime
.There was a problem hiding this comment.
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
There was a problem hiding this comment.
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: