|
| 1 | +# Shared Libraries on Android |
| 2 | +This doc outlines some tricks / gotchas / features of how we ship native code in Chrome on Android. |
| 3 | + |
| 4 | +[TOC] |
| 5 | + |
| 6 | +## Library Packaging |
| 7 | + * Android J & K (ChromePublic.apk): |
| 8 | + * `libchrome.so` is stored compressed and extracted by Android during installation. |
| 9 | + * Android L & M (ChromeModernPublic.apk): |
| 10 | + * `libchrome.so` is stored uncompressed within the apk (with the name `crazy.libchrome.so` to avoid extraction). |
| 11 | + * It is loaded directly from the apk (without extracting) by `mmap()`'ing it. |
| 12 | + * Android N+ (MonochromePublic.apk): |
| 13 | + * `libmonochrome.so` is stored uncompressed (AndroidManifest.xml attribute disables extraction) and loaded directly from the apk (functionality now supported by the system linker). |
| 14 | + |
| 15 | +## Debug Information |
| 16 | +**What is it?** |
| 17 | + * Sections of an ELF that provide debugging and symbolization information (e.g. ability convert addresses to function & line numbers). |
| 18 | + |
| 19 | +**How we use it:** |
| 20 | + * ELF debug information is too big to push to devices, even for local development. |
| 21 | + * All of our APKs include `.so` files with debug information removed via `strip`. |
| 22 | + * Unstripped libraries are stored at `out/Default/lib.unstripped`. |
| 23 | + * Many of our scripts are hardcoded to look for them there. |
| 24 | + |
| 25 | +## Unwind Info & Frame Pointers |
| 26 | +**What are they:** |
| 27 | + * Unwind info is data that describes how to unwind the stack. It is: |
| 28 | + * It is required to support C++ exceptions (which Chrome doesn't use). |
| 29 | + * It can also be used to produce stack traces. |
| 30 | + * It is generally stored in an ELF section called `.eh_frame` & `.eh_frame_hdr`, but arm32 stores it in `.ARM.exidx` and `.ARM.extab`. |
| 31 | + * You can see these sections via: `readelf -S libchrome.so` |
| 32 | + * "Frame Pointers" is a calling convention that ensures every function call has the return address pushed onto the stack. |
| 33 | + * Frame Pointers can also be used to produce stack traces (but without entries for inlined functions). |
| 34 | + |
| 35 | +**How we use them:** |
| 36 | + * We disable unwind information (search for [`exclude_unwind_tables`](https://cs.chromium.org/search/?q=exclude_unwind_tables+file:%5C.gn&type=cs)). |
| 37 | + * For all architectures except arm64, we disable frame pointers in order to reduce binary size (search for [`enable_frame_pointers`](https://cs.chromium.org/search/?q=enable_frame_pointers+file:%5C.gn&type=cs)). |
| 38 | + * 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](testing/using_breakpad_with_content_shell.md)) |
| 39 | + * To facilitate heap profiling, we ship unwind information to arm32 canary & dev channels as a separate file: `assets/unwind_cfi_32` |
| 40 | + |
| 41 | +## JNI Native Methods Resolution |
| 42 | + * For ChromePublic.apk and ChromeModernPublic.apk: |
| 43 | + * `JNI_OnLoad()` is the only exported symbol (enforced by a linker script). |
| 44 | + * Native methods registered explicitly during start-up by generated code. |
| 45 | + * Explicit generation is required because the Android runtime uses the system's `dlsym()`, which doesn't know about Crazy-Linker-opened libraries. |
| 46 | + * For MonochromePublic.apk: |
| 47 | + * `JNI_OnLoad()` and `Java_*` symbols are exported by linker script. |
| 48 | + * No manual JNI registration is done. Symbols are resolved lazily by the runtime. |
| 49 | + |
| 50 | +## Packed Relocations |
| 51 | + * All flavors of `lib(mono)chrome.so` enable "packed relocations", or "APS2 relocations" in order to save binary size. |
| 52 | + * Refer to [this source file](https://android.googlesource.com/platform/bionic/+/refs/heads/master/tools/relocation_packer/src/delta_encoder.h) for an explanation of the format. |
| 53 | + * To process these relocations: |
| 54 | + * Pre-M Android: Our custom linker must be used. |
| 55 | + * M+ Android: The system linker understands the format. |
| 56 | + * To see if relocations are packed, look for `LOOS+#` when running: `readelf -S libchrome.so` |
| 57 | + * Android P+ [supports an even better format](https://android.googlesource.com/platform/bionic/+/8b14256/linker/linker.cpp#2620) known as RELR. |
| 58 | + * We'll likely switch non-Monochrome apks over to using it once it is implemented in `lld`. |
| 59 | + |
| 60 | +## RELRO Sharing |
| 61 | +**What is it?** |
| 62 | + * RELRO refers to the ELF segment `GNU_RELRO`. It contains data that the linker marks as read-only after it applies relocations. |
| 63 | + * To inspect the size of the segment: `readelf --segments libchrome.so` |
| 64 | + * For `lib(mono)chrome.so` on arm32, it's about 2mb. |
| 65 | + * 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. |
| 66 | + * 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. |
| 67 | + * "RELRO sharing" is when this segment is copied into shared memory and shared by multiple processes. |
| 68 | + |
| 69 | +**How does it work?** |
| 70 | + * For Android < N (crazy linker): |
| 71 | + 1. Browser Process: `libchrome.so` loaded normally. |
| 72 | + 2. Browser Process: `GNU_RELRO` segment copied into `ashmem` (shared memory). |
| 73 | + 3. Browser Process (low-end only): RELRO private memory pages swapped out for ashmem ones (using `munmap()` & `mmap()`). |
| 74 | + 4. Browser Process: Load address and shared memory fd passed to renderers / gpu process. |
| 75 | + 5. Renderer Process: Crazy linker tries to load to the given load address. |
| 76 | + * Loading can fail due to address space randomization causing something else to already by loaded at the address. |
| 77 | + 6. Renderer Process: If loading to the desired address succeeds: |
| 78 | + * Linker puts `GNU_RELRO` into private memory and applies relocations as per normal. |
| 79 | + * Afterwards, memory pages are compared against the shared memory and all identical pages are swapped out for ashmem ones (using `munmap()` & `mmap()`). |
| 80 | + * For a more detailed description, refer to comments in [Linker.java](https://cs.chromium.org/chromium/src/base/android/java/src/org/chromium/base/library_loader/Linker.java). |
| 81 | + * For Android N+: |
| 82 | + * The OS maintains a RELRO file on disk with the contents of the GNU_RELRO segment. |
| 83 | + * 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. |
| 84 | + * Chrome uses `MonochromeLibraryPreloader` to call into the same WebView library loading code. |
| 85 | + * When Monochrome is the WebView provider, `libmonochrome.so` is loaded with the system's cached RELRO's applied. |
| 86 | + * `System.loadLibrary()` is called afterwards. |
| 87 | + * 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. |
| 88 | + * For non-low-end Android O+ (where there's a WebView zygote): |
| 89 | + * For non-renderer processes, the above Android N+ logic applies. |
| 90 | + * For renderer processes, the OS starts all Monochrome renderer processes by `fork()`ing the WebView zygote rather than the normal application zygote. |
| 91 | + * In this case, RELRO sharing would be redundant since the entire process' memory is shared with the zygote with copy-on-write semantics. |
| 92 | + |
| 93 | +## Library Prefetching |
| 94 | + * 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). |
| 95 | + * See [//base/android/library_loader/](../base/android/library_loader/). |
| 96 | + |
| 97 | +## Historical Tidbits |
| 98 | + * We used to use the system linker on M (`ModernLinker.java`). |
| 99 | + * This was removed due to [poor performance](https://bugs.chromium.org/p/chromium/issues/detail?id=719977). |
| 100 | + * 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. |
| 101 | + * We now link with `lld`, which supports packed relocations natively and doesn't have these problems. |
| 102 | + |
| 103 | +## See Also |
| 104 | + * [//docs/android_build_instructions.md#Multiple-Chrome-APK-Targets](android_build_instructions.md#Multiple-Chrome-APK-Targets) |
| 105 | + * [//third_party/android_crazy_linker/README.chromium](../third_party/android_crazy_linker/README.chromium) |
| 106 | + * [//base/android/linker/BUILD.gn](../base/android/linker/BUILD.gn) |
0 commit comments