kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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 }