/* Pedantic checking of DWARF files Copyright (C) 2009,2010,2011 Red Hat, Inc. This file is part of elfutils. This file 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. elfutils 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, see . */ // xxx drop as soon as not necessary #define __STDC_FORMAT_MACROS #include "reloc.hh" #include "elf_file.hh" #include "messages.hh" #include "misc.hh" #include "readctx.hh" #include "pri.hh" #include #include #include #include namespace { class reloc_locus : public locus { locus const &_m_ref; size_t _m_index; uint64_t _m_offset; int _m_type; public: reloc_locus (int type, locus const &ref, uint64_t offset) : _m_ref (ref) , _m_index (-1) , _m_offset (offset) , _m_type (type) { } reloc_locus (int type, locus const &ref, unsigned index) : _m_ref (ref) , _m_index (index) , _m_offset (-1) , _m_type (type) { } void set_offset (uint64_t offset) { _m_offset = offset; } virtual std::string format (bool) const { std::stringstream ss; ss << (_m_type == SHT_REL ? ".rel" : ".rela") << " "; if (_m_offset != (uint64_t)-1) ss << pri::hex (_m_offset); else { assert (_m_index != (size_t)-1); ss << "#" << _m_index; } // Do non-brief formatting of referee ss << " of " << _m_ref.format (); return ss.str (); } }; } relocation * relocation_next (relocation_data *reloc, uint64_t offset, locus const &loc, enum skip_type st) { if (reloc == NULL || reloc->rel == NULL) return NULL; while (reloc->index < reloc->size) { struct relocation *rel = reloc->rel + reloc->index; /* This relocation entry is ahead of us. */ if (rel->offset > offset) return NULL; reloc->index++; if (rel->invalid) continue; if (rel->offset < offset) { if (st != skip_ok) { reloc_locus reloc_where (reloc->type, loc, rel->offset); wr_error (reloc_where) << (st == skip_unref ? "relocation targets unreferenced portion of the section." : (assert (st == skip_mismatched), "relocation relocates unknown datum.")) << std::endl; } continue; } return rel; } return NULL; } /* Skip all relocation up to offset, and leave cursor pointing at that relocation, so that next time relocation_next is called, relocation matching that offset is immediately yielded. */ void relocation_skip (struct relocation_data *reloc, uint64_t offset, locus const &loc, enum skip_type st) { if (reloc != NULL && reloc->rel != NULL) relocation_next (reloc, offset - 1, loc, st); } void relocation_reset (struct relocation_data *reloc) { if (reloc != NULL) reloc->index = 0; } /* Skip all the remaining relocations. */ void relocation_skip_rest (relocation_data *reloc, locus const &loc) { if (reloc->rel != NULL) relocation_next (reloc, (uint64_t)-1, loc, skip_mismatched); } static void do_one_relocation (elf_file const *file, relocation_data *reloc, relocation *rel, unsigned rel_width, uint64_t *value, reloc_locus const &reloc_where, rel_target reltgt, GElf_Sym *symbol, GElf_Sym **symptr) { #define require(T, STREAMOPS) \ do { \ if (!(T)) \ { \ wr_error (reloc_where) << STREAMOPS \ << std::endl; \ return; \ } \ } while (0) #define require_valid_section_index \ require (section_index_valid, \ "invalid associated section #" << section_index << '.') symbol = gelf_getsym (reloc->symdata, rel->symndx, symbol); if (symptr != NULL) *symptr = symbol; if (symbol == NULL) { wr_error (&reloc_where, ": couldn't obtain symbol #%d: %s.\n", rel->symndx, elf_errmsg (-1)); return; } GElf_Section section_index = symbol->st_shndx; /* XXX We should handle SHN_XINDEX here. Or, instead, maybe it would be possible to use dwfl, which already does XINDEX translation. */ if (section_index == 0) { wr_error (reloc_where) << "relocation refers to an undefined symbol." << std::endl; return; } // Valid in the sense that it can be used as an index to file->sec bool section_index_valid = section_index < file->size; /* For ET_REL files, we do section layout manually. But we don't update symbol table doing that. So instead of looking at symbol value, look at section address. */ GElf_Addr sym_value = symbol->st_value; if (file->ehdr.e_type == ET_REL && ELF64_ST_TYPE (symbol->st_info) == STT_SECTION) { if (sym_value != 0) wr_message (reloc_where, mc_reloc | mc_impact_1) << "relocation formed using STT_SECTION symbol with non-zero value." << std::endl; require_valid_section_index; sym_value = file->sec[section_index].shdr.sh_addr; } /* It's target value, not section offset. */ if (reltgt == rel_target::rel_value || reltgt == rel_target::rel_address || reltgt == rel_target::rel_exec) { /* If a target value is what's expected, then complain if it's not either SHN_ABS, an SHF_ALLOC section, or SHN_UNDEF. For data forms of address_size, an SHN_UNDEF reloc is acceptable, otherwise reject it. */ if (!(section_index == SHN_ABS || (reltgt == rel_target::rel_address && (section_index == SHN_UNDEF || section_index == SHN_COMMON)))) { if (reltgt != rel_target::rel_address && section_index == SHN_UNDEF) wr_error (&reloc_where, ": relocation of an address is formed using SHN_UNDEF symbol" " (symtab index %d).\n", rel->symndx); else { require_valid_section_index; GElf_Shdr *shdr = &file->sec[section_index].shdr; if ((shdr->sh_flags & SHF_ALLOC) != SHF_ALLOC) wr_message (mc_reloc | mc_impact_3, &reloc_where, ": associated section %s isn't SHF_ALLOC.\n", file->sec[section_index].name); if (reltgt == rel_target::rel_exec && (shdr->sh_flags & SHF_EXECINSTR) != SHF_EXECINSTR) /* This may still be kosher, but it's suspicious. */ wr_message (mc_reloc | mc_impact_2, &reloc_where, ": relocation against %s is suspicious, expected executable section.\n", file->sec[section_index].name); } } } else { require_valid_section_index; section_id secid = file->sec[section_index].id; if (reltgt != secid) // If symtab[symndx].st_shndx does not match the expected // debug section's index, complain. wr_error (reloc_where) << "relocation references section " << (file->sec[section_index].name ?: "") << ", but " << section_locus (secid) << " was expected." << std::endl; } /* Only do the actual relocation if we have ET_REL files. For non-ET_REL files, only do the above checking. */ if (file->ehdr.e_type == ET_REL) { *value = rel->addend + sym_value; if (rel_width == 4) *value = *value & (uint64_t)(uint32_t)-1; } #undef require_valid_section_index #undef require } /* SYMPTR may be NULL, otherwise (**SYMPTR) has to yield valid memory location. When the function returns, (*SYMPTR) is either NULL, in which case we failed or didn't get around to obtain the symbol from symbol table, or non-NULL, in which case the symbol was initialized. */ void relocate_one (struct elf_file const *file, struct relocation_data *reloc, struct relocation *rel, unsigned width, uint64_t *value, locus const &loc, rel_target reltgt, GElf_Sym **symptr) { if (rel->invalid) return; reloc_locus reloc_where (reloc->type, loc, rel->offset); GElf_Sym symbol_mem, *symbol; if (symptr != NULL) { symbol = *symptr; *symptr = NULL; } else symbol = &symbol_mem; if (reltgt == sec_invalid) { wr_message (reloc_where, mc_impact_3 | mc_reloc) << "relocates a datum that shouldn't be relocated.\n"; return; } Elf_Type type = ebl_reloc_simple_type (file->ebl, rel->type); unsigned rel_width; switch (type) { case ELF_T_BYTE: rel_width = 1; break; case ELF_T_HALF: rel_width = 2; break; case ELF_T_WORD: case ELF_T_SWORD: rel_width = 4; break; case ELF_T_XWORD: case ELF_T_SXWORD: rel_width = 8; break; default: /* This has already been diagnosed during the isolated validation of relocation section. */ return; }; if (rel_width != width) wr_error (reloc_where) << rel_width << "-byte relocation relocates " << width << "-byte datum.\n"; // Tolerate if we failed to obtain the symbol table. if (reloc->symdata != NULL) do_one_relocation (file, reloc, rel, rel_width, value, reloc_where, reltgt, symbol, symptr); } static GElf_Rela * get_rel_or_rela (Elf_Data *data, int ndx, GElf_Rela *dst, size_t type) { if (type == SHT_RELA) return gelf_getrela (data, ndx, dst); else { assert (type == SHT_REL); GElf_Rel rel_mem; if (gelf_getrel (data, ndx, &rel_mem) == NULL) return NULL; dst->r_offset = rel_mem.r_offset; dst->r_info = rel_mem.r_info; dst->r_addend = 0; return dst; } } /* Sort the reloc section so that the applicable addresses of relocation entries are monotonously increasing. */ static int compare_rel (const void *a, const void *b) { return ((struct relocation *)a)->offset - ((struct relocation *)b)->offset; } bool read_rel (struct elf_file *file, struct sec *sec, Elf_Data *reldata, bool elf_64) { assert (sec->rel.type == SHT_REL || sec->rel.type == SHT_RELA); bool is_rela = sec->rel.type == SHT_RELA; struct read_ctx ctx; read_ctx_init (&ctx, sec->data, file->other_byte_order); size_t entrysize = elf_64 ? (is_rela ? sizeof (Elf64_Rela) : sizeof (Elf64_Rel)) : (is_rela ? sizeof (Elf32_Rela) : sizeof (Elf32_Rel)); size_t count = reldata->d_size / entrysize; section_locus parent (sec->id); for (unsigned i = 0; i < count; ++i) { reloc_locus where (sec->rel.type, parent, i); REALLOC (&sec->rel, rel); struct relocation *cur = sec->rel.rel + sec->rel.size++; new (cur) relocation (); GElf_Rela rela_mem, *rela = get_rel_or_rela (reldata, i, &rela_mem, sec->rel.type); if (rela == NULL) { wr_error (&where, ": couldn't read relocation.\n"); skip: cur->invalid = true; continue; } int cur_type = GELF_R_TYPE (rela->r_info); if (cur_type == 0) /* No relocation. */ { wr_message (mc_impact_3 | mc_reloc | mc_acc_bloat, &where, ": NONE relocation is superfluous.\n"); goto skip; } cur->offset = rela->r_offset; cur->symndx = GELF_R_SYM (rela->r_info); cur->type = cur_type; where.set_offset (cur->offset); Elf_Type type = ebl_reloc_simple_type (file->ebl, cur->type); int width; switch (type) { case ELF_T_WORD: case ELF_T_SWORD: width = 4; break; case ELF_T_XWORD: case ELF_T_SXWORD: width = 8; break; case ELF_T_BYTE: case ELF_T_HALF: /* Technically legal, but never used. Better have dwarflint flag them as erroneous, because it's more likely these are a result of a bug than actually being used. */ { char buf[64]; wr_error (&where, ": 8 or 16-bit relocation type %s.\n", ebl_reloc_type_name (file->ebl, cur->type, buf, sizeof (buf))); goto skip; } default: { char buf[64]; wr_error (&where, ": invalid relocation %d (%s).\n", cur->type, ebl_reloc_type_name (file->ebl, cur->type, buf, sizeof (buf))); goto skip; } }; if (cur->offset + width >= sec->data->d_size) { wr_error (&where, ": relocation doesn't fall into relocated section.\n"); goto skip; } uint64_t value; if (width == 4) value = dwarflint_read_4ubyte_unaligned ((char *)sec->data->d_buf + cur->offset, file->other_byte_order); else { assert (width == 8); value = dwarflint_read_8ubyte_unaligned ((char *)sec->data->d_buf + cur->offset, file->other_byte_order); } if (is_rela) { if (value != 0) wr_message (mc_impact_2 | mc_reloc, &where, ": SHR_RELA relocates a place with non-zero value (addend=%#" PRIx64", value=%#"PRIx64").\n", rela->r_addend, value); cur->addend = rela->r_addend; } else cur->addend = value; } qsort (sec->rel.rel, sec->rel.size, sizeof (*sec->rel.rel), &compare_rel); return true; }