This doc outlines some tricks / gotchas / features of how we ship native code in Chrome on Android.
[TOC]
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.
- Android Q+ (Trichrome):
- Trichrome uses the same native library as Monochrome:
libmonochrome.so. libmonochrome.sois stored in the shared APK (TrichromeLibrary.apk) so that it can be shared with TrichromeWebView.- It is loaded by
libchromium_android_linker.sousingandroid_dlopen_ext()to enable RELRO sharing.
- Trichrome uses the same native library as Monochrome:
- (Legacy) Android N, O & P (Monochrome):
libmonochrome.sois stored uncompressed within the apk (an AndroidManifest.xml attribute disables extraction).- It is loaded directly from the apk by the system linker.
- It exports all JNI symbols and does not use explicit JNI registration.
- It is not loaded by
libchromium_android_linker.soand 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. |
- Crashpad is a native library providing out-of-process crash dumping. When a dump is requested (e.g. after a crash), a Crashpad handler process is started to produce a dump.
- All of the Crashpad handler code is linked into the package's native
library.
libcrashpad_handler_trampoline.sois a minimal executable packaged with the main native library, stored uncompressed and left unextracted. When a dump is requested,/system/bin/linkeris executed to load the trampoline from the APK, which in turndlopen()s the main native library to load the remaining Crashpad handler code.
What is it?
- Sections of an ELF that provide debugging and symbolization information (e.g. ability convert addresses to function & line numbers).
How we use it:
- ELF debug information is too big to push to devices, even for local development.
- All of our APKs include
.sofiles with debug information removed viastrip. - Unstripped libraries are stored at
out/Default/lib.unstripped.- Many of our scripts are hardcoded to look for them there.
What are they:
- Unwind info is data that describes how to unwind the stack. It is:
- It is required to support C++ exceptions (which Chrome doesn't use).
- It can also be used to produce stack traces.
- It is generally stored in an ELF section called
.eh_frame&.eh_frame_hdr, but arm32 stores it in.ARM.exidxand.ARM.extab.- You can see these sections via:
readelf -S libchrome.so
- You can see these sections via:
- "Frame Pointers" is a calling convention that ensures every function call has the return address pushed onto the stack.
- Frame Pointers can also be used to produce stack traces (but without entries for inlined functions).
How we use them:
- We disable unwind information (search for
exclude_unwind_tables). - For all architectures except arm64, we disable frame pointers in order to reduce binary size (search for
enable_frame_pointers). - Crashes are unwound offline using
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) - To facilitate heap profiling, we ship unwind information to arm32 canary & dev channels as a separate file:
assets/unwind_cfi_32
- For ChromePublic.apk:
JNI_OnLoad()is the only exported symbol (enforced by a linker script).- Native methods registered explicitly during start-up by generated code.
- For TrichromeChrome.aab and (legacy) MonochromePublic.apk:
JNI_OnLoad()andJava_*symbols are exported by linker script.- No manual JNI registration is done. Symbols are resolved lazily by the runtime.
- All flavors of
lib(mono)chrome.soenable "packed relocations", or "APS2 relocations" in order to save binary size.- Refer to this source file for an explanation of the format.
- To process these relocations:
- Pre-M Android: Our custom linker must be used.
- M+ Android: The system linker understands the format.
- To see if relocations are packed, look for
LOOS+#when running:readelf -S libchrome.so - Android P+ supports an even better format known as RELR.
- We'll likely switch non-Monochrome apks over to using it once it is implemented in
lld.
- We'll likely switch non-Monochrome apks over to using it once it is implemented in
What is it?
- RELRO refers to the ELF segment
GNU_RELRO. It contains data that the linker marks as read-only after it applies relocations.- To inspect the size of the segment:
readelf --segments libchrome.so - For
lib(mono)chrome.sothe region occupies about 3.1MiB on arm32 and 2.6MiB on arm64
- To inspect the size of the segment:
- If two processes map this segment to the same virtual address space, then pages of memory within the segment which contain only relative relocations (99% of them) will be byte-for-byte identical.
- "RELRO sharing" is when this segment is moved into shared memory and shared by multiple processes.
- Processes
fork()ed from the app zygote (where the library is loaded) share RELRO (viafork()'s copy-on-write semantics), but this region is not shared with other process types (privileged, utility, GPU)
How does it work?
- For a more detailed description, refer to comments in Linker.java.
- For Android Q+ (Trichrome):
- TrichromeWebView works the same way as on Android N-P.
- TrichromeChrome uses
android_dlopen_ext()andASharedMemory_create()to perform RELRO sharing, and then relies on a subsequent call toSystem.loadLibrary()to enable JNI method resolution without loading the library a second time. - For renderer processes, TrichromeChrome
fork()s from a chrome-specific app zygote.libmonochrome.sois loaded in the zygote beforefork().- Similar to O-P, app zygote provides copy-on-write memory semantics so RELRO sharing is redundant.
- For Android R+ (still Trichrome)
- The RELRO region is created in the App Zygote, picked up by the Browser process, which then redistributes the region to all other processes. The receiving of the region and remapping it on top of the non-shared RELRO happens asynchronously after the library has been loaded. Native code is generally already running at this point. Hence the replacement must be atomic.
- (Legacy) For Android N-P (Monochrome):
- The OS maintains a RELRO file on disk with the contents of the GNU_RELRO segment.
- All Android apps that contain a WebView load
libmonochrome.soat the same virtual address and apply RELRO sharing against the memory-mapped RELRO file. - Chrome uses
WebViewLibraryPreloaderto call into the same WebView library loading code.- When Monochrome is the WebView provider,
libmonochrome.sois loaded with the system's cached RELRO's applied.
- When Monochrome is the WebView provider,
System.loadLibrary()is called afterwards.- When Monochrome is the WebView provider, this only calls JNI_OnLoad, since the library is already loaded. Otherwise, this loads the library and no RELRO sharing occurs.
- (Legacy) For non-low-end Android O-P (where there's a WebView zygote):
- For non-renderer processes, the above Android N+ logic applies.
- For renderer processes, the OS starts all Monochrome renderer processes by
fork()ing the WebView zygote rather than the normal application zygote.
- During start-up, we
fork()a process that reads a byte from each page of the library's memory (or just the ordered range of the library).
- We used to use the system linker on M (
ModernLinker.java).- This was removed due to poor performance.
- We used to use
relocation_packerto 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.- We now link with
lld, which supports packed relocations natively and doesn't have these problems.
- We now link with
- We used to use the Crazy Linker until Android M was deprecated
- It allowed storing
libchrome.souncompressed within the apk before the system linker allowed it (with the namecrazy.libchrome.soto avoid extraction). - It was loaded directly from the apk via
libchromium_android_linker.so. - Only JNI_OnLoad was exported. Explicit JNI registration was required
because the Android runtime uses the system's
dlsym(), which doesn't know about Crazy-Linker-opened libraries. (see JNI README).
- It allowed storing