/* -- ARM64 Relocations -------------------------------------------------------------------- */ /* * What Apple says is a lie. * * They claim in $WHEREEVERTHEYHIDEIT/usr/include/mach-o/arm64/reloc.h: * * | ARM64_RELOC_PAGEOFF12 // offset within page, scaled by r_length * * That's not true. I see r_length = 2 always. But some instructions are ADD * instruction needing a scale of 1 while some are LDR instructions needing * a scale of 8. * * So we need to look at the opcode. Below is a table that I gained by * measuring ld(1). */ static int arm64_insn_scale (uint32_t insn) { /* Measure from what Apple's ld does */ switch (insn & 0xFFC00000) { case 0x39000000: /* strb w0, [x0, #0x20] */ return 1; case 0x39400000: /* ldrb w0, [x0, #0x20] */ return 1; case 0x39800000: /* ldrsb x0, [x0, #0x20] */ return 1; case 0x39c00000: /* ldrsb w0, [x0, #0x20] */ return 1; case 0x3d000000: /* str b0, [x0, #0x20] */ return 1; case 0x3d400000: /* ldr b0, [x0, #0x20] */ return 1; case 0x3d800000: /* str q0, [x0, #0x20] */ return 16; case 0x3dc00000: /* ldr q0, [x0, #0x20] */ return 16; case 0x79000000: /* strh w0, [x0, #0x20] */ return 2; case 0x79400000: /* ldrh w0, [x0, #0x20] */ return 2; case 0x79800000: /* ldrsh x0, [x0, #0x20] */ return 2; case 0x79c00000: /* ldrsh w0, [x0, #0x20] */ return 2; case 0x7d000000: /* str h0, [x0, #0x20] */ return 2; case 0x7d400000: /* ldr h0, [x0, #0x20] */ return 2; case 0x7d800000: /* .long 0x7d804000 */ return 2; case 0x7dc00000: /* .long 0x7dc04000 */ return 2; case 0x91000000: /* add x0, x0, #0x20 */ return 1; case 0xb9000000: /* str w0, [x0, #0x20] */ return 4; case 0xb9400000: /* ldr w0, [x0, #0x20] */ return 4; case 0xb9800000: /* ldrsw x0, [x0, #0x20] */ return 4; case 0xbd000000: /* str s0, [x0, #0x20] */ return 4; case 0xbd400000: /* ldr s0, [x0, #0x20] */ return 4; case 0xbd800000: /* .long 0xbd802000 */ return 4; case 0xf9000000: /* str x0, [x0, #0x20] */ return 8; case 0xf9400000: /* ldr x0, [x0, #0x20] */ return 8; case 0xfd000000: /* str d0, [x0, #0x20] */ return 8; case 0xfd400000: /* ldr d0, [x0, #0x20] */ return 8; default: return -1; } } static natural arm64_handle_reloc (struct relfile_cx *cx, natural P, natural S, natural A, struct relocation_info *reloc) { #define PAGE(X) ((X) >> 12) natural new_addend = 0; int new_has_addend = 0; natural V = A; /* what we come up with */ /* Care for PC relative or not and the addend. */ natural PC = reloc->r_pcrel ? P : 0; S += cx->addend; switch (reloc->r_type) { case ARM64_RELOC_UNSIGNED: { V = S + A - PC; } break; case ARM64_RELOC_BRANCH26: { /* Addend really first? */ V = S - PC; if (sex(28,V) != (snatural)V) { S = find_trampo(cx, S); } V = S - PC; if (sex(28,V) != (int64_t)V) goto range_blame; V = dpb(ldb(26,2, V), 26, 0, A); } break; case ARM64_RELOC_PAGEOFF12: { /* Measure says r_pcrel = 0, r_length = 2, r_extern = 1 is the only supported combination. */ V = S - PC; int sc = arm64_insn_scale(A); if (sc <= 0) goto scale_blame; V = (V & ((1<<12)-1)) / sc; V = dpb(ldb(12, 0, V), 12, 10, A); } break; case ARM64_RELOC_PAGE21: { assert(reloc->r_pcrel); V = PAGE(S) - PAGE(PC); if (sex(21, V) != (snatural)V) goto range_blame; A = dpb(ldb(19, 2, V), 19, 5, A); /* immhi */ A = dpb(ldb(2, 0, V), 2, 29, A); /* immlo */ V = A; } break; case ARM64_RELOC_GOT_LOAD_PAGEOFF12: { S = find_got(cx, S); V = S - PC; int sc = arm64_insn_scale(A); if (sc <= 0) goto scale_blame; V = (V & ((1<<12)-1)) / sc; V = dpb(ldb(12, 0, V), 12, 10, A); } break; case ARM64_RELOC_GOT_LOAD_PAGE21: { S = find_got(cx, S); V = PAGE(S) - PAGE(PC); A = dpb(ldb(19, 2, V), 19, 5, A); /* immhi */ A = dpb(ldb(2, 0, V), 2, 29, A); /* immlo */ V = A; } break; case ARM64_RELOC_ADDEND: { /* xxx */ new_addend = reloc->r_symbolnum; new_has_addend = 1; } break; case ARM64_RELOC_SUBTRACTOR: { /* xxx */ /* This is a symbol rather! I wonder what r_extern is. */ new_addend = -reloc->r_symbolnum; new_has_addend = 1; } break; default: goto punt; } cx->has_addend = new_has_addend; cx->addend = new_addend; return V; punt: error(cx, "\nRelocation type %d (%s) unbeknownst yet.", reloc->r_type, r_type_name(cx, reloc->r_type)); scale_blame: error(cx, "\n **** Scale unknown for insn = %08"PRIx32"\n", (uint32_t)A); range_blame: error(cx, "\nRelocation value 0x%"PRIx64" too large for relocation of type %d (%s).", V, reloc->r_type, r_type_name(cx, reloc->r_type)); #undef PAGE }