kit

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

reloc.c (6096B)


      1 /* ARM32 (Thumb-2) relocation descriptors (width + classification) + the
      2  * split-immediate instruction byte patcher.
      3  *
      4  * Reached through LinkArchDesc.reloc_desc / .reloc_apply_insn (wired in
      5  * link.c) and the arch-aware reloc_desc() / link_reloc_apply() dispatchers.
      6  * Wire encoding + diagnostic name live in src/obj/elf/reloc_arm.c.
      7  *
      8  * Data words (the vector table, data pointers) use the neutral R_ABS32 /
      9  * R_REL32 kinds, claimed by reloc_apply_neutral before this hook runs — so
     10  * only the instruction-embedded Thumb-2 branch kinds appear here.
     11  *
     12  * Thumb-2 byte order: a 32-bit instruction is two independently-little-endian
     13  * 16-bit half-words, the HIGH half-word FIRST in memory. rd_u32_le/wr_u32_le
     14  * would swap them, so arm_rd_t32/arm_wr_t32 read/write half-word-pairs. */
     15 
     16 #include "obj/reloc.h"
     17 
     18 #include "core/bytes.h"
     19 #include "core/core.h"
     20 #include "link/link_arch.h"
     21 #include "obj/obj.h"
     22 
     23 static const RelocDescRow arm32_rows[] = {
     24     {R_ARM_THM_CALL, {4, RELOC_IS_BRANCH}},   /* BL (T1),  +-16 MiB */
     25     {R_ARM_THM_JUMP24, {4, RELOC_IS_BRANCH}}, /* B.W (T4), +-16 MiB */
     26     {R_ARM_THM_JUMP19, {4, RELOC_IS_BRANCH}}, /* B<cond>.W (T3), +-1 MiB */
     27     {R_ARM_THM_MOVW_ABS_NC, {4, 0}}, /* MOVW (T3) low-16 of S+A; not a branch */
     28     {R_ARM_THM_MOVT_ABS, {4, 0}},    /* MOVT (T3) high-16 of S+A; not a branch */
     29 };
     30 
     31 const RelocDesc* arm32_reloc_desc(RelocKind k) {
     32   return reloc_desc_row_find(arm32_rows,
     33                              (u32)(sizeof arm32_rows / sizeof arm32_rows[0]), k);
     34 }
     35 
     36 /* Read/write a 32-bit Thumb-2 instruction stored as two little-endian
     37  * half-words, HIGH half-word first in memory (the Thumb byte order). The
     38  * returned/accepted 32-bit value has hw1 in the high 16 bits, hw2 in the low
     39  * 16 bits. */
     40 static u32 arm_rd_t32(const u8* p) {
     41   return ((u32)rd_u16_le(p) << 16) | (u32)rd_u16_le(p + 2);
     42 }
     43 static void arm_wr_t32(u8* p, u32 instr) {
     44   wr_u16_le(p, (u16)(instr >> 16));
     45   wr_u16_le(p + 2, (u16)(instr & 0xffffu));
     46 }
     47 
     48 int arm32_reloc_apply_insn(Compiler* c, RelocKind k, u8* P_bytes, u64 S, i64 A,
     49                            u64 P) {
     50   switch (k) {
     51     case R_ARM_THM_CALL:
     52     case R_ARM_THM_JUMP24: {
     53       /* BL/B.W T1/T4 25-bit field {S,I1,I2,imm10,imm11}, in 2-byte units:
     54        *   instr[26]=Sbit, instr[25:16]=imm10, instr[13]=J1, instr[11]=J2,
     55        *   instr[10:0]=imm11, with I1=NOT(J1^S), I2=NOT(J2^S).
     56        * Thumb-only on Cortex-M: BL never relaxes to BLX, so the callee's
     57        * Thumb bit (S|1) is masked out of the displacement. The hardware PC
     58        * base is the instruction address + 4 (Thumb pipeline), so the encoded
     59        * byte offset is (S&~1) + A - P - 4. (The field is overwritten; a
     60        * non-zero in-field addend, i.e. `bl sym+N`, is a Phase-2 refinement.) */
     61       i64 disp = (i64)(S & ~(u64)1) + A - (i64)P - 4;
     62       u32 instr, sbit, i1, i2, j1, j2, imm10, imm11;
     63       if (disp & 1)
     64         compiler_panic(c, SRCLOC_NONE,
     65                        "link: THM_CALL misaligned displacement");
     66       if (disp < -(i64)(1 << 24) || disp >= (i64)(1 << 24))
     67         compiler_panic(c, SRCLOC_NONE,
     68                        "link: THM_CALL out of range (need +-16MiB)");
     69       sbit = (u32)((disp >> 24) & 1u);
     70       i1 = (u32)((disp >> 23) & 1u);
     71       i2 = (u32)((disp >> 22) & 1u);
     72       imm10 = (u32)((disp >> 12) & 0x3ffu);
     73       imm11 = (u32)((disp >> 1) & 0x7ffu);
     74       j1 = (~(i1 ^ sbit)) & 1u; /* I1 = NOT(J1^S) -> J1 = NOT(I1^S) */
     75       j2 = (~(i2 ^ sbit)) & 1u;
     76       instr = arm_rd_t32(P_bytes);
     77       instr &= 0xf800d000u; /* keep opcode (11110), hw2[15:14] (BL/B), hw2[12] */
     78       instr |= (sbit << 26) | (imm10 << 16);
     79       instr |= (j1 << 13) | (j2 << 11) | imm11;
     80       arm_wr_t32(P_bytes, instr);
     81       return 1;
     82     }
     83     case R_ARM_THM_JUMP19: {
     84       /* B<cond>.W T3 21-bit field {S,J2,J1,imm6,imm11} (no XOR-with-S, unlike
     85        * THM_CALL). PC base = instruction + 4 (Thumb pipeline). The branch
     86        * targets code, so the callee's Thumb bit (S|1) is masked out. Mirrors
     87        * arch.c:arm32_apply_label_fixup's JUMP19 encoder. */
     88       i64 disp = (i64)(S & ~(u64)1) + A - (i64)P - 4;
     89       u32 instr, sbit, j1, j2, imm6, imm11;
     90       if (disp & 1)
     91         compiler_panic(c, SRCLOC_NONE,
     92                        "link: THM_JUMP19 misaligned displacement");
     93       if (disp < -(i64)(1 << 20) || disp >= (i64)(1 << 20))
     94         compiler_panic(c, SRCLOC_NONE,
     95                        "link: THM_JUMP19 out of range (need +-1MiB)");
     96       sbit = (u32)((disp >> 20) & 1u);
     97       j2 = (u32)((disp >> 19) & 1u);
     98       j1 = (u32)((disp >> 18) & 1u);
     99       imm6 = (u32)((disp >> 12) & 0x3fu);
    100       imm11 = (u32)((disp >> 1) & 0x7ffu);
    101       instr = arm_rd_t32(P_bytes);
    102       /* clear S(26), imm6(21:16), J1(13), J2(11), imm11(10:0); keep opcode,
    103        * cond(25:22), hw2[15:14]=10, hw2[12]=0. */
    104       instr &= ~((1u << 26) | (0x3fu << 16) | (1u << 13) | (1u << 11) | 0x7ffu);
    105       instr |= (sbit << 26) | (imm6 << 16) | (j1 << 13) | (j2 << 11) | imm11;
    106       arm_wr_t32(P_bytes, instr);
    107       return 1;
    108     }
    109     case R_ARM_THM_MOVW_ABS_NC:
    110     case R_ARM_THM_MOVT_ABS: {
    111       /* MOVW/MOVT (T3): a 16-bit immediate scattered as imm4:i:imm3:imm8 ->
    112        *   hw1[3:0]=imm4, hw1[10]=i, hw2[14:12]=imm3, hw2[7:0]=imm8.
    113        * MOVW fills (S+A)[15:0], MOVT fills (S+A)[31:16]. The Thumb bit is NOT
    114        * stripped: a code symbol taken as data (`&fn`) must stay odd so a later
    115        * BLX reaches Thumb, and a data symbol's bit 0 is part of its address. */
    116       u32 instr = arm_rd_t32(P_bytes);
    117       u32 full = (u32)((u64)S + (u64)A);
    118       u32 imm16 = (k == R_ARM_THM_MOVT_ABS) ? (full >> 16) & 0xffffu
    119                                             : full & 0xffffu;
    120       u32 imm4 = (imm16 >> 12) & 0xfu, i = (imm16 >> 11) & 1u,
    121           imm3 = (imm16 >> 8) & 7u, imm8 = imm16 & 0xffu;
    122       /* clear the four immediate fields, keep the opcode + Rd. */
    123       instr &= ~((0xfu << 16) | (1u << 26) | (7u << 12) | 0xffu);
    124       instr |= (imm4 << 16) | (i << 26) | (imm3 << 12) | imm8;
    125       arm_wr_t32(P_bytes, instr);
    126       return 1;
    127     }
    128     default:
    129       return 0;
    130   }
    131 }