kit

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

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 }