This doc outlines some tricks / gotchas / features of how we ship native code in Chrome on Android.
Current Android versions use Trichrome (TrichromeChrome.aab + TrichromeLibrary.apk). Monochrome (MonochromePublic.aab) was used for Android N, O, and P but is now deprecated. Note that Trichrome continues to use the native library named libmonochrome.so.
libmonochrome.so.libmonochrome.so is stored in the shared APK (TrichromeLibrary.apk) so that it can be shared with TrichromeWebView.libchromium_android_linker.so using android_dlopen_ext() to enable RELRO sharing.libmonochrome.so is stored uncompressed within the apk (an AndroidManifest.xml attribute disables extraction).libchromium_android_linker.so and relies on the system's webview zygote for RELRO sharing.The packaging above extends to cover both 32-bit and 64-bit device configurations.
Chrome support 64-bit builds, but these do not ship to Stable. The system WebView APK that ships to those devices contains a 32-bit library, and for 64-bit devices, a 64-bit library as well (32-bit WebView client apps will use the 32-bit library, and vice-versa).
Trichrome uses separate apps for Chrome and WebView, but places shared resources in a third shared-library APK (TrichromeLibrary). The table below shows which native libraries are packaged where. Note that dummy placeholder libraries are inserted where needed, since Android determines supported ABIs from the presence of native libraries, and the ABIs of a shared library APK must match its client app.
Trichrome has 4 permutations on 64-bit to support different device configurations:
| Builds on | Variant | Chrome | Library | WebView |
|---|---|---|---|---|
| 32-bit | trichrome | 32/dummy | 32/combined | 32/dummy |
| 64-bit | trichrome | 32/dummy, 64/dummy | 32/combined, 64/dummy | 32/dummy, 64/webview |
| 64-bit | trichrome_64_32 | 32/dummy, 64/dummy | 32/dummy, 64/combined | 32/webview, 64/dummy |
| 64-bit | trichrome_64 | 64/dummy | 64/combined | 64/dummy |
| 64-bit | trichrome_32 | 32/dummy | 32/combined | 32/dummy |
Monochrome's intent was to eliminate the duplication between the 32-bit Chrome and WebView libraries. In 32-bit Monochrome, a single combined library serves both Chrome and WebView needs. The 64-bit version adds an extra WebView-only library.
In each of these cases, the library name of the combined and WebView-only libraries must match (an Android platform requirement), so both libs are named libmonochrome.so (or libmonochrome_64.so in the 64-bit browser case).
Naming scheme for the various targets: monochrome_(browser ABI)_(extra_webview ABI)
| Builds on | Variant | Description |
|---|---|---|
| 32-bit | monochrome | The original 32-bit-only version |
| 64-bit | monochrome | The original 64-bit version, with 32-bit combined lib and 64-bit WebView. This would be named monochrome_32_64_apk if not for legacy naming. |
| 64-bit | monochrome_64_32 | 64-bit combined lib with 32-bit WebView library. |
| 64-bit | monochrome_64 | 64-bit combined lib only, for eventual pure 64-bit hardware. |
| 64-bit | monochrome_32 | A mirror of the original 32-bit-only version on 64-bit. |
dlopen()s the main native library to load the remaining Crashpad handler code.What is it?
How we use it:
.so files with debug information removed via strip.out/Default/lib.unstripped.What are they:
.eh_frame & .eh_frame_hdr, but arm32 stores it in .ARM.exidx and .ARM.extab.readelf -S libchrome.soHow we use them:
exclude_unwind_tables).enable_frame_pointers).minidump_stackwalk, which can create a stack trace given a snapshot of stack memory and the unstripped library (see //docs/testing/using_crashpad_with_content_shell.md)assets/unwind_cfi_32JNI_OnLoad() is the only exported symbol (enforced by a linker script).JNI_OnLoad() and Java_* symbols are exported by linker script.lib(mono)chrome.so enable “packed relocations”, or “APS2 relocations” in order to save binary size.LOOS+# when running: readelf -S libchrome.solld.What is it?
GNU_RELRO. It contains data that the linker marks as read-only after it applies relocations.readelf --segments libchrome.solib(mono)chrome.so the region occupies about 3.1MiB on arm32 and 2.6MiB on arm64fork()ed from the app zygote (where the library is loaded) share RELRO (via fork()'s copy-on-write semantics), but this region is not shared with other process types (privileged, utility, GPU)How does it work?
android_dlopen_ext() and ASharedMemory_create() to perform RELRO sharing, and then relies on a subsequent call to System.loadLibrary() to enable JNI method resolution without loading the library a second time.fork()s from a chrome-specific app zygote. libmonochrome.so is loaded in the zygote before fork().libmonochrome.so at the same virtual address and apply RELRO sharing against the memory-mapped RELRO file.WebViewLibraryPreloader to call into the same WebView library loading code.libmonochrome.so is loaded with the system‘s cached RELRO’s applied.System.loadLibrary() is called afterwards.fork()ing the WebView zygote rather than the normal application zygote.Some Chrome code is placed in feature-specific libraries and delivered via Dynamic Feature Modules.
A linker-assisted partitioning system automates the placement of code into either the main Chrome library or feature-specific .so libraries. Feature code may continue to make use of core Chrome code (eg. base::) without modification, but Chrome must call feature code through a virtual interface.
How partitioning works
The lld linker is now capable of producing a partitioned library, which is effectively an intermediate single file containing multiple libraries. A separate tool (llvm-objcopy) then splits the file into standalone .so files, invoked through a partitioned shared library GN template.
The primary partition is Chrome's main library (eg. libchrome.so), and other partitions may contain feature code (eg. libvr.so). By specifying a list of C/C++ symbols to use as entrypoints, the linker can collect all code used only through these entrypoints, and place it in a particular partition.
To facilitate partitioning, all references from Chrome to the feature entrypoints must be indirect. That is, Chrome must obtain a symbol from the feature library through dlsym(), cast the pointer to its actual type, and call through the resulting pointer.
Feature code retains the ability to freely call back into Chrome‘s core code. When loading the library, the feature module system uses the feature name to look up a partition name (libfoo.so) in an address offset table built into the main library. The resulting offset is supplied to android_dlopen_ext(), which instructs Android to load the library in a particular reserved address region. This allows the feature library’s relative references back to the main library to work, as if the feature code had been linked into the main library originally. No dynamic symbol resolution is required here.
Implications on code placement
Builds that support partitioned libraries
Partitioned libraries are usable when all of the following are true:
fork() a process that reads a byte from each page of the library's memory (or just the ordered range of the library).ModernLinker.java).relocation_packer to pack relocations after linking, which complicated our build system and caused many problems for our tools because it caused logical addresses to differ from physical addresses.lld, which supports packed relocations natively and doesn't have these problems.libchrome.so uncompressed within the apk before the system linker allowed it (with the name crazy.libchrome.so to avoid extraction).libchromium_android_linker.so.dlsym(), which doesn’t know about Crazy-Linker-opened libraries. (see JNI README).