link.c (3829B)
1 /* x86_64 link-time arch descriptor. 2 * 3 * Implements the LinkArchDesc contract from link/link_arch.h for 4 * EM_X86_64. The PLT/IPLT byte sequences here mirror the inline 5 * encodings previously living in link_dyn.c (PLT0 + per-import entry) 6 * and link_layout.c (IPLT stub) — kept identical byte-for-byte so the 7 * descriptor switchover is a pure refactor. All raw byte values come 8 * from named constants / inline writers in arch/x64/isa.h. */ 9 10 #include "arch/x64/isa.h" 11 #include "core/bytes.h" 12 #include "core/core.h" 13 #include "link/link_arch.h" 14 15 /* PLT0 layout under DF_1_NOW: never executed (loader pre-binds every 16 * slot via .rela.plt before user code runs), so we just emit 32 bytes 17 * of single-byte NOPs. Self-documenting and trivially well-formed for 18 * disassemblers and unwinders that walk the section. */ 19 static void x64_emit_plt0(u8* dst, u64 plt0_vaddr, u64 gotplt_vaddr) { 20 (void)plt0_vaddr; 21 (void)gotplt_vaddr; 22 x64_write_nop_pad(dst, 32u); 23 } 24 25 /* Per-import PLT entry (16 B): 26 * 27 * ff 25 disp32 ; jmpq *[rip + disp_to_slot] (6 B) 28 * 90 90 90 90 90 90 90 90 90 90 ; pad to 16 with single-byte NOPs 29 * 30 * disp32 is measured from the END of the JMP (entry_vaddr + 6) to the 31 * .got.plt slot. The 10-byte tail matches link_dyn.c's prior 32 * memset(0x90)+patch behavior exactly. */ 33 static void x64_emit_plt_entry(u8* dst, u64 entry_vaddr, u64 slot_vaddr) { 34 i64 disp = (i64)slot_vaddr - (i64)(entry_vaddr + X64_JMP_RIPREL_SIZE); 35 i32 disp32 = (i32)(u32)((u64)disp & 0xffffffffu); 36 x64_write_jmp_riprel(dst, disp32); 37 x64_write_nop_pad(dst + X64_JMP_RIPREL_SIZE, 16u - X64_JMP_RIPREL_SIZE); 38 } 39 40 /* IPLT (ifunc) trampoline stub (12 B): 41 * 42 * ff 25 disp32 ; jmpq *[rip + disp_to_slot] (6 B) 43 * 66 0f 1f 44 00 00 ; 6-byte multibyte NOP (6 B) 44 * 45 * Like the PLT entry, disp32 is from the END of the JMP to the 46 * .igot.plt slot. The displacement is invariant under image-base 47 * shift (both ends move together), so it's encoded inline and we 48 * report zero apply-time relocations. */ 49 static u32 x64_emit_iplt_stub(u8* dst, u64 stub_vaddr, u64 slot_vaddr, 50 LinkArchIPltReloc out[2]) { 51 (void)out; 52 i64 disp = (i64)slot_vaddr - (i64)(stub_vaddr + X64_JMP_RIPREL_SIZE); 53 i32 disp32 = (i32)(u32)((u64)disp & 0xffffffffu); 54 x64_write_jmp_riprel(dst, disp32); 55 x64_write_nop6(dst + X64_JMP_RIPREL_SIZE); 56 return 0; 57 } 58 59 /* Width + classification rows + instruction-immediate byte encoders for 60 * x86-64's relocation kinds; defined in src/arch/x64/reloc.c and consulted 61 * through the .reloc_desc / .reloc_apply_insn hooks. */ 62 const RelocDesc* x64_reloc_desc(RelocKind); 63 int x64_reloc_apply_insn(Compiler*, RelocKind, u8*, u64, i64, u64); 64 void x64_jit_tls_le_relax(Compiler*, RelocKind, u8*, u64, u64); 65 66 /* PE/COFF IAT stub for x86_64 (6 B): 67 * 68 * ff 25 disp32 ; jmpq *[rip + disp_to_iat_slot] 69 * 70 * disp32 is signed offset from the END of the JMP (stub_vaddr + 6) 71 * to the IAT slot in .idata. Identical layout to the ELF PLT entry 72 * head, minus the trailing NOP pad — Win64 calls don't need a stub 73 * aligned to a fixed entry stride because there's no PLT0 to share 74 * the address space with. */ 75 void x64_emit_coff_iat_stub(u8* dst, u64 stub_vaddr, u64 iat_slot_vaddr) { 76 i64 disp = (i64)iat_slot_vaddr - (i64)(stub_vaddr + X64_JMP_RIPREL_SIZE); 77 i32 disp32 = (i32)(u32)((u64)disp & 0xffffffffu); 78 x64_write_jmp_riprel(dst, disp32); 79 } 80 81 const LinkArchDesc link_arch_x64 = { 82 .plt0_size = 32u, 83 .plt_entry_size = 16u, 84 .iplt_stub_size = 12u, 85 86 .emit_plt0 = x64_emit_plt0, 87 .emit_plt_entry = x64_emit_plt_entry, 88 .emit_iplt_stub = x64_emit_iplt_stub, 89 90 .reloc_desc = x64_reloc_desc, 91 .reloc_apply_insn = x64_reloc_apply_insn, 92 .jit_tls_le_relax = x64_jit_tls_le_relax, 93 94 .tls_variant_ii = 1, 95 };