Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 1 | # Shared Libraries on Android |
Christopher Grant | db575e9c | 2019-11-28 16:24:02 | [diff] [blame] | 2 | This doc outlines some tricks / gotchas / features of how we ship native code in |
| 3 | Chrome on Android. |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 4 | |
| 5 | [TOC] |
| 6 | |
| 7 | ## Library Packaging |
Andrew Grieve | 84bf1dd | 2020-07-06 20:31:50 | [diff] [blame] | 8 | * Android N, O & P (MonochromePublic.aab): |
| 9 | * `libmonochrome.so` is stored uncompressed within the apk (an |
| 10 | AndroidManifest.xml attribute disables extraction). |
| 11 | * It is loaded directly from the apk by the system linker. |
| 12 | * It exports all JNI symbols and does not use explicit JNI registration. |
| 13 | * It is not loaded by `libchromium_android_linker.so` and relies on the |
| 14 | system's webview zygote for RELRO sharing. |
| 15 | * Android Q (TrichromeChrome.aab + TrichromeLibrary.apk): |
| 16 | * Trichrome uses the exact same native library as Monochrome: |
| 17 | `libmonochrome.so`. |
Egor Pasko | ace6f1c | 2020-11-13 15:38:48 | [diff] [blame] | 18 | * `libmonochrome.so` is stored in the shared APK (TrichromeLibrary.apk) |
Andrew Grieve | 84bf1dd | 2020-07-06 20:31:50 | [diff] [blame] | 19 | so that it can be shared with TrichromeWebView. |
| 20 | * It is loaded by `libchromium_android_linker.so` using |
| 21 | `android_dlopen_ext()` to enable RELRO sharing. |
| 22 | |
Christopher Grant | db575e9c | 2019-11-28 16:24:02 | [diff] [blame] | 23 | ## Build Variants (eg. monochrome_64_32_apk) |
| 24 | The packaging above extends to cover both 32-bit and 64-bit device |
| 25 | configurations. |
| 26 | |
Andrew Grieve | cdbc63e8 | 2020-07-03 16:16:57 | [diff] [blame] | 27 | Chrome support 64-bit builds, but these do not ship to Stable. |
Nate Fischer | 80c1af7df | 2024-01-31 01:29:40 | [diff] [blame] | 28 | The system WebView APK that ships to those devices contains a 32-bit library, |
| 29 | and for 64-bit devices, a 64-bit library as well (32-bit WebView client apps |
Christopher Grant | db575e9c | 2019-11-28 16:24:02 | [diff] [blame] | 30 | will use the 32-bit library, and vice-versa). |
| 31 | |
| 32 | ### Monochrome |
| 33 | Monochrome's intent was to eliminate the duplication between the 32-bit Chrome |
Nate Fischer | 80c1af7df | 2024-01-31 01:29:40 | [diff] [blame] | 34 | and WebView libraries (most of the library is identical). In 32-bit Monochrome, |
| 35 | a single combined library serves both Chrome and WebView needs. The 64-bit |
| 36 | version adds an extra WebView-only library. |
Christopher Grant | db575e9c | 2019-11-28 16:24:02 | [diff] [blame] | 37 | |
| 38 | More recently, additional Monochrome permutations have arrived. First, Google |
| 39 | Play will eventually require that apps offer a 64-bit version to compatible |
| 40 | devices. In Monochrome, this implies swapping the architecture of the Chrome and |
Nate Fischer | 80c1af7df | 2024-01-31 01:29:40 | [diff] [blame] | 41 | WebView libraries (64-bit combined lib, and extra 32-bit WebView lib). Further |
Christopher Grant | db575e9c | 2019-11-28 16:24:02 | [diff] [blame] | 42 | down the road, silicon vendors may drop 32-bit support from their chips, after |
| 43 | which a pure 64-bit version of Monochrome will apply. In each of these cases, |
Nate Fischer | 80c1af7df | 2024-01-31 01:29:40 | [diff] [blame] | 44 | the library name of the combined and WebView-only libraries must match (an |
Christopher Grant | db575e9c | 2019-11-28 16:24:02 | [diff] [blame] | 45 | Android platform requirement), so both libs are named libmonochrome.so (or |
| 46 | libmonochrome_64.so in the 64-bit browser case). |
| 47 | |
| 48 | Since 3 of these variations require a 64-bit build config, it makes sense to |
| 49 | also support the 4th variant on 64-bit, thus allowing a single builder to build |
| 50 | all variants (if desired). Further, a naming scheme must exist to disambiguate |
| 51 | the various targets: |
| 52 | |
| 53 | **monochrome_(browser ABI)_(extra_webview ABI)** |
| 54 | |
Nate Fischer | 80c1af7df | 2024-01-31 01:29:40 | [diff] [blame] | 55 | For example, the 64-bit browser version with extra 32-bit WebView is |
Christopher Grant | db575e9c | 2019-11-28 16:24:02 | [diff] [blame] | 56 | **monochrome_64_32_apk**. The combinations are as follows: |
| 57 | |
| 58 | Builds on | Variant | Description |
| 59 | --- | --- | --- |
| 60 | 32-bit | monochrome | The original 32-bit-only version |
Nate Fischer | 80c1af7df | 2024-01-31 01:29:40 | [diff] [blame] | 61 | 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. |
| 62 | 64-bit | monochrome_64_32 | 64-bit combined lib with 32-bit WebView library. |
Christopher Grant | db575e9c | 2019-11-28 16:24:02 | [diff] [blame] | 63 | 64-bit | monochrome_64 | 64-bit combined lib only, for eventual pure 64-bit hardware. |
| 64 | 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. |
| 65 | |
| 66 | ### Trichrome |
| 67 | Trichrome has the same 4 permutations as Monochrome, but adds another dimension. |
Nate Fischer | 80c1af7df | 2024-01-31 01:29:40 | [diff] [blame] | 68 | Trichrome returns to separate apps for Chrome and WebView, but places shared |
Christopher Grant | db575e9c | 2019-11-28 16:24:02 | [diff] [blame] | 69 | resources in a third shared-library APK. The table below shows which native |
| 70 | libraries are packaged where. Note that **dummy** placeholder libraries are |
| 71 | inserted where needed, since Android determines supported ABIs from the presence |
| 72 | of native libraries, and the ABIs of a shared library APK must match its client |
| 73 | app. |
| 74 | |
Nate Fischer | 80c1af7df | 2024-01-31 01:29:40 | [diff] [blame] | 75 | Builds on | Variant | Chrome | Library | WebView |
Christopher Grant | db575e9c | 2019-11-28 16:24:02 | [diff] [blame] | 76 | --- | --- | --- | --- | --- |
| 77 | 32-bit | trichrome | `32/dummy` | `32/combined` | `32/dummy` |
| 78 | 64-bit | trichrome | `32/dummy`, `64/dummy` | `32/combined`, `64/dummy` | `32/dummy`, `64/webview` |
| 79 | 64-bit | trichrome_64_32 | `32/dummy`, `64/dummy` | `32/dummy`, `64/combined` | `32/webview`, `64/dummy` |
| 80 | 64-bit | trichrome_64 | `64/dummy` | `64/combined` | `64/dummy` |
| 81 | 64-bit | trichrome_32 | `32/dummy` | `32/combined` | `32/dummy` |
| 82 | |
Joshua Peraza | 8be635b | 2019-02-25 21:51:00 | [diff] [blame] | 83 | ## Crashpad Packaging |
| 84 | * Crashpad is a native library providing out-of-process crash dumping. When a |
| 85 | dump is requested (e.g. after a crash), a Crashpad handler process is started |
| 86 | to produce a dump. |
Andrew Grieve | cdbc63e8 | 2020-07-03 16:16:57 | [diff] [blame] | 87 | * Chrome (Android L through M): |
Vlad Tsyrklevich | 0656f2c | 2019-07-30 18:16:32 | [diff] [blame] | 88 | * libchrome_crashpad_handler.so is a standalone executable containing all of |
| 89 | the crash dumping code. It is stored compressed and extracted automatically |
| 90 | by 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] | 91 | * Monochrome (N through P) and SystemWebView (L through P): |
Joshua Peraza | 8be635b | 2019-02-25 21:51:00 | [diff] [blame] | 92 | * All of the Crashpad code is linked into the package's main native library |
| 93 | (e.g. libmonochrome.so). When a dump is requested, /system/bin/app_process |
| 94 | is executed, loading CrashpadMain.java which in turn uses JNI to call into |
| 95 | the native crash dumping code. This approach requires building CLASSPATH |
| 96 | and LD_LIBRARY_PATH variables to ensure app_process can locate |
| 97 | CrashpadMain.java and any native libraries (e.g. system libraries, shared |
| 98 | libraries, split apks, etc.) the package's main native library depends on. |
| 99 | * Monochrome, Trichrome, and SystemWebView (Q+): |
| 100 | * All of the Crashpad handler code is linked into the package's native |
| 101 | library. libcrashpad_handler_trampoline.so is a minimal executable |
| 102 | packaged with the main native library, stored uncompressed and left |
| 103 | unextracted. When a dump is requested, /system/bin/linker is executed to |
| 104 | load the trampoline from the APK, which in turn `dlopen()`s the main |
| 105 | native library to load the remaining Crashpad handler code. A trampoline |
| 106 | is used to de-duplicate shared code between Crashpad and the main native |
| 107 | library packaged with it. This approach isn't used for P- because the |
| 108 | linker doesn't support loading executables on its command line until Q. |
| 109 | This approach also requires building a suitable LD_LIBRARY_PATH to locate |
| 110 | any shared libraries Chrome/WebView depends on. |
| 111 | |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 112 | ## Debug Information |
| 113 | **What is it?** |
| 114 | * Sections of an ELF that provide debugging and symbolization information (e.g. ability convert addresses to function & line numbers). |
| 115 | |
| 116 | **How we use it:** |
| 117 | * ELF debug information is too big to push to devices, even for local development. |
| 118 | * All of our APKs include `.so` files with debug information removed via `strip`. |
| 119 | * Unstripped libraries are stored at `out/Default/lib.unstripped`. |
| 120 | * Many of our scripts are hardcoded to look for them there. |
| 121 | |
| 122 | ## Unwind Info & Frame Pointers |
| 123 | **What are they:** |
| 124 | * Unwind info is data that describes how to unwind the stack. It is: |
| 125 | * It is required to support C++ exceptions (which Chrome doesn't use). |
| 126 | * It can also be used to produce stack traces. |
| 127 | * It is generally stored in an ELF section called `.eh_frame` & `.eh_frame_hdr`, but arm32 stores it in `.ARM.exidx` and `.ARM.extab`. |
| 128 | * You can see these sections via: `readelf -S libchrome.so` |
| 129 | * "Frame Pointers" is a calling convention that ensures every function call has the return address pushed onto the stack. |
| 130 | * Frame Pointers can also be used to produce stack traces (but without entries for inlined functions). |
| 131 | |
| 132 | **How we use them:** |
| 133 | * We disable unwind information (search for [`exclude_unwind_tables`](https://cs.chromium.org/search/?q=exclude_unwind_tables+file:%5C.gn&type=cs)). |
| 134 | * 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)). |
| 135 | * 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)) |
| 136 | * To facilitate heap profiling, we ship unwind information to arm32 canary & dev channels as a separate file: `assets/unwind_cfi_32` |
| 137 | |
| 138 | ## JNI Native Methods Resolution |
Andrew Grieve | cdbc63e8 | 2020-07-03 16:16:57 | [diff] [blame] | 139 | * For ChromePublic.apk: |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 140 | * `JNI_OnLoad()` is the only exported symbol (enforced by a linker script). |
| 141 | * Native methods registered explicitly during start-up by generated code. |
Peter Wen | be712e64 | 2019-11-14 21:36:58 | [diff] [blame] | 142 | * For MonochromePublic.apk and TrichromeChrome.aab: |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 143 | * `JNI_OnLoad()` and `Java_*` symbols are exported by linker script. |
| 144 | * No manual JNI registration is done. Symbols are resolved lazily by the runtime. |
| 145 | |
| 146 | ## Packed Relocations |
| 147 | * All flavors of `lib(mono)chrome.so` enable "packed relocations", or "APS2 relocations" in order to save binary size. |
| 148 | * 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. |
| 149 | * To process these relocations: |
| 150 | * Pre-M Android: Our custom linker must be used. |
| 151 | * M+ Android: The system linker understands the format. |
| 152 | * To see if relocations are packed, look for `LOOS+#` when running: `readelf -S libchrome.so` |
| 153 | * Android P+ [supports an even better format](https://android.googlesource.com/platform/bionic/+/8b14256/linker/linker.cpp#2620) known as RELR. |
| 154 | * We'll likely switch non-Monochrome apks over to using it once it is implemented in `lld`. |
| 155 | |
| 156 | ## RELRO Sharing |
| 157 | **What is it?** |
| 158 | * RELRO refers to the ELF segment `GNU_RELRO`. It contains data that the linker marks as read-only after it applies relocations. |
| 159 | * To inspect the size of the segment: `readelf --segments libchrome.so` |
Egor Pasko | 9783f92 | 2021-03-30 16:41:01 | [diff] [blame] | 160 | * For `lib(mono)chrome.so` the region occupies about 2.4MiB on arm32 and 4.7 MiB on arm64 |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 161 | * 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. |
Egor Pasko | 9783f92 | 2021-03-30 16:41:01 | [diff] [blame] | 162 | * "RELRO sharing" is when this segment is moved into shared memory and shared by multiple processes. |
| 163 | * Processes `fork()`ed from the app zygote (where the library is loaded) share RELRO (via `fork()`'s copy-on-write semantics), but this region is not shared with other process types (privileged, utility, GPU) |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 164 | |
| 165 | **How does it work?** |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 166 | * 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] | 167 | * For Android N-P: |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 168 | * The OS maintains a RELRO file on disk with the contents of the GNU_RELRO segment. |
| 169 | * 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. |
Clark DuVall | d6854933 | 2021-01-29 21:20:02 | [diff] [blame] | 170 | * Chrome uses `WebViewLibraryPreloader` to call into the same WebView library loading code. |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 171 | * When Monochrome is the WebView provider, `libmonochrome.so` is loaded with the system's cached RELRO's applied. |
| 172 | * `System.loadLibrary()` is called afterwards. |
| 173 | * 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] | 174 | * For non-low-end Android O-P (where there's a WebView zygote): |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 175 | * For non-renderer processes, the above Android N+ logic applies. |
| 176 | * For renderer processes, the OS starts all Monochrome renderer processes by `fork()`ing the WebView zygote rather than the normal application zygote. |
| 177 | * In this case, RELRO sharing would be redundant since the entire process' memory is shared with the zygote with copy-on-write semantics. |
Egor Pasko | 56343f4 | 2021-06-15 16:17:29 | [diff] [blame] | 178 | * For Android Q+ (Trichrome): |
Andrew Grieve | 84bf1dd | 2020-07-06 20:31:50 | [diff] [blame] | 179 | * TrichromeWebView works the same way as on Android N-P. |
| 180 | * TrichromeChrome uses `android_dlopen_ext()` and `ASharedMemory_create()` to |
| 181 | perform RELRO sharing, and then relies on a subsequent call to |
| 182 | `System.loadLibrary()` to enable JNI method resolution without loading the |
| 183 | library a second time. |
| 184 | * For renderer processes, TrichromeChrome `fork()`s from a chrome-specific |
| 185 | app zygote. `libmonochrome.so` is loaded in the zygote before `fork()`. |
| 186 | * Similar to O-P, app zygote provides copy-on-write memory semantics so |
| 187 | RELRO sharing is redundant. |
Egor Pasko | 438cf4e | 2022-12-01 14:14:10 | [diff] [blame] | 188 | * For Android R+ (still Trichrome) |
| 189 | * The RELRO region is created in the App Zygote, picked up by the Browser |
| 190 | process, which then redistributes the region to all other processes. The |
| 191 | receiving of the region and remapping it on top of the non-shared RELRO |
| 192 | happens asynchronously after the library has been loaded. Native code is |
| 193 | generally already running at this point. Hence the replacement must be |
| 194 | atomic. |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 195 | |
Christopher Grant | 8fea5a1 | 2019-07-31 19:12:31 | [diff] [blame] | 196 | ## Partitioned libraries |
| 197 | Some Chrome code is placed in feature-specific libraries and delivered via |
| 198 | [Dynamic Feature Modules](android_dynamic_feature_modules.md). |
| 199 | |
| 200 | A linker-assisted partitioning system automates the placement of code into |
| 201 | either the main Chrome library or feature-specific .so libraries. Feature code |
| 202 | may continue to make use of core Chrome code (eg. base::) without modification, |
| 203 | but Chrome must call feature code through a virtual interface. |
| 204 | |
| 205 | **How partitioning works** |
| 206 | |
| 207 | The lld linker is now capable of producing a [partitioned |
| 208 | library](https://lld.llvm.org/Partitions.html), which is effectively an |
| 209 | intermediate single file containing multiple libraries. A separate tool |
| 210 | *(llvm-objcopy)* then splits the file into standalone .so files, invoked through |
| 211 | a [partitioned shared library](https://cs.chromium.org/chromium/src/build/partitioned_shared_library.gni) |
| 212 | GN template. |
| 213 | |
| 214 | The primary partition is Chrome's main library (eg. libchrome.so), and other |
| 215 | partitions may contain feature code (eg. libvr.so). By specifying a list of |
| 216 | C/C++ symbols to use as entrypoints, the linker can collect all code used only |
| 217 | through these entrypoints, and place it in a particular partition. |
| 218 | |
| 219 | To facilitate partitioning, all references from Chrome to the feature |
| 220 | entrypoints must be indirect. That is, Chrome must obtain a symbol from the |
| 221 | feature library through dlsym(), cast the pointer to its actual type, and call |
| 222 | through the resulting pointer. |
| 223 | |
| 224 | Feature code retains the ability to freely call back into Chrome's core code. |
| 225 | When loading the library, the feature module system uses the feature name to |
| 226 | look up a partition name *(libfoo.so)* in an address offset table built into the |
| 227 | main library. The resulting offset is supplied to android_dlopen_ext(), which |
| 228 | instructs Android to load the library in a particular reserved address region. |
| 229 | This allows the feature library's relative references back to the main library |
| 230 | to work, as if the feature code had been linked into the main library |
| 231 | originally. No dynamic symbol resolution is required here. |
| 232 | |
| 233 | **Implications on code placement** |
| 234 | |
| 235 | * Any symbol referenced by multiple partitions ends up in the main library (even |
| 236 | if all calling libraries are feature partitions). |
| 237 | * Symbols that aren't feature code (eg. base::) will be pulled into the |
| 238 | feature's library if only that feature uses the code. This is a benefit, but |
| 239 | can be unexpected. |
| 240 | |
| 241 | **Builds that support partitioned libraries** |
| 242 | |
| 243 | Partitioned libraries are usable when all of the following are true: |
| 244 | * Component build is disabled (component build splits code across GN component |
| 245 | target boundaries instead). |
| 246 | * The compiler is Clang. |
| 247 | * The linker is lld. |
| 248 | |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 249 | ## Library Prefetching |
| 250 | * 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). |
| 251 | * See [//base/android/library_loader/](../base/android/library_loader/). |
| 252 | |
| 253 | ## Historical Tidbits |
| 254 | * We used to use the system linker on M (`ModernLinker.java`). |
| 255 | * This was removed due to [poor performance](https://bugs.chromium.org/p/chromium/issues/detail?id=719977). |
| 256 | * 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. |
| 257 | * We now link with `lld`, which supports packed relocations natively and doesn't have these problems. |
Egor Pasko | 438cf4e | 2022-12-01 14:14:10 | [diff] [blame] | 258 | * We used to use the Crazy Linker until Android M was deprecated |
| 259 | * It allowed storing `libchrome.so` uncompressed within the apk before the |
| 260 | system linker allowed it (with the name `crazy.libchrome.so` to avoid extraction). |
| 261 | * It was loaded directly from the apk via `libchromium_android_linker.so`. |
| 262 | * Only JNI_OnLoad was exported. Explicit JNI registration was required |
| 263 | because the Android runtime uses the system's `dlsym()`, which doesn't know |
Sam Maier | e1df6f2 | 2023-08-11 14:20:40 | [diff] [blame] | 264 | about Crazy-Linker-opened libraries. (see [JNI README](/third_party/jni_zero/README.md)). |
Andrew Grieve | d2ec82d | 2018-05-22 14:28:43 | [diff] [blame] | 265 | |
| 266 | ## See Also |
| 267 | * [//docs/android_build_instructions.md#Multiple-Chrome-APK-Targets](android_build_instructions.md#Multiple-Chrome-APK-Targets) |
| 268 | * [//third_party/android_crazy_linker/README.chromium](../third_party/android_crazy_linker/README.chromium) |
| 269 | * [//base/android/linker/BUILD.gn](../base/android/linker/BUILD.gn) |