|  | /* Ubicom IP2xxx specific support for 32-bit ELF | 
|  | Copyright (C) 2000-2024 Free Software Foundation, Inc. | 
|  |  | 
|  | This file is part of BFD, the Binary File Descriptor library. | 
|  |  | 
|  | This program is free software; you can redistribute it and/or modify | 
|  | it under the terms of the GNU General Public License as published by | 
|  | the Free Software Foundation; either version 3 of the License, or | 
|  | (at your option) any later version. | 
|  |  | 
|  | This program is distributed in the hope that it will be useful, | 
|  | but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | GNU General Public License for more details. | 
|  |  | 
|  | You should have received a copy of the GNU General Public License | 
|  | along with this program; if not, write to the Free Software | 
|  | Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, | 
|  | MA 02110-1301, USA.  */ | 
|  |  | 
|  | #include "sysdep.h" | 
|  | #include "bfd.h" | 
|  | #include "libbfd.h" | 
|  | #include "elf-bfd.h" | 
|  | #include "elf/ip2k.h" | 
|  |  | 
|  | /* Struct used to pass miscellaneous paramaters which | 
|  | helps to avoid overly long parameter lists.  */ | 
|  | struct misc | 
|  | { | 
|  | Elf_Internal_Shdr *  symtab_hdr; | 
|  | Elf_Internal_Rela *  irelbase; | 
|  | bfd_byte *	       contents; | 
|  | Elf_Internal_Sym *   isymbuf; | 
|  | }; | 
|  |  | 
|  | struct ip2k_opcode | 
|  | { | 
|  | unsigned short opcode; | 
|  | unsigned short mask; | 
|  | }; | 
|  |  | 
|  | static bool ip2k_relaxed = false; | 
|  |  | 
|  | static const struct ip2k_opcode ip2k_page_opcode[] = | 
|  | { | 
|  | {0x0010, 0xFFF8},	/* Page.  */ | 
|  | {0x0000, 0x0000}, | 
|  | }; | 
|  |  | 
|  | #define IS_PAGE_OPCODE(code) \ | 
|  | ip2k_is_opcode (code, ip2k_page_opcode) | 
|  |  | 
|  | static const struct ip2k_opcode ip2k_jmp_opcode[] = | 
|  | { | 
|  | {0xE000, 0xE000},	/* Jmp.  */ | 
|  | {0x0000, 0x0000}, | 
|  | }; | 
|  |  | 
|  | #define IS_JMP_OPCODE(code) \ | 
|  | ip2k_is_opcode (code, ip2k_jmp_opcode) | 
|  |  | 
|  | static const struct ip2k_opcode ip2k_snc_opcode[] = | 
|  | { | 
|  | {0xA00B, 0xFFFF},	/* Snc.  */ | 
|  | {0x0000, 0x0000}, | 
|  | }; | 
|  |  | 
|  | #define IS_SNC_OPCODE(code) \ | 
|  | ip2k_is_opcode (code, ip2k_snc_opcode) | 
|  |  | 
|  | static const struct ip2k_opcode ip2k_inc_1sp_opcode[] = | 
|  | { | 
|  | {0x2B81, 0xFFFF},	/* Inc 1(SP).  */ | 
|  | {0x0000, 0x0000}, | 
|  | }; | 
|  |  | 
|  | #define IS_INC_1SP_OPCODE(code) \ | 
|  | ip2k_is_opcode (code, ip2k_inc_1sp_opcode) | 
|  |  | 
|  | static const struct ip2k_opcode ip2k_add_2sp_w_opcode[] = | 
|  | { | 
|  | {0x1F82, 0xFFFF},	/* Add 2(SP),w.  */ | 
|  | {0x0000, 0x0000}, | 
|  | }; | 
|  |  | 
|  | #define IS_ADD_2SP_W_OPCODE(code) \ | 
|  | ip2k_is_opcode (code, ip2k_add_2sp_w_opcode) | 
|  |  | 
|  | static const struct ip2k_opcode ip2k_add_w_wreg_opcode[] = | 
|  | { | 
|  | {0x1C0A, 0xFFFF},	/* Add w,wreg.  */ | 
|  | {0x1E0A, 0xFFFF},	/* Add wreg,w.  */ | 
|  | {0x0000, 0x0000}, | 
|  | }; | 
|  |  | 
|  | #define IS_ADD_W_WREG_OPCODE(code) \ | 
|  | ip2k_is_opcode (code, ip2k_add_w_wreg_opcode) | 
|  |  | 
|  | static const struct ip2k_opcode ip2k_add_pcl_w_opcode[] = | 
|  | { | 
|  | {0x1E09, 0xFFFF},	/* Add pcl,w.  */ | 
|  | {0x0000, 0x0000}, | 
|  | }; | 
|  |  | 
|  | #define IS_ADD_PCL_W_OPCODE(code) \ | 
|  | ip2k_is_opcode (code, ip2k_add_pcl_w_opcode) | 
|  |  | 
|  | static const struct ip2k_opcode ip2k_skip_opcodes[] = | 
|  | { | 
|  | {0xB000, 0xF000},	/* sb */ | 
|  | {0xA000, 0xF000},	/* snb */ | 
|  | {0x7600, 0xFE00},	/* cse/csne #lit */ | 
|  | {0x5800, 0xFC00},	/* incsnz */ | 
|  | {0x4C00, 0xFC00},	/* decsnz */ | 
|  | {0x4000, 0xFC00},	/* cse/csne */ | 
|  | {0x3C00, 0xFC00},	/* incsz */ | 
|  | {0x2C00, 0xFC00},	/* decsz */ | 
|  | {0x0000, 0x0000}, | 
|  | }; | 
|  |  | 
|  | #define IS_SKIP_OPCODE(code) \ | 
|  | ip2k_is_opcode (code, ip2k_skip_opcodes) | 
|  |  | 
|  | /* Relocation tables.  */ | 
|  | static reloc_howto_type ip2k_elf_howto_table [] = | 
|  | { | 
|  | #define IP2K_HOWTO(t,rs,s,bs,pr,bp,name,sm,dm) \ | 
|  | HOWTO(t,			/* type */ \ | 
|  | rs,			/* rightshift */ \ | 
|  | s,			/* size (0 = byte, 1 = short, 2 = long) */ \ | 
|  | bs,			/* bitsize */ \ | 
|  | pr,			/* pc_relative */ \ | 
|  | bp,			/* bitpos */ \ | 
|  | complain_overflow_dont,/* complain_on_overflow */ \ | 
|  | bfd_elf_generic_reloc,/* special_function */ \ | 
|  | name,			/* name */ \ | 
|  | false,		/* partial_inplace */ \ | 
|  | sm,			/* src_mask */ \ | 
|  | dm,			/* dst_mask */ \ | 
|  | pr)			/* pcrel_offset */ | 
|  |  | 
|  | /* This reloc does nothing.  */ | 
|  | IP2K_HOWTO (R_IP2K_NONE, 0,0,0, false, 0, "R_IP2K_NONE", 0, 0), | 
|  | /* A 16 bit absolute relocation.  */ | 
|  | IP2K_HOWTO (R_IP2K_16, 0,2,16, false, 0, "R_IP2K_16", 0, 0xffff), | 
|  | /* A 32 bit absolute relocation.  */ | 
|  | IP2K_HOWTO (R_IP2K_32, 0,4,32, false, 0, "R_IP2K_32", 0, 0xffffffff), | 
|  | /* A 8-bit data relocation for the FR9 field.  Ninth bit is computed specially.  */ | 
|  | IP2K_HOWTO (R_IP2K_FR9, 0,2,9, false, 0, "R_IP2K_FR9", 0, 0x00ff), | 
|  | /* A 4-bit data relocation.  */ | 
|  | IP2K_HOWTO (R_IP2K_BANK, 8,2,4, false, 0, "R_IP2K_BANK", 0, 0x000f), | 
|  | /* A 13-bit insn relocation - word address => right-shift 1 bit extra.  */ | 
|  | IP2K_HOWTO (R_IP2K_ADDR16CJP, 1,2,13, false, 0, "R_IP2K_ADDR16CJP", 0, 0x1fff), | 
|  | /* A 3-bit insn relocation - word address => right-shift 1 bit extra.  */ | 
|  | IP2K_HOWTO (R_IP2K_PAGE3, 14,2,3, false, 0, "R_IP2K_PAGE3", 0, 0x0007), | 
|  | /* Two 8-bit data relocations.  */ | 
|  | IP2K_HOWTO (R_IP2K_LO8DATA, 0,2,8, false, 0, "R_IP2K_LO8DATA", 0, 0x00ff), | 
|  | IP2K_HOWTO (R_IP2K_HI8DATA, 8,2,8, false, 0, "R_IP2K_HI8DATA", 0, 0x00ff), | 
|  | /* Two 8-bit insn relocations.  word address => right-shift 1 bit extra.  */ | 
|  | IP2K_HOWTO (R_IP2K_LO8INSN, 1,2,8, false, 0, "R_IP2K_LO8INSN", 0, 0x00ff), | 
|  | IP2K_HOWTO (R_IP2K_HI8INSN, 9,2,8, false, 0, "R_IP2K_HI8INSN", 0, 0x00ff), | 
|  |  | 
|  | /* Special 1 bit relocation for SKIP instructions.  */ | 
|  | IP2K_HOWTO (R_IP2K_PC_SKIP, 1,2,1, false, 12, "R_IP2K_PC_SKIP", 0xfffe, 0x1000), | 
|  | /* 16 bit word address.  */ | 
|  | IP2K_HOWTO (R_IP2K_TEXT, 1,2,16, false, 0, "R_IP2K_TEXT", 0, 0xffff), | 
|  | /* A 7-bit offset relocation for the FR9 field.  Eigth and ninth bit comes from insn.  */ | 
|  | IP2K_HOWTO (R_IP2K_FR_OFFSET, 0,2,9, false, 0, "R_IP2K_FR_OFFSET", 0x180, 0x007f), | 
|  | /* Bits 23:16 of an address.  */ | 
|  | IP2K_HOWTO (R_IP2K_EX8DATA, 16,2,8, false, 0, "R_IP2K_EX8DATA", 0, 0x00ff), | 
|  | }; | 
|  |  | 
|  |  | 
|  | /* Map BFD reloc types to IP2K ELF reloc types.  */ | 
|  |  | 
|  | static reloc_howto_type * | 
|  | ip2k_reloc_type_lookup (bfd * abfd ATTRIBUTE_UNUSED, | 
|  | bfd_reloc_code_real_type code) | 
|  | { | 
|  | /* Note that the ip2k_elf_howto_table is indxed by the R_ | 
|  | constants.  Thus, the order that the howto records appear in the | 
|  | table *must* match the order of the relocation types defined in | 
|  | include/elf/ip2k.h.  */ | 
|  |  | 
|  | switch (code) | 
|  | { | 
|  | case BFD_RELOC_NONE: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_NONE]; | 
|  | case BFD_RELOC_16: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_16]; | 
|  | case BFD_RELOC_32: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_32]; | 
|  | case BFD_RELOC_IP2K_FR9: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_FR9]; | 
|  | case BFD_RELOC_IP2K_BANK: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_BANK]; | 
|  | case BFD_RELOC_IP2K_ADDR16CJP: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_ADDR16CJP]; | 
|  | case BFD_RELOC_IP2K_PAGE3: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_PAGE3]; | 
|  | case BFD_RELOC_IP2K_LO8DATA: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_LO8DATA]; | 
|  | case BFD_RELOC_IP2K_HI8DATA: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_HI8DATA]; | 
|  | case BFD_RELOC_IP2K_LO8INSN: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_LO8INSN]; | 
|  | case BFD_RELOC_IP2K_HI8INSN: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_HI8INSN]; | 
|  | case BFD_RELOC_IP2K_PC_SKIP: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_PC_SKIP]; | 
|  | case BFD_RELOC_IP2K_TEXT: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_TEXT]; | 
|  | case BFD_RELOC_IP2K_FR_OFFSET: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_FR_OFFSET]; | 
|  | case BFD_RELOC_IP2K_EX8DATA: | 
|  | return &ip2k_elf_howto_table[ (int) R_IP2K_EX8DATA]; | 
|  | default: | 
|  | /* Pacify gcc -Wall.  */ | 
|  | return NULL; | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static reloc_howto_type * | 
|  | ip2k_reloc_name_lookup (bfd *abfd ATTRIBUTE_UNUSED, const char *r_name) | 
|  | { | 
|  | unsigned int i; | 
|  |  | 
|  | for (i = 0; | 
|  | i < sizeof (ip2k_elf_howto_table) / sizeof (ip2k_elf_howto_table[0]); | 
|  | i++) | 
|  | if (ip2k_elf_howto_table[i].name != NULL | 
|  | && strcasecmp (ip2k_elf_howto_table[i].name, r_name) == 0) | 
|  | return &ip2k_elf_howto_table[i]; | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static void | 
|  | ip2k_get_mem (bfd *abfd ATTRIBUTE_UNUSED, | 
|  | bfd_byte *addr, | 
|  | int length, | 
|  | bfd_byte *ptr) | 
|  | { | 
|  | while (length --) | 
|  | * ptr ++ = bfd_get_8 (abfd, addr ++); | 
|  | } | 
|  |  | 
|  | static bool | 
|  | ip2k_is_opcode (bfd_byte *code, const struct ip2k_opcode *opcodes) | 
|  | { | 
|  | unsigned short insn = (code[0] << 8) | code[1]; | 
|  |  | 
|  | while (opcodes->mask != 0) | 
|  | { | 
|  | if ((insn & opcodes->mask) == opcodes->opcode) | 
|  | return true; | 
|  |  | 
|  | opcodes ++; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | #define PAGENO(ABSADDR) ((ABSADDR) & 0xFFFFC000) | 
|  | #define BASEADDR(SEC)	((SEC)->output_section->vma + (SEC)->output_offset) | 
|  |  | 
|  | #define UNDEFINED_SYMBOL (~(bfd_vma)0) | 
|  |  | 
|  | /* Return the value of the symbol associated with the relocation IREL.  */ | 
|  |  | 
|  | static bfd_vma | 
|  | symbol_value (bfd *abfd, | 
|  | Elf_Internal_Shdr *symtab_hdr, | 
|  | Elf_Internal_Sym *isymbuf, | 
|  | Elf_Internal_Rela *irel) | 
|  | { | 
|  | if (ELF32_R_SYM (irel->r_info) < symtab_hdr->sh_info) | 
|  | { | 
|  | Elf_Internal_Sym *isym; | 
|  | asection *sym_sec; | 
|  |  | 
|  | isym = isymbuf + ELF32_R_SYM (irel->r_info); | 
|  | if (isym->st_shndx == SHN_UNDEF) | 
|  | sym_sec = bfd_und_section_ptr; | 
|  | else if (isym->st_shndx == SHN_ABS) | 
|  | sym_sec = bfd_abs_section_ptr; | 
|  | else if (isym->st_shndx == SHN_COMMON) | 
|  | sym_sec = bfd_com_section_ptr; | 
|  | else | 
|  | sym_sec = bfd_section_from_elf_index (abfd, isym->st_shndx); | 
|  |  | 
|  | return isym->st_value + BASEADDR (sym_sec); | 
|  | } | 
|  | else | 
|  | { | 
|  | unsigned long indx; | 
|  | struct elf_link_hash_entry *h; | 
|  |  | 
|  | indx = ELF32_R_SYM (irel->r_info) - symtab_hdr->sh_info; | 
|  | h = elf_sym_hashes (abfd)[indx]; | 
|  | BFD_ASSERT (h != NULL); | 
|  |  | 
|  | if (h->root.type != bfd_link_hash_defined | 
|  | && h->root.type != bfd_link_hash_defweak) | 
|  | return UNDEFINED_SYMBOL; | 
|  |  | 
|  | return (h->root.u.def.value + BASEADDR (h->root.u.def.section)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Determine if the instruction sequence matches that for | 
|  | the prologue of a switch dispatch table with fewer than | 
|  | 128 entries. | 
|  |  | 
|  | sc | 
|  | page	  $nnn0 | 
|  | jmp	  $nnn0 | 
|  | add	  w,wreg | 
|  | add	  pcl,w | 
|  | addr=> | 
|  | page	  $nnn1 | 
|  | jmp	  $nnn1 | 
|  | page	   $nnn2 | 
|  | jmp	   $nnn2 | 
|  | ... | 
|  | page	   $nnnN | 
|  | jmp	   $nnnN | 
|  |  | 
|  | After relaxation. | 
|  | sc | 
|  | page	   $nnn0 | 
|  | jmp	   $nnn0 | 
|  | add	   pcl,w | 
|  | addr=> | 
|  | jmp	   $nnn1 | 
|  | jmp	   $nnn2 | 
|  | ... | 
|  | jmp	  $nnnN  */ | 
|  |  | 
|  | static int | 
|  | ip2k_is_switch_table_128 (bfd *abfd ATTRIBUTE_UNUSED, | 
|  | asection *sec, | 
|  | bfd_vma addr, | 
|  | bfd_byte *contents) | 
|  | { | 
|  | bfd_byte code[4]; | 
|  | int table_index = 0; | 
|  |  | 
|  | /* Check current page-jmp.  */ | 
|  | if (addr + 4 > sec->size) | 
|  | return -1; | 
|  |  | 
|  | ip2k_get_mem (abfd, contents + addr, 4, code); | 
|  |  | 
|  | if ((! IS_PAGE_OPCODE (code + 0)) | 
|  | || (! IS_JMP_OPCODE (code + 2))) | 
|  | return -1; | 
|  |  | 
|  | /* Search back.  */ | 
|  | while (1) | 
|  | { | 
|  | if (addr < 4) | 
|  | return -1; | 
|  |  | 
|  | /* Check previous 2 instructions.  */ | 
|  | ip2k_get_mem (abfd, contents + addr - 4, 4, code); | 
|  | if ((IS_ADD_W_WREG_OPCODE (code + 0)) | 
|  | && (IS_ADD_PCL_W_OPCODE (code + 2))) | 
|  | return table_index; | 
|  |  | 
|  | if ((! IS_PAGE_OPCODE (code + 0)) | 
|  | || (! IS_JMP_OPCODE (code + 2))) | 
|  | return -1; | 
|  |  | 
|  | table_index++; | 
|  | addr -= 4; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Determine if the instruction sequence matches that for | 
|  | the prologue switch dispatch table with fewer than | 
|  | 256 entries but more than 127. | 
|  |  | 
|  | Before relaxation. | 
|  | push	  %lo8insn(label) ; Push address of table | 
|  | push	  %hi8insn(label) | 
|  | add	  w,wreg	  ; index*2 => offset | 
|  | snc			  ; CARRY SET? | 
|  | inc	  1(sp)		  ; Propagate MSB into table address | 
|  | add	  2(sp),w	  ; Add low bits of offset to table address | 
|  | snc			  ; and handle any carry-out | 
|  | inc	  1(sp) | 
|  | addr=> | 
|  | page	  __indjmp	  ; Do an indirect jump to that location | 
|  | jmp	  __indjmp | 
|  | label:			  ; case dispatch table starts here | 
|  | page	   $nnn1 | 
|  | jmp	   $nnn1 | 
|  | page	   $nnn2 | 
|  | jmp	   $nnn2 | 
|  | ... | 
|  | page	   $nnnN | 
|  | jmp	   $nnnN | 
|  |  | 
|  | After relaxation. | 
|  | push	  %lo8insn(label) ; Push address of table | 
|  | push	  %hi8insn(label) | 
|  | add	  2(sp),w	  ; Add low bits of offset to table address | 
|  | snc			  ; and handle any carry-out | 
|  | inc	  1(sp) | 
|  | addr=> | 
|  | page	  __indjmp	  ; Do an indirect jump to that location | 
|  | jmp	  __indjmp | 
|  | label:			  ; case dispatch table starts here | 
|  | jmp	  $nnn1 | 
|  | jmp	  $nnn2 | 
|  | ... | 
|  | jmp	  $nnnN  */ | 
|  |  | 
|  | static int | 
|  | ip2k_is_switch_table_256 (bfd *abfd ATTRIBUTE_UNUSED, | 
|  | asection *sec, | 
|  | bfd_vma addr, | 
|  | bfd_byte *contents) | 
|  | { | 
|  | bfd_byte code[16]; | 
|  | int table_index = 0; | 
|  |  | 
|  | /* Check current page-jmp.  */ | 
|  | if (addr + 4 > sec->size) | 
|  | return -1; | 
|  |  | 
|  | ip2k_get_mem (abfd, contents + addr, 4, code); | 
|  | if ((! IS_PAGE_OPCODE (code + 0)) | 
|  | || (! IS_JMP_OPCODE (code + 2))) | 
|  | return -1; | 
|  |  | 
|  | /* Search back.  */ | 
|  | while (1) | 
|  | { | 
|  | if (addr < 16) | 
|  | return -1; | 
|  |  | 
|  | /* Check previous 8 instructions.  */ | 
|  | ip2k_get_mem (abfd, contents + addr - 16, 16, code); | 
|  | if ((IS_ADD_W_WREG_OPCODE (code + 0)) | 
|  | && (IS_SNC_OPCODE (code + 2)) | 
|  | && (IS_INC_1SP_OPCODE (code + 4)) | 
|  | && (IS_ADD_2SP_W_OPCODE (code + 6)) | 
|  | && (IS_SNC_OPCODE (code + 8)) | 
|  | && (IS_INC_1SP_OPCODE (code + 10)) | 
|  | && (IS_PAGE_OPCODE (code + 12)) | 
|  | && (IS_JMP_OPCODE (code + 14))) | 
|  | return table_index; | 
|  |  | 
|  | if ((IS_ADD_W_WREG_OPCODE (code + 2)) | 
|  | && (IS_SNC_OPCODE (code + 4)) | 
|  | && (IS_INC_1SP_OPCODE (code + 6)) | 
|  | && (IS_ADD_2SP_W_OPCODE (code + 8)) | 
|  | && (IS_SNC_OPCODE (code + 10)) | 
|  | && (IS_INC_1SP_OPCODE (code + 12)) | 
|  | && (IS_JMP_OPCODE (code + 14))) | 
|  | return table_index; | 
|  |  | 
|  | if ((! IS_PAGE_OPCODE (code + 0)) | 
|  | || (! IS_JMP_OPCODE (code + 2))) | 
|  | return -1; | 
|  |  | 
|  | table_index++; | 
|  | addr -= 4; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Returns the expected page state for the given instruction not including | 
|  | the effect of page instructions.  */ | 
|  |  | 
|  | static bfd_vma | 
|  | ip2k_nominal_page_bits (bfd *abfd ATTRIBUTE_UNUSED, | 
|  | asection *sec, | 
|  | bfd_vma addr, | 
|  | bfd_byte *contents) | 
|  | { | 
|  | bfd_vma page = PAGENO (BASEADDR (sec) + addr); | 
|  |  | 
|  | /* Check if section flows into this page. If not then the page | 
|  | bits are assumed to match the PC. This will be true unless | 
|  | the user has a page instruction without a call/jump, in which | 
|  | case they are on their own.  */ | 
|  | if (PAGENO (BASEADDR (sec)) == page) | 
|  | return page; | 
|  |  | 
|  | /* Section flows across page boundary. The page bits should match | 
|  | the PC unless there is a possible flow from the previous page, | 
|  | in which case it is not possible to determine the value of the | 
|  | page bits.  */ | 
|  | while (PAGENO (BASEADDR (sec) + addr - 2) == page) | 
|  | { | 
|  | bfd_byte code[2]; | 
|  |  | 
|  | addr -= 2; | 
|  | ip2k_get_mem (abfd, contents + addr, 2, code); | 
|  | if (!IS_PAGE_OPCODE (code)) | 
|  | continue; | 
|  |  | 
|  | /* Found a page instruction, check if jump table.  */ | 
|  | if (ip2k_is_switch_table_128 (abfd, sec, addr, contents) != -1) | 
|  | /* Jump table => page is conditional.  */ | 
|  | continue; | 
|  |  | 
|  | if (ip2k_is_switch_table_256 (abfd, sec, addr, contents) != -1) | 
|  | /* Jump table => page is conditional.  */ | 
|  | continue; | 
|  |  | 
|  | /* Found a page instruction, check if conditional.  */ | 
|  | if (addr >= 2) | 
|  | { | 
|  | ip2k_get_mem (abfd, contents + addr - 2, 2, code); | 
|  | if (IS_SKIP_OPCODE (code)) | 
|  | /* Page is conditional.  */ | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* Unconditional page instruction => page bits should be correct.  */ | 
|  | return page; | 
|  | } | 
|  |  | 
|  | /* Flow from previous page => page bits are impossible to determine.  */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | ip2k_test_page_insn (bfd *abfd ATTRIBUTE_UNUSED, | 
|  | asection *sec, | 
|  | Elf_Internal_Rela *irel, | 
|  | struct misc *misc) | 
|  | { | 
|  | bfd_vma symval; | 
|  |  | 
|  | /* Get the value of the symbol referred to by the reloc.  */ | 
|  | symval = symbol_value (abfd, misc->symtab_hdr, misc->isymbuf, irel); | 
|  | if (symval == UNDEFINED_SYMBOL) | 
|  | /* This appears to be a reference to an undefined | 
|  | symbol.  Just ignore it--it will be caught by the | 
|  | regular reloc processing.  */ | 
|  | return false; | 
|  |  | 
|  | /* Test if we can delete this page instruction.  */ | 
|  | if (PAGENO (symval + irel->r_addend) != | 
|  | ip2k_nominal_page_bits (abfd, sec, irel->r_offset, misc->contents)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* Parts of a Stabs entry.  */ | 
|  |  | 
|  | #define STRDXOFF   0 | 
|  | #define TYPEOFF    4 | 
|  | #define OTHEROFF   5 | 
|  | #define DESCOFF    6 | 
|  | #define VALOFF     8 | 
|  | #define STABSIZE   12 | 
|  |  | 
|  | /* Adjust all the relocations entries after adding or inserting instructions.  */ | 
|  |  | 
|  | static void | 
|  | adjust_all_relocations (bfd *abfd, | 
|  | asection *sec, | 
|  | bfd_vma addr, | 
|  | bfd_vma endaddr, | 
|  | int count, | 
|  | int noadj) | 
|  | { | 
|  | Elf_Internal_Shdr *symtab_hdr; | 
|  | Elf_Internal_Sym *isymbuf, *isym, *isymend; | 
|  | unsigned int shndx; | 
|  | Elf_Internal_Rela *irel, *irelend, *irelbase; | 
|  | struct elf_link_hash_entry **sym_hashes; | 
|  | struct elf_link_hash_entry **end_hashes; | 
|  | unsigned int symcount; | 
|  | asection *stab; | 
|  |  | 
|  | symtab_hdr = &elf_tdata (abfd)->symtab_hdr; | 
|  | isymbuf = (Elf_Internal_Sym *) symtab_hdr->contents; | 
|  |  | 
|  | shndx = _bfd_elf_section_from_bfd_section (abfd, sec); | 
|  |  | 
|  | irelbase = elf_section_data (sec)->relocs; | 
|  | irelend = irelbase + sec->reloc_count; | 
|  |  | 
|  | for (irel = irelbase; irel < irelend; irel++) | 
|  | { | 
|  | if (ELF32_R_TYPE (irel->r_info) != R_IP2K_NONE) | 
|  | { | 
|  | /* Get the value of the symbol referred to by the reloc.  */ | 
|  | if (ELF32_R_SYM (irel->r_info) < symtab_hdr->sh_info) | 
|  | { | 
|  | asection *sym_sec; | 
|  |  | 
|  | /* A local symbol.  */ | 
|  | isym = isymbuf + ELF32_R_SYM (irel->r_info); | 
|  | sym_sec = bfd_section_from_elf_index (abfd, isym->st_shndx); | 
|  |  | 
|  | if (isym->st_shndx == shndx) | 
|  | { | 
|  | bfd_vma baseaddr = BASEADDR (sec); | 
|  | bfd_vma symval = BASEADDR (sym_sec) + isym->st_value | 
|  | + irel->r_addend; | 
|  |  | 
|  | if ((baseaddr + addr + noadj) <= symval | 
|  | && symval < (baseaddr + endaddr)) | 
|  | irel->r_addend += count; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Do this only for PC space relocations.  */ | 
|  | if (addr <= irel->r_offset && irel->r_offset < endaddr) | 
|  | irel->r_offset += count; | 
|  | } | 
|  |  | 
|  | /* Now fix the stab relocations.  */ | 
|  | stab = bfd_get_section_by_name (abfd, ".stab"); | 
|  | if (stab && stab->reloc_count != 0) | 
|  | { | 
|  | bfd_byte *stabcontents, *stabend, *stabp; | 
|  | bfd_size_type stab_size = stab->rawsize ? stab->rawsize : stab->size; | 
|  |  | 
|  | irelbase = elf_section_data (stab)->relocs; | 
|  | irelend = irelbase + stab->reloc_count; | 
|  |  | 
|  | /* Pull out the contents of the stab section.  */ | 
|  | if (elf_section_data (stab)->this_hdr.contents != NULL) | 
|  | stabcontents = elf_section_data (stab)->this_hdr.contents; | 
|  | else | 
|  | { | 
|  | if (!bfd_malloc_and_get_section (abfd, stab, &stabcontents)) | 
|  | { | 
|  | free (stabcontents); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* We need to remember this.  */ | 
|  | elf_section_data (stab)->this_hdr.contents = stabcontents; | 
|  | } | 
|  |  | 
|  | stabend = stabcontents + stab_size; | 
|  |  | 
|  | for (irel = irelbase; irel < irelend; irel++) | 
|  | { | 
|  | if (ELF32_R_TYPE (irel->r_info) != R_IP2K_NONE) | 
|  | { | 
|  | /* Get the value of the symbol referred to by the reloc.  */ | 
|  | if (ELF32_R_SYM (irel->r_info) < symtab_hdr->sh_info) | 
|  | { | 
|  | asection *sym_sec; | 
|  |  | 
|  | /* A local symbol.  */ | 
|  | isym = isymbuf + ELF32_R_SYM (irel->r_info); | 
|  | sym_sec = bfd_section_from_elf_index (abfd, isym->st_shndx); | 
|  |  | 
|  | if (sym_sec == sec) | 
|  | { | 
|  | const char *name; | 
|  | unsigned char type; | 
|  | bfd_vma value; | 
|  | bfd_vma baseaddr = BASEADDR (sec); | 
|  | bfd_vma symval = BASEADDR (sym_sec) + isym->st_value | 
|  | + irel->r_addend; | 
|  |  | 
|  | if ((baseaddr + addr) <= symval | 
|  | && symval <= (baseaddr + endaddr)) | 
|  | irel->r_addend += count; | 
|  |  | 
|  | /* Go hunt up a function and fix its line info if needed.  */ | 
|  | stabp = stabcontents + irel->r_offset - 8; | 
|  |  | 
|  | /* Go pullout the stab entry.  */ | 
|  | type  = bfd_h_get_8 (abfd, stabp + TYPEOFF); | 
|  | value = bfd_h_get_32 (abfd, stabp + VALOFF); | 
|  |  | 
|  | name = bfd_get_stab_name (type); | 
|  |  | 
|  | if (strcmp (name, "FUN") == 0) | 
|  | { | 
|  | int function_adjusted = 0; | 
|  |  | 
|  | if (symval > (baseaddr + addr)) | 
|  | /* Not in this function.  */ | 
|  | continue; | 
|  |  | 
|  | /* Hey we got a function hit.  */ | 
|  | stabp += STABSIZE; | 
|  | for (;stabp < stabend; stabp += STABSIZE) | 
|  | { | 
|  | /* Go pullout the stab entry.  */ | 
|  | type  = bfd_h_get_8 (abfd, stabp + TYPEOFF); | 
|  | value = bfd_h_get_32 (abfd, stabp + VALOFF); | 
|  |  | 
|  | name = bfd_get_stab_name (type); | 
|  |  | 
|  | if (strcmp (name, "FUN") == 0) | 
|  | { | 
|  | /* Hit another function entry.  */ | 
|  | if (function_adjusted) | 
|  | { | 
|  | /* Adjust the value.  */ | 
|  | value += count; | 
|  |  | 
|  | /* We need to put it back.  */ | 
|  | bfd_h_put_32 (abfd, value,stabp + VALOFF); | 
|  | } | 
|  |  | 
|  | /* And then bale out.  */ | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (strcmp (name, "SLINE") == 0) | 
|  | { | 
|  | /* Got a line entry.  */ | 
|  | if ((baseaddr + addr) <= (symval + value)) | 
|  | { | 
|  | /* Adjust the line entry.  */ | 
|  | value += count; | 
|  |  | 
|  | /* We need to put it back.  */ | 
|  | bfd_h_put_32 (abfd, value,stabp + VALOFF); | 
|  | function_adjusted = 1; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* When adding an instruction back it is sometimes necessary to move any | 
|  | global or local symbol that was referencing the first instruction of | 
|  | the moved block to refer to the first instruction of the inserted block. | 
|  |  | 
|  | For example adding a PAGE instruction before a CALL or JMP requires | 
|  | that any label on the CALL or JMP is moved to the PAGE insn.  */ | 
|  | addr += noadj; | 
|  |  | 
|  | /* Adjust the local symbols defined in this section.  */ | 
|  | isymend = isymbuf + symtab_hdr->sh_info; | 
|  | for (isym = isymbuf; isym < isymend; isym++) | 
|  | { | 
|  | if (isym->st_shndx == shndx | 
|  | && addr <= isym->st_value | 
|  | && isym->st_value < endaddr) | 
|  | isym->st_value += count; | 
|  | } | 
|  |  | 
|  | /* Now adjust the global symbols defined in this section.  */ | 
|  | symcount = (symtab_hdr->sh_size / sizeof (Elf32_External_Sym) | 
|  | - symtab_hdr->sh_info); | 
|  | sym_hashes = elf_sym_hashes (abfd); | 
|  | end_hashes = sym_hashes + symcount; | 
|  | for (; sym_hashes < end_hashes; sym_hashes++) | 
|  | { | 
|  | struct elf_link_hash_entry *sym_hash = *sym_hashes; | 
|  |  | 
|  | if ((sym_hash->root.type == bfd_link_hash_defined | 
|  | || sym_hash->root.type == bfd_link_hash_defweak) | 
|  | && sym_hash->root.u.def.section == sec) | 
|  | { | 
|  | if (addr <= sym_hash->root.u.def.value | 
|  | && sym_hash->root.u.def.value < endaddr) | 
|  | sym_hash->root.u.def.value += count; | 
|  | } | 
|  | } | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Delete some bytes from a section while relaxing.  */ | 
|  |  | 
|  | static bool | 
|  | ip2k_elf_relax_delete_bytes (bfd *abfd, | 
|  | asection *sec, | 
|  | bfd_vma addr, | 
|  | int count) | 
|  | { | 
|  | bfd_byte *contents = elf_section_data (sec)->this_hdr.contents; | 
|  | bfd_vma endaddr = sec->size; | 
|  |  | 
|  | /* Actually delete the bytes.  */ | 
|  | memmove (contents + addr, contents + addr + count, | 
|  | endaddr - addr - count); | 
|  |  | 
|  | sec->size -= count; | 
|  |  | 
|  | adjust_all_relocations (abfd, sec, addr + count, endaddr, -count, 0); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | ip2k_delete_page_insn (bfd *abfd ATTRIBUTE_UNUSED, | 
|  | asection *sec, | 
|  | Elf_Internal_Rela *irel, | 
|  | bool *again, | 
|  | struct misc *misc) | 
|  | { | 
|  | /* Note that we've changed the relocs, section contents, etc.  */ | 
|  | elf_section_data (sec)->relocs = misc->irelbase; | 
|  | elf_section_data (sec)->this_hdr.contents = misc->contents; | 
|  | misc->symtab_hdr->contents = (bfd_byte *) misc->isymbuf; | 
|  |  | 
|  | /* Fix the relocation's type.  */ | 
|  | irel->r_info = ELF32_R_INFO (ELF32_R_SYM (irel->r_info), R_IP2K_NONE); | 
|  |  | 
|  | /* Delete the PAGE insn.  */ | 
|  | if (!ip2k_elf_relax_delete_bytes (abfd, sec, irel->r_offset, 2)) | 
|  | return false; | 
|  |  | 
|  | /* Modified => will need to iterate relaxation again.  */ | 
|  | *again = true; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | ip2k_relax_switch_table_128 (bfd *abfd ATTRIBUTE_UNUSED, | 
|  | asection *sec, | 
|  | Elf_Internal_Rela *irel, | 
|  | bool *again, | 
|  | struct misc *misc) | 
|  | { | 
|  | Elf_Internal_Rela *irelend = misc->irelbase + sec->reloc_count; | 
|  | Elf_Internal_Rela *ireltest = irel; | 
|  | bfd_byte code[4]; | 
|  | bfd_vma addr; | 
|  |  | 
|  | /* Test all page instructions.  */ | 
|  | addr = irel->r_offset; | 
|  | while (1) | 
|  | { | 
|  | if (addr + 4 > sec->size) | 
|  | break; | 
|  |  | 
|  | ip2k_get_mem (abfd, misc->contents + addr, 4, code); | 
|  | if ((! IS_PAGE_OPCODE (code + 0)) | 
|  | || (! IS_JMP_OPCODE (code + 2))) | 
|  | break; | 
|  |  | 
|  | /* Validate relocation entry (every entry should have a matching | 
|  | relocation entry).  */ | 
|  | if (ireltest >= irelend) | 
|  | { | 
|  | _bfd_error_handler (_("ip2k relaxer: switch table without complete matching relocation information.")); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (ireltest->r_offset != addr) | 
|  | { | 
|  | _bfd_error_handler (_("ip2k relaxer: switch table without complete matching relocation information.")); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (! ip2k_test_page_insn (abfd, sec, ireltest, misc)) | 
|  | /* Un-removable page insn => nothing can be done.  */ | 
|  | return true; | 
|  |  | 
|  | addr += 4; | 
|  | ireltest += 2; | 
|  | } | 
|  |  | 
|  | /* Relaxable. Adjust table header.  */ | 
|  | ip2k_get_mem (abfd, misc->contents + irel->r_offset - 4, 4, code); | 
|  | if ((! IS_ADD_W_WREG_OPCODE (code + 0)) | 
|  | || (! IS_ADD_PCL_W_OPCODE (code + 2))) | 
|  | { | 
|  | _bfd_error_handler (_("ip2k relaxer: switch table header corrupt.")); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!ip2k_elf_relax_delete_bytes (abfd, sec, irel->r_offset - 4, 2)) | 
|  | return false; | 
|  |  | 
|  | *again = true; | 
|  |  | 
|  | /* Delete all page instructions in table.  */ | 
|  | while (irel < ireltest) | 
|  | { | 
|  | if (!ip2k_delete_page_insn (abfd, sec, irel, again, misc)) | 
|  | return false; | 
|  | irel += 2; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | ip2k_relax_switch_table_256 (bfd *abfd ATTRIBUTE_UNUSED, | 
|  | asection *sec, | 
|  | Elf_Internal_Rela *irel, | 
|  | bool *again, | 
|  | struct misc *misc) | 
|  | { | 
|  | Elf_Internal_Rela *irelend = misc->irelbase + sec->reloc_count; | 
|  | Elf_Internal_Rela *ireltest = irel; | 
|  | bfd_byte code[12]; | 
|  | bfd_vma addr; | 
|  |  | 
|  | /* Test all page instructions.  */ | 
|  | addr = irel->r_offset; | 
|  |  | 
|  | while (1) | 
|  | { | 
|  | if (addr + 4 > sec->size) | 
|  | break; | 
|  |  | 
|  | ip2k_get_mem (abfd, misc->contents + addr, 4, code); | 
|  |  | 
|  | if ((! IS_PAGE_OPCODE (code + 0)) | 
|  | || (! IS_JMP_OPCODE (code + 2))) | 
|  | break; | 
|  |  | 
|  | /* Validate relocation entry (every entry should have a matching | 
|  | relocation entry).  */ | 
|  | if (ireltest >= irelend) | 
|  | { | 
|  | _bfd_error_handler (_("ip2k relaxer: switch table without complete matching relocation information.")); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (ireltest->r_offset != addr) | 
|  | { | 
|  | _bfd_error_handler (_("ip2k relaxer: switch table without complete matching relocation information.")); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!ip2k_test_page_insn (abfd, sec, ireltest, misc)) | 
|  | /* Un-removable page insn => nothing can be done.  */ | 
|  | return true; | 
|  |  | 
|  | addr += 4; | 
|  | ireltest += 2; | 
|  | } | 
|  |  | 
|  | /* Relaxable. Adjust table header.  */ | 
|  | ip2k_get_mem (abfd, misc->contents + irel->r_offset - 4, 2, code); | 
|  | if (IS_PAGE_OPCODE (code)) | 
|  | addr = irel->r_offset - 16; | 
|  | else | 
|  | addr = irel->r_offset - 14; | 
|  |  | 
|  | ip2k_get_mem (abfd, misc->contents + addr, 12, code); | 
|  | if ((!IS_ADD_W_WREG_OPCODE (code + 0)) | 
|  | || (!IS_SNC_OPCODE (code + 2)) | 
|  | || (!IS_INC_1SP_OPCODE (code + 4)) | 
|  | || (!IS_ADD_2SP_W_OPCODE (code + 6)) | 
|  | || (!IS_SNC_OPCODE (code + 8)) | 
|  | || (!IS_INC_1SP_OPCODE (code + 10))) | 
|  | { | 
|  | _bfd_error_handler (_("ip2k relaxer: switch table header corrupt.")); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* Delete first 3 opcodes.  */ | 
|  | if (!ip2k_elf_relax_delete_bytes (abfd, sec, addr + 0, 6)) | 
|  | return false; | 
|  |  | 
|  | *again = true; | 
|  |  | 
|  | /* Delete all page instructions in table.  */ | 
|  | while (irel < ireltest) | 
|  | { | 
|  | if (!ip2k_delete_page_insn (abfd, sec, irel, again, misc)) | 
|  | return false; | 
|  | irel += 2; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* This function handles relaxation of a section in a specific page.  */ | 
|  |  | 
|  | static bool | 
|  | ip2k_elf_relax_section_page (bfd *abfd, | 
|  | asection *sec, | 
|  | bool *again, | 
|  | struct misc *misc, | 
|  | unsigned long page_start, | 
|  | unsigned long page_end) | 
|  | { | 
|  | Elf_Internal_Rela *irelend = misc->irelbase + sec->reloc_count; | 
|  | Elf_Internal_Rela *irel; | 
|  | int switch_table_128; | 
|  | int switch_table_256; | 
|  |  | 
|  | /* Walk thru the section looking for relaxation opportunities.  */ | 
|  | for (irel = misc->irelbase; irel < irelend; irel++) | 
|  | { | 
|  | if (ELF32_R_TYPE (irel->r_info) != (int) R_IP2K_PAGE3) | 
|  | /* Ignore non page instructions.  */ | 
|  | continue; | 
|  |  | 
|  | if (BASEADDR (sec) + irel->r_offset < page_start) | 
|  | /* Ignore page instructions on earlier page - they have | 
|  | already been processed. Remember that there is code flow | 
|  | that crosses a page boundary.  */ | 
|  | continue; | 
|  |  | 
|  | if (BASEADDR (sec) + irel->r_offset > page_end) | 
|  | /* Flow beyond end of page => nothing more to do for this page.  */ | 
|  | return true; | 
|  |  | 
|  | /* Detect switch tables.  */ | 
|  | switch_table_128 = ip2k_is_switch_table_128 (abfd, sec, irel->r_offset, misc->contents); | 
|  | switch_table_256 = ip2k_is_switch_table_256 (abfd, sec, irel->r_offset, misc->contents); | 
|  |  | 
|  | if ((switch_table_128 > 0) || (switch_table_256 > 0)) | 
|  | /* If the index is greater than 0 then it has already been processed.  */ | 
|  | continue; | 
|  |  | 
|  | if (switch_table_128 == 0) | 
|  | { | 
|  | if (!ip2k_relax_switch_table_128 (abfd, sec, irel, again, misc)) | 
|  | return false; | 
|  |  | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (switch_table_256 == 0) | 
|  | { | 
|  | if (!ip2k_relax_switch_table_256 (abfd, sec, irel, again, misc)) | 
|  | return false; | 
|  |  | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* Simple relax.  */ | 
|  | if (ip2k_test_page_insn (abfd, sec, irel, misc)) | 
|  | { | 
|  | if (!ip2k_delete_page_insn (abfd, sec, irel, again, misc)) | 
|  | return false; | 
|  |  | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* This function handles relaxing for the ip2k. | 
|  |  | 
|  | Principle: Start with the first page and remove page instructions that | 
|  | are not require on this first page. By removing page instructions more | 
|  | code will fit into this page - repeat until nothing more can be achieved | 
|  | for this page. Move on to the next page. | 
|  |  | 
|  | Processing the pages one at a time from the lowest page allows a removal | 
|  | only policy to be used - pages can be removed but are never reinserted.  */ | 
|  |  | 
|  | static bool | 
|  | ip2k_elf_relax_section (bfd *abfd, | 
|  | asection *sec, | 
|  | struct bfd_link_info *link_info, | 
|  | bool *again) | 
|  | { | 
|  | Elf_Internal_Shdr *symtab_hdr; | 
|  | Elf_Internal_Rela *internal_relocs; | 
|  | bfd_byte *contents = NULL; | 
|  | Elf_Internal_Sym *isymbuf = NULL; | 
|  | static asection * first_section = NULL; | 
|  | static unsigned long search_addr; | 
|  | static unsigned long page_start = 0; | 
|  | static unsigned long page_end = 0; | 
|  | static unsigned int pass = 0; | 
|  | static bool new_pass = false; | 
|  | static bool changed = false; | 
|  | struct misc misc; | 
|  |  | 
|  | /* Assume nothing changes.  */ | 
|  | *again = false; | 
|  |  | 
|  | if (first_section == NULL) | 
|  | { | 
|  | ip2k_relaxed = true; | 
|  | first_section = sec; | 
|  | } | 
|  |  | 
|  | if (first_section == sec) | 
|  | { | 
|  | pass++; | 
|  | new_pass = true; | 
|  | } | 
|  |  | 
|  | /* We don't have to do anything for a relocatable link, | 
|  | if this section does not have relocs, or if this is | 
|  | not a code section.  */ | 
|  | if (bfd_link_relocatable (link_info) | 
|  | || sec->reloc_count == 0 | 
|  | || (sec->flags & SEC_RELOC) == 0 | 
|  | || (sec->flags & SEC_HAS_CONTENTS) == 0 | 
|  | || (sec->flags & SEC_CODE) == 0) | 
|  | return true; | 
|  |  | 
|  | symtab_hdr = &elf_tdata (abfd)->symtab_hdr; | 
|  |  | 
|  | internal_relocs = _bfd_elf_link_read_relocs (abfd, sec, NULL, NULL, | 
|  | link_info->keep_memory); | 
|  | if (internal_relocs == NULL) | 
|  | goto error_return; | 
|  |  | 
|  | /* Get section contents cached copy if it exists.  */ | 
|  | if (contents == NULL) | 
|  | { | 
|  | /* Get cached copy if it exists.  */ | 
|  | if (elf_section_data (sec)->this_hdr.contents != NULL) | 
|  | contents = elf_section_data (sec)->this_hdr.contents; | 
|  | else | 
|  | { | 
|  | /* Go get them off disk.  */ | 
|  | if (!bfd_malloc_and_get_section (abfd, sec, &contents)) | 
|  | goto error_return; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Read this BFD's symbols cached copy if it exists.  */ | 
|  | if (isymbuf == NULL && symtab_hdr->sh_info != 0) | 
|  | { | 
|  | isymbuf = (Elf_Internal_Sym *) symtab_hdr->contents; | 
|  | if (isymbuf == NULL) | 
|  | isymbuf = bfd_elf_get_elf_syms (abfd, symtab_hdr, | 
|  | symtab_hdr->sh_info, 0, | 
|  | NULL, NULL, NULL); | 
|  | if (isymbuf == NULL) | 
|  | goto error_return; | 
|  | } | 
|  |  | 
|  | misc.symtab_hdr = symtab_hdr; | 
|  | misc.isymbuf = isymbuf; | 
|  | misc.irelbase = internal_relocs; | 
|  | misc.contents = contents; | 
|  |  | 
|  | /* This is where all the relaxation actually get done.  */ | 
|  | if ((pass == 1) || (new_pass && !changed)) | 
|  | { | 
|  | /* On the first pass we simply search for the lowest page that | 
|  | we havn't relaxed yet. Note that the pass count is reset | 
|  | each time a page is complete in order to move on to the next page. | 
|  | If we can't find any more pages then we are finished.  */ | 
|  | if (new_pass) | 
|  | { | 
|  | pass = 1; | 
|  | new_pass = false; | 
|  | changed = true; /* Pre-initialize to break out of pass 1.  */ | 
|  | search_addr = 0xFFFFFFFF; | 
|  | } | 
|  |  | 
|  | if ((BASEADDR (sec) + sec->size < search_addr) | 
|  | && (BASEADDR (sec) + sec->size > page_end)) | 
|  | { | 
|  | if (BASEADDR (sec) <= page_end) | 
|  | search_addr = page_end + 1; | 
|  | else | 
|  | search_addr = BASEADDR (sec); | 
|  |  | 
|  | /* Found a page => more work to do.  */ | 
|  | *again = true; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | if (new_pass) | 
|  | { | 
|  | new_pass = false; | 
|  | changed = false; | 
|  | page_start = PAGENO (search_addr); | 
|  | page_end = page_start | 0x00003FFF; | 
|  | } | 
|  |  | 
|  | /* Only process sections in range.  */ | 
|  | if ((BASEADDR (sec) + sec->size >= page_start) | 
|  | && (BASEADDR (sec) <= page_end)) | 
|  | { | 
|  | if (!ip2k_elf_relax_section_page (abfd, sec, &changed, &misc, page_start, page_end)) | 
|  | return false; | 
|  | } | 
|  | *again = true; | 
|  | } | 
|  |  | 
|  | /* Perform some house keeping after relaxing the section.  */ | 
|  |  | 
|  | if (isymbuf != NULL | 
|  | && symtab_hdr->contents != (unsigned char *) isymbuf) | 
|  | { | 
|  | if (! link_info->keep_memory) | 
|  | free (isymbuf); | 
|  | else | 
|  | symtab_hdr->contents = (unsigned char *) isymbuf; | 
|  | } | 
|  |  | 
|  | if (contents != NULL | 
|  | && elf_section_data (sec)->this_hdr.contents != contents) | 
|  | { | 
|  | if (! link_info->keep_memory) | 
|  | free (contents); | 
|  | else | 
|  | { | 
|  | /* Cache the section contents for elf_link_input_bfd.  */ | 
|  | elf_section_data (sec)->this_hdr.contents = contents; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (elf_section_data (sec)->relocs != internal_relocs) | 
|  | free (internal_relocs); | 
|  |  | 
|  | return true; | 
|  |  | 
|  | error_return: | 
|  | if (symtab_hdr->contents != (unsigned char *) isymbuf) | 
|  | free (isymbuf); | 
|  | if (elf_section_data (sec)->this_hdr.contents != contents) | 
|  | free (contents); | 
|  | if (elf_section_data (sec)->relocs != internal_relocs) | 
|  | free (internal_relocs); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* Set the howto pointer for a IP2K ELF reloc.  */ | 
|  |  | 
|  | static bool | 
|  | ip2k_info_to_howto_rela (bfd * abfd, | 
|  | arelent * cache_ptr, | 
|  | Elf_Internal_Rela * dst) | 
|  | { | 
|  | unsigned int r_type; | 
|  |  | 
|  | r_type = ELF32_R_TYPE (dst->r_info); | 
|  | if (r_type >= (unsigned int) R_IP2K_max) | 
|  | { | 
|  | /* xgettext:c-format */ | 
|  | _bfd_error_handler (_("%pB: unsupported relocation type %#x"), | 
|  | abfd, r_type); | 
|  | bfd_set_error (bfd_error_bad_value); | 
|  | return false; | 
|  | } | 
|  | cache_ptr->howto = & ip2k_elf_howto_table [r_type]; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* Perform a single relocation. | 
|  | By default we use the standard BFD routines.  */ | 
|  |  | 
|  | static bfd_reloc_status_type | 
|  | ip2k_final_link_relocate (reloc_howto_type *  howto, | 
|  | bfd *		      input_bfd, | 
|  | asection *	      input_section, | 
|  | bfd_byte *	      contents, | 
|  | Elf_Internal_Rela * rel, | 
|  | bfd_vma	      relocation) | 
|  | { | 
|  | static bfd_vma page_addr = 0; | 
|  |  | 
|  | bfd_reloc_status_type r = bfd_reloc_ok; | 
|  | switch (howto->type) | 
|  | { | 
|  | /* Handle data space relocations.  */ | 
|  | case R_IP2K_FR9: | 
|  | case R_IP2K_BANK: | 
|  | if ((relocation & IP2K_DATA_MASK) == IP2K_DATA_VALUE) | 
|  | relocation &= ~IP2K_DATA_MASK; | 
|  | else | 
|  | r = bfd_reloc_notsupported; | 
|  | break; | 
|  |  | 
|  | case R_IP2K_LO8DATA: | 
|  | case R_IP2K_HI8DATA: | 
|  | case R_IP2K_EX8DATA: | 
|  | break; | 
|  |  | 
|  | /* Handle insn space relocations.  */ | 
|  | case R_IP2K_PAGE3: | 
|  | page_addr = BASEADDR (input_section) + rel->r_offset; | 
|  | if ((relocation & IP2K_INSN_MASK) == IP2K_INSN_VALUE) | 
|  | relocation &= ~IP2K_INSN_MASK; | 
|  | else | 
|  | r = bfd_reloc_notsupported; | 
|  | break; | 
|  |  | 
|  | case R_IP2K_ADDR16CJP: | 
|  | if (BASEADDR (input_section) + rel->r_offset != page_addr + 2) | 
|  | { | 
|  | /* No preceding page instruction, verify that it isn't needed.  */ | 
|  | if (PAGENO (relocation + rel->r_addend) != | 
|  | ip2k_nominal_page_bits (input_bfd, input_section, | 
|  | rel->r_offset, contents)) | 
|  | /* xgettext:c-format */ | 
|  | _bfd_error_handler | 
|  | (_("ip2k linker: missing page instruction " | 
|  | "at %#" PRIx64 " (dest = %#" PRIx64 ")"), | 
|  | (uint64_t) (BASEADDR (input_section) + rel->r_offset), | 
|  | (uint64_t) (relocation + rel->r_addend)); | 
|  | } | 
|  | else if (ip2k_relaxed) | 
|  | { | 
|  | /* Preceding page instruction. Verify that the page instruction is | 
|  | really needed. One reason for the relaxation to miss a page is if | 
|  | the section is not marked as executable.  */ | 
|  | if (!ip2k_is_switch_table_128 (input_bfd, input_section, | 
|  | rel->r_offset - 2, contents) | 
|  | && !ip2k_is_switch_table_256 (input_bfd, input_section, | 
|  | rel->r_offset - 2, contents) | 
|  | && (PAGENO (relocation + rel->r_addend) == | 
|  | ip2k_nominal_page_bits (input_bfd, input_section, | 
|  | rel->r_offset - 2, contents))) | 
|  | /* xgettext:c-format */ | 
|  | _bfd_error_handler | 
|  | (_("ip2k linker: redundant page instruction " | 
|  | "at %#" PRIx64 " (dest = %#" PRIx64 ")"), | 
|  | (uint64_t) page_addr, | 
|  | (uint64_t) (relocation + rel->r_addend)); | 
|  | } | 
|  | if ((relocation & IP2K_INSN_MASK) == IP2K_INSN_VALUE) | 
|  | relocation &= ~IP2K_INSN_MASK; | 
|  | else | 
|  | r = bfd_reloc_notsupported; | 
|  | break; | 
|  |  | 
|  | case R_IP2K_LO8INSN: | 
|  | case R_IP2K_HI8INSN: | 
|  | case R_IP2K_PC_SKIP: | 
|  | if ((relocation & IP2K_INSN_MASK) == IP2K_INSN_VALUE) | 
|  | relocation &= ~IP2K_INSN_MASK; | 
|  | else | 
|  | r = bfd_reloc_notsupported; | 
|  | break; | 
|  |  | 
|  | case R_IP2K_16: | 
|  | /* If this is a relocation involving a TEXT | 
|  | symbol, reduce it to a word address.  */ | 
|  | if ((relocation & IP2K_INSN_MASK) == IP2K_INSN_VALUE) | 
|  | howto = &ip2k_elf_howto_table[ (int) R_IP2K_TEXT]; | 
|  | break; | 
|  |  | 
|  | /* Pass others through.  */ | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Only install relocation if above tests did not disqualify it.  */ | 
|  | if (r == bfd_reloc_ok) | 
|  | r = _bfd_final_link_relocate (howto, input_bfd, input_section, | 
|  | contents, rel->r_offset, | 
|  | relocation, rel->r_addend); | 
|  |  | 
|  | return r; | 
|  | } | 
|  |  | 
|  | /* Relocate a IP2K ELF section. | 
|  |  | 
|  | The RELOCATE_SECTION function is called by the new ELF backend linker | 
|  | to handle the relocations for a section. | 
|  |  | 
|  | The relocs are always passed as Rela structures; if the section | 
|  | actually uses Rel structures, the r_addend field will always be | 
|  | zero. | 
|  |  | 
|  | This function is responsible for adjusting the section contents as | 
|  | necessary, and (if using Rela relocs and generating a relocatable | 
|  | output file) adjusting the reloc addend as necessary. | 
|  |  | 
|  | This function does not have to worry about setting the reloc | 
|  | address or the reloc symbol index. | 
|  |  | 
|  | LOCAL_SYMS is a pointer to the swapped in local symbols. | 
|  |  | 
|  | LOCAL_SECTIONS is an array giving the section in the input file | 
|  | corresponding to the st_shndx field of each local symbol. | 
|  |  | 
|  | The global hash table entry for the global symbols can be found | 
|  | via elf_sym_hashes (input_bfd). | 
|  |  | 
|  | When generating relocatable output, this function must handle | 
|  | STB_LOCAL/STT_SECTION symbols specially.  The output symbol is | 
|  | going to be the section symbol corresponding to the output | 
|  | section, which means that the addend must be adjusted | 
|  | accordingly.  */ | 
|  |  | 
|  | static int | 
|  | ip2k_elf_relocate_section (bfd *output_bfd ATTRIBUTE_UNUSED, | 
|  | struct bfd_link_info *info, | 
|  | bfd *input_bfd, | 
|  | asection *input_section, | 
|  | bfd_byte *contents, | 
|  | Elf_Internal_Rela *relocs, | 
|  | Elf_Internal_Sym *local_syms, | 
|  | asection **local_sections) | 
|  | { | 
|  | Elf_Internal_Shdr *symtab_hdr; | 
|  | struct elf_link_hash_entry **sym_hashes; | 
|  | Elf_Internal_Rela *rel; | 
|  | Elf_Internal_Rela *relend; | 
|  |  | 
|  | symtab_hdr = & elf_tdata (input_bfd)->symtab_hdr; | 
|  | sym_hashes = elf_sym_hashes (input_bfd); | 
|  | relend     = relocs + input_section->reloc_count; | 
|  |  | 
|  | for (rel = relocs; rel < relend; rel ++) | 
|  | { | 
|  | reloc_howto_type *	   howto; | 
|  | unsigned long		   r_symndx; | 
|  | Elf_Internal_Sym *	   sym; | 
|  | asection *		   sec; | 
|  | struct elf_link_hash_entry * h; | 
|  | bfd_vma			   relocation; | 
|  | bfd_reloc_status_type	   r; | 
|  | const char *		   name = NULL; | 
|  | int			   r_type; | 
|  |  | 
|  | r_type = ELF32_R_TYPE (rel->r_info); | 
|  | r_symndx = ELF32_R_SYM (rel->r_info); | 
|  | howto  = ip2k_elf_howto_table + r_type; | 
|  | h      = NULL; | 
|  | sym    = NULL; | 
|  | sec    = NULL; | 
|  |  | 
|  | if (r_symndx < symtab_hdr->sh_info) | 
|  | { | 
|  | sym = local_syms + r_symndx; | 
|  | sec = local_sections [r_symndx]; | 
|  | relocation = BASEADDR (sec) + sym->st_value; | 
|  |  | 
|  | name = bfd_elf_string_from_elf_section | 
|  | (input_bfd, symtab_hdr->sh_link, sym->st_name); | 
|  | name = name == NULL ? bfd_section_name (sec) : name; | 
|  | } | 
|  | else | 
|  | { | 
|  | bool warned, ignored; | 
|  | bool unresolved_reloc; | 
|  |  | 
|  | RELOC_FOR_GLOBAL_SYMBOL (info, input_bfd, input_section, rel, | 
|  | r_symndx, symtab_hdr, sym_hashes, | 
|  | h, sec, relocation, | 
|  | unresolved_reloc, warned, ignored); | 
|  |  | 
|  | name = h->root.root.string; | 
|  | } | 
|  |  | 
|  | if (sec != NULL && discarded_section (sec)) | 
|  | RELOC_AGAINST_DISCARDED_SECTION (info, input_bfd, input_section, | 
|  | rel, 1, relend, howto, 0, contents); | 
|  |  | 
|  | if (bfd_link_relocatable (info)) | 
|  | continue; | 
|  |  | 
|  | /* Finally, the sole IP2K-specific part.  */ | 
|  | r = ip2k_final_link_relocate (howto, input_bfd, input_section, | 
|  | contents, rel, relocation); | 
|  |  | 
|  | if (r != bfd_reloc_ok) | 
|  | { | 
|  | const char * msg = NULL; | 
|  |  | 
|  | switch (r) | 
|  | { | 
|  | case bfd_reloc_overflow: | 
|  | (*info->callbacks->reloc_overflow) | 
|  | (info, (h ? &h->root : NULL), name, howto->name, | 
|  | (bfd_vma) 0, input_bfd, input_section, rel->r_offset); | 
|  | break; | 
|  |  | 
|  | case bfd_reloc_undefined: | 
|  | (*info->callbacks->undefined_symbol) | 
|  | (info, name, input_bfd, input_section, rel->r_offset, true); | 
|  | break; | 
|  |  | 
|  | case bfd_reloc_outofrange: | 
|  | msg = _("internal error: out of range error"); | 
|  | break; | 
|  |  | 
|  | /* This is how ip2k_final_link_relocate tells us of a non-kosher | 
|  | reference between insn & data address spaces.  */ | 
|  | case bfd_reloc_notsupported: | 
|  | if (sym != NULL) /* Only if it's not an unresolved symbol.  */ | 
|  | msg = _("unsupported relocation between data/insn address spaces"); | 
|  | break; | 
|  |  | 
|  | case bfd_reloc_dangerous: | 
|  | msg = _("internal error: dangerous relocation"); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | msg = _("internal error: unknown error"); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (msg) | 
|  | (*info->callbacks->warning) (info, msg, name, input_bfd, | 
|  | input_section, rel->r_offset); | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | #define TARGET_BIG_SYM	 ip2k_elf32_vec | 
|  | #define TARGET_BIG_NAME  "elf32-ip2k" | 
|  |  | 
|  | #define ELF_ARCH	 bfd_arch_ip2k | 
|  | #define ELF_MACHINE_CODE EM_IP2K | 
|  | #define ELF_MACHINE_ALT1 EM_IP2K_OLD | 
|  | #define ELF_MAXPAGESIZE  1 /* No pages on the IP2K.  */ | 
|  |  | 
|  | #define elf_info_to_howto_rel			NULL | 
|  | #define elf_info_to_howto			ip2k_info_to_howto_rela | 
|  |  | 
|  | #define elf_backend_can_gc_sections		1 | 
|  | #define elf_backend_rela_normal			1 | 
|  | #define elf_backend_relocate_section		ip2k_elf_relocate_section | 
|  |  | 
|  | #define elf_symbol_leading_char			'_' | 
|  | #define bfd_elf32_bfd_reloc_type_lookup		ip2k_reloc_type_lookup | 
|  | #define bfd_elf32_bfd_reloc_name_lookup	ip2k_reloc_name_lookup | 
|  | #define bfd_elf32_bfd_relax_section		ip2k_elf_relax_section | 
|  |  | 
|  | #include "elf32-target.h" |