This doc outlines some tricks / gotchas / features of how we ship native code in Chrome on Android.
libchrome.so
is stored uncompressed within the apk (with the name crazy.libchrome.so
to avoid extraction).libchromium_android_linker.so
.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.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.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).
Monochrome's intent was to eliminate the duplication between the 32-bit Chrome and Webview libraries (most of the library is identical). In 32-bit Monochrome, a single combined library serves both Chrome and Webview needs. The 64-bit version adds an extra Webview-only library.
More recently, additional Monochrome permutations have arrived. First, Google Play will eventually require that apps offer a 64-bit version to compatible devices. In Monochrome, this implies swapping the architecture of the Chrome and Webview libraries (64-bit combined lib, and extra 32-bit Webview lib). Further down the road, silicon vendors may drop 32-bit support from their chips, after which a pure 64-bit version of Monochrome will apply. 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).
Since 3 of these variations require a 64-bit build config, it makes sense to also support the 4th variant on 64-bit, thus allowing a single builder to build all variants (if desired). Further, a naming scheme must exist to disambiguate the various targets:
monochrome_(browser ABI)_(extra_webview ABI)
For example, the 64-bit browser version with extra 32-bit Webview is monochrome_64_32_apk. The combinations are as follows:
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, to allow building all products on one builder. The result won't be bit-identical to the original, since there are subtle compilation differences. |
Trichrome has the same 4 permutations as Monochrome, but adds another dimension. Trichrome returns to separate apps for Chrome and Webview, but places shared resources in a third shared-library APK. 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.
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 |
dlopen()
s the main native library to load the remaining Crashpad handler code. A trampoline is used to de-duplicate shared code between Crashpad and the main native library packaged with it. This approach isn’t used for P- because the linker doesn't support loading executables on its command line until Q. This approach also requires building a suitable LD_LIBRARY_PATH to locate any shared libraries Chrome/WebView depends on.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.so
How 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_breakpad_with_content_shell.md)assets/unwind_cfi_32
JNI_OnLoad()
is the only exported symbol (enforced by a linker script).dlsym()
, which doesn’t know about Crazy-Linker-opened libraries.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.so
lld
.What is it?
GNU_RELRO
. It contains data that the linker marks as read-only after it applies relocations.readelf --segments libchrome.so
lib(mono)chrome.so
on arm32, it's about 2mb.fork()
ed processes, all pages are already shared (via fork()
's copy-on-write semantics), so RELRO sharing does not apply to them.How does it work?
libchrome.so
loaded normally.GNU_RELRO
segment copied into ashmem
(shared memory).munmap()
& mmap()
).GNU_RELRO
into private memory and applies relocations as per normal.munmap()
& mmap()
).libmonochrome.so
at the same virtual address and apply RELRO sharing against the memory-mapped RELRO file.MonochromeLibraryPreloader
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.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()
.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.