/* Routines related to .debug_info. 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 . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #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 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 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 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 (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 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 ::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::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::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 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::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; } }