Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 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. |
Torne (Richard Coles) | d8bc292 | 2019-05-01 21:26:52 | [diff] [blame] | 12 | * Android N, O & P (MonochromePublic.apk): |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 13 | * `libmonochrome.so` is stored uncompressed (AndroidManifest.xml attribute disables extraction) and loaded directly from the apk (functionality now supported by the system linker). |
Torne (Richard Coles) | d8bc292 | 2019-05-01 21:26:52 | [diff] [blame] | 14 | * Android Q (TrichromeChrome.apk+TrichromeLibrary.apk): |
| 15 | * `libmonochrome.so` is stored in the shared library apk (TrichromeLibrary.apk) instead of in the Chrome apk, so that it can be shared with TrichromeWebView. It's stored uncompressed and loaded directly from the apk the same way as on N-P. Trichrome uses the same native library as Monochrome, so it's still called `libmonochrome.so`. |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 16 | |
Joshua Peraza | 8be635b | 2019-02-25 21:51:00 | [diff] [blame] | 17 | ## Crashpad Packaging |
| 18 | * Crashpad is a native library providing out-of-process crash dumping. When a |
| 19 | dump is requested (e.g. after a crash), a Crashpad handler process is started |
| 20 | to produce a dump. |
| 21 | * Chrome and ChromeModern (Android J through M): |
| 22 | * libcrashpad_handler.so is a standalone executable containing all of the |
| 23 | crash dumping code. It is stored compressed and extracted automatically by |
| 24 | the system, allowing it to be directly executed to produce a crash dump. |
Torne (Richard Coles) | d8bc292 | 2019-05-01 21:26:52 | [diff] [blame] | 25 | * Monochrome (N through P) and SystemWebView (L through P): |
Joshua Peraza | 8be635b | 2019-02-25 21:51:00 | [diff] [blame] | 26 | * All of the Crashpad code is linked into the package's main native library |
| 27 | (e.g. libmonochrome.so). When a dump is requested, /system/bin/app_process |
| 28 | is executed, loading CrashpadMain.java which in turn uses JNI to call into |
| 29 | the native crash dumping code. This approach requires building CLASSPATH |
| 30 | and LD_LIBRARY_PATH variables to ensure app_process can locate |
| 31 | CrashpadMain.java and any native libraries (e.g. system libraries, shared |
| 32 | libraries, split apks, etc.) the package's main native library depends on. |
| 33 | * Monochrome, Trichrome, and SystemWebView (Q+): |
| 34 | * All of the Crashpad handler code is linked into the package's native |
| 35 | library. libcrashpad_handler_trampoline.so is a minimal executable |
| 36 | packaged with the main native library, stored uncompressed and left |
| 37 | unextracted. When a dump is requested, /system/bin/linker is executed to |
| 38 | load the trampoline from the APK, which in turn `dlopen()`s the main |
| 39 | native library to load the remaining Crashpad handler code. A trampoline |
| 40 | is used to de-duplicate shared code between Crashpad and the main native |
| 41 | library packaged with it. This approach isn't used for P- because the |
| 42 | linker doesn't support loading executables on its command line until Q. |
| 43 | This approach also requires building a suitable LD_LIBRARY_PATH to locate |
| 44 | any shared libraries Chrome/WebView depends on. |
| 45 | |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 46 | ## Debug Information |
| 47 | **What is it?** |
| 48 | * Sections of an ELF that provide debugging and symbolization information (e.g. ability convert addresses to function & line numbers). |
| 49 | |
| 50 | **How we use it:** |
| 51 | * ELF debug information is too big to push to devices, even for local development. |
| 52 | * All of our APKs include `.so` files with debug information removed via `strip`. |
| 53 | * Unstripped libraries are stored at `out/Default/lib.unstripped`. |
| 54 | * Many of our scripts are hardcoded to look for them there. |
| 55 | |
| 56 | ## Unwind Info & Frame Pointers |
| 57 | **What are they:** |
| 58 | * Unwind info is data that describes how to unwind the stack. It is: |
| 59 | * It is required to support C++ exceptions (which Chrome doesn't use). |
| 60 | * It can also be used to produce stack traces. |
| 61 | * It is generally stored in an ELF section called `.eh_frame` & `.eh_frame_hdr`, but arm32 stores it in `.ARM.exidx` and `.ARM.extab`. |
| 62 | * You can see these sections via: `readelf -S libchrome.so` |
| 63 | * "Frame Pointers" is a calling convention that ensures every function call has the return address pushed onto the stack. |
| 64 | * Frame Pointers can also be used to produce stack traces (but without entries for inlined functions). |
| 65 | |
| 66 | **How we use them:** |
| 67 | * We disable unwind information (search for [`exclude_unwind_tables`](https://cs.chromium.org/search/?q=exclude_unwind_tables+file:%5C.gn&type=cs)). |
| 68 | * 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)). |
| 69 | * 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)) |
| 70 | * To facilitate heap profiling, we ship unwind information to arm32 canary & dev channels as a separate file: `assets/unwind_cfi_32` |
| 71 | |
| 72 | ## JNI Native Methods Resolution |
| 73 | * For ChromePublic.apk and ChromeModernPublic.apk: |
| 74 | * `JNI_OnLoad()` is the only exported symbol (enforced by a linker script). |
| 75 | * Native methods registered explicitly during start-up by generated code. |
| 76 | * Explicit generation is required because the Android runtime uses the system's `dlsym()`, which doesn't know about Crazy-Linker-opened libraries. |
Torne (Richard Coles) | d8bc292 | 2019-05-01 21:26:52 | [diff] [blame] | 77 | * For MonochromePublic.apk and TrichromeChrome.apk: |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 78 | * `JNI_OnLoad()` and `Java_*` symbols are exported by linker script. |
| 79 | * No manual JNI registration is done. Symbols are resolved lazily by the runtime. |
| 80 | |
| 81 | ## Packed Relocations |
| 82 | * All flavors of `lib(mono)chrome.so` enable "packed relocations", or "APS2 relocations" in order to save binary size. |
| 83 | * 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. |
| 84 | * To process these relocations: |
| 85 | * Pre-M Android: Our custom linker must be used. |
| 86 | * M+ Android: The system linker understands the format. |
| 87 | * To see if relocations are packed, look for `LOOS+#` when running: `readelf -S libchrome.so` |
| 88 | * Android P+ [supports an even better format](https://android.googlesource.com/platform/bionic/+/8b14256/linker/linker.cpp#2620) known as RELR. |
| 89 | * We'll likely switch non-Monochrome apks over to using it once it is implemented in `lld`. |
| 90 | |
| 91 | ## RELRO Sharing |
| 92 | **What is it?** |
| 93 | * RELRO refers to the ELF segment `GNU_RELRO`. It contains data that the linker marks as read-only after it applies relocations. |
| 94 | * To inspect the size of the segment: `readelf --segments libchrome.so` |
| 95 | * For `lib(mono)chrome.so` on arm32, it's about 2mb. |
| 96 | * 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. |
| 97 | * 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. |
| 98 | * "RELRO sharing" is when this segment is copied into shared memory and shared by multiple processes. |
| 99 | |
| 100 | **How does it work?** |
| 101 | * For Android < N (crazy linker): |
| 102 | 1. Browser Process: `libchrome.so` loaded normally. |
| 103 | 2. Browser Process: `GNU_RELRO` segment copied into `ashmem` (shared memory). |
| 104 | 3. Browser Process (low-end only): RELRO private memory pages swapped out for ashmem ones (using `munmap()` & `mmap()`). |
| 105 | 4. Browser Process: Load address and shared memory fd passed to renderers / gpu process. |
| 106 | 5. Renderer Process: Crazy linker tries to load to the given load address. |
| 107 | * Loading can fail due to address space randomization causing something else to already by loaded at the address. |
| 108 | 6. Renderer Process: If loading to the desired address succeeds: |
| 109 | * Linker puts `GNU_RELRO` into private memory and applies relocations as per normal. |
| 110 | * Afterwards, memory pages are compared against the shared memory and all identical pages are swapped out for ashmem ones (using `munmap()` & `mmap()`). |
| 111 | * 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). |
Torne (Richard Coles) | d8bc292 | 2019-05-01 21:26:52 | [diff] [blame] | 112 | * For Android N-P: |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 113 | * The OS maintains a RELRO file on disk with the contents of the GNU_RELRO segment. |
| 114 | * 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. |
| 115 | * Chrome uses `MonochromeLibraryPreloader` to call into the same WebView library loading code. |
| 116 | * When Monochrome is the WebView provider, `libmonochrome.so` is loaded with the system's cached RELRO's applied. |
| 117 | * `System.loadLibrary()` is called afterwards. |
| 118 | * 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. |
Torne (Richard Coles) | d8bc292 | 2019-05-01 21:26:52 | [diff] [blame] | 119 | * For non-low-end Android O-P (where there's a WebView zygote): |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 120 | * For non-renderer processes, the above Android N+ logic applies. |
| 121 | * For renderer processes, the OS starts all Monochrome renderer processes by `fork()`ing the WebView zygote rather than the normal application zygote. |
| 122 | * In this case, RELRO sharing would be redundant since the entire process' memory is shared with the zygote with copy-on-write semantics. |
Torne (Richard Coles) | d8bc292 | 2019-05-01 21:26:52 | [diff] [blame] | 123 | * For Android Q+ (Trichrome): |
| 124 | * TrichromeChrome no longer shares its RELRO data with WebView and no RELRO sharing occurs. TrichromeWebView works the same way as on Android N-P. |
| 125 | * TrichromeChrome's renderer processes are no longer `fork()`ed from the WebView zygote. TrichromeWebView works the same way as on Android N-P. |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 126 | |
| 127 | ## Library Prefetching |
| 128 | * 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). |
| 129 | * See [//base/android/library_loader/](../base/android/library_loader/). |
| 130 | |
| 131 | ## Historical Tidbits |
| 132 | * We used to use the system linker on M (`ModernLinker.java`). |
| 133 | * This was removed due to [poor performance](https://bugs.chromium.org/p/chromium/issues/detail?id=719977). |
| 134 | * 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. |
| 135 | * We now link with `lld`, which supports packed relocations natively and doesn't have these problems. |
| 136 | |
| 137 | ## See Also |
| 138 | * [//docs/android_build_instructions.md#Multiple-Chrome-APK-Targets](android_build_instructions.md#Multiple-Chrome-APK-Targets) |
| 139 | * [//third_party/android_crazy_linker/README.chromium](../third_party/android_crazy_linker/README.chromium) |
| 140 | * [//base/android/linker/BUILD.gn](../base/android/linker/BUILD.gn) |