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 }