/* * c60_reloc.c * * Process C6x-specific dynamic relocations for core dynamic loader. * * Copyright (C) 2009-2014 Texas Instruments Incorporated - http://www.ti.com/ * * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the * distribution. * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include "relocate.h" #include "symtab.h" #include "c60_elf32.h" #include "dload_api.h" #include "util.h" #include "dload_endian.h" #include "c60_reloc.h" #define MASK(n,s) (((1 << n) - 1) << s) /*---------------------------------------------------------------------------*/ /* C6x Relocations Supported */ /* */ /* See the C6000 ELF ABI Specification for more details. */ /* */ /* R_C6000_ABS32 | .field X,32 */ /* R_C6000_ABS16 | .field X,16 */ /* R_C6000_ABS8 | .field X,8 */ /* R_C6000_PCR_S21 | B foo */ /* CALLP foo, B3 */ /* R_C6000_PCR_S12 | BNOP foo */ /* R_C6000_PCR_S10 | BPOS foo, A10 */ /* BDEC foo, A1 */ /* R_C6000_PCR_S7 | ADDKPC foo, B3, 4 */ /* R_C6000_ABS_S16 | MVK sym, A0 */ /* R_C6000_ABS_L16 | MVKL sym, A0 */ /* MVKLH sym, A0 */ /* R_C6000_ABS_H16 | MVKH sym, A0 */ /* R_C6000_SBR_U15_B | LDB *+B14(sym), A1 */ /* ADDAB B14, sym, A1 */ /* R_C6000_SBR_U15_H | LDH *+B14(sym), A1 */ /* ADDAH B14, sym, A1 */ /* R_C6000_SBR_U15_W | LDW *+B14(sym), A1 */ /* ADDAW B14, sym, A1 */ /* R_C6000_SBR_S16 | MVK sym-$bss, A0 */ /* R_C6000_SBR_L16_B | MVKL (sym-$bss), A0 */ /* R_C6000_SBR_L16_H | MVKL (sym-$bss)/2,A0 */ /* R_C6000_SBR_L16_W | MVKL (sym-$bss)/4,A0 */ /* R_C6000_SBR_H16_B | MVKH (sym-$bss), A0 */ /* R_C6000_SBR_H16_H | MVKH (sym-$bss)/2,A0 */ /* R_C6000_SBR_H16_W | MVKH (sym-$bss)/4,A0 */ /* R_C6000_SBR_GOT_U15_W | LDW *+B14[GOT(sym)],A0 */ /* R_C6000_SBR_GOT_L16_W | MVKL $DPR_GOT(sym), A0 */ /* R_C6000_SBR_GOT_H16_W | MVKH $DPR_GOT(sym), A0 */ /* R_C6000_DSBT_INDEX | LDW *+B14[$DSBT_index()], DP */ /* */ /*---------------------------------------------------------------------------*/ /*****************************************************************************/ /* WRITE_RELOC_R() - Perform a relocation into a buffered segment. */ /*****************************************************************************/ static void write_reloc_r(uint8_t* buffered_segment, uint32_t segment_offset, int r_type, uint32_t r) { uint32_t* rel_field_ptr = (uint32_t*)(buffered_segment + segment_offset); #if LOADER_DEBUG /*------------------------------------------------------------------------*/ /* Print some details about the relocation we are about to process. */ /*------------------------------------------------------------------------*/ if(debugging_on) { DLIF_trace("RWRT: segment_offset: %d\n", segment_offset); DLIF_trace("RWRT: buffered_segment: 0x%x\n", (uint32_t)buffered_segment); DLIF_trace("RWRT: rel_field_ptr: 0x%x\n", (uint32_t)rel_field_ptr); DLIF_trace("RWRT: result: 0x%x\n", r); } #endif /*------------------------------------------------------------------------*/ /* Given the relocation type, carry out relocation into a 4 byte packet */ /* within the buffered segment. */ /*------------------------------------------------------------------------*/ switch(r_type) { case R_C6000_ABS32: *rel_field_ptr = r; break; case R_C6000_PREL31: *rel_field_ptr = (*rel_field_ptr & ~MASK(30,0)) | r; break; case R_C6000_ABS16: *((uint16_t*)(buffered_segment + segment_offset)) = r; break; case R_C6000_ABS8: *((uint8_t*)(buffered_segment + segment_offset)) = r; break; case R_C6000_PCR_S21: *rel_field_ptr = (*rel_field_ptr & ~MASK(21,7)) | (r << 7); break; case R_C6000_PCR_S12: *rel_field_ptr = (*rel_field_ptr & ~MASK(12,16)) | (r << 16); break; case R_C6000_PCR_S10: *rel_field_ptr = (*rel_field_ptr & ~MASK(10,13)) | (r << 13); break; case R_C6000_PCR_S7: *rel_field_ptr = (*rel_field_ptr & ~MASK(7,16)) | (r << 16); break; case R_C6000_ABS_S16: *rel_field_ptr = (*rel_field_ptr & ~MASK(16,7)) | (r << 7); break; case R_C6000_ABS_L16: *rel_field_ptr = (*rel_field_ptr & ~MASK(16,7)) | (r << 7); break; case R_C6000_ABS_H16: *rel_field_ptr = (*rel_field_ptr & ~MASK(16,7)) | (r << 7); break; case R_C6000_SBR_U15_B: *rel_field_ptr = (*rel_field_ptr & ~MASK(15,8)) | (r << 8); break; case R_C6000_SBR_U15_H: *rel_field_ptr = (*rel_field_ptr & ~MASK(15,8)) | (r << 8); break; case R_C6000_SBR_U15_W: case R_C6000_DSBT_INDEX: *rel_field_ptr = (*rel_field_ptr & ~MASK(15,8)) | (r << 8); break; case R_C6000_SBR_S16: case R_C6000_SBR_L16_B: case R_C6000_SBR_L16_H: case R_C6000_SBR_L16_W: case R_C6000_SBR_H16_B: case R_C6000_SBR_H16_H: case R_C6000_SBR_H16_W: *rel_field_ptr = (*rel_field_ptr & ~MASK(16,7)) | (r << 7); break; /*---------------------------------------------------------------------*/ /* Linux "import-as-own" copy relocations are not yet supported. */ /*---------------------------------------------------------------------*/ case R_C6000_COPY: default: DLIF_error(DLET_RELOC, "write_reloc_r called with invalid relocation type!\n"); } #if LOADER_DEBUG if (debugging_on) DLIF_trace("reloc_field 0x%x\n", *rel_field_ptr); #endif } /*****************************************************************************/ /* PACK_RESULT() - Pack the result of a relocation calculation for storage */ /* in the relocation field. */ /*****************************************************************************/ static int32_t pack_result(int32_t unpacked_result, int r_type) { switch(r_type) { case R_C6000_ABS32: case R_C6000_ABS16: case R_C6000_ABS8: case R_C6000_ABS_S16: case R_C6000_ABS_L16: case R_C6000_SBR_U15_B: case R_C6000_SBR_S16: case R_C6000_SBR_L16_B: return unpacked_result; case R_C6000_SBR_U15_H: case R_C6000_SBR_L16_H: case R_C6000_PREL31: return unpacked_result >> 1; case R_C6000_PCR_S21: case R_C6000_PCR_S12: case R_C6000_PCR_S10: case R_C6000_PCR_S7: case R_C6000_SBR_U15_W: case R_C6000_SBR_L16_W: case R_C6000_DSBT_INDEX: return unpacked_result >> 2; case R_C6000_ABS_H16: case R_C6000_SBR_H16_B: return unpacked_result >> 16; case R_C6000_SBR_H16_H: return unpacked_result >> 17; case R_C6000_SBR_H16_W: return unpacked_result >> 18; /*---------------------------------------------------------------------*/ /* Linux "import-as-own" copy relocations are not yet supported. */ /*---------------------------------------------------------------------*/ case R_C6000_COPY: default: DLIF_error(DLET_RELOC, "pack_result called with invalid relocation type!\n"); return 0; } } /*****************************************************************************/ /* MASK_RESULT() - Mask the result of a relocation calculation so that it */ /* fits the size of the relocation type's field. */ /*****************************************************************************/ static int32_t mask_result(int32_t unmasked_result, int r_type) { switch(r_type) { case R_C6000_ABS8: return unmasked_result & 0xFF; case R_C6000_ABS32: return unmasked_result; case R_C6000_ABS16: case R_C6000_ABS_S16: case R_C6000_ABS_L16: case R_C6000_ABS_H16: case R_C6000_SBR_S16: case R_C6000_SBR_L16_B: case R_C6000_SBR_L16_H: case R_C6000_SBR_L16_W: case R_C6000_SBR_H16_B: case R_C6000_SBR_H16_H: case R_C6000_SBR_H16_W: return unmasked_result & 0xFFFF; case R_C6000_PCR_S21: return unmasked_result & 0x1FFFFF; case R_C6000_PCR_S12: return unmasked_result & 0xFFF; case R_C6000_PCR_S10: return unmasked_result & 0x3FF; case R_C6000_PCR_S7: return unmasked_result & 0x7F; case R_C6000_SBR_U15_B: case R_C6000_SBR_U15_H: case R_C6000_SBR_U15_W: case R_C6000_DSBT_INDEX: return unmasked_result & 0x7FFF; case R_C6000_PREL31: return unmasked_result & 0x7FFFFFFF; /*---------------------------------------------------------------------*/ /* Linux "import-as-own" copy relocations are not yet supported. */ /*---------------------------------------------------------------------*/ case R_C6000_COPY: default: DLIF_error(DLET_RELOC, "mask_result called with invalid relocation type!\n"); return 0; } } /*****************************************************************************/ /* REL_OVERFLOW() */ /* */ /* Check relocation value against the range associated with a given */ /* relocation type field size and signedness. */ /* */ /*****************************************************************************/ static BOOL rel_overflow(C60_RELOC_TYPE r_type, int32_t reloc_value) { /*------------------------------------------------------------------------*/ /* Select appropriate range check based on relocation type. */ /*------------------------------------------------------------------------*/ switch(r_type) { case R_C6000_ABS16: return ((reloc_value > 65535) || (reloc_value < -32768)); case R_C6000_ABS8: return ((reloc_value > 255) || (reloc_value < -128)); case R_C6000_PCR_S21: return ((reloc_value >= 0x400000) || (reloc_value < -0x400000)); case R_C6000_PCR_S12: return ((reloc_value >= 0x2000) || (reloc_value < -0x2000)); case R_C6000_PCR_S10: return ((reloc_value >= 0x800) || (reloc_value < -0x800)); case R_C6000_PCR_S7: return ((reloc_value >= 0x100) || (reloc_value < -0x100)); case R_C6000_SBR_S16: case R_C6000_ABS_S16: return ((reloc_value >= 0x8000) || (reloc_value < -0x8000)); case R_C6000_SBR_U15_B: return (((uint32_t)reloc_value) >= 0x8000); case R_C6000_SBR_U15_H: return (((uint32_t)reloc_value) >= 0xFFFF); case R_C6000_DSBT_INDEX: case R_C6000_SBR_U15_W: return (((uint32_t)reloc_value) >= 0x1FFFD); /*---------------------------------------------------------------------*/ /* Some relocation types suppress overflow checking at link-time. */ /*---------------------------------------------------------------------*/ case R_C6000_ABS_L16: case R_C6000_ABS_H16: case R_C6000_SBR_L16_B: case R_C6000_SBR_L16_H: case R_C6000_SBR_L16_W: case R_C6000_SBR_H16_B: case R_C6000_SBR_H16_H: case R_C6000_SBR_H16_W: return 0; /*---------------------------------------------------------------------*/ /* 32-bit relocation field values are not checked for overflow. */ /*---------------------------------------------------------------------*/ case R_C6000_ABS32: case R_C6000_PREL31: return 0; /*---------------------------------------------------------------------*/ /* If relocation type did not appear in the above switch, then we */ /* didn't expect to see it. */ /*---------------------------------------------------------------------*/ default: DLIF_error(DLET_RELOC, "rel_overflow called with invalid relocation type!\n"); } return 1; } #if LOADER_DEBUG || LOADER_PROFILE extern int DLREL_relocations; extern time_t DLREL_total_reloc_time; #endif /*****************************************************************************/ /* RELOC_DO() - Process a single relocation entry. */ /*****************************************************************************/ static void reloc_do(C60_RELOC_TYPE r_type, uint32_t segment_vaddr, uint8_t *segment_buffer, uint32_t addend, uint32_t symval, uint32_t spc, int wrong_endian, uint32_t base_pointer, int32_t dsbt_index) { int32_t reloc_value = 0; #if LOADER_DEBUG || LOADER_PROFILE /*------------------------------------------------------------------------*/ /* In debug mode, keep a count of the number of relocations processed. */ /* In profile mode, start the clock on a given relocation. */ /*------------------------------------------------------------------------*/ int start_time = 0; if (debugging_on || profiling_on) { DLREL_relocations++; if (profiling_on) start_time = clock(); } #endif /*------------------------------------------------------------------------*/ /* Calculate the relocation value according to the rules associated with */ /* the given relocation type. */ /*------------------------------------------------------------------------*/ switch(r_type) { /*---------------------------------------------------------------------*/ /* Straight-Up Address relocations (address references). */ /*---------------------------------------------------------------------*/ case R_C6000_ABS32: case R_C6000_ABS16: case R_C6000_ABS8: case R_C6000_ABS_S16: case R_C6000_ABS_L16: case R_C6000_ABS_H16: reloc_value = symval + addend; break; /*---------------------------------------------------------------------*/ /* PC-Relative relocations (calls and branches). */ /*---------------------------------------------------------------------*/ case R_C6000_PCR_S21: case R_C6000_PCR_S12: case R_C6000_PCR_S10: case R_C6000_PCR_S7: { /*------------------------------------------------------------------*/ /* Add SPC to segment address to get the PC. Mask for exec-packet */ /* boundary. */ /*------------------------------------------------------------------*/ int32_t opnd_p = (spc + segment_vaddr) & 0xffffffe0; reloc_value = symval + addend - opnd_p; break; } /*---------------------------------------------------------------------*/ /* "Place"-relative relocations (TDEH). */ /*---------------------------------------------------------------------*/ /* These relocations occur in data and refer to a label that occurs */ /* at some signed 32-bit offset from the place where the relocation */ /* occurs. */ /*---------------------------------------------------------------------*/ case R_C6000_PREL31: { /*------------------------------------------------------------------*/ /* Compute location of relocation entry and subtract it from the */ /* address of the location being referenced (it is computed very */ /* much like a PC-relative relocation, but it occurs in data and */ /* is called a "place"-relative relocation). */ /*------------------------------------------------------------------*/ /* If this is an Elf32_Rel type relocation, then addend is assumed */ /* to have been scaled when it was unpacked (field << 1). */ /*------------------------------------------------------------------*/ /* For Elf32_Rela type relocations the addend is assumed to be a */ /* signed 32-bit integer value. */ /*------------------------------------------------------------------*/ /* Offset is not fetch-packet relative; doesn't need to be masked. */ /*------------------------------------------------------------------*/ int32_t opnd_p = (spc + segment_vaddr); reloc_value = symval + addend - opnd_p; break; } /*---------------------------------------------------------------------*/ /* Static-Base Relative relocations (near-DP). */ /*---------------------------------------------------------------------*/ case R_C6000_SBR_U15_B: case R_C6000_SBR_U15_H: case R_C6000_SBR_U15_W: case R_C6000_SBR_S16: case R_C6000_SBR_L16_B: case R_C6000_SBR_L16_H: case R_C6000_SBR_L16_W: case R_C6000_SBR_H16_B: case R_C6000_SBR_H16_H: case R_C6000_SBR_H16_W: reloc_value = symval + addend - base_pointer; break; /*---------------------------------------------------------------------*/ /* R_C6000_DSBT_INDEX - uses value assigned by the dynamic loader to */ /* be the DSBT index for this module as a scaled offset when */ /* referencing the DSBT. The DSBT base address is in symval and the */ /* static base is in base_pointer. DP-relative offset to slot in */ /* DSBT is the offset of the DSBT relative to the DP plus the */ /* scaled DSBT index into the DSBT. */ /*---------------------------------------------------------------------*/ case R_C6000_DSBT_INDEX: reloc_value = ((symval + addend) - base_pointer) + (dsbt_index << 2); break; /*---------------------------------------------------------------------*/ /* Linux "import-as-own" copy relocation: after DSO initialization, */ /* copy the named object from the DSO into the executable's BSS */ /*---------------------------------------------------------------------*/ /* Linux "import-as-own" copy relocations are not yet supported. */ /*---------------------------------------------------------------------*/ case R_C6000_COPY: /*---------------------------------------------------------------------*/ /* Unrecognized relocation type. */ /*---------------------------------------------------------------------*/ default: DLIF_error(DLET_RELOC, "reloc_do called with invalid relocation type!\n"); break; } /*------------------------------------------------------------------------*/ /* Overflow checking. Is relocation value out of range for the size and */ /* type of the current relocation? */ /*------------------------------------------------------------------------*/ if (rel_overflow(r_type, reloc_value)) DLIF_error(DLET_RELOC, "relocation overflow!\n"); /*------------------------------------------------------------------------*/ /* Move relocation value to appropriate offset for relocation field's */ /* location. */ /*------------------------------------------------------------------------*/ reloc_value = pack_result(reloc_value, r_type); /*------------------------------------------------------------------------*/ /* Mask packed result to the size of the relocation field. */ /*------------------------------------------------------------------------*/ reloc_value = mask_result(reloc_value, r_type); /*------------------------------------------------------------------------*/ /* If necessary, Swap endianness of data at relocation address. */ /*------------------------------------------------------------------------*/ if (wrong_endian) DLIMP_change_endian32((int32_t*)(segment_buffer + spc)); /*------------------------------------------------------------------------*/ /* Write the relocated 4-byte packet back to the segment buffer. */ /*------------------------------------------------------------------------*/ write_reloc_r(segment_buffer, spc, r_type, reloc_value); /*------------------------------------------------------------------------*/ /* Change endianness of segment address back to original. */ /*------------------------------------------------------------------------*/ if (wrong_endian) DLIMP_change_endian32((int32_t*)(segment_buffer + spc)); #if LOADER_DEBUG || LOADER_PROFILE /*------------------------------------------------------------------------*/ /* In profile mode, add elapsed time for this relocation to total time */ /* spent doing relocations. */ /*------------------------------------------------------------------------*/ if (profiling_on) DLREL_total_reloc_time += (clock() - start_time); if (debugging_on) DLIF_trace("reloc_value = 0x%x\n", reloc_value); #endif } /*****************************************************************************/ /* REL_UNPACK_ADDEND() */ /* */ /* Unpack addend value from the relocation field. */ /* */ /*****************************************************************************/ static void rel_unpack_addend(C60_RELOC_TYPE r_type, uint8_t *address, uint32_t *addend) { /*------------------------------------------------------------------------*/ /* C6000 does not support Elf32_Rel type relocations in the dynamic */ /* loader core. We will emit an internal error and abort until this */ /* support is added. I abort here because this is necessarily a target- */ /* specific part of the relocation infrastructure. */ /*------------------------------------------------------------------------*/ *addend = 0; DLIF_error(DLET_RELOC, "Internal Error: unpacking addend values from the relocation " "field is not supported in the C6000 dynamic loader at this " "time; aborting\n"); DLIF_exit(1); } /*****************************************************************************/ /* REL_SWAP_ENDIAN() */ /* */ /* Return TRUE if we should change the endianness of a relocation field. */ /* */ /*****************************************************************************/ static BOOL rel_swap_endian(DLIMP_Dynamic_Module *dyn_module, C60_RELOC_TYPE r_type) { if (dyn_module->wrong_endian) return TRUE; return FALSE; } /*****************************************************************************/ /* REL_CHANGE_ENDIAN() */ /* */ /* Change the endianness of the relocation field at the specified address */ /* in the segment's data. */ /* */ /*****************************************************************************/ static void rel_change_endian(C60_RELOC_TYPE r_type, uint8_t *address) { /*------------------------------------------------------------------------*/ /* On C6000, all instructions are 32-bits wide. */ /*------------------------------------------------------------------------*/ DLIMP_change_endian32((int32_t *)address); } /*****************************************************************************/ /* READ_REL_TABLE() */ /* */ /* Read in an Elf32_Rel type relocation table. This function allocates */ /* host memory for the table. */ /* */ /*****************************************************************************/ static void read_rel_table(struct Elf32_Rel **rel_table, int32_t table_offset, uint32_t relnum, uint32_t relent, LOADER_FILE_DESC *fd, BOOL wrong_endian) { if (relnum == 0) { *rel_table = NULL; return; } *rel_table = (struct Elf32_Rel *)DLIF_malloc(relnum * relent); DLIF_fseek(fd, table_offset, LOADER_SEEK_SET); DLIF_fread(*rel_table, relnum, relent, fd); if (wrong_endian) { int i; for (i = 0; i < relnum; i++) DLIMP_change_rel_endian(*rel_table + i); } } /*****************************************************************************/ /* PROCESS_REL_TABLE() */ /* */ /* Process table of Elf32_Rel type relocations. */ /* */ /*****************************************************************************/ static void process_rel_table(DLOAD_HANDLE handle, DLIMP_Loaded_Segment* seg, struct Elf32_Rel *rel_table, uint32_t relnum, int32_t *start_relidx, uint32_t ti_static_base, DLIMP_Dynamic_Module* dyn_module) { Elf32_Addr seg_start_addr = seg->input_vaddr; Elf32_Addr seg_end_addr = seg_start_addr + seg->phdr.p_memsz; BOOL found = FALSE; int32_t relidx = *start_relidx; /*------------------------------------------------------------------------*/ /* If the given start reloc index is out of range, then start from the */ /* beginning of the given table. */ /*------------------------------------------------------------------------*/ if (relidx >= relnum) relidx = 0; /*------------------------------------------------------------------------*/ /* Spin through Elf32_Rel type relocation table. */ /*------------------------------------------------------------------------*/ for ( ; relidx < relnum; relidx++) { /*---------------------------------------------------------------------*/ /* If the relocation offset falls within the segment, process it. */ /*---------------------------------------------------------------------*/ if (rel_table[relidx].r_offset >= seg_start_addr && rel_table[relidx].r_offset < seg_end_addr) { Elf32_Addr r_symval = 0; C60_RELOC_TYPE r_type = (C60_RELOC_TYPE)ELF32_R_TYPE(rel_table[relidx].r_info); int32_t r_symid = ELF32_R_SYM(rel_table[relidx].r_info); uint8_t *reloc_address = NULL; uint32_t pc = 0; uint32_t addend = 0; BOOL change_endian = FALSE; found = TRUE; /*------------------------------------------------------------------*/ /* If symbol definition is not found, don't do the relocation. */ /* An error is generated by the lookup function. */ /*------------------------------------------------------------------*/ if (!DLSYM_canonical_lookup(handle, r_symid, dyn_module, &r_symval)) continue; /*------------------------------------------------------------------*/ /* Addend value is stored in the relocation field. */ /* We'll need to unpack it from the data for the segment that is */ /* currently being relocated. */ /*------------------------------------------------------------------*/ pc = rel_table[relidx].r_offset - seg->input_vaddr; reloc_address = (uint8_t *)seg->host_address + pc; change_endian = rel_swap_endian(dyn_module, r_type); if (change_endian) rel_change_endian(r_type, reloc_address); rel_unpack_addend( (C60_RELOC_TYPE)ELF32_R_TYPE(rel_table[relidx].r_info), reloc_address, &addend); /*------------------------------------------------------------------*/ /* Perform actual relocation. This is a really wide function */ /* interface and could do with some encapsulation. */ /*------------------------------------------------------------------*/ reloc_do(r_type, seg->phdr.p_vaddr, seg->host_address, addend, r_symval, pc, dyn_module->wrong_endian, ti_static_base, dyn_module->dsbt_index); } else if (found) break; } } /*****************************************************************************/ /* READ_RELA_TABLE() */ /* */ /* Read in an Elf32_Rela type relocation table. This function allocates */ /* host memory for the table. */ /* */ /*****************************************************************************/ static void read_rela_table(struct Elf32_Rela **rela_table, int32_t table_offset, uint32_t relanum, uint32_t relaent, LOADER_FILE_DESC *fd, BOOL wrong_endian) { if (relanum == 0) { *rela_table = NULL; return; } *rela_table = (struct Elf32_Rela *)DLIF_malloc(relanum * relaent); DLIF_fseek(fd, table_offset, LOADER_SEEK_SET); DLIF_fread(*rela_table, relanum, relaent, fd); if (wrong_endian) { int i; for (i = 0; i < relanum; i++) DLIMP_change_rela_endian(*rela_table + i); } } /*****************************************************************************/ /* PROCESS_RELA_TABLE() */ /* */ /* Process a table of Elf32_Rela type relocations. */ /* */ /*****************************************************************************/ static void process_rela_table(DLOAD_HANDLE handle, DLIMP_Loaded_Segment *seg, struct Elf32_Rela *rela_table, uint32_t relanum, int32_t *start_relidx, uint32_t ti_static_base, DLIMP_Dynamic_Module *dyn_module) { Elf32_Addr seg_start_addr = seg->input_vaddr; Elf32_Addr seg_end_addr = seg_start_addr + seg->phdr.p_memsz; BOOL found = FALSE; int32_t relidx = *start_relidx; /*-----------------------------------------------------------------------*/ /* If the given start reloc index is out of range, then start from */ /* the beginning of the given table. */ /*-----------------------------------------------------------------------*/ if (relidx > relanum) relidx = 0; /*-----------------------------------------------------------------------*/ /* Spin through RELA relocation table. */ /*-----------------------------------------------------------------------*/ for ( ; relidx < relanum; relidx++) { /*-------------------------------------------------------------------*/ /* If the relocation offset falls within the segment, process it. */ /*-------------------------------------------------------------------*/ if (rela_table[relidx].r_offset >= seg_start_addr && rela_table[relidx].r_offset < seg_end_addr) { Elf32_Addr r_symval; C60_RELOC_TYPE r_type = (C60_RELOC_TYPE)ELF32_R_TYPE(rela_table[relidx].r_info); int32_t r_symid = ELF32_R_SYM(rela_table[relidx].r_info); found = TRUE; /*---------------------------------------------------------------*/ /* If symbol definition is not found, don't do the relocation. */ /* An error is generated by the lookup function. */ /*---------------------------------------------------------------*/ if (!DLSYM_canonical_lookup(handle, r_symid, dyn_module, &r_symval)) continue; /*---------------------------------------------------------------*/ /* Perform actual relocation. This is a really wide function */ /* interface and could do with some encapsulation. */ /*---------------------------------------------------------------*/ reloc_do(r_type, seg->phdr.p_vaddr, seg->host_address, rela_table[relidx].r_addend, r_symval, rela_table[relidx].r_offset - seg->input_vaddr, dyn_module->wrong_endian, ti_static_base, dyn_module->dsbt_index); } else if (found) break; } } /*****************************************************************************/ /* PROCESS_GOT_RELOCS() */ /* */ /* Process all GOT relocations. It is possible to have both Elf32_Rel */ /* and Elf32_Rela type relocations in the same file, so we handle tham */ /* both. */ /* */ /*****************************************************************************/ static void process_got_relocs(DLOAD_HANDLE handle, struct Elf32_Rel* rel_table, uint32_t relnum, struct Elf32_Rela* rela_table, uint32_t relanum, DLIMP_Dynamic_Module* dyn_module) { DLIMP_Loaded_Segment *seg = (DLIMP_Loaded_Segment*)(dyn_module->loaded_module->loaded_segments.buf); uint32_t num_segs = dyn_module->loaded_module->loaded_segments.size; int32_t rel_relidx = 0; int32_t rela_relidx = 0; uint32_t seg_idx = 0; uint32_t ti_static_base = 0; /*------------------------------------------------------------------------*/ /* Get the value of the static base (__TI_STATIC_BASE) which will be */ /* passed into the relocation table processing functions. */ /*------------------------------------------------------------------------*/ if (!DLSYM_lookup_local_symtab("__TI_STATIC_BASE", dyn_module->symtab, dyn_module->symnum, &ti_static_base)) DLIF_error(DLET_RELOC, "Could not resolve value of __TI_STATIC_BASE\n"); /*------------------------------------------------------------------------*/ /* Process relocations segment by segment. */ /*------------------------------------------------------------------------*/ for (seg_idx = 0; seg_idx < num_segs; seg_idx++) { /*---------------------------------------------------------------------*/ /* Relocations should not occur in uninitialized segments. */ /*---------------------------------------------------------------------*/ if (!seg[seg_idx].phdr.p_filesz) continue; if (rela_table) process_rela_table(handle, (seg + seg_idx), rela_table, relanum, &rela_relidx, ti_static_base, dyn_module); if (rel_table) process_rel_table(handle, (seg + seg_idx), rel_table, relnum, &rel_relidx, ti_static_base, dyn_module); } } /*****************************************************************************/ /* PROCESS_PLTGOT_RELOCS() */ /* */ /* Process all PLTGOT relocation entries. The PLTGOT relocation table */ /* can be either Elf32_Rel or Elf32_Rela type. All PLTGOT relocations */ /* ar guaranteed to belong to the same segment. */ /* */ /*****************************************************************************/ static void process_pltgot_relocs(DLOAD_HANDLE handle, void* plt_reloc_table, int reltype, uint32_t pltnum, DLIMP_Dynamic_Module* dyn_module) { Elf32_Addr r_offset = (reltype == DT_REL) ? ((struct Elf32_Rel *)plt_reloc_table)->r_offset : ((struct Elf32_Rela *)plt_reloc_table)->r_offset; DLIMP_Loaded_Segment* seg = (DLIMP_Loaded_Segment*)(dyn_module->loaded_module->loaded_segments.buf); uint32_t num_segs = dyn_module->loaded_module->loaded_segments.size; int32_t plt_relidx = 0; uint32_t seg_idx = 0; uint32_t ti_static_base = 0; /*------------------------------------------------------------------------*/ /* Get the value of the static base (__TI_STATIC_BASE) which will be */ /* passed into the relocation table processing functions. */ /*------------------------------------------------------------------------*/ if (!DLSYM_lookup_local_symtab("__TI_STATIC_BASE", dyn_module->symtab, dyn_module->symnum, &ti_static_base)) DLIF_error(DLET_RELOC, "Could not resolve value of __TI_STATIC_BASE\n"); /*------------------------------------------------------------------------*/ /* For each segment s, check if the relocation falls within s. If so, */ /* then all other relocations are guaranteed to fall with s. Process */ /* all relocations and then return. */ /*------------------------------------------------------------------------*/ for (seg_idx = 0; seg_idx < num_segs; seg_idx++) { Elf32_Addr seg_start_addr = seg[seg_idx].input_vaddr; Elf32_Addr seg_end_addr = seg_start_addr + seg[seg_idx].phdr.p_memsz; /*---------------------------------------------------------------------*/ /* Relocations should not occur in uninitialized segments. */ /*---------------------------------------------------------------------*/ if(!seg[seg_idx].phdr.p_filesz) continue; if (r_offset >= seg_start_addr && r_offset < seg_end_addr) { if (reltype == DT_REL) process_rel_table(handle, (seg + seg_idx), (struct Elf32_Rel *)plt_reloc_table, pltnum, &plt_relidx, ti_static_base, dyn_module); else process_rela_table(handle, (seg + seg_idx), (struct Elf32_Rela *)plt_reloc_table, pltnum, &plt_relidx, ti_static_base, dyn_module); break; } } } /*****************************************************************************/ /* RELOCATE() - Perform RELA and REL type relocations for given ELF object */ /* file that we are in the process of loading and relocating. */ /*****************************************************************************/ void DLREL_c60_relocate(DLOAD_HANDLE handle, LOADER_FILE_DESC *fd, DLIMP_Dynamic_Module *dyn_module) { struct Elf32_Dyn *dyn_nugget = dyn_module->dyntab; struct Elf32_Rela *rela_table = NULL; struct Elf32_Rel *rel_table = NULL; struct Elf32_Rela *rela_plt_table = NULL; struct Elf32_Rel *rel_plt_table = NULL; /*------------------------------------------------------------------------*/ /* Read the size of the relocation table (DT_RELASZ) and the size per */ /* relocation (DT_RELAENT) from the dynamic segment. */ /*------------------------------------------------------------------------*/ uint32_t relasz = DLIMP_get_first_dyntag(DT_RELASZ, dyn_nugget); uint32_t relaent = DLIMP_get_first_dyntag(DT_RELAENT, dyn_nugget); uint32_t relanum = 0; /*------------------------------------------------------------------------*/ /* Read the size of the relocation table (DT_RELSZ) and the size per */ /* relocation (DT_RELENT) from the dynamic segment. */ /*------------------------------------------------------------------------*/ uint32_t relsz = DLIMP_get_first_dyntag(DT_RELSZ, dyn_nugget); uint32_t relent = DLIMP_get_first_dyntag(DT_RELENT, dyn_nugget); uint32_t relnum = 0; /*------------------------------------------------------------------------*/ /* Read the size of the relocation table (DT_PLTRELSZ) and the type of */ /* of the PLTGOT relocation table (DT_PLTREL): one of DT_REL or DT_RELA */ /*------------------------------------------------------------------------*/ uint32_t pltrelsz = DLIMP_get_first_dyntag(DT_PLTRELSZ, dyn_nugget); int pltreltyp = DLIMP_get_first_dyntag(DT_PLTREL, dyn_nugget); uint32_t pltnum = 0; /*------------------------------------------------------------------------*/ /* Find/record DSBT index associated with this module. */ /*------------------------------------------------------------------------*/ if (is_dsbt_module(dyn_module) && (dyn_module->dsbt_index == DSBT_INDEX_INVALID)) dyn_module->dsbt_index = DLIF_get_dsbt_index(dyn_module->loaded_module->file_handle); /*------------------------------------------------------------------------*/ /* Read the PLTGOT relocation table from the file */ /* The PLTGOT table is a subsection at the end of either the DT_REL or */ /* DT_RELA table. The size of the table it belongs to DT_REL(A)SZ */ /* includes the size of the PLTGOT table. So it must be adjusted so that */ /* the GOT relocation tables only contain actual GOT relocations. */ /*------------------------------------------------------------------------*/ if (pltrelsz != INT_MAX && pltrelsz != 0) { if (pltreltyp == DT_REL) { pltnum = pltrelsz/relent; relsz -= pltrelsz; read_rel_table((&rel_plt_table), DLIMP_get_first_dyntag(DT_JMPREL, dyn_nugget), pltnum, relent, fd, dyn_module->wrong_endian); } else if (pltreltyp == DT_RELA) { pltnum = pltrelsz/relaent; relasz -= pltrelsz; read_rela_table((&rela_plt_table), DLIMP_get_first_dyntag(DT_JMPREL, dyn_nugget), pltnum, relaent, fd, dyn_module->wrong_endian); } else { DLIF_error(DLET_RELOC, "DT_PLTREL is invalid: must be either %d or %d\n", DT_REL, DT_RELA); } } /*------------------------------------------------------------------------*/ /* Read the DT_RELA GOT relocation table from the file */ /*------------------------------------------------------------------------*/ if (relasz != INT_MAX && relasz != 0) { relanum = relasz/relaent; read_rela_table(&rela_table, DLIMP_get_first_dyntag(DT_RELA, dyn_nugget), relanum, relaent, fd, dyn_module->wrong_endian); } /*------------------------------------------------------------------------*/ /* Read the DT_REL GOT relocation table from the file */ /*------------------------------------------------------------------------*/ if (relsz != INT_MAX && relsz != 0) { relnum = relsz/relent; read_rel_table(&rel_table, DLIMP_get_first_dyntag(DT_REL, dyn_nugget), relnum, relent, fd, dyn_module->wrong_endian); } /*------------------------------------------------------------------------*/ /* Process the PLTGOT relocations */ /*------------------------------------------------------------------------*/ if (rela_plt_table) process_pltgot_relocs(handle, rela_plt_table, pltreltyp, pltnum, dyn_module); if (rel_plt_table) process_pltgot_relocs(handle, rel_plt_table, pltreltyp, pltnum, dyn_module); /*------------------------------------------------------------------------*/ /* Process the GOT relocations */ /*------------------------------------------------------------------------*/ if (rel_table || rela_table) process_got_relocs(handle, rel_table, relnum, rela_table, relanum, dyn_module); /*-------------------------------------------------------------------------*/ /* Free memory used for ELF relocation table copies. */ /*-------------------------------------------------------------------------*/ if (rela_table) DLIF_free(rela_table); if (rel_table) DLIF_free(rel_table); if (rela_plt_table) DLIF_free(rela_plt_table); if (rel_plt_table) DLIF_free(rel_plt_table); } /*****************************************************************************/ /* UNIT TESTING INTERFACE */ /*****************************************************************************/ #ifdef UNIT_TEST void unit_c60_reloc_do(C60_RELOC_TYPE r_type, uint8_t *address_space, uint32_t addend, uint32_t symval, uint32_t pc, uint32_t static_base, int wrong_endian, int32_t dsbt_index) { reloc_do(r_type, (uint32_t)address_space, address_space, addend, symval, pc, FALSE, static_base, dsbt_index); } #if 0 /* RELA TYPE RELOCATIONS HAVE ADDEND IN RELOCATION ENTRY */ void unit_c60_rel_unpack_addend(C60_RELOC_TYPE r_type, uint8_t* address, uint32_t* addend) { rel_unpack_addend(r_type, address, addend); } #endif BOOL unit_c60_rel_overflow(C60_RELOC_TYPE r_type, int32_t reloc_value) { return rel_overflow(r_type, reloc_value); } #endif