/* * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * @APPLE_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. 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_LICENSE_HEADER_END@ */ #ifdef SHLIB #include "shlib.h" #endif /* SHLIB */ /* * This file contains the routines to do relocation for the arm. */ #include #if !(defined(KLD) && defined(__STATIC__)) #include #include #else /* defined(KLD) && defined(__STATIC__) */ #include #endif /* !(defined(KLD) && defined(__STATIC__)) */ #include #include #include #include #include #include #include "stuff/bool.h" #include "stuff/bytesex.h" #include "ld.h" #include "live_refs.h" #include "objects.h" #include "sections.h" #include "pass1.h" #include "symbols.h" #include "pass2.h" #include "generic_reloc.h" #include "arm_reloc.h" #include "indirect_sections.h" #include "dylibs.h" #define U_ABS(l) (((long)(l))<0 ? (unsigned long)(-(l)) : (l)) /* * arm_reloc() relocates the contents of the specified section for the * relocation entries using the section map from the current object (cur_obj). * * Or if refs is not NULL it is being called by to get the addresses or * merged_symbols from the item being referenced by the relocation entry(s) at * reloc_index. This is used by mark_fine_relocs_references_live() when * -dead_strip is specified to determined what is being referenced and is only * called when all sections have fine_relocs (that is why refs is only filled * in when nfine_relocs != 0). When refs is not NULL, only refs is filled in * and returned and the contents are not relocated. */ __private_extern__ void arm_reloc( char *contents, struct relocation_info *relocs, struct section_map *section_map, struct live_refs *refs, unsigned long reloc_index) { unsigned long i, j, symbolnum, value, input_pc, output_pc; unsigned long instruction, immediate, low_bit; struct nlist *nlists; char *strings; enum bool force_extern_reloc, relocated_extern_thumb_symbol; enum bool relocated_extern_arm_symbol; struct undefined_map *undefined_map; struct merged_symbol *merged_symbol; struct section_map *local_map, *pair_local_map; struct relocation_info *reloc, *pair_reloc; struct scattered_relocation_info *sreloc, *spair_reloc; unsigned long r_address, r_symbolnum, r_pcrel, r_length, r_extern, r_scattered, r_value, pair_r_symbolnum, pair_r_value; enum reloc_type_arm r_type, pair_r_type; unsigned long other_half; unsigned long offset; unsigned long br14_disp_sign; #if defined(DEBUG) || defined(RLD) /* * The compiler "warnings: ... may be used uninitialized in this * function" can safely be ignored */ merged_symbol = NULL; local_map = NULL; instruction = 0; other_half = 0; immediate = 0; offset = 0; pair_r_symbolnum = 0; pair_r_value = 0; pair_local_map = NULL; #endif /* defined(DEBUG) || defined(RLD) */ if(refs != NULL) memset(refs, '\0', sizeof(struct live_refs)); else reloc_index = 0; for(i = reloc_index; i < section_map->s->nreloc; i++){ br14_disp_sign = 0; force_extern_reloc = FALSE; relocated_extern_thumb_symbol = FALSE; relocated_extern_arm_symbol = FALSE; /* * Break out the fields of the relocation entry and set pointer to * the type of relocation entry it is (for updating later). */ if((relocs[i].r_address & R_SCATTERED) != 0){ sreloc = (struct scattered_relocation_info *)(relocs + i); reloc = NULL; r_scattered = 1; r_address = sreloc->r_address; r_pcrel = sreloc->r_pcrel; r_length = sreloc->r_length; r_value = sreloc->r_value; r_type = (enum reloc_type_arm)sreloc->r_type; r_extern = 0; /* * Since the r_value field is reserved in a ARM_RELOC_PAIR * type to report the correct error a check for a stray * ARM_RELOC_PAIR relocation types needs to be done before * it is assumed that r_value is legal. A ARM_RELOC_PAIR * only follows ARM_RELOC_{SECTDIFF,LOCAL_SECTDIFF} relocation * types and it is an error to see one otherwise. */ if(r_type == ARM_RELOC_PAIR){ error_with_cur_obj("stray relocation ARM_RELOC_PAIR entry " "(%lu) in section (%.16s,%.16s)", i, section_map->s->segname, section_map->s->sectname); continue; } /* calculate the r_symbolnum (n_sect) from the r_value */ r_symbolnum = 0; for(j = 0; j < cur_obj->nsection_maps; j++){ if(r_value >= cur_obj->section_maps[j].s->addr && r_value < cur_obj->section_maps[j].s->addr + cur_obj->section_maps[j].s->size){ r_symbolnum = j + 1; break; } } if(r_symbolnum == 0){ /* * The edge case where the last address past then end of * of the last section is referenced. */ for(j = 0; j < cur_obj->nsection_maps; j++){ if(r_value == cur_obj->section_maps[j].s->addr + cur_obj->section_maps[j].s->size){ r_symbolnum = j + 1; break; } } if(r_symbolnum == 0){ error_with_cur_obj("r_value (0x%x) field of relocation " "entry %lu in section (%.16s,%.16s) out of range", (unsigned int)r_value, i, section_map->s->segname, section_map->s->sectname); return; } } } else{ reloc = relocs + i; sreloc = NULL; r_scattered = 0; r_address = reloc->r_address; r_pcrel = reloc->r_pcrel; r_length = reloc->r_length; r_extern = reloc->r_extern; r_symbolnum = reloc->r_symbolnum; r_type = (enum reloc_type_arm)reloc->r_type; r_value = 0; } /* * ARM_RELOC_PAIR relocation types only follows ARM_RELOC_{SECTDIFF, * LOCAL_SECTDIFF} relocation types and it is an error to see one * otherwise. */ if(r_type == ARM_RELOC_PAIR){ error_with_cur_obj("stray relocation ARM_RELOC_PAIR entry " "(%lu) in section (%.16s,%.16s)", i, section_map->s->segname, section_map->s->sectname); continue; } /* * The r_address field is really an offset into the contents of the * section and must reference something inside the section (Note * that this is not the case for ARM_RELOC_PAIR entries but this * can't be one with the above checks). */ if(r_address >= section_map->s->size){ error_with_cur_obj("r_address (0x%x) field of relocation entry " "%lu in section (%.16s,%.16s) out of range", (unsigned int)r_address, i, section_map->s->segname, section_map->s->sectname); return; } /* * If this relocation type is to have a pair make sure it is there * and then break out it's fields. */ pair_r_type = (enum reloc_type_arm)0; pair_reloc = NULL; spair_reloc = NULL; if(r_type == ARM_RELOC_SECTDIFF || r_type == ARM_RELOC_LOCAL_SECTDIFF){ if(r_scattered != 1){ error_with_cur_obj("relocation entry (%lu) in section " "(%.16s,%.16s) r_type is ARM_RELOC_SECTDIFF but " "relocation entry not scattered type", i, section_map->s->segname, section_map->s->sectname); continue; } if(i + 1 < section_map->s->nreloc){ pair_reloc = relocs + i + 1; if((pair_reloc->r_address & R_SCATTERED) != 0){ spair_reloc = (struct scattered_relocation_info *) pair_reloc; pair_reloc = NULL; pair_r_type = (enum reloc_type_arm)spair_reloc->r_type; pair_r_value = spair_reloc->r_value; other_half = spair_reloc->r_address; } else{ error_with_cur_obj("relocation entry (%lu) in section " "(%.16s,%.16s) following associated relocation " "entry not scattered type", i, section_map->s->segname, section_map->s->sectname); continue; } } if((pair_reloc == NULL && spair_reloc == NULL) || pair_r_type != ARM_RELOC_PAIR){ error_with_cur_obj("relocation entry (%lu) in section " "(%.16s,%.16s) missing following associated " "ARM_RELOC_PAIR entry", i, section_map->s->segname, section_map->s->sectname); continue; } /* * Calculate the pair_r_symbolnum (n_sect) from the * pair_r_value. */ pair_r_symbolnum = 0; for(j = 0; j < cur_obj->nsection_maps; j++){ if(pair_r_value >= cur_obj->section_maps[j].s->addr && pair_r_value < cur_obj->section_maps[j].s->addr + cur_obj->section_maps[j].s->size){ pair_r_symbolnum = j + 1; break; } } if(pair_r_symbolnum == 0){ error_with_cur_obj("r_value (0x%x) field of relocation " "entry %lu in section (%.16s,%.16s) out of range", (unsigned int)r_value, i + 1, section_map->s->segname, section_map->s->sectname); return; } } /* * If r_extern is set this relocation entry is an external entry * else it is a local entry (or scattered entry). */ if(r_extern){ /* * This is an external relocation entry. So the value to be * added to the item to be relocated is the value of the symbol. * r_symbolnum is an index into the input file's symbol table * of the symbol being refered to. The symbol must be an * undefined or coalesced symbol to be used in an external * relocation entry. */ if(r_symbolnum >= cur_obj->symtab->nsyms){ error_with_cur_obj("r_symbolnum (%lu) field of external " "relocation entry %lu in section (%.16s,%.16s) out of " "range", r_symbolnum, i, section_map->s->segname, section_map->s->sectname); return; } symbolnum = r_symbolnum; undefined_map = bsearch(&symbolnum, cur_obj->undefined_maps, cur_obj->nundefineds, sizeof(struct undefined_map), (int (*)(const void *, const void *))undef_bsearch); if(undefined_map != NULL){ merged_symbol = undefined_map->merged_symbol; } else{ nlists = (struct nlist *)(cur_obj->obj_addr + cur_obj->symtab->symoff); strings = (char *)(cur_obj->obj_addr + cur_obj->symtab->stroff); if((nlists[symbolnum].n_type & N_EXT) != N_EXT){ error_with_cur_obj("r_symbolnum (%lu) field of external" " relocation entry %lu in section (%.16s,%.16s) " "refers to a non-external symbol", symbolnum, i, section_map->s->segname, section_map->s->sectname); return; } /* * We must allow and create references to defined global * coalesced symbols with external relocation entries so * that the dynamic linker can relocate all references to * the same symbol. */ if((nlists[symbolnum].n_type & N_TYPE) == N_SECT && (cur_obj->section_maps[nlists[symbolnum].n_sect-1]. s->flags & SECTION_TYPE) == S_COALESCED){ merged_symbol = lookup_symbol(strings + nlists[symbolnum].n_un.n_strx); if(merged_symbol->name_len == 0){ fatal("internal error, in arm_reloc() failed to " "lookup coalesced symbol %s", strings + nlists[symbolnum].n_un.n_strx); } } else{ if((nlists[symbolnum].n_type & N_EXT) != N_EXT || (nlists[symbolnum].n_type & N_TYPE) != N_UNDF){ error_with_cur_obj("r_symbolnum (%lu) field of " "external relocation entry %lu in section " "(%.16s,%.16s) refers to a non-undefined " "symbol", symbolnum, i, section_map->s->segname, section_map->s->sectname); return; } print_obj_name(cur_obj); fatal("internal error, in arm_reloc() symbol index %lu " "in above file not in undefined map", symbolnum); } } if(refs == NULL && ((merged_symbol->nlist.n_type & N_TYPE) == N_SECT && (get_output_section(merged_symbol->nlist.n_sect)-> flags & SECTION_TYPE) == S_COALESCED)){ if(((merged_symbol->nlist.n_type & N_PEXT) == N_PEXT && keep_private_externs == FALSE) || dynamic == FALSE || (output_for_dyld && has_dynamic_linker_command)) force_extern_reloc = FALSE; else force_extern_reloc = TRUE; } /* * If this is an indirect symbol resolve indirection (all chains * of indirect symbols have been resolved so that they point at * a symbol that is not an indirect symbol). */ if((merged_symbol->nlist.n_type & N_TYPE) == N_INDR) merged_symbol = (struct merged_symbol *) merged_symbol->nlist.n_value; /* * If we are being called only to get the references for this * relocation entry fill it in and return. */ if(refs != NULL){ refs->ref1.ref_type = LIVE_REF_SYMBOL; refs->ref1.merged_symbol = merged_symbol; refs->ref2.ref_type = LIVE_REF_NONE; return; } /* * If the symbol is undefined (or common) or a global coalesced * symbol where we need to force an external relocation entry * and we are not prebinding no relocation is done. Or if the * output file is a multi module MH_DYLIB no relocation is done * unless the symbol is a private extern or we are prebinding. */ if(((merged_symbol->nlist.n_type & N_TYPE) == N_UNDF) || (force_extern_reloc == TRUE && prebinding == FALSE) || ((filetype == MH_DYLIB && multi_module_dylib == TRUE) && (((merged_symbol->nlist.n_type & N_PEXT) != N_PEXT) && prebinding == FALSE) ) ) value = 0; else{ value = merged_symbol->nlist.n_value; /* * Pointers to thumb symbols must have their low bit set * only after they have been relocated. Also arm BL * instructions to thumb symbols must be conveted to BLX * instructions. This external relocation entry will be * relocated, so if it is for a thumb symbol then set * relocated_extern_thumb_symbol so it can be used later. */ if((merged_symbol->nlist.n_desc & N_ARM_THUMB_DEF)) relocated_extern_thumb_symbol = TRUE; else relocated_extern_arm_symbol = TRUE; /* * To know which type (local or scattered) of relocation * entry to convert this one to (if relocation entries are * saved) the offset to be added to the symbol's value is * needed to see if it reaches outside the block in which * the symbol is in. In here if the offset is not zero then * it is assumed to reach out of the block and a scattered * relocation entry is used. */ input_pc = section_map->s->addr + r_address; if(r_type == ARM_RELOC_VANILLA){ switch(r_length){ case 0: /* byte */ offset = get_byte((char *)(contents + r_address)); break; case 1: /* word (2 byte) */ offset = get_short((short *)(contents + r_address)); break; case 2: /* long (4 byte) */ offset = get_long((long *)(contents + r_address)); break; default: /* the error check is catched below */ break; } if(r_pcrel) offset += input_pc; } else{ instruction = get_long((long *)(contents + r_address)); switch(r_type){ case ARM_RELOC_BR24: offset = instruction & 0x00ffffff; /* sign extend if needed */ if((offset & 0x00800000) != 0) offset |= 0xff000000; /* The value in the instruction is shifted by 2 */ offset = offset << 2; /* * Note the pc added will be +8 from the pc of the * branch instruction. And the assembler creating * this instruction takes that into account when * calculating the displacement in the instruction. */ if(r_pcrel) offset += input_pc; break; case ARM_THUMB_RELOC_BR22: /* * The code below assumes ARM is little endian * such that "the first 16-bit thumb instruction" * is the low 16 bits and "the second 16-bit thumb * instruction" is the high 16 bits of the 32-bits * in the variable instruction. */ /* the first instruction has the upper eleven bits of the two byte displacement */ offset = (instruction & 0x7FF) << 12; /* sign extend if needed */ if((offset & 0x400000) != 0) offset |= 0xFF800000; /* the second instruction has the lower eleven bits of the two byte displacement. Add that times two to get the offset added to the symbol */ offset += 2*((instruction >> 16) & 0x7FF); /* * Note the pc added will be +4 from the pc of the * branch instruction. And the assembler creating * this instruction takes that into account when * calculating the displacement in the instruction. */ if(r_pcrel) offset += input_pc; break; default: /* the error check is catched below */ break; } } } if((merged_symbol->nlist.n_type & N_TYPE) == N_SECT) output_sections[merged_symbol->nlist.n_sect]->referenced = TRUE; } else{ /* * This is a local relocation entry (the value to which the item * to be relocated is refering to is defined in section number * r_symbolnum). So the address of that section in the input * file is subtracted and the value of that section in the * output is added to the item being relocated. */ value = 0; /* * If the symbol is not in any section the value to be added to * the item to be relocated is the zero above and any pc * relative change in value added below. */ if(r_symbolnum == R_ABS){ /* * If we are being called only to get the references for * this relocation entry fill in it has none and return. */ if(refs != NULL){ refs->ref1.ref_type = LIVE_REF_NONE; refs->ref2.ref_type = LIVE_REF_NONE; return; } } else{ if(r_symbolnum > cur_obj->nsection_maps){ error_with_cur_obj("r_symbolnum (%lu) field of local " "relocation entry %lu in section (%.16s,%.16s) " "out of range", r_symbolnum, i, section_map->s->segname, section_map->s->sectname); return; } local_map = &(cur_obj->section_maps[r_symbolnum - 1]); local_map->output_section->referenced = TRUE; pair_local_map = NULL; if(r_type == ARM_RELOC_SECTDIFF || r_type == ARM_RELOC_LOCAL_SECTDIFF){ pair_local_map = &(cur_obj->section_maps[pair_r_symbolnum - 1]); pair_local_map->output_section->referenced = TRUE; } if(local_map->nfine_relocs == 0 && (pair_local_map == NULL || pair_local_map->nfine_relocs == 0) ){ if(r_type == ARM_RELOC_SECTDIFF || r_type == ARM_RELOC_LOCAL_SECTDIFF){ value = - local_map->s->addr + (local_map->output_section->s.addr + local_map->offset) + pair_local_map->s->addr - (pair_local_map->output_section->s.addr + pair_local_map->offset); } else{ value = - local_map->s->addr + (local_map->output_section->s.addr + local_map->offset); } } else{ /* * For items to be relocated that refer to a section * with fine relocation the value is set (not adjusted * with addition). So the new value is directly * calculated from the old value. */ if(r_pcrel){ input_pc = section_map->s->addr + r_address; if(section_map->nfine_relocs == 0) output_pc = section_map->output_section->s.addr + section_map->offset + r_address; else output_pc = section_map->output_section->s.addr + fine_reloc_output_offset(section_map, r_address); } else{ input_pc = 0; output_pc = 0; } /* * Get the value of the expresion of the item to be * relocated. */ if(r_type == ARM_RELOC_VANILLA || r_type == ARM_RELOC_SECTDIFF || r_type == ARM_RELOC_LOCAL_SECTDIFF){ switch(r_length){ case 0: /* byte */ value = get_byte((char *)(contents + r_address)); break; case 1: /* word (2 byte) */ value = get_short((short *)(contents + r_address)); break; case 2: /* long (4 byte) */ value = get_long((long *)(contents + r_address)); break; default: /* the error check is catched below */ break; } } else{ instruction = get_long((long *)(contents + r_address)); switch(r_type){ case ARM_RELOC_BR24: value = instruction & 0x00ffffff; if((value & 0x00800000) != 0) value |= 0xff000000; /* The value (displacement) is shifted by 2 */ value = value << 2; /* * For a BLX instruction, set bit[1] of the * result to the H bit. */ if((instruction & 0xff000000) == 0xfb000000) value |= 0x2; /* The pc added will be +8 from the pc */ value += 8; break; case ARM_THUMB_RELOC_BR22: /* * The code below assumes ARM is little endian * such that "the first 16-bit thumb * instruction" is the low 16 bits and "the * second 16-bit thumb instruction" is the high * 16 bits of the 32-bits in the variable * instruction. */ /* the first instruction has the upper eleven bits of the two byte displacement */ value = (instruction & 0x7FF) << 12; /* sign extend if needed */ if((value & 0x400000) != 0) value |= 0xFF800000; /* the second instruction has the lower eleven bits of the two byte displacement. Add that times two to get the target address */ value += 2*((instruction >> 16) & 0x7FF); /* The pc added will be +4 from the pc */ value += 4; /* * For BLX, the resulting address is forced to * be word-aligned by clearing bit[1]. */ if(((instruction & 0x18000000) == 0x08000000) && ((input_pc + value) & 0x2)) value -= 2; break; default: /* the error check is catched below */ break; } } if(r_type == ARM_RELOC_SECTDIFF || r_type == ARM_RELOC_LOCAL_SECTDIFF){ /* * For ARM_RELOC_SECTDIFF's the item to be * relocated, in value, is the value of the * expression: * r_value - pair_r_value + offset * To set the value of the relocated expression, * it is set from relocating the two r_value's and * adding back in the offset. So here get the * offset from the value of the expression. */ value += input_pc; /* adjust for pcrel */ offset = value - r_value + pair_r_value; /* * If we are being called only to get the references * for this relocation entry fill it in and return. */ if(refs != NULL){ fine_reloc_output_ref( local_map, r_value - local_map->s->addr, &(refs->ref1) ); fine_reloc_output_ref( local_map, pair_r_value - local_map->s->addr, &(refs->ref2) ); return; } /* * Now build up the value of the relocated * expression one part at a time. First set the * new value to the relocated r_value. */ if(local_map->nfine_relocs != 0){ /* * Check to see if this reference is legal with * respect to indirect sections. */ legal_reference(section_map, r_address, local_map, r_value - local_map->s->addr, i, r_type != ARM_RELOC_LOCAL_SECTDIFF); value = fine_reloc_output_address(local_map, r_value - local_map->s->addr, local_map->output_section->s.addr); } else{ value = local_map->output_section->s.addr + local_map->offset + r_value - local_map->s->addr; } /* Second subtract the relocated pair_r_value. */ if(pair_local_map->nfine_relocs != 0){ /* * Check to see if this reference is legal with * respect to indirect sections. */ legal_reference(section_map, r_address, pair_local_map, pair_r_value - pair_local_map->s->addr, i, TRUE); value -= fine_reloc_output_address(pair_local_map, pair_r_value - pair_local_map->s->addr, pair_local_map->output_section->s.addr); } else{ value -= pair_local_map->output_section->s.addr + pair_local_map->offset + pair_r_value - pair_local_map->s->addr; } /* Third add in the offset. */ value += offset; value -= output_pc; /* adjust for pcrel */ } else{ /* * If the relocation entry is not a scattered * relocation entry then the relocation is based on * the value of value of the expresion of the item * to be relocated. If it is a scattered relocation * entry then the relocation is based on the r_value * in the relocation entry and the offset part of * the expression at the item to be relocated is * extracted so it can be added after the relocation * is done. */ value += input_pc; if(r_scattered == 0){ r_value = value; offset = 0; } else{ offset = value - r_value; } /* * Check to see if this reference is legal with * respect to indirect sections. */ legal_reference(section_map, r_address, local_map, r_value - local_map->s->addr + offset, i, FALSE); /* * If we are being called only to get the references * for this relocation entry fill it in and return. */ if(refs != NULL){ fine_reloc_output_ref( local_map, r_value - local_map->s->addr, &(refs->ref1) ); refs->ref2.ref_type = LIVE_REF_NONE; return; } value = fine_reloc_output_address(local_map, r_value - local_map->s->addr, local_map->output_section->s.addr); value -= output_pc; value += offset; } if(r_type == ARM_RELOC_VANILLA || r_type == ARM_RELOC_LOCAL_SECTDIFF || r_type == ARM_RELOC_SECTDIFF){ switch(r_length){ case 0: /* byte */ if( (value & 0xffffff00) && ((value & 0xffffff80) != 0xffffff80)) error_with_cur_obj("relocation for entry " "%lu in section (%.16s,%.16s) does not " "fit in 1 byte", i, section_map->s->segname, section_map->s->sectname); set_byte((char *)(contents + r_address), value); break; case 1: /* word (2 byte) */ if( (value & 0xffff0000) && ((value & 0xffff8000) != 0xffff8000)) error_with_cur_obj("relocation for entry " "%lu in section (%.16s,%.16s) does not " "fit in 2 bytes", i, section_map->s->segname, section_map->s->sectname); set_short((short *)(contents + r_address), value); break; case 2: /* long (4 byte) */ set_long((long *)(contents + r_address), value); break; default: error_with_cur_obj("r_length field of " "relocation entry %lu in section (%.16s," "%.16s) invalid", i, section_map->s->segname, section_map->s->sectname); return; } } else{ switch(r_type){ case ARM_RELOC_BR24: /* The pc added will be +8 from the pc */ value -= 8; /* * An ARM BLX targetting an ARM symbol or * a local symbol needs to be converted to a * BL. This could happen if it was originally * targetting a thumb stub which will be * optimized away. */ if(((fine_reloc_arm(local_map, r_value - local_map->s->addr) == TRUE) || (fine_reloc_local(local_map, r_value - local_map->s->addr) == TRUE)) && ((instruction & 0xfe000000) == 0xfa000000)) instruction = 0xeb000000; /* * For arm branch instructions if the target is * a thumb symbol it must be converted to a * branch and exchange instruction (unless it * already is one). */ if((fine_reloc_thumb(local_map, r_value - local_map->s->addr) == TRUE) || ((instruction & 0xfe000000) == 0xfa000000)){ /* * Only unconditional BL can be converted * to BLX */ if(((instruction & 0xff000000) != 0xeb000000) && ((instruction & 0xfe000000) != 0xfa000000)) error_with_cur_obj("relocation error " "for relocation entry %lu in " "section (%.16s,%.16s) (branch " "cannot be converted to BLX)", i, section_map->s->segname, section_map->s->sectname); /* * The H bit of the BLX instruction (bit 24) * contains bit 1 of the target address. */ instruction = (0xfa000000 | ((value & 0x2) << 23)); /* * This code assumes the thumb symbol * address is two byte aligned. This next * line clears the last two bits so the next * test will not cause an error */ value &= ~0x2; } if((value & 0x3) != 0) error_with_cur_obj("relocation error " "for relocation entry %lu in section " "(%.16s,%.16s) (displacement not a " "multiple of 4 bytes)", i, section_map->s->segname, section_map->s->sectname); if((value & 0xfe000000) != 0xfe000000 && (value & 0xfe000000) != 0x00000000) error_with_cur_obj("relocation overflow " "for relocation entry %lu in section " "(%.16s,%.16s) (displacement too large)" , i, section_map->s->segname, section_map->s->sectname); instruction = (instruction & 0xff000000) | ((value >> 2) & 0x00ffffff); break; case ARM_THUMB_RELOC_BR22: /* The pc added will be +4 from the pc */ value -= 4; /* * Here we have a BL instruction targetting an * arm symbol -- convert it to a BLX */ if((fine_reloc_arm(local_map, r_value - local_map->s->addr) == TRUE) && ((instruction & 0xf800f800) == 0xf800f000)) instruction &= 0xefffffff; /* * Here we have a BLX instruction targetting a * thumb symbol or a local symbol (which we will * boldly assume to be thumb in the absence of * any evidence to the contrary) -- convert it * to a BL */ if(((fine_reloc_thumb(local_map, r_value - local_map->s->addr) == TRUE) || (fine_reloc_local(local_map, r_value - local_map->s->addr) == TRUE)) && ((instruction & 0xf800f800) == 0xe800f000)) instruction |= 0x10000000; /* immediate must be multiple of four bytes. * This enforces the requirement that * instruction[0] must be zero for a BLX. */ if((instruction & 0xf800f800) == 0xe800f000 && (value & 0x2) != 0) value += 2; instruction = (instruction & 0xf800f800) | (value & 0x7ff000) >> 12 | (value & 0xffe) << 15; break; default: error_with_cur_obj("r_type field of " "relocation entry %lu in section (%.16s," "%.16s) invalid", i, section_map->s->segname, section_map->s->sectname); return; } set_long((long *)(contents + r_address), instruction); } goto update_reloc; } } } if(r_pcrel){ /* * This is a relocation entry is also pc relative which means * the value of the pc will get added to it when it is executed. * The item being relocated has the value of the pc in the input * file subtracted from it. So to relocate this the value of * pc in the input file is added and then value of the output * pc is subtracted (since the offset into the section remains * constant it is not added in and then subtracted out). */ if(section_map->nfine_relocs == 0) value += + section_map->s->addr /* + r_address */ - (section_map->output_section->s.addr + section_map->offset /* + r_address */); else value += + section_map->s->addr + r_address - (section_map->output_section->s.addr + fine_reloc_output_offset(section_map, r_address)); } if(r_type == ARM_RELOC_VANILLA || r_type == ARM_RELOC_LOCAL_SECTDIFF || r_type == ARM_RELOC_SECTDIFF){ /* * Pointers to thumb symbols must have their low bit set, but * only after they have been relocated. Code above determined * if this is and external relocation entry for a thumb symbol * that is being relocated and if so set * relocated_extern_thumb_symbol. So now if this is a VANILLA * relocation entry for a pointer set the low bit from * relocated_extern_thumb_symbol. */ if(r_type == ARM_RELOC_VANILLA) low_bit = (relocated_extern_thumb_symbol == TRUE) ? 1 : 0; else low_bit = 0; /* * This is part of the cctools_aek-thumb-hack branch. It seems * like a reasonable error check but I don't see how it could * ever get triggered by any code going though the assember. */ if(r_type == ARM_RELOC_VANILLA && relocated_extern_thumb_symbol == TRUE && r_pcrel) error_with_cur_obj("relocation for entry %lu in section " "(%.16s,%.16s) is VANILLA PC-relative to a thumb " "symbol %s", i, section_map->s->segname, section_map->s->sectname, merged_symbol->nlist.n_un.n_name); switch(r_length){ case 0: /* byte */ value += get_byte((char *)(contents + r_address)); if( (value & 0xffffff00) && ((value & 0xffffff80) != 0xffffff80)) error_with_cur_obj("relocation for entry %lu in section" " (%.16s,%.16s) does not fit in 1 byte", i, section_map->s->segname, section_map->s->sectname); set_byte((char *)(contents + r_address), value | low_bit); break; case 1: /* word (2 byte) */ value += get_short((short *)(contents + r_address)); if( (value & 0xffff0000) && ((value & 0xffff8000) != 0xffff8000)) error_with_cur_obj("relocation for entry %lu in section" " (%.16s,%.16s) does not fit in 2 bytes", i, section_map->s->segname, section_map->s->sectname); set_short((short *)(contents + r_address), value | low_bit); break; case 2: /* long (4 byte) */ value += get_long((long *)(contents + r_address)); set_long((long *)(contents + r_address), value | low_bit); break; default: error_with_cur_obj("r_length field of relocation entry %lu " "in section (%.16s,%.16s) invalid", i, section_map->s->segname, section_map->s->sectname); return; } } /* * Do arm specific relocation based on the r_type. */ else{ instruction = get_long((long *)(contents + r_address)); switch(r_type){ case ARM_RELOC_BR24: immediate = instruction & 0x00ffffff; if((immediate & 0x00800000) != 0) immediate |= 0xff000000; /* The value in the instruction is shifted by 2 */ immediate = immediate << 2; /* In a BLX, bit 1 of the immediate is at bit 24 * of the instruction. */ if((instruction & 0xfe000000) == 0xfa000000) immediate |= (instruction & 0x01000000) >> 23; immediate += value; /* * Here we have a BLX instruction that targets an * arm symbol -- convert it to a BL instruction. */ if((r_extern == TRUE) && (relocated_extern_arm_symbol == TRUE) && ((instruction & 0xfe000000) == 0xfa000000)) instruction = 0xeb000000; /* * For arm branch instructions if the target is a thumb * symbol it must be converted to a branch and exchange * instruction (unless it already is one). */ else if(((r_extern == TRUE) && (relocated_extern_thumb_symbol == TRUE)) || ((instruction & 0xfe000000) == 0xfa000000)){ /* only unconditional BL can be converted to BLX */ if(((instruction & 0xff000000) != 0xeb000000) && ((instruction & 0xfe000000) != 0xfa000000)) error_with_cur_obj("relocation error for relocation" " entry %lu in section (%.16s,%.16s) (branch " "cannot be converted to BLX)", i, section_map->s->segname, section_map->s->sectname); /* the H bit of the BLX instruction (bit 24) contains bit 1 of the target address */ instruction = (0xfa000000 | ((immediate & 0x2) << 23)); /* this code assumes the thumb symbol address is two byte aligned. This next line clears the last two bits so the next test will not cause an error */ immediate &= ~0x2; } if((immediate & 0x3) != 0) error_with_cur_obj("relocation error for relocation " "entry %lu in section (%.16s,%.16s) (displacement " "not a multiple of 4 bytes)", i, section_map->s->segname, section_map->s->sectname); if((immediate & 0xfe000000) != 0xfe000000 && (immediate & 0xfe000000) != 0x00000000) error_with_cur_obj("relocation overflow for relocation " "entry %lu in section (%.16s,%.16s) (displacement " "too large)", i, section_map->s->segname, section_map->s->sectname); instruction = (instruction & 0xff000000) | (immediate & 0x03ffffff) >> 2; break; case ARM_THUMB_RELOC_BR22: /* * The code below assumes ARM is little endian such that * "the first 16-bit thumb instruction" is the low 16 bits * and "the second 16-bit thumb instruction" is the high 16 * bits of the 32-bits in the variable instruction. */ /* the first instruction has the upper eleven bits of the two byte displacement */ immediate = (instruction & 0x7FF) << 12; /* sign extend if needed */ if((immediate & 0x400000) != 0) immediate |= 0xFF800000; /* the second instruction has the lower eleven bits of the two byte displacement. Add that times two to get the target address */ immediate += 2*((instruction >> 16) & 0x7FF); /* * For BLX, the resulting address is forced to be word- * aligned by clearing bit[1]. */ if((instruction & 0xf800f800) == 0xe800f000 && (r_address & 0x2)) immediate -= 2; immediate += value; /* * The target address for a thumb branch must be to a * two-byte address. */ if((immediate & 0x1) != 0) error_with_cur_obj("relocation error for relocation " "entry %lu in section (%.16s,%.16s) (displacement " "not a multiple of 2 bytes)", i, section_map->s->segname, section_map->s->sectname); /* * Here we have a BLX instruction that targets a * thumb symbol -- convert it to a BL instruction. */ if((r_extern == TRUE) && (relocated_extern_thumb_symbol == TRUE) && ((instruction & 0xf800f800) == 0xe800f000)) instruction |= 0x10000000; /* * For thumb branch instructions if the target is not a * thumb symbol (an arm symbol) it must be converted to a * branch and exchange instruction (if it is not already * one). */ if(r_extern == TRUE && relocated_extern_arm_symbol == TRUE && (instruction & 0xf800f800) != 0xe800f000){ /* Make sure we have a high+low BL */ if((instruction & 0xf800f800) != 0xf800f000) error_with_cur_obj("relocation error for relocation" " entry %lu in section (%.16s,%.16s) (unknown " "branch type)", i, section_map->s->segname, section_map->s->sectname); /* Convert BL to BLX: clear top H bit of second insr */ instruction &= 0xefffffff; } /* immediate must be multiple of four bytes. * This enforces the requirement that * instruction[0] must be zero for a BLX. */ if((instruction & 0xf800f800) == 0xe800f000 && (immediate & 0x2) != 0) immediate += 2; if((immediate & 0xffc00000) != 0xffc00000 && (immediate & 0xffc00000) != 0x00000000) error_with_cur_obj("relocation overflow for relocation " "entry %lu in section (%.16s,%.16s) (displacement " "too large)", i, section_map->s->segname, section_map->s->sectname); instruction = (instruction & 0xf800f800) | (immediate & 0x7ff000) >> 12 | (immediate & 0xffe) << 15; break; case ARM_THUMB_32BIT_BRANCH: break; default: error_with_cur_obj("r_type field of relocation entry %lu " "in section (%.16s,%.16s) invalid", i, section_map->s->segname, section_map->s->sectname); continue; } set_long((long *)(contents + r_address), instruction); } /* * If relocation entries are to be saved in the output file then * update the entry for the output file. */ update_reloc: ; #ifndef RLD if(save_reloc || output_for_dyld){ if(r_extern){ /* * If we are prebinding and this is a lazy pointer section * change the relocation entry to a ARM_RELOC_PB_LA_PTR * type. This stuffs the value of the lazy pointer as it * wouldn't be prebound in the r_value field. So if the * prebounding can't be used at runtime the value of the * lazy pointer will get set back to the r_value by dyld. */ if(prebinding == TRUE && (section_map->s->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS){ sreloc = (struct scattered_relocation_info *)reloc; r_scattered = 1; sreloc->r_scattered = r_scattered; sreloc->r_address = r_address; sreloc->r_pcrel = r_pcrel; sreloc->r_length = r_length; sreloc->r_type = ARM_RELOC_PB_LA_PTR; sreloc->r_value = value; } /* * For external relocation entries that the symbol is * defined (not undefined or common) but not when we are * forcing an external relocation entry for a global * coalesced symbol and if the output file is not a multi * module MH_DYLIB or the symbol is a private extern, it is * changed to a local relocation entry using the section * that symbol is defined in. If still undefined or forcing * an external relocation entry for a global coalesced * symbol, then the index of the symbol in the output file * is set into r_symbolnum. */ else if((merged_symbol->nlist.n_type & N_TYPE) != N_UNDF && (merged_symbol->nlist.n_type & N_TYPE) != N_PBUD && force_extern_reloc == FALSE && ((filetype != MH_DYLIB || multi_module_dylib == FALSE) || (merged_symbol->nlist.n_type & N_PEXT) == N_PEXT)){ reloc->r_extern = 0; /* * If this symbol was in the base file then no futher * relocation can ever be done (the symbols in the base * file are fixed). Or if the symbol was an absolute * symbol. */ if(merged_symbol->definition_object == base_obj || (merged_symbol->nlist.n_type & N_TYPE) == N_ABS){ reloc->r_symbolnum = R_ABS; } else{ /* * The symbol that this relocation entry is refering * to is defined so convert this external relocation * entry into a local or scattered relocation entry. * If the item to be relocated has an offset added * to the symbol's value and the output is not for * dyld make it a scattered relocation entry else * make it a local relocation entry. */ if(offset == 0 || output_for_dyld){ reloc->r_symbolnum =merged_symbol->nlist.n_sect; } else{ sreloc = (struct scattered_relocation_info *) reloc; r_scattered = 1; sreloc->r_scattered = r_scattered; sreloc->r_address = r_address; sreloc->r_pcrel = r_pcrel; sreloc->r_length = r_length; sreloc->r_type = r_type; sreloc->r_value = merged_symbol->nlist.n_value; } } } else{ reloc->r_symbolnum = merged_symbol_output_index(merged_symbol); } } else if(r_scattered == 0){ /* * If we are prebinding and this is a lazy pointer section * change the relocation entry to a ARM_RELOC_PB_LA_PTR * type. This stuffs the value of the lazy pointer as it * wouldn't be prebound in the r_value field. So if the * prebounding can't be used at runtime the value of the * lazy pointer will get set back to the r_value by dyld. */ if(prebinding == TRUE && (section_map->s->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS){ sreloc = (struct scattered_relocation_info *)reloc; r_scattered = 1; sreloc->r_scattered = r_scattered; sreloc->r_address = r_address; sreloc->r_pcrel = r_pcrel; sreloc->r_length = r_length; sreloc->r_type = ARM_RELOC_PB_LA_PTR; sreloc->r_value = value; } /* * For local relocation entries the section number is * changed to the section number in the output file. */ else if(reloc->r_symbolnum != R_ABS){ if(local_map->nfine_relocs == 0){ reloc->r_symbolnum = local_map->output_section->output_sectnum; } else{ reloc->r_symbolnum = fine_reloc_output_sectnum(local_map, r_value - local_map->s->addr); } } } else{ /* * This is a scattered relocation entry. If the output is * for dyld convert it to a local relocation entry so as * to not overflow the 24-bit r_address field in a scattered * relocation entry. The overflow would happen in * reloc_output_for_dyld() in sections.c when it adjusts * the r_address fields of the relocation entries. */ if(output_for_dyld){ reloc = (struct relocation_info *)sreloc; r_scattered = 0; reloc->r_address = r_address; reloc->r_pcrel = r_pcrel; reloc->r_extern = 0; reloc->r_length = r_length; reloc->r_type = r_type; if(local_map->nfine_relocs == 0){ reloc->r_symbolnum = local_map->output_section->output_sectnum; } else{ reloc->r_symbolnum = fine_reloc_output_sectnum(local_map, r_value - local_map->s->addr); } } else{ /* * For scattered relocation entries the r_value field is * relocated. */ if(local_map->nfine_relocs == 0) sreloc->r_value += - local_map->s->addr + local_map->output_section->s.addr + local_map->offset; else sreloc->r_value = fine_reloc_output_address(local_map, r_value - local_map->s->addr, local_map->output_section->s.addr); } } /* * If this section that the reloation is being done for has fine * relocation then the offset in the r_address field has to be * set to where it will end up in the output file. Otherwise * it simply has to have the offset to where this contents * appears in the output file. */ if(r_scattered == 0){ if(section_map->nfine_relocs == 0){ reloc->r_address += section_map->offset; } else{ reloc->r_address = fine_reloc_output_offset(section_map, r_address); } } else{ if(section_map->nfine_relocs == 0){ /* this can overflow the 24-bit sreloc->r_address */ sreloc->r_address += section_map->offset; } else{ sreloc->r_address =fine_reloc_output_offset(section_map, r_address); } } /* * If their was a paired relocation entry then update the * paired relocation entry. */ if(pair_r_type == ARM_RELOC_PAIR){ if(pair_reloc != NULL){ /* I don't think arm has any pairs that are not scattered relocs so this should never happen */ pair_reloc->r_address = other_half; } else if(spair_reloc != NULL){ if(r_type == ARM_RELOC_SECTDIFF || r_type == ARM_RELOC_LOCAL_SECTDIFF){ /* * For ARM_RELOC_SECTDIFF relocation entries (which * are always scattered types) the r_value field is * relocated. */ if(pair_local_map->nfine_relocs == 0) spair_reloc->r_value += - pair_local_map->s->addr + (pair_local_map->output_section->s.addr + pair_local_map->offset); else spair_reloc->r_value = fine_reloc_output_address(pair_local_map, pair_r_value - pair_local_map->s->addr, pair_local_map->output_section->s.addr); } } else{ fatal("internal error, in arm_reloc() pair_r_type " "is ARM_RELOC_PAIR but pair_reloc and spair_reloc " "are NULL"); } } } #endif /* !defined(RLD) */ /* * If their was a paired relocation entry then it has been processed * so skip it by incrementing the index of the relocation entry that * is being processed. */ if(pair_r_type == ARM_RELOC_PAIR) i++; } }