Shared Libraries on Android
This doc outlines some tricks / gotchas / features of how we ship native code in Chrome on Android.
- Android J & K (ChromePublic.apk):
libchrome.so is stored compressed and extracted by Android during installation.
- Android L & M (ChromeModernPublic.apk):
libchrome.so is stored uncompressed within the apk (with the name
crazy.libchrome.so to avoid extraction).
- It is loaded directly from the apk (without extracting) by
- Android N+ (MonochromePublic.apk):
libmonochrome.so is stored uncompressed (AndroidManifest.xml attribute disables extraction) and loaded directly from the apk (functionality now supported by the system linker).
- 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.
- Chrome and ChromeModern (Android J through M):
- libcrashpad_handler.so is a standalone executable containing all of the crash dumping code. It is stored compressed and extracted automatically by the system, allowing it to be directly executed to produce a crash dump.
- Monochrome (N through P), Trichrome (P), and SystemWebView (P-):
- All of the Crashpad code is linked into the package‘s main native library (e.g. libmonochrome.so). When a dump is requested, /system/bin/app_process is executed, loading CrashpadMain.java which in turn uses JNI to call into the native crash dumping code. This approach requires building CLASSPATH and LD_LIBRARY_PATH variables to ensure app_process can locate CrashpadMain.java and any native libraries (e.g. system libraries, shared libraries, split apks, etc.) the package’s main native library depends on.
- Monochrome, Trichrome, and SystemWebView (Q+):
- All of the Crashpad handler code is linked into the package‘s native library. libcrashpad_handler_trampoline.so is a minimal executable packaged with the main native library, stored uncompressed and left unextracted. When a dump is requested, /system/bin/linker is executed to load the trampoline from the APK, which in turn
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?
- 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
.so files with debug information removed via
- Unstripped libraries are stored at
- Many of our scripts are hardcoded to look for them there.
Unwind Info & Frame Pointers
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_hdr, but arm32 stores it in
- You can see these sections via:
readelf -S libchrome.so
- “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
- For all architectures except arm64, we disable frame pointers in order to reduce binary size (search for
- 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_breakpad_with_content_shell.md)
- To facilitate heap profiling, we ship unwind information to arm32 canary & dev channels as a separate file:
JNI Native Methods Resolution
- For ChromePublic.apk and ChromeModernPublic.apk:
JNI_OnLoad() is the only exported symbol (enforced by a linker script).
- Native methods registered explicitly during start-up by generated code.
- Explicit generation is required because the Android runtime uses the system‘s
dlsym(), which doesn’t know about Crazy-Linker-opened libraries.
- For MonochromePublic.apk:
Java_* 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.so enable “packed relocations”, or “APS2 relocations” in order to save binary size.
- 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
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
lib(mono)chrome.so on arm32, it's about 2mb.
- 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.
- Note: For
fork()ed processes, all pages are already shared (via
fork()'s copy-on-write semantics), so RELRO sharing does not apply to them.
- “RELRO sharing” is when this segment is copied into shared memory and shared by multiple processes.
How does it work?
- For Android < N (crazy linker):
- Browser Process:
libchrome.so loaded normally.
- Browser Process:
GNU_RELRO segment copied into
ashmem (shared memory).
- Browser Process (low-end only): RELRO private memory pages swapped out for ashmem ones (using
- Browser Process: Load address and shared memory fd passed to renderers / gpu process.
- Renderer Process: Crazy linker tries to load to the given load address.
- Loading can fail due to address space randomization causing something else to already by loaded at the address.
- Renderer Process: If loading to the desired address succeeds:
- Linker puts
GNU_RELRO into private memory and applies relocations as per normal.
- Afterwards, memory pages are compared against the shared memory and all identical pages are swapped out for ashmem ones (using
- For a more detailed description, refer to comments in Linker.java.
- For Android N+:
- 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.so at the same virtual address and apply RELRO sharing against the memory-mapped RELRO file.
- Chrome uses
MonochromeLibraryPreloader to call into the same WebView library loading code.
- When Monochrome is the WebView provider,
libmonochrome.so is loaded with the system‘s cached RELRO’s applied.
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.
- For non-low-end Android O+ (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.
- In this case, RELRO sharing would be redundant since the entire process' memory is shared with the zygote with copy-on-write semantics.
- 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 (
- We used to use
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.
- We now link with
lld, which supports packed relocations natively and doesn't have these problems.