From: Petr Machata Date: Fri, 23 Oct 2009 12:42:25 +0000 (+0200) Subject: dwarflint: .debug_line logic in file of its own X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=672525b45f8c729776ccb9c2b5656e30d1069dad;p=thirdparty%2Felfutils.git dwarflint: .debug_line logic in file of its own * ... and reloc code, too --- diff --git a/src/Makefile.am b/src/Makefile.am index fb61e3ea1..13d9b3e18 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -90,8 +90,10 @@ dwarflint_SOURCES = dwarfstrings.c \ dwarflint/checks.cc dwarflint/checks.hh \ dwarflint/checks-low.cc dwarflint/checks-low.hh \ dwarflint/addr-record.cc dwarflint/addr-record.h \ + dwarflint/reloc.cc dwarflint/reloc.h \ dwarflint/checks-high.hh \ dwarflint/check_debug_abbrev.cc \ + dwarflint/check_debug_line.cc \ dwarflint/check_matching_ranges.cc \ dwarflint/check_range_out_of_scope.cc \ dwarflint/check_expected_trees.cc diff --git a/src/dwarflint/check_debug_line.cc b/src/dwarflint/check_debug_line.cc new file mode 100644 index 000000000..a3993e521 --- /dev/null +++ b/src/dwarflint/check_debug_line.cc @@ -0,0 +1,551 @@ +#include "checks-low.hh" + +#include +#include "../libdw/known-dwarf.h" +#include "dwarfstrings.h" + +#include + +namespace +{ + class check_debug_line + : public check + { + section *_m_sec; + + struct include_directory_t + { + std::string name; + bool used; + }; + typedef std::vector include_directories_t; + include_directories_t _m_include_directories; + + struct file_t + { + const char *name; + uint64_t dir_idx; + bool used; + }; + typedef std::vector files_t; + files_t _m_files; + + public: + explicit check_debug_line (dwarflint &lint) + : _m_sec (lint.check (_m_sec)) + { + addr_record line_tables; + WIPE (line_tables); + if (!check_line_structural (&_m_sec->file, + &_m_sec->sect, + &line_tables)) + throw check_base::failed (""); //xxx + + check_debug_info *info = NULL; + info = toplev_check (lint, info); + if (info != NULL) + for (std::vector::iterator it = info->cus.begin (); + it != info->cus.end (); ++it) + for (size_t i = 0; i < it->line_refs.size; ++i) + { + struct ref *ref = it->line_refs.refs + i; + if (!addr_record_has_addr (&line_tables, ref->addr)) + { + std::stringstream ss; + ss << ": unresolved reference to .debug_line table " + << "0x" << std::hex << ref->addr << "."; + wr_error (&ref->who, "%s\n", ss.str ().c_str ()); + } + } + addr_record_free (&line_tables); + } + + /* Directory index. */ + bool read_directory_index (read_ctx *ctx, + const char *name, uint64_t *ptr, + where *where, bool &retval) + { + size_t nfile = _m_files.size () + 1; + if (!checked_read_uleb128 (ctx, ptr, + where, "directory index")) + return false; + if (*name == '/' && *ptr != 0) + wr_message (mc_impact_2 | mc_line | mc_header, where, + ": file #%zd has absolute pathname," + " but refers to directory != 0.\n", nfile); + if (*ptr > _m_include_directories.size ()) + /* Not >=, dirs are indexed from 1. */ + { + std::stringstream ss; + ss << ": file #" << nfile + << " refers to directory #" << *ptr + << ", which wasn't defined.\n"; + wr_message (mc_impact_4 | mc_line | mc_header, where, + "%s\n", ss.str ().c_str ()); + /* Consumer might choke on that. */ + retval = false; + } + else if (*ptr != 0) + _m_include_directories[*ptr - 1].used = true; + return true; + } + + bool + use_file (uint64_t file_idx, where *where) + { + if (file_idx == 0 || file_idx > _m_files.size ()) + { + std::stringstream ss; + ss << ": DW_LNS_set_file: invalid file index " << file_idx << '.'; + wr_error (where, "%s\n", ss.str ().c_str ()); + return false; + } + else + _m_files[file_idx - 1].used = true; + return true; + } + + bool + check_line_structural (struct elf_file *file, + struct sec *sec, + struct addr_record *line_tables); + }; + + reg reg_debug_line; +} + +bool +check_debug_line::check_line_structural (struct elf_file *file, + struct sec *sec, + struct addr_record *line_tables) +{ + struct read_ctx ctx; + read_ctx_init (&ctx, sec->data, file->other_byte_order); + bool retval = true; + + while (!read_ctx_eof (&ctx)) + { + struct where where = WHERE (sec->id, NULL); + uint64_t set_offset = read_ctx_get_offset (&ctx); + where_reset_1 (&where, set_offset); + addr_record_add (line_tables, set_offset); + const unsigned char *set_begin = ctx.ptr; + + /* Size. */ + uint32_t size32; + uint64_t size; + int offset_size; + if (!read_ctx_read_4ubyte (&ctx, &size32)) + { + wr_error (&where, ": can't read table length.\n"); + return false; + } + if (!read_size_extra (&ctx, size32, &size, &offset_size, &where)) + return false; + + struct read_ctx sub_ctx; + const unsigned char *set_end = ctx.ptr + size; + if (!read_ctx_init_sub (&sub_ctx, &ctx, set_begin, set_end)) + { + not_enough: + wr_error (&where, PRI_NOT_ENOUGH, "next unit"); + return false; + } + sub_ctx.ptr = ctx.ptr; + sub_ctx.begin = ctx.begin; + + { + /* Version. */ + uint16_t version; + if (!read_ctx_read_2ubyte (&sub_ctx, &version)) + { + wr_error (&where, ": can't read set version.\n"); + skip: + retval = false; + goto next; + } + if (!supported_version (version, 2, &where, 2, 3)) + goto skip; + + /* Header length. */ + uint64_t header_length; + if (!read_ctx_read_offset (&sub_ctx, offset_size == 8, &header_length)) + { + wr_error (&where, ": can't read attribute value.\n"); + goto skip; + } + const unsigned char *program_start = sub_ctx.ptr + header_length; + + /* Minimum instruction length. */ + uint8_t minimum_i_length; + if (!read_ctx_read_ubyte (&sub_ctx, &minimum_i_length)) + { + wr_error (&where, ": can't read minimum instruction length.\n"); + goto skip; + } + + /* Default value of is_stmt. */ + uint8_t default_is_stmt; + if (!read_ctx_read_ubyte (&sub_ctx, &default_is_stmt)) + { + wr_error (&where, ": can't read default_is_stmt.\n"); + goto skip; + } + /* 7.21: The boolean values "true" and "false" used by the line + number information program are encoded as a single byte + containing the value 0 for "false," and a non-zero value for + "true." [But give a notice if it's not 0 or 1.] */ + if (default_is_stmt != 0 + && default_is_stmt != 1) + wr_message (mc_line | mc_impact_2 | mc_header, &where, + ": default_is_stmt should be 0 or 1, not %ud\n", + default_is_stmt); + + /* Line base. */ + int8_t line_base; + if (!read_ctx_read_ubyte (&sub_ctx, (uint8_t *)&line_base)) + { + wr_error (&where, ": can't read line_base.\n"); + goto skip; + } + + /* Line range. */ + uint8_t line_range; + if (!read_ctx_read_ubyte (&sub_ctx, &line_range)) + { + wr_error (&where, ": can't read line_range.\n"); + goto skip; + } + + /* Opcode base. */ + uint8_t opcode_base; + if (!read_ctx_read_ubyte (&sub_ctx, &opcode_base)) + { + wr_error (&where, ": can't read opcode_base.\n"); + goto skip; + } + + /* Standard opcode lengths. */ + if (opcode_base == 0) + { + wr_error (&where, ": opcode base set to 0.\n"); + opcode_base = 1; // so that in following, our -1s don't underrun + } + uint8_t std_opc_lengths[opcode_base - 1]; /* -1, opcodes go from 1. */ + for (unsigned i = 0; i < (unsigned)(opcode_base - 1); ++i) + if (!read_ctx_read_ubyte (&sub_ctx, std_opc_lengths + i)) + { + wr_error (&where, + ": can't read length of standard opcode #%d.\n", i); + goto skip; + } + + while (!read_ctx_eof (&sub_ctx)) + { + const char *name = read_ctx_read_str (&sub_ctx); + if (name == NULL) + { + wr_error (&where, + ": can't read name of include directory #%zd.\n", + /* Numbered from 1. */ + _m_include_directories.size () + 1); + goto skip; + } + if (*name == 0) + break; + + _m_include_directories.push_back ((include_directory_t){name, false}); + } + + /* File names. */ + while (1) + { + const char *name = read_ctx_read_str (&sub_ctx); + if (name == NULL) + { + wr_error (&where, + ": can't read name of file #%zd.\n", + _m_files.size () + 1); /* Numbered from 1. */ + goto skip; + } + if (*name == 0) + break; + + uint64_t dir_idx; + if (!read_directory_index (&sub_ctx, name, &dir_idx, &where, retval)) + goto skip; + + /* Time of last modification. */ + uint64_t timestamp; + if (!checked_read_uleb128 (&sub_ctx, ×tamp, + &where, "timestamp of file entry")) + goto skip; + + /* Size of the file. */ + uint64_t file_size; + if (!checked_read_uleb128 (&sub_ctx, &file_size, + &where, "file size of file entry")) + goto skip; + + _m_files.push_back ((struct file_t){name, dir_idx, false}); + } + + /* Skip the rest of the header. */ + if (sub_ctx.ptr > program_start) + { + std::stringstream ss; + ss << ": header claims that it has a size of 0x" + << std::hex << header_length + << ", but in fact it has a size of 0x" + << sub_ctx.ptr - program_start + header_length << '.'; + + wr_error (&where, "%s\n", ss.str ().c_str ()); + /* Assume that the header lies, and what follows is in + fact line number program. */ + retval = false; + } + else if (sub_ctx.ptr < program_start) + { + struct where wh = WHERE (sec_line, NULL); + if (!check_zero_padding (&sub_ctx, cat (mc_line, mc_header), &where)) + wr_message_padding_n0 (cat (mc_line, mc_header), &wh, + read_ctx_get_offset (&sub_ctx), + program_start - sub_ctx.begin); + sub_ctx.ptr = program_start; + } + + bool terminated = false; + bool first_file = true; + bool seen_opcode = false; + while (!read_ctx_eof (&sub_ctx)) + { + where_reset_2 (&where, read_ctx_get_offset (&sub_ctx)); + uint8_t opcode; + if (!read_ctx_read_ubyte (&sub_ctx, &opcode)) + { + wr_error (&where, ": can't read opcode.\n"); + goto skip; + } + + unsigned operands = 0; + uint8_t extended = 0; + switch (opcode) + { + /* Extended opcodes. */ + case 0: + { + uint64_t skip_len; + if (!checked_read_uleb128 (&sub_ctx, &skip_len, &where, + "length of extended opcode")) + goto skip; + const unsigned char *next = sub_ctx.ptr + skip_len; + if (!read_ctx_read_ubyte (&sub_ctx, &extended)) + { + wr_error (&where, ": can't read extended opcode.\n"); + goto skip; + } + + bool handled = true; + switch (extended) + { + case DW_LNE_end_sequence: + terminated = true; + break; + + case DW_LNE_set_address: + { + uint64_t ctx_offset = read_ctx_get_offset (&sub_ctx); + uint64_t addr; + if (!read_ctx_read_offset (&sub_ctx, + file->addr_64, &addr)) + { + wr_error (&where, ": can't read operand of DW_LNE_set_address.\n"); + goto skip; + } + + struct relocation *rel; + if ((rel = relocation_next (&sec->rel, ctx_offset, + &where, skip_mismatched))) + relocate_one (file, &sec->rel, rel, + file->addr_64 ? 8 : 4, + &addr, &where, rel_address, NULL); + else if (file->ehdr.e_type == ET_REL) + wr_message (mc_impact_2 | mc_line | mc_reloc, &where, + PRI_LACK_RELOCATION, "DW_LNE_set_address"); + break; + } + + case DW_LNE_define_file: + { + const char *name; + if ((name = read_ctx_read_str (&sub_ctx)) == NULL) + { + wr_error (&where, + ": can't read filename operand of DW_LNE_define_file.\n"); + goto skip; + } + uint64_t dir_idx; + if (!read_directory_index (&sub_ctx, name, &dir_idx, + &where, retval)) + goto skip; + _m_files.push_back + ((struct file_t){name, dir_idx, false}); + operands = 2; /* Skip mtime & size of the file. */ + } + + /* See if we know about any other standard opcodes. */ + default: + handled = false; + switch (extended) + { +#define ONE_KNOWN_DW_LNE(NAME, CODE) case CODE: break; + ALL_KNOWN_DW_LNE +#undef ONE_KNOWN_DW_LNE + default: + /* No we don't, emit a warning. */ + wr_message (mc_impact_2 | mc_line, &where, + ": unknown extended opcode #%d.\n", extended); + }; + }; + + if (sub_ctx.ptr > next) + { + std::stringstream ss; + ss << ": opcode claims that it has a size of 0x" + << std::hex << skip_len + << ", but in fact it has a size of 0x" + << (skip_len + (next - sub_ctx.ptr)) << '.'; + wr_error (&where, "%s\n", ss.str ().c_str ()); + retval = false; + } + else if (sub_ctx.ptr < next) + { + if (handled + && !check_zero_padding (&sub_ctx, mc_line, &where)) + { + struct where wh = WHERE (sec_line, NULL); + wr_message_padding_n0 (mc_line, &wh, + read_ctx_get_offset (&sub_ctx), + next - sub_ctx.begin); + } + sub_ctx.ptr = next; + } + break; + } + + /* Standard opcodes that need validation or have + non-ULEB operands. */ + case DW_LNS_fixed_advance_pc: + { + uint16_t a; + if (!read_ctx_read_2ubyte (&sub_ctx, &a)) + { + wr_error (&where, ": can't read operand of DW_LNS_fixed_advance_pc.\n"); + goto skip; + } + break; + } + + case DW_LNS_set_file: + { + uint64_t file_idx; + if (!checked_read_uleb128 (&sub_ctx, &file_idx, &where, + "DW_LNS_set_file operand")) + goto skip; + if (!use_file (file_idx, &where)) + retval = false; + first_file = false; + } + break; + + case DW_LNS_set_isa: + // XXX is it possible to validate this? + operands = 1; + break; + + /* All the other opcodes. */ + default: + if (opcode < opcode_base) + operands = std_opc_lengths[opcode - 1]; + + switch (opcode) + { +#define ONE_KNOWN_DW_LNS(NAME, CODE) case CODE: break; + ALL_KNOWN_DW_LNS +#undef ONE_KNOWN_DW_LNS + + default: + if (opcode < opcode_base) + wr_message (mc_impact_2 | mc_line, &where, + ": unknown standard opcode #%d.\n", opcode); + }; + }; + + for (unsigned i = 0; i < operands; ++i) + { + uint64_t operand; + char buf[128]; + if (opcode != 0) + sprintf (buf, "operand #%d of DW_LNS_%s", + i, dwarf_line_standard_opcode_string (opcode)); + else + sprintf (buf, "operand #%d of extended opcode %d", + i, extended); + if (!checked_read_uleb128 (&sub_ctx, &operand, &where, buf)) + goto skip; + } + + if (first_file) + { + if (!use_file (1, &where)) + retval = false; + first_file = false; + } + + if (opcode != 0 || extended != DW_LNE_end_sequence) + seen_opcode = true; + } + + for (size_t i = 0; i < _m_include_directories.size (); ++i) + if (!_m_include_directories[i].used) + wr_message (mc_impact_3 | mc_acc_bloat | mc_line | mc_header, + &where, ": the include #%zd `%s' is not used.\n", + i + 1, _m_include_directories[i].name.c_str ()); + + for (size_t i = 0; i < _m_files.size (); ++i) + if (!_m_files[i].used) + wr_message (mc_impact_3 | mc_acc_bloat | mc_line | mc_header, + &where, ": the file #%zd `%s' is not used.\n", + i + 1, _m_files[i].name); + + if (!seen_opcode) + wr_message (mc_line | mc_acc_bloat | mc_impact_3, &where, + ": empty line number program.\n"); + + struct where wh = WHERE (sec_line, NULL); + if (!terminated) + wr_error (&where, + ": sequence of opcodes not terminated with DW_LNE_end_sequence.\n"); + else if (sub_ctx.ptr != sub_ctx.end + && !check_zero_padding (&sub_ctx, mc_line, &wh)) + wr_message_padding_n0 (mc_line, &wh, + /*begin*/read_ctx_get_offset (&sub_ctx), + /*end*/sub_ctx.end - sub_ctx.begin); + } + + /* XXX overlaps in defined addresses are probably OK, one + instruction can be derived from several statements. But + certain flags in table should be consistent in that case, + namely is_stmt, basic_block, end_sequence, prologue_end, + epilogue_begin, isa. */ + + next: + if (!read_ctx_skip (&ctx, size)) + goto not_enough; + } + + if (retval) + relocation_skip_rest (&sec->rel, sec->id); + + return retval; +} diff --git a/src/dwarflint/checks-low.cc b/src/dwarflint/checks-low.cc index 17259273c..fbcfaa609 100644 --- a/src/dwarflint/checks-low.cc +++ b/src/dwarflint/checks-low.cc @@ -394,44 +394,3 @@ namespace reg > reg_debug_pubnames; reg > reg_debug_pubtypes; } - -namespace -{ - class check_debug_line - : public check - { - section *_m_sec; - - public: - explicit check_debug_line (dwarflint &lint) - : _m_sec (lint.check (_m_sec)) - { - addr_record line_tables; - WIPE (line_tables); - if (!check_line_structural (&_m_sec->file, - &_m_sec->sect, - &line_tables)) - throw check_base::failed (""); //xxx - - check_debug_info *info = NULL; - info = toplev_check (lint, info); - if (info != NULL) - for (std::vector::iterator it = info->cus.begin (); - it != info->cus.end (); ++it) - for (size_t i = 0; i < it->line_refs.size; ++i) - { - struct ref *ref = it->line_refs.refs + i; - if (!addr_record_has_addr (&line_tables, ref->addr)) - { - std::stringstream ss; - ss << ": unresolved reference to .debug_line table " - << "0x" << std::hex << ref->addr << "."; - wr_error (&ref->who, "%s\n", ss.str ().c_str ()); - } - } - addr_record_free (&line_tables); - } - }; - - reg reg_debug_line; -} diff --git a/src/dwarflint/low.c b/src/dwarflint/low.c index 3ceef8c25..671c2655e 100644 --- a/src/dwarflint/low.c +++ b/src/dwarflint/low.c @@ -42,7 +42,6 @@ #include #include "../libdw/dwarf.h" -#include "../libdw/known-dwarf.h" #include "../libebl/libebl.h" #include "dwarfstrings.h" #include "low.h" @@ -62,8 +61,6 @@ check_category (enum message_category cat) #define PRI_CU "CU 0x%" PRIx64 #define PRI_DIE "DIE 0x%" PRIx64 -#define PRI_NOT_ENOUGH ": not enough data for %s.\n" -#define PRI_LACK_RELOCATION ": %s seems to lack a relocation.\n" static struct cu *cu_find_cu (struct cu *cu_chain, uint64_t offset); @@ -561,7 +558,7 @@ check_global_die_references (struct cu *cu_chain) return retval; } -static bool +bool read_size_extra (struct read_ctx *ctx, uint32_t size32, uint64_t *sizep, int *offset_sizep, struct where *wh) { @@ -590,7 +587,7 @@ read_size_extra (struct read_ctx *ctx, uint32_t size32, uint64_t *sizep, return true; } -static bool +bool check_zero_padding (struct read_ctx *ctx, enum message_category category, struct where *wh) @@ -610,252 +607,6 @@ check_zero_padding (struct read_ctx *ctx, return true; } -static struct where -where_from_reloc (struct relocation_data *reloc, struct where *ref) -{ - struct where where - = WHERE (reloc->type == SHT_REL ? sec_rel : sec_rela, NULL); - where_reset_1 (&where, reloc->rel[reloc->index].offset); - where.ref = ref; - return where; -} - -enum skip_type -{ - skip_unref = 0, - skip_mismatched = 1, - skip_ok, -}; - -static struct relocation * -relocation_next (struct relocation_data *reloc, uint64_t offset, - struct where *where, 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) - { - struct where reloc_where = where_from_reloc (reloc, where); - where_reset_2 (&reloc_where, rel->offset); - void (*w) (const struct where *, const char *, ...) = wr_error; - (*w) (&reloc_where, - ((const char *[]) - {": relocation targets unreferenced portion of the section.\n", - ": relocation is mismatched.\n"})[st]); - } - 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. */ -static void -relocation_skip (struct relocation_data *reloc, uint64_t offset, - struct where *where, enum skip_type st) -{ - if (reloc != NULL && reloc->rel != NULL) - relocation_next (reloc, offset - 1, where, st); -} - -/* Skip all the remaining relocations. */ -static void -relocation_skip_rest (struct sec *sec) -{ - if (sec->rel.rel != NULL) - relocation_next (&sec->rel, (uint64_t)-1, &WHERE (sec->id, NULL), - skip_mismatched); -} - -/* 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. */ -static void -relocate_one (struct elf_file *file, - struct relocation_data *reloc, - struct relocation *rel, - unsigned width, uint64_t *value, struct where *where, - enum section_id offset_into, GElf_Sym **symptr) -{ - if (rel->invalid) - return; - - struct where reloc_where = where_from_reloc (reloc, where); - where_reset_2 (&reloc_where, rel->offset); - struct where reloc_ref_where = reloc_where; - reloc_ref_where.next = where; - - GElf_Sym symbol_mem, *symbol; - if (symptr != NULL) - { - symbol = *symptr; - *symptr = NULL; - } - else - symbol = &symbol_mem; - - if (offset_into == sec_invalid) - { - wr_message (mc_impact_3 | mc_reloc, &reloc_ref_where, - ": 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_ref_where, - ": %d-byte relocation relocates %d-byte datum.\n", - rel_width, width); - - /* Tolerate that we might have failed to obtain the symbol table. */ - if (reloc->symdata != NULL) - { - 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; - } - - uint64_t 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. */ - - /* 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. */ - uint64_t sym_value = symbol->st_value; - if (file->ehdr.e_type == ET_REL - && ELF64_ST_TYPE (symbol->st_info) == STT_SECTION) - { - assert (sym_value == 0); - sym_value = file->sec[section_index].shdr.sh_addr; - } - - /* It's target value, not section offset. */ - if (offset_into == rel_value - || offset_into == rel_address - || offset_into == 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 - || (offset_into == rel_address - && (section_index == SHN_UNDEF - || section_index == SHN_COMMON)))) - { - if (offset_into != rel_address && section_index == SHN_UNDEF) - wr_error (&reloc_where, - ": relocation of an address is formed against SHN_UNDEF symbol" - " (symtab index %d).\n", rel->symndx); - else - { - 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 (offset_into == 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 - { - enum section_id id; - /* If symtab[symndx].st_shndx does not match the expected - debug section's index, complain. */ - if (section_index >= file->size) - wr_error (&reloc_where, - ": invalid associated section #%" PRId64 ".\n", - section_index); - else if ((id = file->sec[section_index].id) != offset_into) - { - char *wh1 = id != sec_invalid - ? strdup (where_fmt (&WHERE (id, NULL), NULL)) - : (char *)file->sec[section_index].name; - char *wh2 = strdup (where_fmt (&WHERE (offset_into, NULL), NULL)); - wr_error (&reloc_where, - ": relocation references section %s, but %s was expected.\n", - wh1, wh2); - free (wh2); - if (id != sec_invalid) - free (wh1); - } - } - - /* 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; - } - } -} - static enum section_id reloc_target (uint8_t form, struct abbrev_attrib *at) { @@ -968,7 +719,7 @@ reloc_target_loc (uint8_t opcode) return rel_value; } -static bool +bool supported_version (unsigned version, size_t num_supported, struct where *where, ...) { @@ -1855,7 +1606,7 @@ check_info_structural (struct elf_file *file, ": CU lengths don't exactly match Elf_Data contents."); else /* Did we consume all the relocations? */ - relocation_skip_rest (sec); + relocation_skip_rest (&sec->rel, sec->id); /* If we managed to read up everything, now do abbrev usage analysis. */ @@ -2391,7 +2142,7 @@ check_pub_structural (struct elf_file *file, } if (retval) - relocation_skip_rest (sec); + relocation_skip_rest (&sec->rel, sec->id); return retval; } @@ -2860,7 +2611,7 @@ check_loc_or_range_structural (struct elf_file *file, if (retval) { - relocation_skip_rest (sec); + relocation_skip_rest (&sec->rel, sec->id); /* We check that all CUs have the same address size when building the CU chain. So just take the address size of the first CU in @@ -3042,494 +2793,3 @@ read_rel (struct elf_file *file, sizeof (*sec->rel.rel), &compare); return true; } - -bool -check_line_structural (struct elf_file *file, - struct sec *sec, - struct addr_record *line_tables) -{ - struct read_ctx ctx; - read_ctx_init (&ctx, sec->data, file->other_byte_order); - bool retval = true; - - while (!read_ctx_eof (&ctx)) - { - struct where where = WHERE (sec->id, NULL); - uint64_t set_offset = read_ctx_get_offset (&ctx); - where_reset_1 (&where, set_offset); - addr_record_add (line_tables, set_offset); - const unsigned char *set_begin = ctx.ptr; - - /* Size. */ - uint32_t size32; - uint64_t size; - int offset_size; - if (!read_ctx_read_4ubyte (&ctx, &size32)) - { - wr_error (&where, ": can't read table length.\n"); - return false; - } - if (!read_size_extra (&ctx, size32, &size, &offset_size, &where)) - return false; - - struct read_ctx sub_ctx; - const unsigned char *set_end = ctx.ptr + size; - if (!read_ctx_init_sub (&sub_ctx, &ctx, set_begin, set_end)) - { - not_enough: - wr_error (&where, PRI_NOT_ENOUGH, "next unit"); - return false; - } - sub_ctx.ptr = ctx.ptr; - sub_ctx.begin = ctx.begin; - - { - /* Version. */ - uint16_t version; - if (!read_ctx_read_2ubyte (&sub_ctx, &version)) - { - wr_error (&where, ": can't read set version.\n"); - skip: - retval = false; - goto next; - } - if (!supported_version (version, 2, &where, 2, 3)) - goto skip; - - /* Header length. */ - uint64_t header_length; - if (!read_ctx_read_offset (&sub_ctx, offset_size == 8, &header_length)) - { - wr_error (&where, ": can't read attribute value.\n"); - goto skip; - } - const unsigned char *program_start = sub_ctx.ptr + header_length; - - /* Minimum instruction length. */ - uint8_t minimum_i_length; - if (!read_ctx_read_ubyte (&sub_ctx, &minimum_i_length)) - { - wr_error (&where, ": can't read minimum instruction length.\n"); - goto skip; - } - - /* Default value of is_stmt. */ - uint8_t default_is_stmt; - if (!read_ctx_read_ubyte (&sub_ctx, &default_is_stmt)) - { - wr_error (&where, ": can't read default_is_stmt.\n"); - goto skip; - } - /* 7.21: The boolean values "true" and "false" used by the line - number information program are encoded as a single byte - containing the value 0 for "false," and a non-zero value for - "true." [But give a notice if it's not 0 or 1.] */ - if (default_is_stmt != 0 - && default_is_stmt != 1) - wr_message (mc_line | mc_impact_2 | mc_header, &where, - ": default_is_stmt should be 0 or 1, not %ud\n", - default_is_stmt); - - /* Line base. */ - int8_t line_base; - if (!read_ctx_read_ubyte (&sub_ctx, (uint8_t *)&line_base)) - { - wr_error (&where, ": can't read line_base.\n"); - goto skip; - } - - /* Line range. */ - uint8_t line_range; - if (!read_ctx_read_ubyte (&sub_ctx, &line_range)) - { - wr_error (&where, ": can't read line_range.\n"); - goto skip; - } - - /* Opcode base. */ - uint8_t opcode_base; - if (!read_ctx_read_ubyte (&sub_ctx, &opcode_base)) - { - wr_error (&where, ": can't read opcode_base.\n"); - goto skip; - } - - /* Standard opcode lengths. */ - if (opcode_base == 0) - { - wr_error (&where, ": opcode base set to 0.\n"); - opcode_base = 1; // so that in following, our -1s don't underrun - } - uint8_t std_opc_lengths[opcode_base - 1]; /* -1, opcodes go from 1. */ - for (unsigned i = 0; i < (unsigned)(opcode_base - 1); ++i) - if (!read_ctx_read_ubyte (&sub_ctx, std_opc_lengths + i)) - { - wr_error (&where, - ": can't read length of standard opcode #%d.\n", i); - goto skip; - } - - /* Include directories. */ - struct include_directory_t - { - const char *name; - bool used; - }; - struct include_directories_t - { - size_t size; - size_t alloc; - struct include_directory_t *dirs; - } include_directories; - WIPE (include_directories); - - while (!read_ctx_eof (&sub_ctx)) - { - const char *name = read_ctx_read_str (&sub_ctx); - if (name == NULL) - { - wr_error (&where, - ": can't read name of include directory #%zd.\n", - include_directories.size + 1); /* Numbered from 1. */ - goto skip; - } - if (*name == 0) - break; - - REALLOC (&include_directories, dirs); - include_directories.dirs[include_directories.size++] = - (struct include_directory_t){name, false}; - } - - /* File names. */ - struct file_t - { - const char *name; - uint64_t dir_idx; - bool used; - }; - struct files_t - { - size_t size; - size_t alloc; - struct file_t *files; - } files; - WIPE (files); - - /* Directory index. */ - bool read_directory_index (const char *name, uint64_t *ptr) - { - if (!checked_read_uleb128 (&sub_ctx, ptr, - &where, "directory index")) - return false; - if (*name == '/' && *ptr != 0) - wr_message (mc_impact_2 | mc_line | mc_header, &where, - ": file #%zd has absolute pathname, but refers to directory != 0.\n", - files.size + 1); - if (*ptr > include_directories.size) /* Not >=, dirs indexed from 1. */ - { - wr_message (mc_impact_4 | mc_line | mc_header, &where, - ": file #%zd refers to directory #%" PRId64 ", which wasn't defined.\n", - files.size + 1, *ptr); - /* Consumer might choke on that. */ - retval = false; - } - else if (*ptr != 0) - include_directories.dirs[*ptr - 1].used = true; - return true; - } - - while (1) - { - const char *name = read_ctx_read_str (&sub_ctx); - if (name == NULL) - { - wr_error (&where, - ": can't read name of file #%zd.\n", - files.size + 1); /* Numbered from 1. */ - goto skip; - } - if (*name == 0) - break; - - uint64_t dir_idx; - if (!read_directory_index (name, &dir_idx)) - goto skip; - - /* Time of last modification. */ - uint64_t timestamp; - if (!checked_read_uleb128 (&sub_ctx, ×tamp, - &where, "timestamp of file entry")) - goto skip; - - /* Size of the file. */ - uint64_t file_size; - if (!checked_read_uleb128 (&sub_ctx, &file_size, - &where, "file size of file entry")) - goto skip; - - REALLOC (&files, files); - files.files[files.size++] - = (struct file_t){name, dir_idx, false}; - } - - /* Skip the rest of the header. */ - if (sub_ctx.ptr > program_start) - { - wr_error (&where, - ": header claims that it has a size of %#" PRIx64 - ", but in fact it has a size of %#" PRIx64 ".\n", - header_length, sub_ctx.ptr - program_start + header_length); - /* Assume that the header lies, and what follows is in - fact line number program. */ - retval = false; - } - else if (sub_ctx.ptr < program_start) - { - if (!check_zero_padding (&sub_ctx, mc_line | mc_header, &where)) - wr_message_padding_n0 (mc_line | mc_header, &WHERE (sec_line, NULL), - read_ctx_get_offset (&sub_ctx), - program_start - sub_ctx.begin); - sub_ctx.ptr = program_start; - } - - bool terminated = false; - bool first_file = true; - bool seen_opcode = false; - while (!read_ctx_eof (&sub_ctx)) - { - where_reset_2 (&where, read_ctx_get_offset (&sub_ctx)); - uint8_t opcode; - if (!read_ctx_read_ubyte (&sub_ctx, &opcode)) - { - wr_error (&where, ": can't read opcode.\n"); - goto skip; - } - - void use_file (uint64_t file_idx) - { - if (file_idx == 0 || file_idx > files.size) - { - wr_error (&where, - ": DW_LNS_set_file: invalid file index %" PRId64 ".\n", - file_idx); - retval = false; - } - else - files.files[file_idx - 1].used = true; - } - - unsigned operands = 0; - uint8_t extended = 0; - switch (opcode) - { - /* Extended opcodes. */ - case 0: - { - uint64_t skip_len; - if (!checked_read_uleb128 (&sub_ctx, &skip_len, &where, - "length of extended opcode")) - goto skip; - const unsigned char *next = sub_ctx.ptr + skip_len; - if (!read_ctx_read_ubyte (&sub_ctx, &extended)) - { - wr_error (&where, ": can't read extended opcode.\n"); - goto skip; - } - - bool handled = true; - switch (extended) - { - case DW_LNE_end_sequence: - terminated = true; - break; - - case DW_LNE_set_address: - { - uint64_t ctx_offset = read_ctx_get_offset (&sub_ctx); - uint64_t addr; - if (!read_ctx_read_offset (&sub_ctx, - file->addr_64, &addr)) - { - wr_error (&where, ": can't read operand of DW_LNE_set_address.\n"); - goto skip; - } - - struct relocation *rel; - if ((rel = relocation_next (&sec->rel, ctx_offset, - &where, skip_mismatched))) - relocate_one (file, &sec->rel, rel, - file->addr_64 ? 8 : 4, - &addr, &where, rel_address, NULL); - else if (file->ehdr.e_type == ET_REL) - wr_message (mc_impact_2 | mc_line | mc_reloc, &where, - PRI_LACK_RELOCATION, "DW_LNE_set_address"); - break; - } - - case DW_LNE_define_file: - { - const char *name; - if ((name = read_ctx_read_str (&sub_ctx)) == NULL) - { - wr_error (&where, - ": can't read filename operand of DW_LNE_define_file.\n"); - goto skip; - } - uint64_t dir_idx; - if (!read_directory_index (name, &dir_idx)) - goto skip; - REALLOC (&files, files); - files.files[files.size++] = - (struct file_t){name, dir_idx, false}; - operands = 2; /* Skip mtime & size of the file. */ - } - - /* See if we know about any other standard opcodes. */ - default: - handled = false; - switch (extended) - { -#define ONE_KNOWN_DW_LNE(NAME, CODE) case CODE: break; - ALL_KNOWN_DW_LNE -#undef ONE_KNOWN_DW_LNE - default: - /* No we don't, emit a warning. */ - wr_message (mc_impact_2 | mc_line, &where, - ": unknown extended opcode #%d.\n", extended); - }; - }; - - if (sub_ctx.ptr > next) - { - wr_error (&where, - ": opcode claims that it has a size of %#" PRIx64 - ", but in fact it has a size of %#" PRIx64 ".\n", - skip_len, skip_len + (next - sub_ctx.ptr)); - retval = false; - } - else if (sub_ctx.ptr < next) - { - if (handled - && !check_zero_padding (&sub_ctx, mc_line, &where)) - wr_message_padding_n0 (mc_line, &WHERE (sec_line, NULL), - read_ctx_get_offset (&sub_ctx), - next - sub_ctx.begin); - sub_ctx.ptr = next; - } - break; - } - - /* Standard opcodes that need validation or have - non-ULEB operands. */ - case DW_LNS_fixed_advance_pc: - { - uint16_t a; - if (!read_ctx_read_2ubyte (&sub_ctx, &a)) - { - wr_error (&where, ": can't read operand of DW_LNS_fixed_advance_pc.\n"); - goto skip; - } - break; - } - - case DW_LNS_set_file: - { - uint64_t file_idx; - if (!checked_read_uleb128 (&sub_ctx, &file_idx, &where, - "DW_LNS_set_file operand")) - goto skip; - use_file (file_idx); - first_file = false; - } - break; - - case DW_LNS_set_isa: - // XXX is it possible to validate this? - operands = 1; - break; - - /* All the other opcodes. */ - default: - if (opcode < opcode_base) - operands = std_opc_lengths[opcode - 1]; - - switch (opcode) - { -#define ONE_KNOWN_DW_LNS(NAME, CODE) case CODE: break; - ALL_KNOWN_DW_LNS -#undef ONE_KNOWN_DW_LNS - - default: - if (opcode < opcode_base) - wr_message (mc_impact_2 | mc_line, &where, - ": unknown standard opcode #%d.\n", opcode); - }; - }; - - for (unsigned i = 0; i < operands; ++i) - { - uint64_t operand; - char buf[128]; - if (opcode != 0) - sprintf (buf, "operand #%d of DW_LNS_%s", - i, dwarf_line_standard_opcode_string (opcode)); - else - sprintf (buf, "operand #%d of extended opcode %d", - i, extended); - if (!checked_read_uleb128 (&sub_ctx, &operand, &where, buf)) - goto skip; - } - - if (first_file) - { - use_file (1); - first_file = false; - } - - if (opcode != 0 || extended != DW_LNE_end_sequence) - seen_opcode = true; - } - - for (size_t i = 0; i < include_directories.size; ++i) - if (!include_directories.dirs[i].used) - wr_message (mc_impact_3 | mc_acc_bloat | mc_line | mc_header, - &where, ": the include #%zd `%s' is not used.\n", - i + 1, include_directories.dirs[i].name); - - for (size_t i = 0; i < files.size; ++i) - if (!files.files[i].used) - wr_message (mc_impact_3 | mc_acc_bloat | mc_line | mc_header, - &where, ": the file #%zd `%s' is not used.\n", - i + 1, files.files[i].name); - - if (!seen_opcode) - wr_message (mc_line | mc_acc_bloat | mc_impact_3, &where, - ": empty line number program.\n"); - if (!terminated) - wr_error (&where, - ": sequence of opcodes not terminated with DW_LNE_end_sequence.\n"); - else if (sub_ctx.ptr != sub_ctx.end - && !check_zero_padding (&sub_ctx, mc_line, - &WHERE (sec_line, NULL))) - wr_message_padding_n0 (mc_line, &WHERE (sec_line, NULL), - /*begin*/read_ctx_get_offset (&sub_ctx), - /*end*/sub_ctx.end - sub_ctx.begin); - } - - /* XXX overlaps in defined addresses are probably OK, one - instruction can be derived from several statements. But - certain flags in table should be consistent in that case, - namely is_stmt, basic_block, end_sequence, prologue_end, - epilogue_begin, isa. */ - - next: - if (!read_ctx_skip (&ctx, size)) - goto not_enough; - } - - if (retval) - relocation_skip_rest (sec); - - return retval; -} diff --git a/src/dwarflint/low.h b/src/dwarflint/low.h index a116c0771..9b8299c2c 100644 --- a/src/dwarflint/low.h +++ b/src/dwarflint/low.h @@ -23,8 +23,8 @@ Network licensing program, please visit www.openinventionnetwork.com . */ -#ifndef DWARFLINT_HL_H -#define DWARFLINT_HL_H +#ifndef DWARFLINT_LOW_H +#define DWARFLINT_LOW_H #include "../libdw/libdw.h" #include "../libebl/libebl.h" @@ -32,6 +32,7 @@ #include "messages.h" #include "readctx.h" #include "addr-record.h" +#include "reloc.h" #ifdef __cplusplus extern "C" @@ -41,30 +42,6 @@ extern "C" #endif struct hl_ctx; - struct relocation - { - uint64_t offset; - uint64_t addend; - int symndx; - int type; - bool invalid; /* Whether this one relocation should be - ignored. Necessary so that we don't report - invalid & missing relocation twice. */ - }; - - struct relocation_data - { - Elf_Data *symdata; /* Symbol table associated with this - relocation section. */ - size_t type; /* SHT_REL or SHT_RELA. */ - - struct relocation *rel; /* Array of relocations. May be NULL - if there are no associated - relocation data. */ - size_t size; - size_t alloc; - size_t index; /* Current index. */ - }; struct sec { @@ -124,6 +101,15 @@ extern "C" extern bool address_aligned (uint64_t addr, uint64_t align); extern bool necessary_alignment (uint64_t start, uint64_t length, uint64_t align); + extern bool read_size_extra (struct read_ctx *ctx, uint32_t size32, + uint64_t *sizep, int *offset_sizep, + struct where *wh); +#define PRI_NOT_ENOUGH ": not enough data for %s.\n" + extern bool supported_version (unsigned version, + size_t num_supported, struct where *where, ...); + extern bool check_zero_padding (struct read_ctx *ctx, + enum message_category category, + struct where *wh); struct section_coverage { @@ -256,4 +242,4 @@ extern "C" } #endif -#endif/*DWARFLINT_HL_H*/ +#endif/*DWARFLINT_LOW_H*/ diff --git a/src/dwarflint/reloc.cc b/src/dwarflint/reloc.cc new file mode 100644 index 000000000..af50a326f --- /dev/null +++ b/src/dwarflint/reloc.cc @@ -0,0 +1,251 @@ +#include "reloc.h" +#include "messages.h" +#include "low.h" +#include +#include +#include + +static struct where +where_from_reloc (struct relocation_data *reloc, struct where *ref) +{ + struct where where + = WHERE (reloc->type == SHT_REL ? sec_rel : sec_rela, NULL); + where_reset_1 (&where, reloc->rel[reloc->index].offset); + where.ref = ref; + return where; +} + +relocation * +relocation_next (relocation_data *reloc, uint64_t offset, + struct where *where, 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) + { + struct where reloc_where = where_from_reloc (reloc, where); + where_reset_2 (&reloc_where, rel->offset); + std::stringstream ss; + if (st == skip_unref) + ss << ": relocation targets " + "unreferenced portion of the section."; + else + ss << ": relocation is mismatched."; + wr_error (&reloc_where, "%s\n", ss.str ().c_str ()); + } + 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, + struct where *where, enum skip_type st) +{ + if (reloc != NULL && reloc->rel != NULL) + relocation_next (reloc, offset - 1, where, st); +} + +/* Skip all the remaining relocations. */ +void +relocation_skip_rest (struct relocation_data *reloc, + enum section_id id) +{ + if (reloc->rel != NULL) + { + where wh = WHERE (id, NULL); + relocation_next (reloc, (uint64_t)-1, &wh, + skip_mismatched); + } +} + +/* 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 *file, + struct relocation_data *reloc, + struct relocation *rel, + unsigned width, uint64_t *value, struct where *where, + enum section_id offset_into, GElf_Sym **symptr) +{ + if (rel->invalid) + return; + + struct where reloc_where = where_from_reloc (reloc, where); + where_reset_2 (&reloc_where, rel->offset); + struct where reloc_ref_where = reloc_where; + reloc_ref_where.next = where; + + GElf_Sym symbol_mem, *symbol; + if (symptr != NULL) + { + symbol = *symptr; + *symptr = NULL; + } + else + symbol = &symbol_mem; + + if (offset_into == sec_invalid) + { + wr_message (mc_impact_3 | mc_reloc, &reloc_ref_where, + ": 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_ref_where, + ": %d-byte relocation relocates %d-byte datum.\n", + rel_width, width); + + /* Tolerate that we might have failed to obtain the symbol table. */ + if (reloc->symdata != NULL) + { + 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; + } + + uint64_t 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. */ + + /* 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. */ + uint64_t sym_value = symbol->st_value; + if (file->ehdr.e_type == ET_REL + && ELF64_ST_TYPE (symbol->st_info) == STT_SECTION) + { + assert (sym_value == 0); + sym_value = file->sec[section_index].shdr.sh_addr; + } + + /* It's target value, not section offset. */ + if (offset_into == rel_value + || offset_into == rel_address + || offset_into == 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 + || (offset_into == rel_address + && (section_index == SHN_UNDEF + || section_index == SHN_COMMON)))) + { + if (offset_into != rel_address && section_index == SHN_UNDEF) + wr_error (&reloc_where, + ": relocation of an address is formed against SHN_UNDEF symbol" + " (symtab index %d).\n", rel->symndx); + else + { + 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 (offset_into == 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 + { + enum section_id id; + /* If symtab[symndx].st_shndx does not match the expected + debug section's index, complain. */ + if (section_index >= file->size) + { + std::stringstream ss; + ss << ": invalid associated section #" << section_index << "."; + wr_error (&reloc_where, "%s\n", ss.str ().c_str ()); + } + else if ((id = file->sec[section_index].id) != offset_into) + { + std::stringstream ss; + ss << ": relocation references section " + << (id != sec_invalid + ? where_fmt (WHERE (id, NULL)) + : file->sec[section_index].name) + << ", but " << where_fmt (WHERE (offset_into, NULL)) + << " was expected."; + wr_error (&reloc_where, "%s\n", ss.str ().c_str ()); + } + } + + /* 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; + } + } +} diff --git a/src/dwarflint/reloc.h b/src/dwarflint/reloc.h new file mode 100644 index 000000000..0804155eb --- /dev/null +++ b/src/dwarflint/reloc.h @@ -0,0 +1,96 @@ +/* Pedantic checking of DWARF files. + Copyright (C) 2008,2009 Red Hat, Inc. + This file is part of Red Hat elfutils. + + Red Hat elfutils 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; version 2 of the License. + + Red Hat 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 Red Hat elfutils; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA. + + Red Hat elfutils is an included package of the Open Invention Network. + An included package of the Open Invention Network is a package for which + Open Invention Network licensees cross-license their patents. No patent + license is granted, either expressly or impliedly, by designation as an + included package. Should you wish to participate in the Open Invention + Network licensing program, please visit www.openinventionnetwork.com + . */ + +#ifndef DWARFLINT_RELOC_H +#define DWARFLINT_RELOC_H + +#include "where.h" +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#else +# include +#endif + + struct elf_file; + + struct relocation + { + uint64_t offset; + uint64_t addend; + int symndx; + int type; + bool invalid; /* Whether this one relocation should be + ignored. Necessary so that we don't report + invalid & missing relocation twice. */ + }; + + struct relocation_data + { + Elf_Data *symdata; /* Symbol table associated with this + relocation section. */ + size_t type; /* SHT_REL or SHT_RELA. */ + + struct relocation *rel; /* Array of relocations. May be NULL + if there are no associated + relocation data. */ + size_t size; + size_t alloc; + size_t index; /* Current index. */ + }; + + enum skip_type + { + skip_unref = 0, + skip_mismatched = 1, + skip_ok, + }; + + struct relocation *relocation_next (struct relocation_data *reloc, + uint64_t offset, struct where *where, + enum skip_type st); + + void relocation_skip (struct relocation_data *reloc, uint64_t offset, + struct where *where, enum skip_type st); + + void relocation_skip_rest (struct relocation_data *reloc, + enum section_id id); + + void relocate_one (struct elf_file *file, + struct relocation_data *reloc, + struct relocation *rel, + unsigned width, uint64_t *value, struct where *where, + enum section_id offset_into, GElf_Sym **symptr); + +#define PRI_LACK_RELOCATION ": %s seems to lack a relocation.\n" + +#ifdef __cplusplus +} +#endif + +#endif//DWARFLINT_RELOC_H diff --git a/src/dwarflint/where.h b/src/dwarflint/where.h index 7aa3ee2ec..bfa9e3157 100644 --- a/src/dwarflint/where.h +++ b/src/dwarflint/where.h @@ -79,6 +79,12 @@ extern "C" #ifdef __cplusplus } + +inline const char * +where_fmt (where const &wh) +{ + return where_fmt (&wh); +} #endif #endif//DWARFLINT_WHERE_H