Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 1 | # Native Relocations |
| 2 | |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 3 | [TOC] |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 4 | |
| 5 | ## What are they? |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 6 | * They tell the runtime linker a list of addresses to post-process after |
| 7 | loading the executable into memory. |
| 8 | * There are several types of relocations, but >99% of them are "relative" |
Andrew Grieve | 9534b16 | 2022-11-16 03:54:01 | [diff] [blame] | 9 | relocations and are created any time a global symbol or compile-time |
| 10 | initialized static local is initialized with the address of something, and |
| 11 | the compiler cannot optimize away the relocation (e.g. for pointers where |
| 12 | not all uses are visible, or for values that are passed to functions that |
| 13 | are not inlined). |
| 14 | |
| 15 | |
| 16 | Examples of things that require relative relocations: |
| 17 | |
| 18 | ```C++ |
| 19 | // Pointer to yourself. |
| 20 | extern const void* const kUserDataKey = &kUserDataKey; |
| 21 | // Array of pointers. |
| 22 | extern const char* const kMemoryDumpAllowedArgs[] = {"dumps", nullptr}; |
| 23 | // Array of structs that contain one or more pointers. |
| 24 | extern const StringPiece kStrings[] = {"one, "two"}; |
| 25 | ``` |
| 26 | |
| 27 | Vtables are arrays of function pointers, and so require relative relocations. |
| 28 | However, on some Chrome platforms we use a non-standard ABI that uses offsets |
| 29 | rather than pointers in order to remove the relocations overhead |
| 30 | ([crbug/589384]). |
| 31 | |
| 32 | [crbug/589384]: https://crbug.com/589384 |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 33 | |
| 34 | ### Linux & Android Relocations (ELF Format) |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 35 | * Relocations are stored in sections named: `.rel.dyn`, `.rel.plt`, |
| 36 | `.rela.dyn`, or `.rela.plt`. |
Andrew Grieve | 9534b16 | 2022-11-16 03:54:01 | [diff] [blame] | 37 | * Relocations are stored in sections of type: `REL`, `RELA`, [`APS2`][APS2], or |
| 38 | [`RELR`][RELR]. |
| 39 | * `REL` is default for arm32. It uses two words to per relocation: `address`, |
| 40 | `flags`. |
| 41 | * `RELA` is default for arm64. It uses three words per relocation: `address`, |
| 42 | `flags`, `addend`. |
| 43 | * [`APS2`][APS2] is what Chrome for Android uses. It stores the same fields |
| 44 | as REL` / `RELA`, but uses variable length ints (LEB128) and run-length |
| 45 | encoding. |
| 46 | * [`RELR`][RELR] is what [Chrome OS uses], and is supported in Android P+ |
| 47 | ([tracking bug for enabling]). It encodes only relative relocations and |
| 48 | uses a bitmask to do so (which works well since all symbols that require |
| 49 | relocations live in `.data.rel.ro`). |
| 50 | |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 51 | [APS2]: android_native_libraries.md#Packed-Relocations |
Andrew Grieve | 9534b16 | 2022-11-16 03:54:01 | [diff] [blame] | 52 | [RELR]: https://maskray.me/blog/2021-10-31-relative-relocations-and-relr |
| 53 | [tracking bug for enabling]: https://bugs.chromium.org/p/chromium/issues/detail?id=895194 |
| 54 | [Chrome OS uses]: https://chromium-review.googlesource.com/c/chromiumos/overlays/chromiumos-overlay/+/1210982 |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 55 | |
| 56 | ### Windows Relocations (PE Format) |
Andrew Grieve | 9534b16 | 2022-11-16 03:54:01 | [diff] [blame] | 57 | * For PE files, relocations are stored in the [`.reloc` section][win_relocs]. |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 58 | * Each relocation is stored using 2 bytes. Each `.reloc` section has a small |
| 59 | overhead as well. |
| 60 | * 64-bit executables have fewer relocations thanks to the ability to use |
| 61 | RIP-relative (instruction-relative) addressing. |
| 62 | |
| 63 | [win_relocs]: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#the-reloc-section-image-only |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 64 | |
| 65 | ## Why do they matter? |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 66 | ### Binary Size |
| 67 | * On Linux, relocations are stored very inefficiently. |
| 68 | * As of Oct 2019: |
| 69 | * Chrome on Linux has a `.rela.dyn` section of more than 14MiB! |
| 70 | * Chrome on Android uses [`APS2`] to compress these down to ~300kb. |
| 71 | * Chrome on Android with [`RELR`] would require only 60kb, but is |
| 72 | [not yet enabled][relr_bug]. |
| 73 | * Chrome on Windows (x64) has `.relocs` sections that sum to 620KiB. |
| 74 | |
| 75 | [relr_bug]: https://bugs.chromium.org/p/chromium/issues/detail?id=895194 |
| 76 | |
| 77 | ### Memory Overhead |
Andrew Grieve | 9534b16 | 2022-11-16 03:54:01 | [diff] [blame] | 78 | * On Windows, relocations are applied by the kernel during page faults. There |
| 79 | is therefore [almost no memory overhead] from relocations, as the memory they |
| 80 | are applied to is still considered "clean" memory and shared between |
| 81 | processes. |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 82 | * On Linux and Android, memory with relocations cannot be loaded read-only and |
| 83 | result in dirty memory. 99% of these symbols live in `.data.rel.ro`, which as |
| 84 | of Oct 2019 is ~6.5MiB on Linux and ~2MiB on Android. `.data.rel.ro` is data |
| 85 | that *would* have been put into `.rodata` and mapped read-only if not for the |
| 86 | required relocations. The memory does not get written to after it's |
| 87 | relocated, so the linker makes it read-only once relocations are applied (but |
| 88 | by that point the damage is done and we have the dirty pages). |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 89 | * On Linux, we share this overhead between processes via the [zygote]. |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 90 | * [On Android][relro_sharing], we share this overhead between processes by |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 91 | loading the shared library at the same address in all processes, and then |
| 92 | `mremap` onto shared memory to dedupe after-the-fact. |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 93 | |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 94 | [almost no memory overhead]: https://devblogs.microsoft.com/oldnewthing/20160413-00/?p=93301 |
Tom Anderson | 93e49e49 | 2019-12-23 19:55:37 | [diff] [blame] | 95 | [zygote]: linux/zygote.md |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 96 | [relro_sharing]: android_native_libraries.md#relro-sharing |
| 97 | |
| 98 | ### Start-up Time |
Andrew Grieve | 9534b16 | 2022-11-16 03:54:01 | [diff] [blame] | 99 | * On Windows, relocations are applied just-in-time, and so their overhead is |
| 100 | both small and difficult to measure. |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 101 | * On other platforms, the runtime linker applies all relocations upfront. |
| 102 | * On low-end Android, it can take ~100ms (measured on a first-gen Android Go |
| 103 | devices with APS2 relocations). |
Andrew Grieve | 9534b16 | 2022-11-16 03:54:01 | [diff] [blame] | 104 | * On a Pixel 4a, it's ~50ms, and with RELR relocations, it's closer to 15ms. |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 105 | * On Linux, it's [closer to 20ms][zygote]. |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 106 | |
| 107 | ## How do I see them? |
| 108 | |
| 109 | ```sh |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 110 | # For ELF files: |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 111 | third_party/llvm-build/Release+Asserts/bin/llvm-readelf --relocs out/Release/libmonochrome.so |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 112 | |
| 113 | # For PE files: |
| 114 | python tools\win\pe_summarize.py out\Release\chrome.dll |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 115 | ``` |
| 116 | |
| 117 | ## Can I avoid them? |
| 118 | It's not practical to avoid them altogether, but there are times when you can be |
| 119 | smart about them. |
| 120 | |
| 121 | For Example: |
| 122 | ```c++ |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 123 | // The following uses 2 bytes of padding for each smaller string but creates no relocations. |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 124 | // Total size overhead: 4 * 5 = 20 bytes. |
| 125 | const char kArr[][5] = {"as", "ab", "asdf", "fi"}; |
| 126 | |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 127 | // The following requires no string padding, but uses 4 relocatable pointers. |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 128 | // Total size overhead: |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 129 | // Linux 64-bit: (8 bytes per pointer + 24 bytes per relocation) * 4 entries + 14 bytes of char = 142 bytes |
| 130 | // Windows 64-bit: (8 bytes per pointer + 2 bytes per relocation) * 4 entries + 14 bytes of char = 54 bytes |
| 131 | // CrOS 64-bit: (8 bytes per pointer + ~0 bytes per relocation) * 4 entries + 14 bytes of char = ~46 bytes |
| 132 | // Android 32-bit: (4 bytes per pointer + ~0 bytes per relocation) * 4 entries + 14 bytes of char = ~30 bytes |
| 133 | const char * const kArr2[] = {"as", "ab", "asdf", "fi"}; |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 134 | ``` |
| 135 | |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 136 | Notes: |
| 137 | * String literals (but not char arrays) are de-duped with others in the binary, |
| 138 | so it is possible that the second example above might use 14 fewer bytes. |
| 139 | * Not all string literals require relocations. Which ones require them depends |
| 140 | on the ABI. Generally, All global variables that are initialized to the |
| 141 | address of something require them. |
| 142 | |
| 143 | Here's a simpler example: |
| 144 | |
| 145 | ```c++ |
| 146 | // No pointer, no relocation. Just 5 bytes of character data. |
Andrew Grieve | 9534b16 | 2022-11-16 03:54:01 | [diff] [blame] | 147 | extern const char kText[] = "asdf"; |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 148 | |
| 149 | // Requires pointer, relocation, and character data. |
| 150 | // In most cases there is no advantage to pointers for strings. |
Andrew Grieve | 9534b16 | 2022-11-16 03:54:01 | [diff] [blame] | 151 | // When not "extern", the compiler can often figure out how to avoid the relocation. |
| 152 | extern const char* const kText = "asdf"; |
Andrew Grieve | 0eeb5fd | 2019-11-25 21:04:26 | [diff] [blame] | 153 | ``` |
Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame] | 154 | |
| 155 | Another thing to look out for: |
| 156 | * Large data structures with relocations that you don't need random access to, |
| 157 | or which are seldom needed. |
| 158 | * For such cases, it might be better to store the data encoded and then |
| 159 | decode when required. |