Andrew Grieve | 0cb64c2 | 2019-10-10 14:45:39 | [diff] [blame^] | 1 | # Native Relocations |
| 2 | |
| 3 | *** note |
| 4 | Information here is mostly Android & Linux-specific and may not be 100% accurate. |
| 5 | *** |
| 6 | |
| 7 | ## What are they? |
| 8 | * For ELF files, they are sections of type REL, RELA, or RELR. They generally |
| 9 | have the name ".rel.dyn" and ".rel.plt". |
| 10 | * They tell the runtime linker a list of addresses to post-process after |
| 11 | loading the executable into memory. |
| 12 | * There are several types of relocations, but >99% of them are "relative" |
| 13 | relocations and are created any time a global variable or constant is |
| 14 | initialized with the address of something. |
| 15 | * This includes vtables, function pointers, and string literals, but not |
| 16 | `char[]`. |
| 17 | * Each relocation is stored as either 2 or 3 words, based on the architecture. |
| 18 | * On Android, they are compressed, which trades off runtime performance for |
| 19 | smaller file size. |
| 20 | * As of Oct 2019, Chrome on Android has about 390000 of them. |
| 21 | * Windows and Mac have them as well, but I don't know how they differ. |
| 22 | |
| 23 | ## Why do they matter? |
| 24 | * **Binary Size:** Except on Android, relocations are stored very |
| 25 | inefficiently. |
| 26 | * Chrome on Linux has a `.rela.dyn` section of more than 14MiB! |
| 27 | * Android uses a [custom compression scheme][android_relro1] to shrink them |
| 28 | down to ~300kb. |
| 29 | * There is an even better [RELR][RELR] encoding available on Android P+, but |
| 30 | not widely available on Linux yet. It makes relocations ~60kb. |
| 31 | * **Memory Overhead:** Symbols with relocations cannot be loaded read-only |
| 32 | and result in "dirty" memory. 99% of these symbols live in `.data.rel.ro`, |
| 33 | which as of Oct 2019 is ~6.5MiB on Linux and ~2MiB on Android. |
| 34 | `.data.rel.ro` is data that *would* have been put into `.rodata` and mapped |
| 35 | read-only if not for the required relocations. It does not get written to |
| 36 | after it's relocated, so the linker makes it read-only once relocations are |
| 37 | applied (but by that point the damage is done and we have the dirty pages). |
| 38 | * On Linux, we share this overhead between processes via the [zygote]. |
| 39 | * [On Android][android_relro2], we share this overhead between processes by |
| 40 | loading the shared library at the same address in all processes, and then |
| 41 | `mremap` onto shared memory to dedupe after-the-fact. |
| 42 | * **Start-up Time** The runtime linker applies relocations when loading the |
| 43 | executable. On low-end Android, it can take ~100ms (measured on a first-gen |
| 44 | Android Go devices with APS2 relocations). On Linux, it's |
| 45 | [closer to 20ms][zygote]. |
| 46 | |
| 47 | [zygote]: linux_zygote.md |
| 48 | [RELR]: https://reviews.llvm.org/D48247 |
| 49 | [android_relro1]: android_native_libraries.md#Packed-Relocations |
| 50 | [android_relro2]: android_native_libraries.md#relro-sharing |
| 51 | |
| 52 | ## How do I see them? |
| 53 | |
| 54 | ```sh |
| 55 | third_party/llvm-build/Release+Asserts/bin/llvm-readelf --relocs out/Release/libmonochrome.so |
| 56 | ``` |
| 57 | |
| 58 | ## Can I avoid them? |
| 59 | It's not practical to avoid them altogether, but there are times when you can be |
| 60 | smart about them. |
| 61 | |
| 62 | For Example: |
| 63 | ```c++ |
| 64 | // Wastes 2 bytes for each smaller string but creates no relocations. |
| 65 | // Total size overhead: 4 * 5 = 20 bytes. |
| 66 | const char kArr[][5] = {"as", "ab", "asdf", "fi"}; |
| 67 | |
| 68 | // String data stored optimally, but uses 4 relocatable pointers. |
| 69 | // Total size overhead: |
| 70 | // 64-bit: 8 bytes per pointer + 24 bytes per relocation + 14 bytes of char = 142 bytes |
| 71 | // 32-bit: 4 bytes per pointer + 8 bytes per relocation + 14 bytes of char = 62 bytes |
| 72 | const char *kArr2[] = {"as", "ab", "asdf", "fi"}; |
| 73 | ``` |
| 74 | |
| 75 | Note: |
| 76 | * String literals are de-duped with others in the binary, so it's possible that |
| 77 | the second example above might use 14 fewer bytes. |
| 78 | * Not all string literals require relocations. Only those that are stored into |
| 79 | global variables require them. |
| 80 | |
| 81 | Another thing to look out for: |
| 82 | * Large data structures with relocations that you don't need random access to, |
| 83 | or which are seldom needed. |
| 84 | * For such cases, it might be better to store the data encoded and then |
| 85 | decode when required. |