diff options
Diffstat (limited to 'bfd/elflink.c')
-rw-r--r-- | bfd/elflink.c | 713 |
1 files changed, 713 insertions, 0 deletions
diff --git a/bfd/elflink.c b/bfd/elflink.c index 77aae5e..4da5d79 100644 --- a/bfd/elflink.c +++ b/bfd/elflink.c @@ -6348,6 +6348,716 @@ struct elf_outext_info struct elf_final_link_info *finfo; }; + +/* Support for evaluating a complex relocation. + + Complex relocations are generalized, self-describing relocations. The + implementation of them consists of two parts: complex symbols, and the + relocations themselves. + + The relocations are use a reserved elf-wide relocation type code (R_RELC + external / BFD_RELOC_RELC internal) and an encoding of relocation field + information (start bit, end bit, word width, etc) into the addend. This + information is extracted from CGEN-generated operand tables within gas. + + Complex symbols are mangled symbols (BSF_RELC external / STT_RELC + internal) representing prefix-notation expressions, including but not + limited to those sorts of expressions normally encoded as addends in the + addend field. The symbol mangling format is: + + <node> := <literal> + | <unary-operator> ':' <node> + | <binary-operator> ':' <node> ':' <node> + ; + + <literal> := 's' <digits=N> ':' <N character symbol name> + | 'S' <digits=N> ':' <N character section name> + | '#' <hexdigits> + ; + + <binary-operator> := as in C + <unary-operator> := as in C, plus "0-" for unambiguous negation. */ + +static void +set_symbol_value (bfd * bfd_with_globals, + struct elf_final_link_info * finfo, + int symidx, + bfd_vma val) +{ + bfd_boolean is_local; + Elf_Internal_Sym * sym; + struct elf_link_hash_entry ** sym_hashes; + struct elf_link_hash_entry * h; + + sym_hashes = elf_sym_hashes (bfd_with_globals); + sym = finfo->internal_syms + symidx; + is_local = ELF_ST_BIND(sym->st_info) == STB_LOCAL; + + if (is_local) + { + /* It is a local symbol: move it to the + "absolute" section and give it a value. */ + sym->st_shndx = SHN_ABS; + sym->st_value = val; + } + else + { + /* It is a global symbol: set its link type + to "defined" and give it a value. */ + h = sym_hashes [symidx]; + while (h->root.type == bfd_link_hash_indirect + || h->root.type == bfd_link_hash_warning) + h = (struct elf_link_hash_entry *) h->root.u.i.link; + h->root.type = bfd_link_hash_defined; + h->root.u.def.value = val; + h->root.u.def.section = bfd_abs_section_ptr; + } +} + +static bfd_boolean +resolve_symbol (const char * name, + bfd * input_bfd, + struct elf_final_link_info * finfo, + bfd_vma * result, + size_t locsymcount) +{ + Elf_Internal_Sym * sym; + struct bfd_link_hash_entry * global_entry; + const char * candidate = NULL; + Elf_Internal_Shdr * symtab_hdr; + asection * sec = NULL; + size_t i; + + symtab_hdr = & elf_tdata (input_bfd)->symtab_hdr; + + for (i = 0; i < locsymcount; ++ i) + { + sym = finfo->internal_syms + i; + sec = finfo->sections [i]; + + if (ELF_ST_BIND (sym->st_info) != STB_LOCAL) + continue; + + candidate = bfd_elf_string_from_elf_section (input_bfd, + symtab_hdr->sh_link, + sym->st_name); +#ifdef DEBUG + printf ("Comparing string: '%s' vs. '%s' = 0x%x\n", + name, candidate, (unsigned int)sym->st_value); +#endif + if (candidate && strcmp (candidate, name) == 0) + { + * result = sym->st_value; + + if (sym->st_shndx > SHN_UNDEF && + sym->st_shndx < SHN_LORESERVE) + { +#ifdef DEBUG + printf ("adjusting for sec '%s' @ 0x%x + 0x%x\n", + sec->output_section->name, + (unsigned int)sec->output_section->vma, + (unsigned int)sec->output_offset); +#endif + * result += sec->output_offset + sec->output_section->vma; + } +#ifdef DEBUG + printf ("Found symbol with effective value %8.8x\n", (unsigned int)* result); +#endif + return TRUE; + } + } + + /* Hmm, haven't found it yet. perhaps it is a global. */ + global_entry = bfd_link_hash_lookup (finfo->info->hash, name, FALSE, FALSE, TRUE); + if (!global_entry) + return FALSE; + + if (global_entry->type == bfd_link_hash_defined + || global_entry->type == bfd_link_hash_defweak) + { + * result = global_entry->u.def.value + + global_entry->u.def.section->output_section->vma + + global_entry->u.def.section->output_offset; +#ifdef DEBUG + printf ("Found GLOBAL symbol '%s' with value %8.8x\n", + global_entry->root.string, (unsigned int)*result); +#endif + return TRUE; + } + + if (global_entry->type == bfd_link_hash_common) + { + *result = global_entry->u.def.value + + bfd_com_section_ptr->output_section->vma + + bfd_com_section_ptr->output_offset; +#ifdef DEBUG + printf ("Found COMMON symbol '%s' with value %8.8x\n", + global_entry->root.string, (unsigned int)*result); +#endif + return TRUE; + } + + return FALSE; +} + +static bfd_boolean +resolve_section (const char * name, + asection * sections, + bfd_vma * result) +{ + asection * curr; + unsigned int len; + + for (curr = sections; curr; curr = curr->next) + if (strcmp (curr->name, name) == 0) + { + *result = curr->vma; + return TRUE; + } + + /* Hmm. still haven't found it. try pseudo-section names. */ + for (curr = sections; curr; curr = curr->next) + { + len = strlen (curr->name); + if (len > strlen (name)) + continue; + + if (strncmp (curr->name, name, len) == 0) + { + if (strncmp (".end", name + len, 4) == 0) + { + *result = curr->vma + curr->size; + return TRUE; + } + + /* Insert more pseudo-section names here, if you like. */ + } + } + + return FALSE; +} + +static void +undefined_reference (const char * reftype, + const char * name) +{ + _bfd_error_handler (_("undefined %s reference in complex symbol: %s"), reftype, name); +} + +static bfd_boolean +eval_symbol (bfd_vma * result, + char * sym, + char ** advanced, + bfd * input_bfd, + struct elf_final_link_info * finfo, + bfd_vma addr, + bfd_vma section_offset, + size_t locsymcount, + int signed_p) +{ + int len; + int symlen; + bfd_vma a; + bfd_vma b; + const int bufsz = 4096; + char symbuf [bufsz]; + const char * symend; + bfd_boolean symbol_is_section = FALSE; + + len = strlen (sym); + symend = sym + len; + + if (len < 1 || len > bufsz) + { + bfd_set_error (bfd_error_invalid_operation); + return FALSE; + } + + switch (* sym) + { + case '.': + * result = addr + section_offset; + * advanced = sym + 1; + return TRUE; + + case '#': + ++ sym; + * result = strtoul (sym, advanced, 16); + return TRUE; + + case 'S': + symbol_is_section = TRUE; + case 's': + ++ sym; + symlen = strtol (sym, &sym, 10); + ++ sym; /* Skip the trailing ':'. */ + + if ((symend < sym) || ((symlen + 1) > bufsz)) + { + bfd_set_error (bfd_error_invalid_operation); + return FALSE; + } + + memcpy (symbuf, sym, symlen); + symbuf [symlen] = '\0'; + * advanced = sym + symlen; + + /* Is it always possible, with complex symbols, that gas "mis-guessed" + the symbol as a section, or vice-versa. so we're pretty liberal in our + interpretation here; section means "try section first", not "must be a + section", and likewise with symbol. */ + + if (symbol_is_section) + { + if ((resolve_section (symbuf, finfo->output_bfd->sections, result) != TRUE) + && (resolve_symbol (symbuf, input_bfd, finfo, result, locsymcount) != TRUE)) + { + undefined_reference ("section", symbuf); + return FALSE; + } + } + else + { + if ((resolve_symbol (symbuf, input_bfd, finfo, result, locsymcount) != TRUE) + && (resolve_section (symbuf, finfo->output_bfd->sections, + result) != TRUE)) + { + undefined_reference ("symbol", symbuf); + return FALSE; + } + } + + return TRUE; + + /* All that remains are operators. */ + +#define UNARY_OP(op) \ + if (strncmp (sym, #op, strlen (#op)) == 0) \ + { \ + sym += strlen (#op); \ + if (* sym == ':') \ + ++ sym; \ + if (eval_symbol (& a, sym, & sym, input_bfd, finfo, addr, \ + section_offset, locsymcount, signed_p) \ + != TRUE) \ + return FALSE; \ + if (signed_p) \ + * result = op ((signed)a); \ + else \ + * result = op a; \ + * advanced = sym; \ + return TRUE; \ + } + +#define BINARY_OP(op) \ + if (strncmp (sym, #op, strlen (#op)) == 0) \ + { \ + sym += strlen (#op); \ + if (* sym == ':') \ + ++ sym; \ + if (eval_symbol (& a, sym, & sym, input_bfd, finfo, addr, \ + section_offset, locsymcount, signed_p) \ + != TRUE) \ + return FALSE; \ + ++ sym; \ + if (eval_symbol (& b, sym, & sym, input_bfd, finfo, addr, \ + section_offset, locsymcount, signed_p) \ + != TRUE) \ + return FALSE; \ + if (signed_p) \ + * result = ((signed) a) op ((signed) b); \ + else \ + * result = a op b; \ + * advanced = sym; \ + return TRUE; \ + } + + default: + UNARY_OP (0-); + BINARY_OP (<<); + BINARY_OP (>>); + BINARY_OP (==); + BINARY_OP (!=); + BINARY_OP (<=); + BINARY_OP (>=); + BINARY_OP (&&); + BINARY_OP (||); + UNARY_OP (~); + UNARY_OP (!); + BINARY_OP (*); + BINARY_OP (/); + BINARY_OP (%); + BINARY_OP (^); + BINARY_OP (|); + BINARY_OP (&); + BINARY_OP (+); + BINARY_OP (-); + BINARY_OP (<); + BINARY_OP (>); +#undef UNARY_OP +#undef BINARY_OP + _bfd_error_handler (_("unknown operator '%c' in complex symbol"), * sym); + bfd_set_error (bfd_error_invalid_operation); + return FALSE; + } +} + +/* Entry point to evaluator, called from elf_link_input_bfd. */ + +static bfd_boolean +evaluate_complex_relocation_symbols (bfd * input_bfd, + struct elf_final_link_info * finfo, + size_t locsymcount) +{ + const struct elf_backend_data * bed; + Elf_Internal_Shdr * symtab_hdr; + struct elf_link_hash_entry ** sym_hashes; + asection * reloc_sec; + bfd_boolean result = TRUE; + + /* For each section, we're going to check and see if it has any + complex relocations, and we're going to evaluate any of them + we can. */ + + if (finfo->info->relocatable) + return TRUE; + + symtab_hdr = & elf_tdata (input_bfd)->symtab_hdr; + sym_hashes = elf_sym_hashes (input_bfd); + bed = get_elf_backend_data (input_bfd); + + for (reloc_sec = input_bfd->sections; reloc_sec; reloc_sec = reloc_sec->next) + { + Elf_Internal_Rela * internal_relocs; + unsigned long i; + + /* This section was omitted from the link. */ + if (! reloc_sec->linker_mark) + continue; + + /* Only process sections containing relocs. */ + if ((reloc_sec->flags & SEC_RELOC) == 0) + continue; + + if (reloc_sec->reloc_count == 0) + continue; + + /* Read in the relocs for this section. */ + internal_relocs + = _bfd_elf_link_read_relocs (input_bfd, reloc_sec, NULL, + (Elf_Internal_Rela *) NULL, + FALSE); + if (internal_relocs == NULL) + continue; + + for (i = reloc_sec->reloc_count; i--;) + { + Elf_Internal_Rela * rel; + char * sym_name; + unsigned long index; + Elf_Internal_Sym * sym; + bfd_vma result; + bfd_vma section_offset; + bfd_vma addr; + int signed_p = 0; + + rel = internal_relocs + i; + section_offset = reloc_sec->output_section->vma + + reloc_sec->output_offset; + addr = rel->r_offset; + + index = ELF32_R_SYM (rel->r_info); + if (bed->s->arch_size == 64) + index >>= 24; + + if (index < locsymcount) + { + /* The symbol is local. */ + sym = finfo->internal_syms + index; + + /* We're only processing STT_RELC or STT_SRELC type symbols. */ + if ((ELF_ST_TYPE (sym->st_info) != STT_RELC) && + (ELF_ST_TYPE (sym->st_info) != STT_SRELC)) + continue; + + sym_name = bfd_elf_string_from_elf_section + (input_bfd, symtab_hdr->sh_link, sym->st_name); + + signed_p = (ELF_ST_TYPE (sym->st_info) == STT_SRELC); + } + else + { + /* The symbol is global. */ + struct elf_link_hash_entry * h; + + if (elf_bad_symtab (input_bfd)) + continue; + + h = sym_hashes [index - locsymcount]; + while ( h->root.type == bfd_link_hash_indirect + || h->root.type == bfd_link_hash_warning) + h = (struct elf_link_hash_entry *) h->root.u.i.link; + + if (h->type != STT_RELC && h->type != STT_SRELC) + continue; + + signed_p = (h->type == STT_SRELC); + sym_name = (char *) h->root.root.string; + } +#ifdef DEBUG + printf ("Encountered a complex symbol!"); + printf (" (input_bfd %s, section %s, reloc %ld\n", + input_bfd->filename, reloc_sec->name, i); + printf (" symbol: idx %8.8lx, name %s\n", + index, sym_name); + printf (" reloc : info %8.8lx, addr %8.8lx\n", + rel->r_info, addr); + printf (" Evaluating '%s' ...\n ", sym_name); +#endif + if (eval_symbol (& result, sym_name, & sym_name, input_bfd, + finfo, addr, section_offset, locsymcount, + signed_p)) + /* Symbol evaluated OK. Update to absolute value. */ + set_symbol_value (input_bfd, finfo, index, result); + + else + result = FALSE; + } + + if (internal_relocs != elf_section_data (reloc_sec)->relocs) + free (internal_relocs); + } + + /* If nothing went wrong, then we adjusted + everything we wanted to adjust. */ + return result; +} + +static void +put_value (bfd_vma size, + unsigned long chunksz, + bfd * input_bfd, + bfd_vma x, + bfd_byte * location) +{ + location += (size - chunksz); + + for (; size; size -= chunksz, location -= chunksz, x >>= (chunksz * 8)) + { + switch (chunksz) + { + default: + case 0: + abort (); + case 1: + bfd_put_8 (input_bfd, x, location); + break; + case 2: + bfd_put_16 (input_bfd, x, location); + break; + case 4: + bfd_put_32 (input_bfd, x, location); + break; + case 8: +#ifdef BFD64 + bfd_put_64 (input_bfd, x, location); +#else + abort (); +#endif + break; + } + } +} + +static bfd_vma +get_value (bfd_vma size, + unsigned long chunksz, + bfd * input_bfd, + bfd_byte * location) +{ + bfd_vma x = 0; + + for (; size; size -= chunksz, location += chunksz) + { + switch (chunksz) + { + default: + case 0: + abort (); + case 1: + x = (x << (8 * chunksz)) | bfd_get_8 (input_bfd, location); + break; + case 2: + x = (x << (8 * chunksz)) | bfd_get_16 (input_bfd, location); + break; + case 4: + x = (x << (8 * chunksz)) | bfd_get_32 (input_bfd, location); + break; + case 8: +#ifdef BFD64 + x = (x << (8 * chunksz)) | bfd_get_64 (input_bfd, location); +#else + abort (); +#endif + break; + } + } + return x; +} + +static void +decode_complex_addend + (unsigned long * start, /* in bits */ + unsigned long * oplen, /* in bits */ + unsigned long * len, /* in bits */ + unsigned long * wordsz, /* in bytes */ + unsigned long * chunksz, /* in bytes */ + unsigned long * lsb0_p, + unsigned long * signed_p, + unsigned long * trunc_p, + unsigned long encoded) +{ + * start = encoded & 0x3F; + * len = (encoded >> 6) & 0x3F; + * oplen = (encoded >> 12) & 0x3F; + * wordsz = (encoded >> 18) & 0xF; + * chunksz = (encoded >> 22) & 0xF; + * lsb0_p = (encoded >> 27) & 1; + * signed_p = (encoded >> 28) & 1; + * trunc_p = (encoded >> 29) & 1; +} + +void +bfd_elf_perform_complex_relocation + (bfd * output_bfd ATTRIBUTE_UNUSED, + struct bfd_link_info * info, + bfd * input_bfd, + asection * input_section, + bfd_byte * contents, + Elf_Internal_Rela * rel, + Elf_Internal_Sym * local_syms, + asection ** local_sections) +{ + const struct elf_backend_data * bed; + Elf_Internal_Shdr * symtab_hdr; + asection * sec; + bfd_vma relocation = 0, shift, x; + unsigned long r_symndx; + bfd_vma mask; + unsigned long start, oplen, len, wordsz, + chunksz, lsb0_p, signed_p, trunc_p; + + /* Perform this reloc, since it is complex. + (this is not to say that it necessarily refers to a complex + symbol; merely that it is a self-describing CGEN based reloc. + i.e. the addend has the complete reloc information (bit start, end, + word size, etc) encoded within it.). */ + r_symndx = ELF32_R_SYM (rel->r_info); + bed = get_elf_backend_data (input_bfd); + if (bed->s->arch_size == 64) + r_symndx >>= 24; + +#ifdef DEBUG + printf ("Performing complex relocation %ld...\n", r_symndx); +#endif + + symtab_hdr = & elf_tdata (input_bfd)->symtab_hdr; + if (r_symndx < symtab_hdr->sh_info) + { + /* The symbol is local. */ + Elf_Internal_Sym * sym; + + sym = local_syms + r_symndx; + sec = local_sections [r_symndx]; + relocation = sym->st_value; + if (sym->st_shndx > SHN_UNDEF && + sym->st_shndx < SHN_LORESERVE) + relocation += (sec->output_offset + + sec->output_section->vma); + } + else + { + /* The symbol is global. */ + struct elf_link_hash_entry **sym_hashes; + struct elf_link_hash_entry * h; + + sym_hashes = elf_sym_hashes (input_bfd); + h = sym_hashes [r_symndx]; + + while (h->root.type == bfd_link_hash_indirect + || h->root.type == bfd_link_hash_warning) + h = (struct elf_link_hash_entry *) h->root.u.i.link; + + if (h->root.type == bfd_link_hash_defined + || h->root.type == bfd_link_hash_defweak) + { + sec = h->root.u.def.section; + relocation = h->root.u.def.value; + + if (! bfd_is_abs_section (sec)) + relocation += (sec->output_section->vma + + sec->output_offset); + } + if (h->root.type == bfd_link_hash_undefined + && !((*info->callbacks->undefined_symbol) + (info, h->root.root.string, input_bfd, + input_section, rel->r_offset, + info->unresolved_syms_in_objects == RM_GENERATE_ERROR + || ELF_ST_VISIBILITY (h->other)))) + return; + } + + decode_complex_addend (& start, & oplen, & len, & wordsz, + & chunksz, & lsb0_p, & signed_p, + & trunc_p, rel->r_addend); + + mask = (((1L << (len - 1)) - 1) << 1) | 1; + + if (lsb0_p) + shift = (start + 1) - len; + else + shift = (8 * wordsz) - (start + len); + + x = get_value (wordsz, chunksz, input_bfd, contents + rel->r_offset); + +#ifdef DEBUG + printf ("Doing complex reloc: " + "lsb0? %ld, signed? %ld, trunc? %ld, wordsz %ld, " + "chunksz %ld, start %ld, len %ld, oplen %ld\n" + " dest: %8.8lx, mask: %8.8lx, reloc: %8.8lx\n", + lsb0_p, signed_p, trunc_p, wordsz, chunksz, start, len, + oplen, x, mask, relocation); +#endif + + if (! trunc_p) + { + /* Now do an overflow check. */ + if (bfd_check_overflow ((signed_p ? + complain_overflow_signed : + complain_overflow_unsigned), + len, 0, (8 * wordsz), + relocation) == bfd_reloc_overflow) + (*_bfd_error_handler) + ("%s (%s + 0x%lx): relocation overflow: 0x%lx %sdoes not fit " + "within 0x%lx", + input_bfd->filename, input_section->name, rel->r_offset, + relocation, (signed_p ? "(signed) " : ""), mask); + } + + /* Do the deed. */ + x = (x & ~(mask << shift)) | ((relocation & mask) << shift); + +#ifdef DEBUG + printf (" relocation: %8.8lx\n" + " shifted mask: %8.8lx\n" + " shifted/masked reloc: %8.8lx\n" + " result: %8.8lx\n", + relocation, (mask << shift), + ((relocation & mask) << shift), x); +#endif + put_value (wordsz, chunksz, input_bfd, x, contents + rel->r_offset); +} + /* When performing a relocatable link, the input relocations are preserved. But, if they reference global symbols, the indices referenced must be updated. Update all the relocations in @@ -7509,6 +8219,9 @@ elf_link_input_bfd (struct elf_final_link_info *finfo, bfd *input_bfd) return FALSE; } + if (! evaluate_complex_relocation_symbols (input_bfd, finfo, locsymcount)) + return FALSE; + /* Relocate the contents of each section. */ sym_hashes = elf_sym_hashes (input_bfd); for (o = input_bfd->sections; o != NULL; o = o->next) |