reloc_apply.c (9416B)
1 /* Arch-neutral relocation byte application (the obj-core half). 2 * 3 * reloc_apply_neutral() patches the kinds whose byte encoding is a plain 4 * little-endian data word — absolute / pc-relative writes, the x86-64 GOT/PLT/ 5 * dynamic data slots, the RISC-V data ADD/SUB/SET arithmetic, and the ULEB128 6 * codec — i.e. everything that carries NO instruction-field knowledge. It is 7 * pure obj-core: no link or arch dependency, so it stays usable by every 8 * loader (static linker, JIT linker, assembler, emulator) without pulling in 9 * the link layer. 10 * 11 * The instruction-immediate encoders (AArch64 imm19/imm26/ADRP page math; 12 * RISC-V U/I/S/B/J + RVC scatter and the 0x800 HI20 bias; x86-64 rel8) live in 13 * each backend's src/arch/<arch>/reloc.c and are reached through 14 * LinkArchDesc.reloc_apply_insn. The single public byte-patcher entry, 15 * link_reloc_apply(), dispatches neutral-then-arch from src/link/ 16 * link_reloc_apply.c — housed in the link layer because resolving the per-arch 17 * slice needs link_arch_desc_for() (same boundary call as WS-B's reloc_desc()). 18 * See doc/plan/RELOC.md (WS-C). */ 19 20 #include "obj/reloc_apply.h" 21 22 #include <string.h> 23 24 #include "core/bytes.h" 25 26 /* ---- ULEB128 codec for R_RISCV_{SET,SUB}_ULEB128 ---- 27 * 28 * These RISC-V relocs patch a variable-length ULEB128 field in place 29 * (DWARF .debug_rnglists / .debug_loclists / .debug_line encode 30 * symbol differences this way). The crux: ULEB128 is variable-length, 31 * but rewriting it must NOT shift the section layout, so we re-encode 32 * the new value into the SAME number of bytes the assembler reserved 33 * at the site. ULEB128 permits "redundant" encodings: extra low-order 34 * groups of zero with the continuation bit set, terminated by a final 35 * group whose continuation bit is clear (RISC-V psABI / DWARF v5 36 * §7.6). We exploit that to pad to a fixed width. 37 * 38 * RELOC_ULEB128_MAX_BYTES bounds a 64-bit value: ceil(64/7) = 10. */ 39 #define RELOC_ULEB128_MAX_BYTES 10u 40 #define RELOC_ULEB128_CONT 0x80u /* continuation bit */ 41 #define RELOC_ULEB128_MASK 0x7fu /* 7 payload bits per byte */ 42 43 /* Length of the ULEB128 field encoded at p: count bytes up to and 44 * including the first whose continuation bit is clear. */ 45 static u32 reloc_uleb128_len(const u8* p) { 46 u32 n = 0; 47 for (;;) { 48 u8 byte = p[n++]; 49 if (!(byte & RELOC_ULEB128_CONT)) break; 50 if (n >= RELOC_ULEB128_MAX_BYTES) break; 51 } 52 return n; 53 } 54 55 /* Decode the ULEB128 value encoded at p (assumes a well-formed field 56 * of at most RELOC_ULEB128_MAX_BYTES). */ 57 static u64 reloc_uleb128_read(const u8* p) { 58 u64 v = 0; 59 u32 shift = 0; 60 u32 n = 0; 61 for (;;) { 62 u8 byte = p[n++]; 63 if (shift < 64) v |= (u64)(byte & RELOC_ULEB128_MASK) << shift; 64 shift += 7; 65 if (!(byte & RELOC_ULEB128_CONT)) break; 66 if (n >= RELOC_ULEB128_MAX_BYTES) break; 67 } 68 return v; 69 } 70 71 /* Re-encode v as a ULEB128 occupying exactly `width` bytes, padding 72 * with redundant continuation groups so the in-place field size is 73 * preserved. The final byte's continuation bit is clear; every prior 74 * byte's is set, carrying the next 7 value bits (or zero once v is 75 * exhausted). */ 76 static void reloc_uleb128_write_fixed(u8* p, u64 v, u32 width) { 77 u32 i; 78 for (i = 0; i < width; ++i) { 79 u8 byte = (u8)(v & RELOC_ULEB128_MASK); 80 v >>= 7; 81 if (i + 1u < width) byte |= RELOC_ULEB128_CONT; 82 p[i] = byte; 83 } 84 } 85 86 int reloc_apply_neutral(Compiler* c, RelocKind k, u8* P_bytes, u64 S, i64 A, 87 u64 P) { 88 switch (k) { 89 case R_ABS32: 90 case R_X64_32S: 91 case R_X64_TPOFF32: 92 case R_X64_DTPOFF32: { 93 /* All write a 32-bit value at the site. ABS32 / _32S take an 94 * absolute (unsigned / sign-extended) symbol address; TPOFF32 / 95 * DTPOFF32 take the caller-precomputed TP-relative offset. At the 96 * byte level the encoding is identical. */ 97 u64 v = S + (u64)A; 98 wr_u32_le(P_bytes, (u32)(v & 0xffffffffu)); 99 return 1; 100 } 101 case R_ABS64: 102 case R_TPOFF64: 103 case R_X64_RELATIVE: { 104 /* R_X64_RELATIVE: (S + A) — for static-with-relocs paths the 105 * linker writes the relocated value directly; the dynamic 106 * loader would otherwise do the same fixup at load time. */ 107 u64 v = S + (u64)A; 108 wr_u64_le(P_bytes, v); 109 return 1; 110 } 111 case R_X64_GLOB_DAT: 112 case R_X64_JUMP_SLOT: { 113 /* Dynamic linker normally applies these; for static-with-relocs 114 * paths we write the resolved symbol value (S) into the GOT/PLT 115 * slot. Addend is unused per the x86_64 psABI. */ 116 wr_u64_le(P_bytes, S); 117 return 1; 118 } 119 case R_X64_COPY: 120 compiler_panic(c, SRCLOC_NONE, 121 "link: R_X64_COPY belongs in dynamic loader, " 122 "not static link"); 123 return 1; 124 case R_REL32: 125 case R_PC32: 126 case R_X64_PLT32: 127 case R_X64_GOTPCREL: 128 case R_X64_GOTPCRELX: 129 case R_X64_REX_GOTPCRELX: 130 case R_X64_GOTPC32: 131 case R_X64_GOTTPOFF: 132 case R_X64_TLV: { 133 /* GOTTPOFF (TLS Initial-Exec) is a RIP-relative load of a GOT slot 134 * that the linker fills with the symbol's TP-relative offset; the 135 * fixup is identical to GOTPCREL once the target has been redirected 136 * to that slot (see link_layout_got). */ 137 /* TLV (Mach-O x86_64 descriptor model): RIP-relative disp32 in 138 * `movq sym@TLVP(%rip), %rdi`, redirected by the linker to address the 139 * __thread_ptrs slot (S = slot vaddr). The -4 RIP bias rides in the 140 * addend, same as the other RIP-relative kinds here. */ 141 /* AArch64 ELF: PREL32 maps to either of these; both encode a 142 * 32-bit signed PC-relative displacement. The kit-canonical 143 * distinction (section-relative vs PC-relative) collapses on 144 * AArch64 because the linker resolves to absolute vaddrs. 145 * 146 * x86_64 PLT32: in a static link there is no PLT, so the 147 * displacement collapses to a plain 32-bit PC-relative call. */ 148 i64 v = (i64)S + A - (i64)P; 149 wr_u32_le(P_bytes, (u32)((u64)v & 0xffffffffu)); 150 return 1; 151 } 152 case R_REL64: 153 case R_PC64: { 154 /* 64-bit PC-relative; AArch64 R_AARCH64_PREL64. Used by 155 * `.quad sym1 - sym2` style symbol-difference encodings (e.g. 156 * the arm64 kernel image_size header field). */ 157 i64 v = (i64)S + A - (i64)P; 158 wr_u64_le(P_bytes, (u64)v); 159 return 1; 160 } 161 case R_ABS8: 162 P_bytes[0] = (u8)((S + (u64)A) & 0xffu); 163 return 1; 164 case R_ABS16: { 165 u64 v = S + (u64)A; 166 wr_u16_le(P_bytes, (u16)(v & 0xffffu)); 167 return 1; 168 } 169 case R_PREL16: { 170 i64 v = (i64)S + A - (i64)P; 171 wr_u16_le(P_bytes, (u16)((u64)v & 0xffffu)); 172 return 1; 173 } 174 case R_ADD8: { 175 /* word8 += S + A. Used (paired with a SUB8 against another sym 176 * at the same site) to encode symbol differences. */ 177 u8 cur = P_bytes[0]; 178 P_bytes[0] = (u8)(cur + (u8)((S + (u64)A) & 0xffu)); 179 return 1; 180 } 181 case R_SUB8: { 182 u8 cur = P_bytes[0]; 183 P_bytes[0] = (u8)(cur - (u8)((S + (u64)A) & 0xffu)); 184 return 1; 185 } 186 case R_ADD16: { 187 u16 cur = rd_u16_le(P_bytes); 188 wr_u16_le(P_bytes, (u16)(cur + (u16)((S + (u64)A) & 0xffffu))); 189 return 1; 190 } 191 case R_SUB16: { 192 u16 cur = rd_u16_le(P_bytes); 193 wr_u16_le(P_bytes, (u16)(cur - (u16)((S + (u64)A) & 0xffffu))); 194 return 1; 195 } 196 case R_ADD32: { 197 u32 cur = rd_u32_le(P_bytes); 198 wr_u32_le(P_bytes, (u32)(cur + (u32)((S + (u64)A) & 0xffffffffu))); 199 return 1; 200 } 201 case R_SUB32: { 202 u32 cur = rd_u32_le(P_bytes); 203 wr_u32_le(P_bytes, (u32)(cur - (u32)((S + (u64)A) & 0xffffffffu))); 204 return 1; 205 } 206 case R_ADD64: { 207 u64 cur = rd_u64_le(P_bytes); 208 wr_u64_le(P_bytes, cur + S + (u64)A); 209 return 1; 210 } 211 case R_SUB64: { 212 u64 cur = rd_u64_le(P_bytes); 213 wr_u64_le(P_bytes, cur - S - (u64)A); 214 return 1; 215 } 216 case R_SUB6: { 217 /* Bottom 6 bits of byte = (byte - (S + A)) & 0x3f. */ 218 u8 cur = P_bytes[0]; 219 u8 v = (u8)((cur & 0x3fu) - (u8)((S + (u64)A) & 0x3fu)); 220 P_bytes[0] = (u8)((cur & 0xc0u) | (v & 0x3fu)); 221 return 1; 222 } 223 case R_SET6: { 224 u8 cur = P_bytes[0]; 225 P_bytes[0] = (u8)((cur & 0xc0u) | (u8)((S + (u64)A) & 0x3fu)); 226 return 1; 227 } 228 case R_SET_ULEB128: { 229 /* Variable-length ULEB128 field set to (S + A). These come as a 230 * PAIR at the same offset (RISC-V psABI): SET_ULEB128 sets the 231 * field, a following SUB_ULEB128 then subtracts the second 232 * symbol — net encoding (sym_hi - sym_lo) for DWARF symbol 233 * differences. Re-encode into the original field width so the 234 * section layout doesn't shift. */ 235 u32 width = reloc_uleb128_len(P_bytes); 236 u64 v = S + (u64)A; 237 reloc_uleb128_write_fixed(P_bytes, v, width); 238 return 1; 239 } 240 case R_SUB_ULEB128: { 241 /* field -= (S + A), preserving the original ULEB128 width. The 242 * paired SET_ULEB128 ran first (same offset); we read back the 243 * value it wrote and subtract this symbol's resolved address. */ 244 u32 width = reloc_uleb128_len(P_bytes); 245 u64 cur = reloc_uleb128_read(P_bytes); 246 u64 v = cur - (S + (u64)A); 247 reloc_uleb128_write_fixed(P_bytes, v, width); 248 return 1; 249 } 250 default: 251 return 0; /* not an arch-neutral kind — caller tries the arch hook */ 252 } 253 }