reloc_apply.c (9082B)
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 /* All three write a 32-bit value at the site. ABS32 / _32S 93 * take an absolute (unsigned / sign-extended) symbol address; 94 * TPOFF32 takes the (caller-precomputed) TP-relative offset. 95 * At the byte level the encoding is identical. */ 96 u64 v = S + (u64)A; 97 wr_u32_le(P_bytes, (u32)(v & 0xffffffffu)); 98 return 1; 99 } 100 case R_ABS64: 101 case R_TPOFF64: 102 case R_X64_RELATIVE: { 103 /* R_X64_RELATIVE: (S + A) — for static-with-relocs paths the 104 * linker writes the relocated value directly; the dynamic 105 * loader would otherwise do the same fixup at load time. */ 106 u64 v = S + (u64)A; 107 wr_u64_le(P_bytes, v); 108 return 1; 109 } 110 case R_X64_GLOB_DAT: 111 case R_X64_JUMP_SLOT: { 112 /* Dynamic linker normally applies these; for static-with-relocs 113 * paths we write the resolved symbol value (S) into the GOT/PLT 114 * slot. Addend is unused per the x86_64 psABI. */ 115 wr_u64_le(P_bytes, S); 116 return 1; 117 } 118 case R_X64_COPY: 119 compiler_panic(c, SRCLOC_NONE, 120 "link: R_X64_COPY belongs in dynamic loader, " 121 "not static link"); 122 return 1; 123 case R_REL32: 124 case R_PC32: 125 case R_X64_PLT32: 126 case R_X64_GOTPCREL: 127 case R_X64_GOTPCRELX: 128 case R_X64_REX_GOTPCRELX: 129 case R_X64_GOTPC32: 130 case R_X64_GOTTPOFF: { 131 /* GOTTPOFF (TLS Initial-Exec) is a RIP-relative load of a GOT slot 132 * that the linker fills with the symbol's TP-relative offset; the 133 * fixup is identical to GOTPCREL once the target has been redirected 134 * to that slot (see link_layout_got). */ 135 /* AArch64 ELF: PREL32 maps to either of these; both encode a 136 * 32-bit signed PC-relative displacement. The kit-canonical 137 * distinction (section-relative vs PC-relative) collapses on 138 * AArch64 because the linker resolves to absolute vaddrs. 139 * 140 * x86_64 PLT32: in a static link there is no PLT, so the 141 * displacement collapses to a plain 32-bit PC-relative call. */ 142 i64 v = (i64)S + A - (i64)P; 143 wr_u32_le(P_bytes, (u32)((u64)v & 0xffffffffu)); 144 return 1; 145 } 146 case R_REL64: 147 case R_PC64: { 148 /* 64-bit PC-relative; AArch64 R_AARCH64_PREL64. Used by 149 * `.quad sym1 - sym2` style symbol-difference encodings (e.g. 150 * the arm64 kernel image_size header field). */ 151 i64 v = (i64)S + A - (i64)P; 152 wr_u64_le(P_bytes, (u64)v); 153 return 1; 154 } 155 case R_ABS8: 156 P_bytes[0] = (u8)((S + (u64)A) & 0xffu); 157 return 1; 158 case R_ABS16: { 159 u64 v = S + (u64)A; 160 wr_u16_le(P_bytes, (u16)(v & 0xffffu)); 161 return 1; 162 } 163 case R_PREL16: { 164 i64 v = (i64)S + A - (i64)P; 165 wr_u16_le(P_bytes, (u16)((u64)v & 0xffffu)); 166 return 1; 167 } 168 case R_ADD8: { 169 /* word8 += S + A. Used (paired with a SUB8 against another sym 170 * at the same site) to encode symbol differences. */ 171 u8 cur = P_bytes[0]; 172 P_bytes[0] = (u8)(cur + (u8)((S + (u64)A) & 0xffu)); 173 return 1; 174 } 175 case R_SUB8: { 176 u8 cur = P_bytes[0]; 177 P_bytes[0] = (u8)(cur - (u8)((S + (u64)A) & 0xffu)); 178 return 1; 179 } 180 case R_ADD16: { 181 u16 cur = rd_u16_le(P_bytes); 182 wr_u16_le(P_bytes, (u16)(cur + (u16)((S + (u64)A) & 0xffffu))); 183 return 1; 184 } 185 case R_SUB16: { 186 u16 cur = rd_u16_le(P_bytes); 187 wr_u16_le(P_bytes, (u16)(cur - (u16)((S + (u64)A) & 0xffffu))); 188 return 1; 189 } 190 case R_ADD32: { 191 u32 cur = rd_u32_le(P_bytes); 192 wr_u32_le(P_bytes, (u32)(cur + (u32)((S + (u64)A) & 0xffffffffu))); 193 return 1; 194 } 195 case R_SUB32: { 196 u32 cur = rd_u32_le(P_bytes); 197 wr_u32_le(P_bytes, (u32)(cur - (u32)((S + (u64)A) & 0xffffffffu))); 198 return 1; 199 } 200 case R_ADD64: { 201 u64 cur = rd_u64_le(P_bytes); 202 wr_u64_le(P_bytes, cur + S + (u64)A); 203 return 1; 204 } 205 case R_SUB64: { 206 u64 cur = rd_u64_le(P_bytes); 207 wr_u64_le(P_bytes, cur - S - (u64)A); 208 return 1; 209 } 210 case R_SUB6: { 211 /* Bottom 6 bits of byte = (byte - (S + A)) & 0x3f. */ 212 u8 cur = P_bytes[0]; 213 u8 v = (u8)((cur & 0x3fu) - (u8)((S + (u64)A) & 0x3fu)); 214 P_bytes[0] = (u8)((cur & 0xc0u) | (v & 0x3fu)); 215 return 1; 216 } 217 case R_SET6: { 218 u8 cur = P_bytes[0]; 219 P_bytes[0] = (u8)((cur & 0xc0u) | (u8)((S + (u64)A) & 0x3fu)); 220 return 1; 221 } 222 case R_SET_ULEB128: { 223 /* Variable-length ULEB128 field set to (S + A). These come as a 224 * PAIR at the same offset (RISC-V psABI): SET_ULEB128 sets the 225 * field, a following SUB_ULEB128 then subtracts the second 226 * symbol — net encoding (sym_hi - sym_lo) for DWARF symbol 227 * differences. Re-encode into the original field width so the 228 * section layout doesn't shift. */ 229 u32 width = reloc_uleb128_len(P_bytes); 230 u64 v = S + (u64)A; 231 reloc_uleb128_write_fixed(P_bytes, v, width); 232 return 1; 233 } 234 case R_SUB_ULEB128: { 235 /* field -= (S + A), preserving the original ULEB128 width. The 236 * paired SET_ULEB128 ran first (same offset); we read back the 237 * value it wrote and subtract this symbol's resolved address. */ 238 u32 width = reloc_uleb128_len(P_bytes); 239 u64 cur = reloc_uleb128_read(P_bytes); 240 u64 v = cur - (S + (u64)A); 241 reloc_uleb128_write_fixed(P_bytes, v, width); 242 return 1; 243 } 244 default: 245 return 0; /* not an arch-neutral kind — caller tries the arch hook */ 246 } 247 }