/* * Copyright (c) 2007-2008 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #include #include #include #if KERNEL #include #include #else #include #include /* Get machine.h from the kernel source so we can support all platforms * that the kernel supports. Otherwise we're at the mercy of the host. */ #include "../../osfmk/mach/machine.h" #endif #define DEBUG_ASSERT_COMPONENT_NAME_STRING "kxld" #include #include "kxld_array.h" #include "kxld_demangle.h" #include "kxld_dict.h" #include "kxld_reloc.h" #include "kxld_sect.h" #include "kxld_seg.h" #include "kxld_sym.h" #include "kxld_symtab.h" #include "kxld_util.h" #include "kxld_vtable.h" #if KXLD_PIC_KEXTS /* This will try to pull in mach/machine.h, so it has to come after the * explicit include above. */ #include #endif /* include target-specific relocation prototypes */ #include #if KXLD_USER_OR_X86_64 #include #endif #if KXLD_USER_OR_ARM #include #endif #if KXLD_USER_OR_ARM64 #include #endif #define KXLD_TARGET_NONE (u_int) 0x0 #define KXLD_TARGET_VALUE (u_int) 0x1 #define KXLD_TARGET_SECTNUM (u_int) 0x2 #define KXLD_TARGET_SYMBOLNUM (u_int) 0x3 #define KXLD_TARGET_LOOKUP (u_int) 0x4 #define KXLD_TARGET_GOT (u_int) 0x5 #define ABSOLUTE_VALUE(x) (((x) < 0) ? -(x) : (x)) #define LO16(x) (0x0000FFFF & x) #define LO16S(x) ((0x0000FFFF & x) << 16) #define HI16(x) (0xFFFF0000 & x) #define HI16S(x) ((0xFFFF0000 & x) >> 16) #define BIT15(x) (0x00008000 & x) #define BR14I(x) (0xFFFF0003 & x) #define BR14D(x) (0x0000FFFC & x) #define BR24I(x) (0xFC000003 & x) #define BR24D(x) (0x03FFFFFC & x) #define HADISP 0x00010000 #define BR14_LIMIT 0x00008000 #define BR24_LIMIT 0x02000000 #define IS_COND_BR_INSTR(x) ((x & 0xFC000000) == 0x40000000) #define IS_NOT_ALWAYS_TAKEN(x) ((x & 0x03E00000) != 0x02800000) #define FLIP_PREDICT_BIT(x) x ^= 0x00200000 #define SIGN_EXTEND_MASK(n) (1 << ((n) - 1)) #define SIGN_EXTEND(x,n) (((x) ^ SIGN_EXTEND_MASK(n)) - SIGN_EXTEND_MASK(n)) #define BR14_NBITS_DISPLACEMENT 16 #define BR24_NBITS_DISPLACEMENT 26 #define X86_64_RIP_RELATIVE_LIMIT 0x80000000UL /******************************************************************************* * Prototypes *******************************************************************************/ #if KXLD_USER_OR_I386 static boolean_t generic_reloc_has_pair(u_int _type) __attribute__((const)); static u_int generic_reloc_get_pair_type(u_int _prev_type) __attribute__((const)); static boolean_t generic_reloc_has_got(u_int _type) __attribute__((const)); static kern_return_t generic_process_reloc(const KXLDRelocator *relocator, u_char *instruction, u_int length, u_int pcrel, kxld_addr_t base_pc, kxld_addr_t link_pc, kxld_addr_t link_disp, u_int type, kxld_addr_t target, kxld_addr_t pair_target, boolean_t swap); #endif /* KXLD_USER_OR_I386 */ #if KXLD_USER_OR_X86_64 static boolean_t x86_64_reloc_has_pair(u_int _type) __attribute__((const)); static u_int x86_64_reloc_get_pair_type(u_int _prev_type) __attribute__((const)); static boolean_t x86_64_reloc_has_got(u_int _type) __attribute__((const)); static kern_return_t x86_64_process_reloc(const KXLDRelocator *relocator, u_char *instruction, u_int length, u_int pcrel, kxld_addr_t base_pc, kxld_addr_t link_pc, kxld_addr_t link_disp, u_int type, kxld_addr_t target, kxld_addr_t pair_target, boolean_t swap); static kern_return_t calculate_displacement_x86_64(uint64_t target, uint64_t adjustment, int32_t *instr32); #endif /* KXLD_USER_OR_X86_64 */ #if KXLD_USER_OR_ARM static boolean_t arm_reloc_has_pair(u_int _type) __attribute__((const)); static u_int arm_reloc_get_pair_type(u_int _prev_type) __attribute__((const)); static boolean_t arm_reloc_has_got(u_int _type) __attribute__((const)); static kern_return_t arm_process_reloc(const KXLDRelocator *relocator, u_char *instruction, u_int length, u_int pcrel, kxld_addr_t base_pc, kxld_addr_t link_pc, kxld_addr_t link_disp, u_int type, kxld_addr_t target, kxld_addr_t pair_target, boolean_t swap); #endif /* KXLD_USER_OR_ARM */ #if KXLD_USER_OR_ARM64 static boolean_t arm64_reloc_has_pair(u_int _type) __attribute__((const)); static u_int arm64_reloc_get_pair_type(u_int _prev_type) __attribute__((const)); static boolean_t arm64_reloc_has_got(u_int _type) __attribute__((const)); static kern_return_t arm64_process_reloc(const KXLDRelocator *relocator, u_char *instruction, u_int length, u_int pcrel, kxld_addr_t base_pc, kxld_addr_t link_pc, kxld_addr_t link_disp, u_int type, kxld_addr_t target, kxld_addr_t pair_target, boolean_t swap); #endif /* KXLD_USER_OR_ARM64 */ #if KXLD_USER_OR_ILP32 static kxld_addr_t get_pointer_at_addr_32(const KXLDRelocator *relocator, const u_char *data, u_long offset) __attribute__((pure, nonnull)); #endif /* KXLD_USER_OR_ILP32 */ #if KXLD_USER_OR_LP64 static kxld_addr_t get_pointer_at_addr_64(const KXLDRelocator *relocator, const u_char *data, u_long offset) __attribute__((pure, nonnull)); #endif /* KXLD_USER_OR_LP64 */ static u_int count_relocatable_relocs(const KXLDRelocator *relocator, const struct relocation_info *relocs, u_int nrelocs) __attribute__((pure)); static kern_return_t calculate_targets(KXLDRelocator *relocator, kxld_addr_t *_target, kxld_addr_t *_pair_target, const KXLDReloc *reloc); static kxld_addr_t align_raw_function_address(const KXLDRelocator *relocator, kxld_addr_t value); static kern_return_t get_target_by_address_lookup(kxld_addr_t *target, kxld_addr_t addr, const KXLDArray *sectarray); static kern_return_t check_for_direct_pure_virtual_call( const KXLDRelocator *relocator, u_long offset); #if KXLD_PIC_KEXTS static u_long get_macho_data_size_for_array(const KXLDArray *relocs); static kern_return_t export_macho_for_array(const KXLDRelocator *relocator, const KXLDArray *relocs, struct relocation_info **dstp); #endif /* KXLD_PIC_KEXTS */ /******************************************************************************* *******************************************************************************/ kern_return_t kxld_relocator_init(KXLDRelocator *relocator, u_char *file, const KXLDSymtab *symtab, const KXLDArray *sectarray, cpu_type_t cputype, cpu_subtype_t cpusubtype __unused, boolean_t swap) { kern_return_t rval = KERN_FAILURE; check(relocator); switch(cputype) { #if KXLD_USER_OR_I386 case CPU_TYPE_I386: relocator->reloc_has_pair = generic_reloc_has_pair; relocator->reloc_get_pair_type = generic_reloc_get_pair_type; relocator->reloc_has_got = generic_reloc_has_got; relocator->process_reloc = generic_process_reloc; relocator->function_align = 0; relocator->is_32_bit = TRUE; relocator->may_scatter = TRUE; break; #endif /* KXLD_USER_OR_I386 */ #if KXLD_USER_OR_X86_64 case CPU_TYPE_X86_64: relocator->reloc_has_pair = x86_64_reloc_has_pair; relocator->reloc_get_pair_type = x86_64_reloc_get_pair_type; relocator->reloc_has_got = x86_64_reloc_has_got; relocator->process_reloc = x86_64_process_reloc; relocator->function_align = 0; relocator->is_32_bit = FALSE; relocator->may_scatter = FALSE; break; #endif /* KXLD_USER_OR_X86_64 */ #if KXLD_USER_OR_ARM case CPU_TYPE_ARM: relocator->reloc_has_pair = arm_reloc_has_pair; relocator->reloc_get_pair_type = arm_reloc_get_pair_type; relocator->reloc_has_got = arm_reloc_has_got; relocator->process_reloc = arm_process_reloc; relocator->function_align = 1; relocator->is_32_bit = TRUE; relocator->may_scatter = FALSE; break; #endif /* KXLD_USER_OR_ARM */ #if KXLD_USER_OR_ARM64 case CPU_TYPE_ARM64: relocator->reloc_has_pair = arm64_reloc_has_pair; relocator->reloc_get_pair_type = arm64_reloc_get_pair_type; relocator->reloc_has_got = arm64_reloc_has_got; relocator->process_reloc = arm64_process_reloc; relocator->function_align = 0; relocator->is_32_bit = FALSE; relocator->may_scatter = FALSE; break; #endif /* KXLD_USER_OR_ARM64 */ default: rval = KERN_FAILURE; kxld_log(kKxldLogLinking, kKxldLogErr, kKxldLogArchNotSupported, cputype); goto finish; } relocator->file = file; relocator->symtab = symtab; relocator->sectarray = sectarray; relocator->is_32_bit = kxld_is_32_bit(cputype); relocator->swap = swap; rval = KERN_SUCCESS; finish: return rval; } /******************************************************************************* *******************************************************************************/ kern_return_t kxld_reloc_create_macho(KXLDArray *relocarray, const KXLDRelocator *relocator, const struct relocation_info *srcs, u_int nsrcs) { kern_return_t rval = KERN_FAILURE; KXLDReloc *reloc = NULL; u_int nrelocs = 0; const struct relocation_info *src = NULL; const struct scattered_relocation_info *scatsrc = NULL; u_int i = 0; u_int reloc_index = 0; check(relocarray); check(srcs); /* If there are no relocation entries, just return */ if (!nsrcs) { rval = KERN_SUCCESS; goto finish; } /* Count the number of non-pair relocs */ nrelocs = count_relocatable_relocs(relocator, srcs, nsrcs); if (nrelocs) { /* Allocate the array of relocation entries */ rval = kxld_array_init(relocarray, sizeof(KXLDReloc), nrelocs); require_noerr(rval, finish); /* Initialize the relocation entries */ for (i = 0; i < nsrcs; ++i) { src = srcs + i; scatsrc = (const struct scattered_relocation_info *) src; /* A section-based relocation entry can be skipped for absolute * symbols. */ if (!(relocator->may_scatter && (src->r_address & R_SCATTERED)) && !(src->r_extern) && (R_ABS == src->r_symbolnum)) { continue; } /* Pull out the data from the relocation entries. The target_type * depends on the r_extern bit: * Scattered -> Section Lookup by Address * Local (not extern) -> Section by Index * Extern -> Symbolnum by Index */ reloc = kxld_array_get_item(relocarray, reloc_index++); if (relocator->may_scatter && (src->r_address & R_SCATTERED)) { reloc->address = scatsrc->r_address; reloc->pcrel = scatsrc->r_pcrel; reloc->length = scatsrc->r_length; reloc->reloc_type = scatsrc->r_type; reloc->target = scatsrc->r_value; reloc->target_type = KXLD_TARGET_LOOKUP; } else { reloc->address = src->r_address; reloc->pcrel = src->r_pcrel; reloc->length = src->r_length; reloc->reloc_type = src->r_type; reloc->target = src->r_symbolnum; if (0 == src->r_extern) { reloc->target_type = KXLD_TARGET_SECTNUM; reloc->target -= 1; } else { reloc->target_type = KXLD_TARGET_SYMBOLNUM; } } /* Find the pair entry if it exists */ if (relocator->reloc_has_pair(reloc->reloc_type)) { ++i; require_action(i < nsrcs, finish, rval=KERN_FAILURE); src = srcs + i; scatsrc = (const struct scattered_relocation_info *) src; if (relocator->may_scatter && (src->r_address & R_SCATTERED)) { require_action(relocator->reloc_get_pair_type( reloc->reloc_type) == scatsrc->r_type, finish, rval=KERN_FAILURE); reloc->pair_address= scatsrc->r_address; reloc->pair_target = scatsrc->r_value; reloc->pair_target_type = KXLD_TARGET_LOOKUP; } else { require_action(relocator->reloc_get_pair_type( reloc->reloc_type) == scatsrc->r_type, finish, rval=KERN_FAILURE); reloc->pair_address = scatsrc->r_address; if (src->r_extern) { reloc->pair_target = src->r_symbolnum; reloc->pair_target_type = KXLD_TARGET_SYMBOLNUM; } else { reloc->pair_target = src->r_address; reloc->pair_target_type = KXLD_TARGET_VALUE; } } } else { reloc->pair_target = 0; if (relocator->reloc_has_got(reloc->reloc_type)) { reloc->pair_target_type = KXLD_TARGET_GOT; } else { reloc->pair_target_type = KXLD_TARGET_NONE; } } } } rval = KERN_SUCCESS; finish: return rval; } /******************************************************************************* * Relocatable relocs : * 1) Are not _PAIR_ relocs * 2) Don't reference N_ABS symbols *******************************************************************************/ static u_int count_relocatable_relocs(const KXLDRelocator *relocator, const struct relocation_info *relocs, u_int nrelocs) { u_int num_nonpair_relocs = 0; u_int i = 0; const struct relocation_info *reloc = NULL; const struct scattered_relocation_info *sreloc = NULL; check(relocator); check(relocs); /* Loop over all of the relocation entries */ num_nonpair_relocs = 1; for (i = 1; i < nrelocs; ++i) { reloc = relocs + i; if (reloc->r_address & R_SCATTERED) { /* A scattered relocation entry is relocatable as long as it's not a * pair. */ sreloc = (const struct scattered_relocation_info *) reloc; num_nonpair_relocs += !relocator->reloc_has_pair(sreloc->r_type); } else { /* A normal relocation entry is relocatable if it is not a pair and * if it is not a section-based relocation for an absolute symbol. */ num_nonpair_relocs += !(relocator->reloc_has_pair(reloc->r_type) || (0 == reloc->r_extern && R_ABS == reloc->r_symbolnum)); } } return num_nonpair_relocs; } /******************************************************************************* *******************************************************************************/ void kxld_relocator_clear(KXLDRelocator *relocator) { bzero(relocator, sizeof(*relocator)); } /******************************************************************************* *******************************************************************************/ boolean_t kxld_relocator_has_pair(const KXLDRelocator *relocator, u_int r_type) { check(relocator); return relocator->reloc_has_pair(r_type); } /******************************************************************************* *******************************************************************************/ u_int kxld_relocator_get_pair_type(const KXLDRelocator *relocator, u_int prev_r_type) { check(relocator); return relocator->reloc_get_pair_type(prev_r_type); } /******************************************************************************* *******************************************************************************/ boolean_t kxld_relocator_has_got(const KXLDRelocator *relocator, u_int r_type) { check(relocator); return relocator->reloc_has_got(r_type); } /******************************************************************************* *******************************************************************************/ KXLDSym * kxld_reloc_get_symbol(const KXLDRelocator *relocator, const KXLDReloc *reloc, const u_char *data) { KXLDSym *sym = NULL; kxld_addr_t value = 0; check(reloc); switch (reloc->target_type) { case KXLD_TARGET_SYMBOLNUM: sym = kxld_symtab_get_symbol_by_index(relocator->symtab, reloc->target); break; case KXLD_TARGET_SECTNUM: if (data) { value = kxld_relocator_get_pointer_at_addr(relocator, data, reloc->address); sym = kxld_symtab_get_cxx_symbol_by_value(relocator->symtab, value); } break; default: sym = NULL; break; } return sym; } /******************************************************************************* *******************************************************************************/ kern_return_t kxld_reloc_get_reloc_index_by_offset(const KXLDArray *relocs, kxld_size_t offset, u_int *idx) { kern_return_t rval = KERN_FAILURE; KXLDReloc *reloc = NULL; u_int i = 0; for (i = 0; i < relocs->nitems; ++i) { reloc = kxld_array_get_item(relocs, i); if (reloc->address == offset) break; } if (i >= relocs->nitems) { rval = KERN_FAILURE; goto finish; } *idx = i; rval = KERN_SUCCESS; finish: return rval; } /******************************************************************************* *******************************************************************************/ KXLDReloc * kxld_reloc_get_reloc_by_offset(const KXLDArray *relocs, kxld_addr_t offset) { kern_return_t rval = KERN_FAILURE; KXLDReloc *reloc = NULL; u_int i = 0; rval = kxld_reloc_get_reloc_index_by_offset(relocs, offset, &i); if (rval) goto finish; reloc = kxld_array_get_item(relocs, i); finish: return reloc; } #if KXLD_PIC_KEXTS /******************************************************************************* *******************************************************************************/ u_long kxld_reloc_get_macho_header_size() { return sizeof(struct dysymtab_command); } /******************************************************************************* *******************************************************************************/ u_long kxld_reloc_get_macho_data_size(const KXLDArray *locrelocs, const KXLDArray *extrelocs) { u_long rval = 0; rval += get_macho_data_size_for_array(locrelocs); rval += get_macho_data_size_for_array(extrelocs); return (rval); } /******************************************************************************* *******************************************************************************/ kern_return_t kxld_reloc_export_macho(const KXLDRelocator *relocator, const KXLDArray *locrelocs, const KXLDArray *extrelocs, u_char *buf, u_long *header_offset, u_long header_size, u_long *data_offset, u_long size) { kern_return_t rval = KERN_FAILURE; struct dysymtab_command *dysymtabhdr = NULL; struct relocation_info *start = NULL; struct relocation_info *dst = NULL; u_long count = 0; u_long data_size = 0; check(locrelocs); check(extrelocs); check(buf); check(header_offset); check(data_offset); require_action(sizeof(*dysymtabhdr) <= header_size - *header_offset, finish, rval=KERN_FAILURE); dysymtabhdr = (struct dysymtab_command *) ((void *) (buf + *header_offset)); *header_offset += sizeof(*dysymtabhdr); data_size = kxld_reloc_get_macho_data_size(locrelocs, extrelocs); require_action((*data_offset + data_size) <= size, finish, rval=KERN_FAILURE); start = dst = (struct relocation_info *) ((void *) (buf + *data_offset)); rval = export_macho_for_array(relocator, locrelocs, &dst); require_noerr(rval, finish); rval = export_macho_for_array(relocator, extrelocs, &dst); require_noerr(rval, finish); count = dst - start; memset(dysymtabhdr, 0, sizeof(*dysymtabhdr)); dysymtabhdr->cmd = LC_DYSYMTAB; dysymtabhdr->cmdsize = (uint32_t) sizeof(*dysymtabhdr); dysymtabhdr->locreloff = (uint32_t) *data_offset; dysymtabhdr->nlocrel = (uint32_t) count; *data_offset += count * sizeof(struct relocation_info); rval = KERN_SUCCESS; finish: return rval; } #endif /* KXLD_PIC_KEXTS */ /******************************************************************************* *******************************************************************************/ kxld_addr_t kxld_relocator_get_pointer_at_addr(const KXLDRelocator *relocator, const u_char *data, u_long offset) { kxld_addr_t value; KXLD_3264_FUNC(relocator->is_32_bit, value, get_pointer_at_addr_32, get_pointer_at_addr_64, relocator, data, offset); return value; } #if KXLD_USER_OR_ILP32 /******************************************************************************* *******************************************************************************/ static kxld_addr_t get_pointer_at_addr_32(const KXLDRelocator *relocator, const u_char *data, u_long offset) { uint32_t addr = 0; check(relocator); addr = *(const uint32_t *) ((void *) (data + offset)); #if !KERNEL if (relocator->swap) { addr = OSSwapInt32(addr); } #endif return align_raw_function_address(relocator, addr); } #endif /* KXLD_USER_OR_ILP32 */ #if KXLD_USER_OR_LP64 /******************************************************************************* *******************************************************************************/ static kxld_addr_t get_pointer_at_addr_64(const KXLDRelocator *relocator, const u_char *data, u_long offset) { uint64_t addr = 0; check(relocator); addr = *(const uint64_t *) ((void *) (data + offset)); #if !KERNEL if (relocator->swap) { addr = OSSwapInt64(addr); } #endif return align_raw_function_address(relocator, addr); } #endif /* KXLD_USER_OR_LP64 */ /******************************************************************************* *******************************************************************************/ void kxld_relocator_set_vtables(KXLDRelocator *relocator, const KXLDDict *vtables) { relocator->vtables = vtables; } /******************************************************************************* * When we're inspecting the raw binary and not the symbol table, value may * hold a THUMB address (with bit 0 set to 1) but the index will have the real * address (bit 0 set to 0). So if bit 0 is set here, we clear it. This only * impacts ARM for now, but it's implemented as a generic function alignment * mask. *******************************************************************************/ static kxld_addr_t align_raw_function_address(const KXLDRelocator *relocator, kxld_addr_t value) { if (relocator->function_align) { value &= ~((1ULL << relocator->function_align) - 1); } return value; } /******************************************************************************* *******************************************************************************/ kern_return_t kxld_relocator_process_sect_reloc(KXLDRelocator *relocator, const KXLDReloc *reloc, const KXLDSect *sect) { kern_return_t rval = KERN_FAILURE; u_char *instruction = NULL; kxld_addr_t target = 0; kxld_addr_t pair_target = 0; kxld_addr_t base_pc = 0; kxld_addr_t link_pc = 0; kxld_addr_t link_disp = 0; check(relocator); check(reloc); check(sect); /* Find the instruction */ instruction = sect->data + reloc->address; /* Calculate the target */ rval = calculate_targets(relocator, &target, &pair_target, reloc); require_noerr(rval, finish); base_pc = reloc->address; link_pc = base_pc + sect->link_addr; link_disp = sect->link_addr - sect->base_addr; /* Relocate */ rval = relocator->process_reloc(relocator, instruction, reloc->length, reloc->pcrel, base_pc, link_pc, link_disp, reloc->reloc_type, target, pair_target, relocator->swap); require_noerr(rval, finish); /* Return */ relocator->current_vtable = NULL; rval = KERN_SUCCESS; finish: return rval; } /******************************************************************************* *******************************************************************************/ kern_return_t kxld_reloc_update_symindex(KXLDReloc *reloc, u_int symindex) { kern_return_t rval = KERN_FAILURE; require_action(reloc->target_type == KXLD_TARGET_SYMBOLNUM, finish, rval = KERN_FAILURE); reloc->target = symindex; rval = KERN_SUCCESS; finish: return rval; } /******************************************************************************* *******************************************************************************/ kern_return_t kxld_relocator_process_table_reloc(KXLDRelocator *relocator, const KXLDReloc *reloc, const KXLDSeg *seg, kxld_addr_t link_addr) { kern_return_t rval = KERN_FAILURE; u_char *instruction = NULL; kxld_addr_t target = 0; kxld_addr_t pair_target = 0; kxld_addr_t base_pc = 0; kxld_addr_t link_pc = 0; u_long offset = 0; check(relocator); check(reloc); /* Find the instruction */ offset = (u_long)(seg->fileoff + (reloc->address - seg->base_addr)); instruction = relocator->file + offset; /* Calculate the target */ rval = calculate_targets(relocator, &target, &pair_target, reloc); require_noerr(rval, finish); base_pc = reloc->address; link_pc = base_pc + link_addr; /* Relocate */ rval = relocator->process_reloc(relocator, instruction, reloc->length, reloc->pcrel, base_pc, link_pc, link_addr, reloc->reloc_type, target, pair_target, relocator->swap); require_noerr(rval, finish); /* Return */ relocator->current_vtable = NULL; rval = KERN_SUCCESS; finish: return rval; } /******************************************************************************* *******************************************************************************/ static kern_return_t calculate_targets(KXLDRelocator *relocator, kxld_addr_t *_target, kxld_addr_t *_pair_target, const KXLDReloc *reloc) { kern_return_t rval = KERN_FAILURE; const KXLDSect *sect = NULL; const KXLDSym *sym = NULL; kxld_addr_t target = 0; kxld_addr_t pair_target = 0; char *demangled_name = NULL; size_t demangled_length = 0; check(_target); check(_pair_target); *_target = 0; *_pair_target = 0; /* Find the target based on the lookup type */ switch(reloc->target_type) { case KXLD_TARGET_LOOKUP: require_action(reloc->pair_target_type == KXLD_TARGET_NONE || reloc->pair_target_type == KXLD_TARGET_LOOKUP || reloc->pair_target_type == KXLD_TARGET_VALUE, finish, rval=KERN_FAILURE); rval = get_target_by_address_lookup(&target, reloc->target, relocator->sectarray); require_noerr(rval, finish); if (reloc->pair_target_type == KXLD_TARGET_LOOKUP) { rval = get_target_by_address_lookup(&pair_target, reloc->pair_target, relocator->sectarray); require_noerr(rval, finish); } else if (reloc->pair_target_type == KXLD_TARGET_VALUE) { pair_target = reloc->pair_target; } break; case KXLD_TARGET_SECTNUM: require_action(reloc->pair_target_type == KXLD_TARGET_NONE || reloc->pair_target_type == KXLD_TARGET_VALUE, finish, rval=KERN_FAILURE); /* Get the target's section by section number */ sect = kxld_array_get_item(relocator->sectarray, reloc->target); require_action(sect, finish, rval=KERN_FAILURE); /* target is the change in the section's address */ target = sect->link_addr - sect->base_addr; if (reloc->pair_target_type) { pair_target = reloc->pair_target; } else { /* x86_64 needs to know when we have a non-external relocation, * so we hack that information in here. */ pair_target = TRUE; } break; case KXLD_TARGET_SYMBOLNUM: require_action(reloc->pair_target_type == KXLD_TARGET_NONE || reloc->pair_target_type == KXLD_TARGET_GOT || reloc->pair_target_type == KXLD_TARGET_SYMBOLNUM || reloc->pair_target_type == KXLD_TARGET_VALUE, finish, rval=KERN_FAILURE); /* Get the target's symbol by symbol number */ sym = kxld_symtab_get_symbol_by_index(relocator->symtab, reloc->target); require_action(sym, finish, rval=KERN_FAILURE); /* If this symbol is a padslot that has already been replaced, then the * only way a relocation entry can still reference it is if there is a * vtable that has not been patched. The vtable patcher uses the * MetaClass structure to find classes for patching, so an unpatched * vtable means that there is an OSObject-dervied class that is missing * its OSDeclare/OSDefine macros. */ require_action(!kxld_sym_is_padslot(sym) || !kxld_sym_is_replaced(sym), finish, rval=KERN_FAILURE; kxld_log(kKxldLogLinking, kKxldLogErr, kKxldLogRelocatingPatchedSym, kxld_demangle(sym->name, &demangled_name, &demangled_length))); target = sym->link_addr; if (kxld_sym_is_vtable(sym)) { relocator->current_vtable = kxld_dict_find(relocator->vtables, sym->name); } /* Some relocation types need the GOT entry address instead of the * symbol's actual address. These types don't have pair relocation * entries, so we store the GOT entry address as the pair target. */ if (reloc->pair_target_type == KXLD_TARGET_VALUE) { pair_target = reloc->pair_target; } else if (reloc->pair_target_type == KXLD_TARGET_SYMBOLNUM ) { sym = kxld_symtab_get_symbol_by_index(relocator->symtab, reloc->pair_target); require_action(sym, finish, rval=KERN_FAILURE); pair_target = sym->link_addr; } else if (reloc->pair_target_type == KXLD_TARGET_GOT) { pair_target = sym->got_addr; } break; default: rval = KERN_FAILURE; goto finish; } *_target = target; *_pair_target = pair_target; rval = KERN_SUCCESS; finish: if (demangled_name) kxld_free(demangled_name, demangled_length); return rval; } /******************************************************************************* *******************************************************************************/ static kern_return_t get_target_by_address_lookup(kxld_addr_t *target, kxld_addr_t addr, const KXLDArray *sectarray) { kern_return_t rval = KERN_FAILURE; const KXLDSect *sect = NULL; kxld_addr_t start = 0; kxld_addr_t end = 0; u_int i = 0; check(target); check(sectarray); *target = 0; for (i = 0; i < sectarray->nitems; ++i) { sect = kxld_array_get_item(sectarray, i); start = sect->base_addr; end = start + sect->size; if (start <= addr && addr < end) break; sect = NULL; } require_action(sect, finish, rval=KERN_FAILURE); *target = sect->link_addr - sect->base_addr; rval = KERN_SUCCESS; finish: return rval; } /******************************************************************************* *******************************************************************************/ static kern_return_t check_for_direct_pure_virtual_call(const KXLDRelocator *relocator, u_long offset) { kern_return_t rval = KERN_FAILURE; const KXLDVTableEntry *entry = NULL; if (relocator->current_vtable) { entry = kxld_vtable_get_entry_for_offset(relocator->current_vtable, offset, relocator->is_32_bit); require_action(!entry || !entry->patched.name || !kxld_sym_name_is_pure_virtual(entry->patched.name), finish, rval=KERN_FAILURE; kxld_log(kKxldLogLinking, kKxldLogErr, kKxldLogDirectPureVirtualCall)); } rval = KERN_SUCCESS; finish: return rval; } #if KXLD_PIC_KEXTS /******************************************************************************* *******************************************************************************/ static u_long get_macho_data_size_for_array(const KXLDArray *relocs) { const KXLDReloc *reloc = NULL; u_int i = 0; u_long size = 0; check(relocs); for (i = 0; i < relocs->nitems; ++i) { reloc = kxld_array_get_item(relocs, i); if (!reloc->pcrel) { size += sizeof(struct relocation_info); if(reloc->pair_target_type != KXLD_TARGET_NONE) { size += sizeof(struct relocation_info); } } } return size; } /******************************************************************************* *******************************************************************************/ static kern_return_t export_macho_for_array(const KXLDRelocator *relocator, const KXLDArray *relocs, struct relocation_info **dstp) { kern_return_t rval = KERN_FAILURE; const KXLDReloc *reloc = NULL; struct relocation_info *dst = NULL; struct scattered_relocation_info *scatdst = NULL; u_int i = 0; dst = *dstp; for (i = 0; i < relocs->nitems; ++i) { reloc = kxld_array_get_item(relocs, i); scatdst = (struct scattered_relocation_info *) dst; if (reloc->pcrel) { continue; } switch (reloc->target_type) { case KXLD_TARGET_LOOKUP: scatdst->r_address = reloc->address; scatdst->r_pcrel = reloc->pcrel; scatdst->r_length = reloc->length; scatdst->r_type = reloc->reloc_type; scatdst->r_value = reloc->target; scatdst->r_scattered = 1; break; case KXLD_TARGET_SECTNUM: dst->r_address = reloc->address; dst->r_pcrel = reloc->pcrel; dst->r_length = reloc->length; dst->r_type = reloc->reloc_type; dst->r_symbolnum = reloc->target + 1; dst->r_extern = 0; break; case KXLD_TARGET_SYMBOLNUM: /* Assume that everything will be slid together; otherwise, * there is no sensible value for the section number. */ dst->r_address = reloc->address; dst->r_pcrel = reloc->pcrel; dst->r_length = reloc->length; dst->r_type = reloc->reloc_type; dst->r_symbolnum = 1; dst->r_extern = 0; break; default: rval = KERN_FAILURE; goto finish; } ++dst; if(reloc->pair_target_type != KXLD_TARGET_NONE) { ++i; require_action(i < relocs->nitems, finish, rval=KERN_FAILURE); scatdst = (struct scattered_relocation_info *) dst; switch (reloc->pair_target_type) { case KXLD_TARGET_LOOKUP: scatdst->r_address = reloc->pair_address; scatdst->r_pcrel = reloc->pcrel; scatdst->r_length = reloc->length; scatdst->r_type = relocator->reloc_get_pair_type(reloc->reloc_type); scatdst->r_value = reloc->pair_target; scatdst->r_scattered = 1; break; case KXLD_TARGET_SECTNUM: dst->r_address = reloc->pair_address; dst->r_pcrel = reloc->pcrel; dst->r_length = reloc->length; dst->r_type = relocator->reloc_get_pair_type(reloc->reloc_type); dst->r_symbolnum = reloc->pair_target + 1; dst->r_extern = 0; break; case KXLD_TARGET_SYMBOLNUM: dst->r_address = reloc->pair_address; dst->r_pcrel = reloc->pcrel; dst->r_length = reloc->length; dst->r_type = relocator->reloc_get_pair_type(reloc->reloc_type); dst->r_symbolnum = 1; dst->r_extern = 0; break; default: rval = KERN_FAILURE; goto finish; } ++dst; } } rval = KERN_SUCCESS; finish: *dstp = dst; return rval; } #endif /* KXLD_PIC_KEXTS */ #if KXLD_USER_OR_I386 /******************************************************************************* *******************************************************************************/ static boolean_t generic_reloc_has_pair(u_int _type) { enum reloc_type_generic type = _type; return (type == GENERIC_RELOC_SECTDIFF || type == GENERIC_RELOC_LOCAL_SECTDIFF); } /******************************************************************************* *******************************************************************************/ static u_int generic_reloc_get_pair_type(u_int _prev_type __unused) { return GENERIC_RELOC_PAIR; } /******************************************************************************* *******************************************************************************/ static boolean_t generic_reloc_has_got(u_int _type __unused) { return FALSE; } /******************************************************************************* *******************************************************************************/ static kern_return_t generic_process_reloc(const KXLDRelocator *relocator, u_char *instruction, u_int length, u_int pcrel, kxld_addr_t _base_pc, kxld_addr_t _link_pc, kxld_addr_t _link_disp __unused, u_int _type, kxld_addr_t _target, kxld_addr_t _pair_target, boolean_t swap __unused) { kern_return_t rval = KERN_FAILURE; uint32_t base_pc = (uint32_t) _base_pc; uint32_t link_pc = (uint32_t) _link_pc; uint32_t *instr_addr = NULL; uint32_t instr_data = 0; uint32_t target = (uint32_t) _target; uint32_t pair_target = (uint32_t) _pair_target; enum reloc_type_generic type = _type; check(instruction); require_action(length == 2, finish, rval=KERN_FAILURE); if (pcrel) target = target + base_pc - link_pc; instr_addr = (uint32_t *) ((void *) instruction); instr_data = *instr_addr; #if !KERNEL if (swap) instr_data = OSSwapInt32(instr_data); #endif rval = check_for_direct_pure_virtual_call(relocator, instr_data); require_noerr(rval, finish); switch (type) { case GENERIC_RELOC_VANILLA: instr_data += target; break; case GENERIC_RELOC_SECTDIFF: case GENERIC_RELOC_LOCAL_SECTDIFF: instr_data = instr_data + target - pair_target; break; case GENERIC_RELOC_PB_LA_PTR: rval = KERN_FAILURE; goto finish; case GENERIC_RELOC_PAIR: default: rval = KERN_FAILURE; goto finish; } #if !KERNEL if (swap) instr_data = OSSwapInt32(instr_data); #endif *instr_addr = instr_data; rval = KERN_SUCCESS; finish: return rval; } #endif /* KXLD_USER_OR_I386 */ #if KXLD_USER_OR_X86_64 /******************************************************************************* *******************************************************************************/ static boolean_t x86_64_reloc_has_pair(u_int _type) { enum reloc_type_x86_64 type = _type; return (type == X86_64_RELOC_SUBTRACTOR); } /******************************************************************************* *******************************************************************************/ static u_int x86_64_reloc_get_pair_type(u_int _prev_type __unused) { return X86_64_RELOC_UNSIGNED; } /******************************************************************************* *******************************************************************************/ static boolean_t x86_64_reloc_has_got(u_int _type) { enum reloc_type_x86_64 type = _type; return (type == X86_64_RELOC_GOT_LOAD || type == X86_64_RELOC_GOT); } /******************************************************************************* *******************************************************************************/ static kern_return_t x86_64_process_reloc(const KXLDRelocator *relocator __unused, u_char *instruction, u_int length, u_int pcrel, kxld_addr_t _base_pc __unused, kxld_addr_t _link_pc, kxld_addr_t _link_disp, u_int _type, kxld_addr_t _target, kxld_addr_t _pair_target, boolean_t swap __unused) { kern_return_t rval = KERN_FAILURE; enum reloc_type_x86_64 type = _type; int32_t *instr32p = NULL; int32_t instr32 = 0; uint64_t *instr64p = NULL; uint64_t instr64 = 0; uint64_t target = _target; uint64_t pair_target = _pair_target; uint64_t link_pc = (uint64_t) _link_pc; uint64_t link_disp = (uint64_t) _link_disp; uint64_t adjustment = 0; check(instruction); require_action(length == 2 || length == 3, finish, rval=KERN_FAILURE); if (length == 2) { instr32p = (int32_t *) ((void *) instruction); instr32 = *instr32p; #if !KERNEL if (swap) instr32 = OSSwapInt32(instr32); #endif rval = check_for_direct_pure_virtual_call(relocator, instr32); require_noerr(rval, finish); /* There are a number of different small adjustments for pc-relative * relocation entries. The general case is to subtract the size of the * relocation (represented by the length parameter), and it applies to * the GOT types and external SIGNED types. The non-external signed types * have a different adjustment corresponding to the specific type. */ switch (type) { case X86_64_RELOC_SIGNED: if (pair_target) { adjustment = 0; break; } /* Fall through */ case X86_64_RELOC_SIGNED_1: if (pair_target) { adjustment = 1; break; } /* Fall through */ case X86_64_RELOC_SIGNED_2: if (pair_target) { adjustment = 2; break; } /* Fall through */ case X86_64_RELOC_SIGNED_4: if (pair_target) { adjustment = 4; break; } /* Fall through */ case X86_64_RELOC_BRANCH: case X86_64_RELOC_GOT: case X86_64_RELOC_GOT_LOAD: adjustment = (1 << length); break; default: break; } /* Perform the actual relocation. All of the 32-bit relocations are * pc-relative except for SUBTRACTOR, so a good chunk of the logic is * stuck in calculate_displacement_x86_64. The signed relocations are * a special case, because when they are non-external, the instruction * already contains the pre-relocation displacement, so we only need to * find the difference between how far the PC was relocated, and how * far the target is relocated. Since the target variable already * contains the difference between the target's base and link * addresses, we add the difference between the PC's base and link * addresses to the adjustment variable. This will yield the * appropriate displacement in calculate_displacement. */ switch (type) { case X86_64_RELOC_BRANCH: require_action(pcrel, finish, rval=KERN_FAILURE); adjustment += link_pc; break; case X86_64_RELOC_SIGNED: case X86_64_RELOC_SIGNED_1: case X86_64_RELOC_SIGNED_2: case X86_64_RELOC_SIGNED_4: require_action(pcrel, finish, rval=KERN_FAILURE); adjustment += (pair_target) ? (link_disp) : (link_pc); break; case X86_64_RELOC_GOT: case X86_64_RELOC_GOT_LOAD: require_action(pcrel, finish, rval=KERN_FAILURE); adjustment += link_pc; target = pair_target; break; case X86_64_RELOC_SUBTRACTOR: require_action(!pcrel, finish, rval=KERN_FAILURE); instr32 = (int32_t) (target - pair_target); break; case X86_64_RELOC_UNSIGNED: default: rval = KERN_FAILURE; goto finish; } /* Call calculate_displacement for the pc-relative relocations */ if (pcrel) { rval = calculate_displacement_x86_64(target, adjustment, &instr32); require_noerr(rval, finish); } #if !KERNEL if (swap) instr32 = OSSwapInt32(instr32); #endif *instr32p = instr32; } else { instr64p = (uint64_t *) ((void *) instruction); instr64 = *instr64p; #if !KERNEL if (swap) instr64 = OSSwapInt64(instr64); #endif rval = check_for_direct_pure_virtual_call(relocator, (u_long) instr64); require_noerr(rval, finish); switch (type) { case X86_64_RELOC_UNSIGNED: require_action(!pcrel, finish, rval=KERN_FAILURE); instr64 += target; break; case X86_64_RELOC_SUBTRACTOR: require_action(!pcrel, finish, rval=KERN_FAILURE); instr64 = target - pair_target; break; case X86_64_RELOC_SIGNED_1: case X86_64_RELOC_SIGNED_2: case X86_64_RELOC_SIGNED_4: case X86_64_RELOC_GOT_LOAD: case X86_64_RELOC_BRANCH: case X86_64_RELOC_SIGNED: case X86_64_RELOC_GOT: default: rval = KERN_FAILURE; goto finish; } #if !KERNEL if (swap) instr64 = OSSwapInt64(instr64); #endif *instr64p = instr64; } rval = KERN_SUCCESS; finish: return rval; } /******************************************************************************* *******************************************************************************/ static kern_return_t calculate_displacement_x86_64(uint64_t target, uint64_t adjustment, int32_t *instr32) { kern_return_t rval = KERN_FAILURE; int64_t displacement; uint64_t difference; displacement = *instr32 + target - adjustment; difference = ABSOLUTE_VALUE(displacement); require_action(difference < X86_64_RIP_RELATIVE_LIMIT, finish, rval=KERN_FAILURE; kxld_log(kKxldLogLinking, kKxldLogErr, kKxldLogRelocationOverflow)); *instr32 = (int32_t) displacement; rval = KERN_SUCCESS; finish: return rval; } #endif /* KXLD_USER_OR_X86_64 */ #if KXLD_USER_OR_ARM /******************************************************************************* *******************************************************************************/ static boolean_t arm_reloc_has_pair(u_int _type) { enum reloc_type_arm type = _type; switch(type) { case ARM_RELOC_SECTDIFF: return TRUE; default: return FALSE; } return FALSE; } /******************************************************************************* *******************************************************************************/ static u_int arm_reloc_get_pair_type(u_int _prev_type __unused) { return ARM_RELOC_PAIR; } /******************************************************************************* *******************************************************************************/ static boolean_t arm_reloc_has_got(u_int _type __unused) { return FALSE; } /******************************************************************************* *******************************************************************************/ static kern_return_t arm_process_reloc(const KXLDRelocator *relocator __unused, u_char *instruction, u_int length, u_int pcrel, kxld_addr_t _base_pc __unused, kxld_addr_t _link_pc __unused, kxld_addr_t _link_disp __unused, u_int _type __unused, kxld_addr_t _target __unused, kxld_addr_t _pair_target __unused, boolean_t swap __unused) { kern_return_t rval = KERN_FAILURE; uint32_t *instr_addr = NULL; uint32_t instr_data = 0; uint32_t base_pc = (uint32_t) _base_pc; uint32_t link_pc = (uint32_t) _link_pc; uint32_t target = (uint32_t) _target; int32_t displacement = 0; enum reloc_type_arm type = _type; check(instruction); require_action(length == 2, finish, rval=KERN_FAILURE); if (pcrel) displacement = target + base_pc - link_pc; instr_addr = (uint32_t *) ((void *) instruction); instr_data = *instr_addr; #if !KERNEL if (swap) instr_data = OSSwapInt32(instr_data); #endif rval = check_for_direct_pure_virtual_call(relocator, instr_data); require_noerr(rval, finish); switch (type) { case ARM_RELOC_VANILLA: instr_data += target; break; /* * If the displacement is 0 (the offset between the pc and the target has * not changed), then we don't need to do anything for BR24 and BR22 * relocs. As it turns out, because kexts build with -mlong-calls all * relocations currently end up being either vanilla (handled above) or * BR22/BR24 with a displacement of 0. * We could handle other displacements here but to keep things simple, we * won't until it is needed (at which point the kernelcache will fail to * link) */ case ARM_RELOC_BR24: require_action(pcrel, finish, rval=KERN_FAILURE); require_action(displacement == 0, finish, rval=KERN_FAILURE); break; case ARM_THUMB_RELOC_BR22: require_action(pcrel, finish, rval=KERN_FAILURE); require_action(displacement == 0, finish, rval=KERN_FAILURE); break; case ARM_RELOC_SECTDIFF: case ARM_RELOC_LOCAL_SECTDIFF: case ARM_RELOC_PB_LA_PTR: rval = KERN_FAILURE; goto finish; case ARM_RELOC_PAIR: default: rval = KERN_FAILURE; goto finish; } #if !KERNEL if (swap) instr_data = OSSwapInt32(instr_data); #endif *instr_addr = instr_data; rval = KERN_SUCCESS; finish: return rval; } #endif /* KXLD_USER_OR_ARM */ #if KXLD_USER_OR_ARM64 /******************************************************************************* *******************************************************************************/ boolean_t arm64_reloc_has_pair(u_int _type) { return (_type == ARM64_RELOC_SUBTRACTOR); } /******************************************************************************* *******************************************************************************/ u_int arm64_reloc_get_pair_type(u_int _prev_type __unused) { if (_prev_type == ARM64_RELOC_SUBTRACTOR) { return ARM64_RELOC_UNSIGNED; } else { return -1u; } } /******************************************************************************* *******************************************************************************/ boolean_t arm64_reloc_has_got(u_int _type) { return (_type == ARM64_RELOC_GOT_LOAD_PAGE21 || _type == ARM64_RELOC_GOT_LOAD_PAGEOFF12); } /******************************************************************************* *******************************************************************************/ kern_return_t arm64_process_reloc(const KXLDRelocator *relocator __unused, u_char *instruction, u_int length, u_int pcrel, kxld_addr_t _base_pc __unused, kxld_addr_t _link_pc, kxld_addr_t _link_disp __unused, u_int _type, kxld_addr_t _target, kxld_addr_t _pair_target __unused, boolean_t swap) { kern_return_t rval = KERN_FAILURE; enum reloc_type_arm64 type = _type; uint64_t target = _target; uint64_t link_pc = (uint64_t) _link_pc; uint64_t difference = 0; int64_t displacement = 0; uint32_t addend = 0; check(instruction); require_action((length == 2 || length == 3), finish, rval=KERN_FAILURE); if (length == 2) { uint32_t *instr32p = (uint32_t *) (void *) instruction; uint32_t instr32 = *instr32p; #if !KERNEL if (swap) instr32 = OSSwapInt32(instr32); #endif switch (type) { case ARM64_RELOC_BRANCH26: require_action(pcrel, finish, rval=KERN_FAILURE); addend = (instr32 & 0x03FFFFFF) << 2; addend = SIGN_EXTEND(addend, 27); displacement = (target - link_pc + addend); difference = ABSOLUTE_VALUE(displacement); displacement = (displacement >> 2); require_action(difference < (128 * 1024 * 1024), finish, rval = KERN_FAILURE; kxld_log(kKxldLogLinking, kKxldLogErr, kKxldLogRelocationOverflow)); instr32 = (instr32 & 0xFC000000) | (displacement & 0x03FFFFFF); break; default: rval = KERN_FAILURE; goto finish; } #if !KERNEL if (swap) instr32 = OSSwapInt32(instr32); #endif *instr32p = instr32; } else { /* length == 3 */ uint64_t *instr64p = (uint64_t *) (void *) instruction; uint64_t instr64 = *instr64p; #if !KERNEL if (swap) instr64 = OSSwapInt64(instr64); #endif switch (type) { case ARM64_RELOC_UNSIGNED: require_action(!pcrel, finish, rval=KERN_FAILURE); instr64 += target; break; default: rval = KERN_FAILURE; goto finish; } #if !KERNEL if (swap) instr64 = OSSwapInt64(instr64); #endif *instr64p = instr64; } rval = KERN_SUCCESS; finish: return rval; } #endif /* KXLD_USER_OR_ARM64 */