| /* Routines related to .debug_info. |
| |
| Copyright (C) 2009, 2010, 2011 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 |
| <http://www.openinventionnetwork.com>. */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include <config.h> |
| #endif |
| |
| #include <cassert> |
| #include <algorithm> |
| #include "../libdw/c++/dwarf" |
| |
| #include "messages.hh" |
| #include "dwarf_version.hh" |
| #include "pri.hh" |
| #include "option.hh" |
| #include "sections.hh" |
| #include "checked_read.hh" |
| #include "check_debug_loc_range.hh" |
| #include "check_debug_abbrev.hh" |
| #include "check_debug_info.hh" |
| #include "check_debug_line.hh" |
| #include "check_debug_aranges.hh" |
| |
| checkdescriptor const * |
| read_cu_headers::descriptor () |
| { |
| static checkdescriptor cd |
| (checkdescriptor::create ("read_cu_headers") |
| .hidden ()); |
| return &cd; |
| } |
| |
| static void_option |
| dump_die_offsets ("Dump DIE offsets to stderr as the tree is iterated.", |
| "dump-offsets"); |
| |
| checkdescriptor const * |
| check_debug_info::descriptor () |
| { |
| static checkdescriptor cd |
| (checkdescriptor::create ("check_debug_info") |
| .groups ("@low") |
| .schedule (false) |
| .option (dump_die_offsets) |
| .description ( |
| "Checks for low-level structure of .debug_info. In addition it " |
| "checks:\n" |
| " - for dangling reference to .debug_abbrev section\n" |
| " - that reported CU address sizes are consistent\n" |
| " - that rangeptr values are aligned to CU address size\n" |
| " - it is checked that DW_AT_low_pc and DW_AT_high_pc are relocated " |
| "consistently\n" |
| " - that DIE references are well formed (both intra-CU and inter-CU) " |
| "and that local reference isn't needlessly formed as global\n" |
| " - that .debug_string references are well formed and referred strings " |
| "are properly NUL-terminated\n" |
| " - that referenced abbreviations actually exist\n" |
| " - that DIEs with children have the DW_AT_sibling attribute and that " |
| "the sibling actually is at the address reported at that attribute\n" |
| " - that the DIE chain is terminated\n" |
| " - that the last sibling in chain has no DW_AT_sibling attribute\n" |
| " - that the DIE with children actually has children (i.e. that the " |
| "chain is not empty)\n" |
| " - for format constraints (such as that there are no 64-bit CUs inside " |
| "DWARF 2 file)\n" |
| " - in 32-bit CUs, that location attributes are not formed with " |
| "DW_FORM_data8\n" |
| " - all the attribute checks done by check_debug_abbrev are done here " |
| "for attributes with DW_FORM_indirect. Indirect form is forbidden " |
| "to be again indirect\n" |
| " - that all abbreviations are used\n" |
| " - that relocations are valid. In ET_REL files that certain fields " |
| "are relocated\n" |
| )); |
| return &cd; |
| } |
| |
| static reg<check_debug_info> reg_debug_info; |
| |
| namespace |
| { |
| bool |
| check_category (enum message_category cat) |
| { |
| return message_accept (&warning_criteria, cat); |
| } |
| |
| bool |
| check_die_references (cu *cu, ref_record *die_refs) |
| { |
| bool retval = true; |
| for (ref_record::const_iterator it = die_refs->begin (); |
| it != die_refs->end (); ++it) |
| if (!cu->die_addrs.has_addr (it->addr)) |
| { |
| wr_error (it->who) |
| << "unresolved reference to " << pri::DIE (it->addr) |
| << '.' << std::endl; |
| retval = false; |
| } |
| return retval; |
| } |
| |
| bool |
| check_global_die_references (struct cu *cu_chain) |
| { |
| bool retval = true; |
| for (struct cu *it = cu_chain; it != NULL; it = it->next) |
| for (ref_record::const_iterator rt = it->die_refs.begin (); |
| rt != it->die_refs.end (); ++rt) |
| { |
| struct cu *ref_cu = NULL; |
| for (struct cu *jt = cu_chain; jt != NULL; jt = jt->next) |
| if (jt->die_addrs.has_addr (rt->addr)) |
| { |
| ref_cu = jt; |
| break; |
| } |
| |
| if (ref_cu == NULL) |
| { |
| wr_error (rt->who) |
| << "unresolved (non-CU-local) reference to " |
| << pri::hex (rt->addr) << '.' << std::endl; |
| retval = false; |
| } |
| else if (ref_cu == it) |
| /* This is technically not a problem, so long as the |
| reference is valid, which it is. But warn about this |
| anyway, perhaps local reference could be formed on |
| smaller number of bytes. */ |
| wr_message (rt->who, mc_impact_2 | mc_acc_suboptimal | mc_die_rel) |
| << "local reference to " << pri::DIE (rt->addr) |
| << " formed as global." << std::endl; |
| } |
| |
| return retval; |
| } |
| |
| std::vector <cu_head> |
| read_info_headers (struct elf_file *file, |
| struct sec *sec, |
| struct relocation_data *reloc) |
| { |
| struct read_ctx ctx; |
| read_ctx_init (&ctx, sec->data, file->other_byte_order); |
| uint64_t off_start, off_end; |
| bool fail = false; |
| |
| std::vector <cu_head> ret; |
| while (!read_ctx_eof (&ctx)) |
| { |
| const unsigned char *cu_begin = ctx.ptr; |
| uint64_t offset = read_ctx_get_offset (&ctx); |
| cu_head head (offset); |
| |
| /* Reading CU head is a bit tricky, because we don't know if |
| we have run into (superfluous but allowed) zero padding |
| between CUs. */ |
| |
| if (!read_ctx_need_data (&ctx, 4) |
| && read_check_zero_padding (&ctx, &off_start, &off_end)) |
| { |
| wr_message_padding_0 (mc_info | mc_header, head.where, |
| off_start, off_end); |
| break; |
| } |
| |
| /* CU length. In DWARF 2, (uint32_t)-1 is simply a CU of that |
| length. In DWARF 3+ that's an escape for 64bit length. |
| Unfortunately to read CU version, we have to get through |
| this field. So we just assume that (uint32_t)-1 is an |
| escape in all cases. */ |
| uint32_t size32; |
| if (!read_ctx_read_4ubyte (&ctx, &size32)) |
| { |
| wr_error (head.where) << "can't read CU length." << std::endl; |
| throw check_base::failed (); |
| } |
| if (size32 == 0 |
| && read_check_zero_padding (&ctx, &off_start, &off_end)) |
| { |
| wr_message_padding_0 (mc_info | mc_header, head.where, |
| off_start, off_end); |
| break; |
| } |
| |
| Dwarf_Off cu_size; |
| if (!read_size_extra (&ctx, size32, &cu_size, |
| &head.offset_size, head.where)) |
| throw check_base::failed (); |
| |
| if (!read_ctx_need_data (&ctx, cu_size)) |
| { |
| wr_error (head.where) |
| << "section doesn't have enough data to read CU of size " |
| << cu_size << '.' << std::endl; |
| throw check_base::failed (); |
| } |
| |
| /* CU size captures the size from the end of the length field |
| to the end of the CU. */ |
| const unsigned char *cu_end = ctx.ptr + cu_size; |
| |
| /* Version. */ |
| uint16_t version; |
| if (!read_ctx_read_2ubyte (&ctx, &version)) |
| { |
| wr_error (head.where) << "can't read version." << std::endl; |
| throw check_base::failed (); |
| } |
| if (dwarf_version::get (version) == NULL) |
| { |
| wr_error (head.where) << "unsupported CU version " |
| << version << '.' << std::endl; |
| throw check_base::failed (); |
| } |
| if (version == 2 && head.offset_size == 8) // xxx? |
| /* Keep going. It's a standard violation, but we may still |
| be able to read the unit under consideration and do |
| high-level checks. */ |
| wr_error (head.where) << "invalid 64-bit unit in DWARF 2 format.\n"; |
| head.version = version; |
| |
| /* Abbrev table offset. */ |
| uint64_t ctx_offset = read_ctx_get_offset (&ctx); |
| if (!read_ctx_read_offset (&ctx, head.offset_size == 8, |
| &head.abbrev_offset)) |
| { |
| wr_error (head.where) |
| << "can't read abbrev table offset." << std::endl; |
| throw check_base::failed (); |
| } |
| |
| struct relocation *rel |
| = relocation_next (reloc, ctx_offset, head.where, skip_ok); |
| if (rel != NULL) |
| { |
| relocate_one (file, reloc, rel, head.offset_size, |
| &head.abbrev_offset, head.where, sec_abbrev, NULL); |
| rel->invalid = true; // mark as invalid so it's skipped |
| // next time we pass by this |
| } |
| else if (file->ehdr.e_type == ET_REL) |
| wr_message (head.where, mc_impact_2 | mc_info | mc_reloc) |
| << pri::lacks_relocation ("abbrev table offset") << std::endl; |
| |
| /* Address size. */ |
| error_code err = read_address_size (&ctx, file->addr_64, |
| &head.address_size, head.where); |
| if (err == err_fatal) |
| throw check_base::failed (); |
| else if (err == err_nohl) |
| fail = true; |
| |
| head.head_size = ctx.ptr - cu_begin; // Length of the headers itself. |
| head.total_size = cu_end - cu_begin; // Length including headers field. |
| head.size = head.total_size - head.head_size; |
| |
| if (!read_ctx_skip (&ctx, head.size)) |
| { |
| wr_error (head.where) << pri::not_enough ("next CU") << std::endl; |
| throw check_base::failed (); |
| } |
| |
| ret.push_back (head); |
| } |
| |
| if (fail) |
| throw check_base::failed (); |
| |
| return ret; |
| } |
| |
| rel_target |
| reloc_target (form const *form, attribute const *attribute) |
| { |
| switch (form->name ()) |
| { |
| case DW_FORM_strp: |
| return sec_str; |
| |
| case DW_FORM_addr: |
| |
| switch (attribute->name ()) |
| { |
| case DW_AT_low_pc: |
| case DW_AT_high_pc: |
| case DW_AT_entry_pc: |
| return rel_target::rel_exec; |
| |
| case DW_AT_const_value: |
| /* Appears in some kernel modules. It's not allowed by the |
| standard, but leave that for high-level checks. */ |
| return rel_target::rel_address; |
| }; |
| |
| break; |
| |
| case DW_FORM_ref_addr: |
| return sec_info; |
| |
| case DW_FORM_data1: |
| case DW_FORM_data2: |
| /* While these are technically legal, they are never used in |
| DWARF sections. So better mark them as illegal, and have |
| dwarflint flag them. */ |
| return sec_invalid; |
| |
| case DW_FORM_data4: |
| case DW_FORM_data8: |
| case DW_FORM_sec_offset: |
| |
| switch (attribute->name ()) |
| { |
| case DW_AT_stmt_list: |
| return sec_line; |
| |
| case DW_AT_location: |
| case DW_AT_string_length: |
| case DW_AT_return_addr: |
| case DW_AT_data_member_location: |
| case DW_AT_frame_base: |
| case DW_AT_segment: |
| case DW_AT_static_link: |
| case DW_AT_use_location: |
| case DW_AT_vtable_elem_location: |
| return sec_loc; |
| |
| case DW_AT_mac_info: |
| return sec_mac; |
| |
| case DW_AT_ranges: |
| return sec_ranges; |
| } |
| |
| break; |
| |
| case DW_FORM_string: |
| case DW_FORM_ref1: |
| case DW_FORM_ref2: |
| case DW_FORM_ref4: |
| /* Shouldn't be relocated. */ |
| return sec_invalid; |
| |
| case DW_FORM_sdata: |
| case DW_FORM_udata: |
| case DW_FORM_flag: |
| case DW_FORM_flag_present: |
| case DW_FORM_ref_udata: |
| assert (!"Can't be relocated!"); |
| |
| case DW_FORM_block1: |
| case DW_FORM_block2: |
| case DW_FORM_block4: |
| case DW_FORM_block: |
| assert (!"Should be handled specially!"); |
| }; |
| |
| std::cout << "XXX don't know how to handle form=" << *form |
| << ", at=" << *attribute << std::endl; |
| |
| return rel_target::rel_value; |
| } |
| |
| struct value_check_cb_ctx |
| { |
| struct read_ctx *const ctx; |
| die_locus const *where; |
| struct cu *const cu; |
| ref_record *local_die_refs; |
| Elf_Data *strings; |
| struct coverage *strings_coverage; |
| struct coverage *pc_coverage; |
| bool *need_rangesp; |
| int *retval_p; |
| }; |
| |
| typedef void (*value_check_cb_t) (uint64_t addr, |
| struct value_check_cb_ctx const *ctx); |
| |
| /* Callback for local DIE references. */ |
| void |
| check_die_ref_local (uint64_t addr, struct value_check_cb_ctx const *ctx) |
| { |
| assert (ctx->ctx->end > ctx->ctx->begin); |
| if (addr > (uint64_t)(ctx->ctx->end - ctx->ctx->begin)) |
| { |
| wr_error (*ctx->where) |
| << "invalid reference outside the CU: " << pri::hex (addr) |
| << '.' << std::endl; |
| return; |
| } |
| |
| if (ctx->local_die_refs != NULL) |
| /* Address holds a CU-local reference, so add CU offset |
| to turn it into section offset. */ |
| ctx->local_die_refs->push_back (ref (addr + ctx->cu->head->offset, |
| *ctx->where)); |
| } |
| |
| /* Callback for global DIE references. */ |
| void |
| check_die_ref_global (uint64_t addr, struct value_check_cb_ctx const *ctx) |
| { |
| ctx->cu->die_refs.push_back (ref (addr, *ctx->where)); |
| } |
| |
| /* Callback for strp values. */ |
| void |
| check_strp (uint64_t addr, struct value_check_cb_ctx const *ctx) |
| { |
| if (ctx->strings == NULL) |
| wr_error (*ctx->where) |
| << "strp attribute, but no .debug_str data." << std::endl; |
| else if (addr >= ctx->strings->d_size) |
| wr_error (*ctx->where) |
| << "invalid offset outside .debug_str: " << pri::hex (addr) |
| << '.' << std::endl; |
| else |
| { |
| /* Record used part of .debug_str. */ |
| const char *buf = static_cast <const char *> (ctx->strings->d_buf); |
| const char *startp = buf + addr; |
| const char *data_end = buf + ctx->strings->d_size; |
| const char *strp = startp; |
| while (strp < data_end && *strp != 0) |
| ++strp; |
| if (strp == data_end) |
| { |
| wr_error (*ctx->where) |
| << "string at .debug_str: " << pri::hex (addr) |
| << " is not zero-terminated." << std::endl; |
| *ctx->retval_p = -2; |
| } |
| |
| if (ctx->strings_coverage != NULL) |
| ctx->strings_coverage->add (addr, strp - startp + 1); |
| } |
| } |
| |
| /* Callback for rangeptr values. */ |
| void |
| check_rangeptr (uint64_t value, struct value_check_cb_ctx const *ctx) |
| { |
| if ((value % ctx->cu->head->address_size) != 0) |
| wr_message (*ctx->where, mc_ranges | mc_impact_2) |
| << "rangeptr value " << pri::hex (value) |
| << " not aligned to CU address size." << std::endl; |
| *ctx->need_rangesp = true; |
| ctx->cu->range_refs.push_back (ref (value, *ctx->where)); |
| } |
| |
| /* Callback for lineptr values. */ |
| void |
| check_lineptr (uint64_t value, struct value_check_cb_ctx const *ctx) |
| { |
| if (ctx->cu->stmt_list.addr != (uint64_t)-1) |
| wr_error (*ctx->where) |
| << "DW_AT_stmt_list mentioned twice in a CU." << std::endl; |
| ctx->cu->stmt_list = ref (value, *ctx->where); |
| } |
| |
| /* Callback for locptr values. */ |
| void |
| check_locptr (uint64_t value, struct value_check_cb_ctx const *ctx) |
| { |
| ctx->cu->loc_refs.push_back (ref (value, *ctx->where)); |
| } |
| |
| void |
| check_decl_file (uint64_t value, struct value_check_cb_ctx const *ctx) |
| { |
| ctx->cu->decl_file_refs.push_back (ref (value, *ctx->where)); |
| } |
| |
| /* The real sibling checking takes place down in read_die_chain. |
| Here we just make sure that the value is non-zero. That value is |
| clearly invalid, and we use it to mark absent DW_AT_sibling. */ |
| void |
| check_sibling_non0 (uint64_t addr, struct value_check_cb_ctx const *ctx) |
| { |
| if (addr == 0) |
| { |
| wr_error (*ctx->where) |
| << "has a value of 0." << std::endl; |
| // Don't let this up. |
| *ctx->retval_p = -2; |
| } |
| } |
| |
| /* |
| Returns: |
| -2 in case of error that we have to note and return, but for now |
| we can carry on |
| -1 in case of error |
| +0 in case of no error, but the chain only consisted of a |
| terminating zero die. |
| +1 in case some dies were actually loaded |
| */ |
| int |
| read_die_chain (dwarf_version const *ver, |
| elf_file const &file, |
| read_ctx *ctx, |
| cu *cu, |
| abbrev_table const *abbrevs, |
| Elf_Data *strings, |
| ref_record *local_die_refs, |
| coverage *strings_coverage, |
| relocation_data *reloc, |
| coverage *pc_coverage, |
| bool *need_rangesp, |
| unsigned level) |
| { |
| bool got_die = false; |
| uint64_t sibling_addr = 0; |
| uint64_t die_off, prev_die_off = 0; |
| struct abbrev *abbrev = NULL; |
| unsigned long die_count = 0; |
| int retval = 0; |
| |
| struct value_check_cb_ctx cb_ctx = { |
| ctx, NULL, cu, |
| local_die_refs, |
| strings, strings_coverage, |
| pc_coverage, |
| need_rangesp, |
| &retval |
| }; |
| |
| while (!read_ctx_eof (ctx)) |
| { |
| die_off = read_ctx_get_offset (ctx); |
| /* Shift reported DIE offset by CU offset, to match the way |
| readelf reports DIEs. */ |
| die_locus where (cu->head->offset + die_off); |
| cb_ctx.where = &where; |
| |
| uint64_t abbr_code; |
| |
| if (!checked_read_uleb128 (ctx, &abbr_code, where, "abbrev code")) |
| return -1; |
| |
| #define DEF_PREV_WHERE die_locus prev_where (cu->head->offset + prev_die_off) |
| |
| /* Check sibling value advertised last time through the loop. */ |
| if (sibling_addr != 0) |
| { |
| if (abbr_code == 0) |
| { |
| DEF_PREV_WHERE; |
| wr_error (&prev_where, |
| ": is the last sibling in chain, " |
| "but has a DW_AT_sibling attribute.\n"); |
| /* dwarf_siblingof uses DW_AT_sibling to look for |
| sibling DIEs. The value can't be right (there _is_ |
| no sibling), so don't let this up. */ |
| retval = -2; |
| } |
| else if (sibling_addr != die_off) |
| { |
| DEF_PREV_WHERE; |
| wr_error (prev_where) |
| << "this DIE claims that its sibling is " |
| << pri::hex (sibling_addr) << " but it's actually " |
| << pri::hex (die_off) << '.' << std::endl; |
| retval = -2; |
| } |
| sibling_addr = 0; |
| } |
| else if (abbr_code != 0 |
| && abbrev != NULL && abbrev->has_children) |
| { |
| /* Even if it has children, the DIE can't have a sibling |
| attribute if it's the last DIE in chain. That's the |
| reason we can't simply check this when loading |
| abbrevs. */ |
| DEF_PREV_WHERE; |
| wr_message (prev_where, mc_die_rel | mc_acc_suboptimal | mc_impact_4) |
| << "This DIE had children, but no DW_AT_sibling attribute." |
| << std::endl; |
| } |
| #undef DEF_PREV_WHERE |
| |
| prev_die_off = die_off; |
| |
| /* The section ended. */ |
| if (abbr_code == 0) |
| break; |
| |
| prev_die_off = die_off; |
| got_die = true; |
| |
| /* Find the abbrev matching the code. */ |
| abbrev = abbrevs->find_abbrev (abbr_code); |
| if (abbrev == NULL) |
| { |
| wr_error (where) |
| << "abbrev section at " << pri::hex (abbrevs->offset) |
| << " doesn't contain code " << abbr_code << '.' << std::endl; |
| return -1; |
| } |
| abbrev->used = true; |
| |
| if (dump_die_offsets) |
| std::cerr << "[" << level << "] " |
| << where << ": abbrev " << abbr_code |
| << "; DIE tag 0x" << std::hex << abbrev->tag << std::endl; |
| |
| // DWARF 4 Ch. 7.5: compilation unit header [is] followed by a |
| // single DW_TAG_compile_unit or DW_TAG_partial_unit. |
| bool is_cudie = level == 0 |
| && (abbrev->tag == DW_TAG_compile_unit |
| || abbrev->tag == DW_TAG_partial_unit); |
| if (level == 0) |
| { |
| if (++die_count > 1) |
| wr_error (where) |
| << "toplevel DIE chain contains more than one DIE." |
| << std::endl; |
| else if (!is_cudie) |
| { |
| wr_error (cu->head->where) |
| << "toplevel DIE must be either compile_unit or partial_unit." |
| << std::endl; |
| retval = -2; |
| } |
| } |
| |
| cu->die_addrs.add (cu->head->offset + die_off); |
| |
| uint64_t low_pc = (uint64_t)-1, high_pc = (uint64_t)-1; |
| bool low_pc_relocated = false, high_pc_relocated = false; |
| bool high_pc_relative = false; |
| GElf_Sym low_pc_symbol_mem, *low_pc_symbol = &low_pc_symbol_mem; |
| GElf_Sym high_pc_symbol_mem, *high_pc_symbol = &high_pc_symbol_mem; |
| |
| /* Attribute values. */ |
| for (struct abbrev_attrib *it = abbrev->attribs; |
| it->name != 0 || it->form != 0; ++it) |
| { |
| where.set_attrib_name (it->name); |
| int form_name = it->form; |
| |
| // In following, attribute may be NULL, but form never |
| // should. We always need to know the form to be able to |
| // read .debug_info, so we fail in check_debug_abbrev if |
| // it's invalid or unknown. |
| attribute const *attribute = ver->get_attribute (it->name); |
| form const *form = ver->get_form (form_name); |
| if (attribute != NULL |
| && ver->form_class (form, attribute) == cl_indirect) |
| { |
| uint64_t value; |
| if (!read_sc_value (&value, form->width (cu->head), |
| ctx, where)) |
| return -1; |
| form_name = value; |
| form = check_debug_abbrev::check_form |
| (ver, attribute, form_name, where, true); |
| // N.B. check_form emits appropriate error messages. |
| if (form == NULL) |
| return -1; |
| } |
| assert (form != NULL); |
| |
| dw_class cls = attribute != NULL |
| ? ver->form_class (form, attribute) |
| : max_dw_class; |
| if (cls == cl_indirect) |
| { |
| wr_error (&where, ": indirect form is again indirect.\n"); |
| return -1; |
| } |
| |
| value_check_cb_t value_check_cb = NULL; |
| |
| /* For checking lineptr, rangeptr, locptr. */ |
| bool check_someptr = false; |
| enum message_category extra_mc = mc_none; |
| |
| uint64_t ctx_offset = read_ctx_get_offset (ctx) + cu->head->offset; |
| bool type_is_rel = file.ehdr.e_type == ET_REL; |
| |
| /* Whether the value should be relocated first. Note that |
| relocations are really required only in REL files, so |
| missing relocations are not warned on even with |
| rel_require, unless type_is_rel. */ |
| enum |
| { |
| rel_no, // don't allow a relocation |
| rel_require, // require a relocation |
| rel_nonzero, // require a relocation if value != 0 |
| } relocate = rel_no; |
| |
| /* Point to variable that you want to copy relocated value |
| to. */ |
| uint64_t *valuep = NULL; |
| |
| /* Point to variable that you want set to `true' in case the |
| value was relocated. */ |
| bool *relocatedp = NULL; |
| |
| /* Point to variable that you want set to symbol that the |
| relocation was made against. */ |
| GElf_Sym **symbolp = NULL; |
| |
| static dw_class_set ref_classes |
| (cl_reference, cl_loclistptr, cl_lineptr, cl_macptr, |
| cl_rangelistptr); |
| |
| if (form != NULL && cls != max_dw_class && ref_classes.test (cls)) |
| { |
| form_bitness_t bitness = form->bitness (); |
| if ((bitness == fb_32 && cu->head->offset_size == 8) |
| || (bitness == fb_64 && cu->head->offset_size == 4)) |
| wr_error (where) |
| << "reference attribute with form \"" |
| << elfutils::dwarf::forms::name (form_name) << "\" in " |
| << (8 * cu->head->offset_size) << "-bit CU." |
| << std::endl; |
| } |
| |
| /* Setup pointer checking. */ |
| switch (cls) |
| { |
| case cl_loclistptr: |
| check_someptr = true; |
| value_check_cb = check_locptr; |
| extra_mc = mc_loc; |
| break; |
| |
| case cl_rangelistptr: |
| check_someptr = true; |
| value_check_cb = check_rangeptr; |
| extra_mc = mc_ranges; |
| break; |
| |
| case cl_lineptr: |
| check_someptr = true; |
| value_check_cb = check_lineptr; |
| extra_mc = mc_line; |
| break; |
| |
| default: |
| ; |
| } |
| |
| /* Setup low_pc / high_pc checking. */ |
| switch (it->name) |
| { |
| case DW_AT_low_pc: |
| relocatedp = &low_pc_relocated; |
| symbolp = &low_pc_symbol; |
| valuep = &low_pc; |
| break; |
| |
| case DW_AT_high_pc: |
| relocatedp = &high_pc_relocated; |
| symbolp = &high_pc_symbol; |
| valuep = &high_pc; |
| if (cls == cl_constant) |
| high_pc_relative = true; |
| else if (cls != cl_address) |
| { |
| wr_error (&where, ": DW_AT_high_pc in unknown form.\n"); |
| retval = -2; |
| } |
| break; |
| |
| case DW_AT_decl_file: |
| value_check_cb = check_decl_file; |
| break; |
| } |
| |
| /* Setup per-form checking & relocation. */ |
| switch (form_name) |
| { |
| case DW_FORM_strp: |
| value_check_cb = check_strp; |
| case DW_FORM_sec_offset: |
| relocate = rel_require; |
| break; |
| |
| case DW_FORM_ref_addr: |
| value_check_cb = check_die_ref_global; |
| case DW_FORM_addr: |
| /* In non-rel files, neither addr, nor ref_addr /need/ |
| a relocation. */ |
| relocate = rel_nonzero; |
| break; |
| |
| case DW_FORM_ref_udata: |
| case DW_FORM_ref1: |
| case DW_FORM_ref2: |
| case DW_FORM_ref4: |
| case DW_FORM_ref8: |
| value_check_cb = check_die_ref_local; |
| break; |
| |
| case DW_FORM_data4: |
| case DW_FORM_data8: |
| if (check_someptr) |
| relocate = rel_require; |
| break; |
| } |
| |
| /* Attribute value. */ |
| uint64_t value; |
| read_ctx block; |
| |
| storage_class_t storclass = form->storage_class (); |
| if (!read_generic_value (ctx, form->width (cu->head), storclass, |
| where, &value, &block)) |
| { |
| // Note that for fw_uleb and fw_sleb we report the |
| // error the second time now. |
| wr_error (where) |
| << "can't read value of attribute " |
| << elfutils::dwarf::attributes::name (it->name) |
| << '.' << std::endl; |
| return -1; |
| } |
| if (storclass == sc_block) |
| { |
| if (cls == cl_exprloc) |
| { |
| uint64_t expr_start |
| = cu->head->offset + read_ctx_get_offset (ctx) - value; |
| // xxx should we disallow relocation of length |
| // field? See check_debug_loc_range::op_read_form |
| if (!check_location_expression |
| (ver, file, &block, cu, |
| expr_start, reloc, value, where)) |
| return -1; |
| } |
| else |
| relocation_skip (reloc, read_ctx_get_offset (ctx), |
| where, skip_mismatched); |
| } |
| |
| /* Relocate the value if appropriate. */ |
| struct relocation *rel; |
| if ((rel = relocation_next (reloc, ctx_offset, |
| where, skip_mismatched))) |
| { |
| if (relocate == rel_no) |
| wr_message (where, (mc_impact_4 | mc_die_other |
| | mc_reloc | extra_mc)) |
| << "unexpected relocation of " |
| << elfutils::dwarf::forms::name (form_name) |
| << '.' << std::endl; |
| |
| if (attribute != NULL) |
| { |
| form_width_t width = form->width (cu->head); |
| relocate_one (&file, reloc, rel, width, &value, where, |
| reloc_target (form, attribute), symbolp); |
| } |
| |
| if (relocatedp != NULL) |
| *relocatedp = true; |
| } |
| else |
| { |
| if (symbolp != NULL) |
| memset (*symbolp, 0, sizeof (**symbolp)); |
| if (type_is_rel |
| && (relocate == rel_require |
| || (relocate == rel_nonzero |
| && value != 0))) |
| wr_message (where, (mc_impact_2 | mc_die_other |
| | mc_reloc | extra_mc)) |
| << pri::lacks_relocation |
| (elfutils::dwarf::forms::name (form_name)) |
| << std::endl; |
| } |
| |
| /* Dispatch value checking. */ |
| if (it->name == DW_AT_sibling) |
| { |
| /* Full-blown DIE reference checking is too heavy-weight |
| and not practical (error messages wise) for checking |
| siblings. */ |
| assert (value_check_cb == check_die_ref_local |
| || value_check_cb == check_die_ref_global); |
| value_check_cb = check_sibling_non0; |
| valuep = &sibling_addr; |
| } |
| |
| if (value_check_cb != NULL) |
| value_check_cb (value, &cb_ctx); |
| |
| /* Store the relocated value. Note valuep may point to |
| low_pc or high_pc. */ |
| if (valuep != NULL) |
| *valuep = value; |
| } |
| where.set_attrib_name (-1); |
| |
| if (high_pc != (uint64_t)-1 && low_pc != (uint64_t)-1 |
| && high_pc_relative) |
| { |
| if (high_pc_relocated) |
| wr_message (where, mc_die_other | mc_impact_2 | mc_reloc) |
| << "DW_AT_high_pc is a constant (=relative), but is relocated." |
| << std::endl; |
| high_pc += low_pc; |
| } |
| |
| /* Check PC coverage. We do that only for CU DIEs. Any DIEs |
| lower in the tree (should) take subset of addresses taken |
| by the CU DIE. */ |
| if (is_cudie && low_pc != (uint64_t)-1) |
| { |
| cu->low_pc = low_pc; |
| |
| if (high_pc != (uint64_t)-1 && high_pc > low_pc) |
| pc_coverage->add (low_pc, high_pc - low_pc); |
| } |
| |
| if (high_pc != (uint64_t)-1 && low_pc != (uint64_t)-1) |
| { |
| if (!high_pc_relative && high_pc_relocated != low_pc_relocated) |
| wr_message (where, mc_die_other | mc_impact_2 | mc_reloc) |
| << "only one of DW_AT_low_pc and DW_AT_high_pc is relocated." |
| << std::endl; |
| else |
| { |
| if (!high_pc_relative) |
| check_range_relocations (where, mc_die_other, |
| &file, |
| low_pc_symbol, high_pc_symbol, |
| "DW_AT_low_pc and DW_AT_high_pc"); |
| /* If there is no coverage, these attributes should |
| not ever be there. */ |
| if (low_pc > high_pc || low_pc == high_pc) |
| wr_message (where, mc_die_other | mc_impact_3) |
| << "DW_AT_low_pc value not below DW_AT_high_pc." |
| << std::endl; |
| } |
| } |
| |
| if (abbrev->has_children) |
| { |
| int st = read_die_chain (ver, file, ctx, cu, abbrevs, strings, |
| local_die_refs, |
| strings_coverage, reloc, |
| pc_coverage, need_rangesp, level + 1); |
| if (st == -1) |
| return -1; |
| else if (st == -2) |
| retval = -2; |
| else if (st == 0) |
| wr_message (mc_impact_3 | mc_acc_suboptimal | mc_die_rel, |
| &where, |
| ": abbrev has_children, but the chain was empty.\n"); |
| } |
| |
| if (read_ctx_eof (ctx)) |
| { |
| if (level > 0) |
| // DWARF 4 Ch. 2.3: A chain of sibling entries is |
| // terminated by a null entry. N.B. the CU DIE is a |
| // singleton, not part of a DIE chain. |
| wr_error (where) |
| << "DIE chain not terminated with null entry." << std::endl; |
| break; |
| } |
| } |
| |
| if (sibling_addr != 0) |
| wr_error (die_locus (cu->head->offset + prev_die_off)) |
| << "this DIE should have had its sibling at " << pri::hex (sibling_addr) |
| << ", but the DIE chain ended." << std::endl; |
| |
| if (retval != 0) |
| return retval; |
| else |
| return got_die ? 1 : 0; |
| } |
| } |
| |
| read_cu_headers::read_cu_headers (checkstack &stack, dwarflint &lint) |
| : _m_sec_info (lint.check (stack, _m_sec_info)) |
| , cu_headers (read_info_headers (&_m_sec_info->file, |
| &_m_sec_info->sect, |
| _m_sec_info->reldata ())) |
| { |
| } |
| |
| bool |
| check_debug_info::check_cu_structural (struct read_ctx *ctx, |
| struct cu *const cu, |
| Elf_Data *strings, |
| struct coverage *strings_coverage, |
| struct relocation_data *reloc) |
| { |
| check_debug_abbrev::abbrev_map const &abbrev_tables = _m_abbrevs->abbrevs; |
| |
| if (dump_die_offsets) |
| fprintf (stderr, "%s: CU starts\n", cu->head->where.format ().c_str ()); |
| bool retval = true; |
| |
| dwarf_version const *ver = dwarf_version::get (cu->head->version); |
| assert (ver != NULL); |
| |
| /* Look up Abbrev table for this CU. */ |
| check_debug_abbrev::abbrev_map::const_iterator abbrev_it |
| = abbrev_tables.find (cu->head->abbrev_offset); |
| if (abbrev_it == abbrev_tables.end ()) |
| { |
| wr_error (cu->head->where) |
| << "couldn't find abbrev section with offset " |
| << pri::addr (cu->head->abbrev_offset) << '.' << std::endl; |
| return false; |
| } |
| struct abbrev_table const &abbrevs = abbrev_it->second; |
| |
| /* Read DIEs. */ |
| ref_record local_die_refs; |
| |
| cu->cudie_offset = read_ctx_get_offset (ctx) + cu->head->offset; |
| int st = read_die_chain (ver, _m_file, ctx, cu, &abbrevs, strings, |
| &local_die_refs, strings_coverage, |
| (reloc != NULL && reloc->size > 0) ? reloc : NULL, |
| &_m_cov, &_m_need_ranges, 0); |
| if (st < 0) |
| { |
| _m_abbr_skip.push_back (abbrevs.offset); |
| retval = false; |
| } |
| else if (st == 0) |
| wr_error (cu->head->where) |
| << "CU contains no DIEs." << std::endl; |
| else if (!check_die_references (cu, &local_die_refs)) |
| retval = false; |
| |
| return retval; |
| } |
| |
| check_debug_info::check_debug_info (checkstack &stack, dwarflint &lint) |
| : _m_sec_info (lint.check (stack, _m_sec_info)) |
| , _m_sec_str (lint.check (stack, _m_sec_str)) |
| , _m_file (_m_sec_info->file) |
| , _m_abbrevs (lint.check (stack, _m_abbrevs)) |
| , _m_cu_headers (lint.check (stack, _m_cu_headers)) |
| , _m_need_ranges (false) |
| { |
| std::vector <cu_head> const &cu_headers = _m_cu_headers->cu_headers; |
| sec &sec = _m_sec_info->sect; |
| Elf_Data *const strings = _m_sec_str->sect.data; |
| |
| ref_record die_refs; |
| |
| bool success = true; |
| |
| coverage *strings_coverage = |
| (strings != NULL && check_category (mc_strings)) |
| ? new coverage () : NULL; |
| |
| struct relocation_data *reloc = sec.rel.size > 0 ? &sec.rel : NULL; |
| if (reloc != NULL) |
| relocation_reset (reloc); |
| |
| struct read_ctx ctx; |
| read_ctx_init (&ctx, sec.data, _m_file.other_byte_order); |
| for (std::vector <cu_head>::const_iterator it = cu_headers.begin (); |
| it != cu_headers.end (); ++it) |
| { |
| cu_head const &head = *it; |
| cu_locus where = head.where; |
| { |
| cu cur; |
| memset (&cur, 0, sizeof (cur)); |
| cur.head = &head; |
| cur.low_pc = cur.stmt_list.addr = (uint64_t)-1; |
| cur.next = (cu *)(uintptr_t)0xdead; |
| cus.push_back (cur); |
| } |
| cu &cur = cus.back (); |
| |
| assert (read_ctx_need_data (&ctx, head.total_size)); |
| |
| // Make CU context begin just before the CU length, so that |
| // DIE offsets are computed correctly. |
| struct read_ctx cu_ctx; |
| const unsigned char *cu_end = ctx.ptr + head.total_size; |
| read_ctx_init_sub (&cu_ctx, &ctx, ctx.ptr, cu_end); |
| cu_ctx.ptr += head.head_size; |
| |
| if (!check_cu_structural (&cu_ctx, &cur, |
| strings, strings_coverage, reloc)) |
| { |
| success = false; |
| break; |
| } |
| |
| if (cu_ctx.ptr != cu_ctx.end) |
| { |
| uint64_t off_start, off_end; |
| if (read_check_zero_padding (&cu_ctx, &off_start, &off_end)) |
| wr_message_padding_0 (mc_info, where, off_start, off_end); |
| else |
| { |
| // Garbage coordinates: |
| uint64_t start = read_ctx_get_offset (&ctx) + off_start; |
| uint64_t end = read_ctx_get_offset (&ctx) + head.total_size; |
| wr_message_padding_n0 (mc_info, where, start, end); |
| } |
| } |
| |
| int i = read_ctx_skip (&ctx, head.total_size); |
| assert (i); |
| } |
| |
| if (success) |
| { |
| section_locus wh (sec_info); |
| if (ctx.ptr != ctx.end) |
| /* Did we read up everything? */ |
| wr_message (mc_die_other | mc_impact_4, &wh, |
| ": CU lengths don't exactly match Elf_Data contents."); |
| else |
| /* Did we consume all the relocations? */ |
| relocation_skip_rest (&sec.rel, wh); |
| |
| /* If we managed to read up everything, now do abbrev usage |
| analysis. */ |
| for (check_debug_abbrev::abbrev_map::const_iterator it |
| = _m_abbrevs->abbrevs.begin (); |
| it != _m_abbrevs->abbrevs.end (); ++it) |
| if (it->second.used |
| && std::find (_m_abbr_skip.begin (), _m_abbr_skip.end (), |
| it->first) == _m_abbr_skip.end ()) |
| for (size_t i = 0; i < it->second.size; ++i) |
| if (!it->second.abbr[i].used) |
| wr_message (it->second.abbr[i].where, |
| mc_impact_3 | mc_acc_bloat | mc_abbrevs) |
| << "abbreviation is never used." << std::endl; |
| } |
| |
| // re-link CUs so that they form a chain again. This is to |
| // interface with legacy code. |
| { |
| cu *last = NULL; |
| for (std::vector<cu>::iterator it = cus.begin (); |
| it != cus.end (); ++it) |
| { |
| cu *cur = &*it; |
| if (last != NULL) |
| last->next = cur; |
| last = cur; |
| } |
| if (last != NULL) |
| last->next = NULL; |
| } |
| |
| /* We used to check that all CUs have the same address size. Now |
| that we validate address_size of each CU against the ELF header, |
| that's not necessary anymore. */ |
| |
| check_global_die_references (!cus.empty () ? &cus.front () : NULL); |
| |
| if (strings_coverage != NULL) |
| { |
| if (success) |
| { |
| struct hole_info info = {sec_str, mc_strings, strings->d_buf, 0}; |
| strings_coverage->find_holes (0, strings->d_size, found_hole, &info); |
| } |
| delete strings_coverage; |
| } |
| |
| // If we were unsuccessful, fail now. |
| if (!success) |
| throw check_base::failed (); |
| |
| if (cus.size () > 0) |
| assert (cus.back ().next == NULL); |
| } |
| |
| check_debug_info::~check_debug_info () |
| { |
| } |
| |
| cu * |
| check_debug_info::find_cu (::Dwarf_Off offset) |
| { |
| for (std::vector<cu>::iterator it = cus.begin (); |
| it != cus.end (); ++it) |
| if (it->head->offset == offset) |
| return &*it; |
| |
| return NULL; |
| } |
| |
| checkdescriptor const * |
| check_debug_info_refs::descriptor () |
| { |
| static checkdescriptor cd |
| (checkdescriptor::create ("check_debug_info_refs") |
| .groups ("@low") |
| .schedule (false) |
| .description ( |
| "This pass checks:\n" |
| " - for outstanding unresolved references from .debug_info to .debug_line\n" |
| " - that each CU has an associated aranges entry (that even if there is " |
| "no .debug_aranges to begin with).\n")); |
| return &cd; |
| } |
| |
| static reg<check_debug_info_refs> reg_debug_info_refs; |
| |
| check_debug_info_refs::check_debug_info_refs (checkstack &stack, |
| dwarflint &lint) |
| : _m_info (lint.check (stack, _m_info)) |
| , _m_line (lint.toplev_check (stack, _m_line)) |
| , _m_aranges (lint.toplev_check (stack, _m_aranges)) |
| { |
| // XXX if .debug_line is present and broken, we don't want to report |
| // every unsatisfied reference. If .debug_line is absent and |
| // references are present, we want to diagnose that in one line. If |
| // .debug_line is present and valid, then we want to check each |
| // reference separately. |
| for (std::vector<cu>::iterator it = _m_info->cus.begin (); |
| it != _m_info->cus.end (); ++it) |
| { |
| if (it->stmt_list.addr == (uint64_t)-1) |
| for (ref_record::const_iterator jt = it->decl_file_refs.begin (); |
| jt != it->decl_file_refs.end (); ++jt) |
| wr_error (jt->who) |
| << "references .debug_line table, but CU DIE lacks DW_AT_stmt_list." |
| << std::endl; |
| else if (_m_line == NULL |
| || !_m_line->has_line_table (it->stmt_list.addr)) |
| wr_error (it->stmt_list.who) |
| << "unresolved reference to .debug_line table " |
| << pri::hex (it->stmt_list.addr) << '.' << std::endl; |
| |
| if (_m_aranges != NULL && !it->has_arange) |
| wr_message (it->head->where, |
| mc_impact_3 | mc_acc_suboptimal | mc_aranges | mc_info) |
| << "no aranges table is associated with this CU." << std::endl; |
| } |
| } |