/* Low-level checking of .debug_aranges. Copyright (C) 2009, 2010, 2011 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. elfutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // xxx drop as soon as not necessary #define __STDC_FORMAT_MACROS #ifdef HAVE_CONFIG_H # include #endif #include #include "elf_file.hh" #include "sections.hh" #include "check_debug_aranges.hh" #include "check_debug_info.hh" #include "check_debug_loc_range.hh" #include "cu_coverage.hh" #include "checked_read.hh" #include "misc.hh" #include "pri.hh" char const * locus_simple_fmt::cudie_n () { return "CU DIE"; } std::string arange_locus::format (bool brief) const { std::stringstream ss; if (!brief) ss << section_name[sec_aranges] << ": "; if (_m_arange_offset != (Dwarf_Off)-1) ss << "arange 0x" << std::hex << _m_arange_offset; else if (_m_table_offset != (Dwarf_Off)-1) ss << "table " << std::dec << _m_table_offset; else ss << "arange"; if (_m_cudie_locus != NULL) ss << " (" << _m_cudie_locus->format (true) << ')'; return ss.str (); } checkdescriptor const * check_debug_aranges::descriptor () { static checkdescriptor cd (checkdescriptor::create ("check_debug_aranges") .groups ("@low") .description ( "Checks for low-level structure of .debug_aranges. In addition it " "checks:\n" " - that relocations are valid. In ET_REL files that certain fields " "are relocated\n" " - for dangling and duplicate CU references\n" " - for garbage inside padding\n" " - for zero-length ranges\n" " - that the ranges cover all the address range covered by CUs\n" )); return &cd; } static reg reg_debug_aranges; static struct cu * cu_find_cu (struct cu *cu_chain, uint64_t offset) { for (struct cu *it = cu_chain; it != NULL; it = it->next) if (it->head->offset == offset) return it; return NULL; } #define PRI_CU "CU 0x%" PRIx64 namespace { struct hole_user { elf_file *elf; section_id id; char const *what; bool reverse; hole_user (elf_file *a_elf, section_id a_id, char const *a_what, bool a_reverse) : elf (a_elf) , id (a_id) , what (a_what) , reverse (a_reverse) {} }; } static bool hole (uint64_t start, uint64_t length, void *user) { /* We need to check alignment vs. the covered section. Find where the hole lies. */ ::hole_user &info = *static_cast< ::hole_user *> (user); struct elf_file *elf = info.elf; struct sec *sec = NULL; for (size_t i = 1; i < elf->size; ++i) { struct sec *it = elf->sec + i; GElf_Shdr *shdr = &it->shdr; Elf64_Addr s_end = shdr->sh_addr + shdr->sh_size; if (start >= shdr->sh_addr && start + length < s_end) { sec = it; /* Simply assume the first section that the hole intersects. */ break; } } if (sec == NULL || !necessary_alignment (start, length, sec->shdr.sh_addralign)) { char buf[128]; char const *what = info.what; char const *cu = "CU DIEs"; if (info.reverse) { char const *tmp = what; what = cu; cu = tmp; } wr_message (section_locus (info.id), mc_aranges | mc_impact_3) << "addresses " << range_fmt (buf, sizeof (buf), start, start + length) << " are covered with " << cu << ", but not with " << what << "." << std::endl; } if (sec == NULL) wr_error (NULL, "Couldn't find the section containing the above hole.\n"); return true; } static void compare_coverage_1 (struct elf_file *file, struct coverage *coverage, struct coverage *other, enum section_id id, char const *what, bool reverse) { struct coverage cov = *coverage - *other; hole_user info (file, id, what, reverse); cov.find_ranges (hole, &info); } static void compare_coverage (struct elf_file *file, struct coverage *coverage, struct coverage *other, enum section_id id, char const *what) { compare_coverage_1 (file, coverage, other, id, what, false); compare_coverage_1 (file, other, coverage, id, what, true); } inline static void aranges_coverage_add (struct coverage *aranges_coverage, uint64_t begin, uint64_t length, locus const &loc) { if (aranges_coverage->is_overlap (begin, length)) { char buf[128]; /* Not a show stopper, this shouldn't derail high-level. */ wr_message (loc, mc_aranges | mc_impact_2 | mc_error) << "the range " << range_fmt (buf, sizeof buf, begin, begin + length) << " overlaps with another one." << std::endl; } aranges_coverage->add (begin, length); } /* COVERAGE is portion of address space covered by CUs (either via low_pc/high_pc pairs, or via DW_AT_ranges references). If non-NULL, analysis of arange coverage is done against that set. */ static bool check_aranges_structural (struct elf_file *file, struct sec *sec, struct cu *cu_chain, struct coverage *coverage) { struct read_ctx ctx; read_ctx_init (&ctx, sec->data, file->other_byte_order); bool retval = true; struct coverage *aranges_coverage = coverage != NULL ? new struct coverage () : NULL; while (!read_ctx_eof (&ctx)) { arange_locus where (read_ctx_get_offset (&ctx)); const unsigned char *atab_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 *atab_end = ctx.ptr + size; if (false) { next: if (!read_ctx_skip (&ctx, size)) /* A "can't happen" error. */ goto not_enough; continue; } if (!read_ctx_init_sub (&sub_ctx, &ctx, atab_begin, atab_end)) { not_enough: wr_error (&where, PRI_NOT_ENOUGH, "next table"); return false; } sub_ctx.ptr = ctx.ptr; /* Version. */ uint16_t version; if (!read_ctx_read_2ubyte (&sub_ctx, &version)) { wr_error (&where, ": can't read version.\n"); retval = false; goto next; } if (!supported_version (version, 1, where, 2)) { retval = false; goto next; } /* CU offset. */ uint64_t cu_offset; uint64_t ctx_offset = sub_ctx.ptr - ctx.begin; if (!read_ctx_read_offset (&sub_ctx, offset_size == 8, &cu_offset)) { wr_error (&where, ": can't read debug info offset.\n"); retval = false; goto next; } struct relocation *rel; if ((rel = relocation_next (&sec->rel, ctx_offset, where, skip_mismatched))) relocate_one (file, &sec->rel, rel, offset_size, &cu_offset, where, sec_info, NULL); else if (file->ehdr.e_type == ET_REL) wr_message (mc_impact_2 | mc_aranges | mc_reloc | mc_header, &where, PRI_LACK_RELOCATION, "debug info offset"); struct cu *cu = NULL; if (cu_chain != NULL && (cu = cu_find_cu (cu_chain, cu_offset)) == NULL) wr_error (&where, ": unresolved reference to " PRI_CU ".\n", cu_offset); cudie_locus cudie_loc (cu != NULL ? cu->cudie_offset : -1); if (cu != NULL) { where.set_cudie (&cudie_loc); if (cu->has_arange) wr_error (where) << "there has already been arange section for this CU." << std::endl; else cu->has_arange = true; } /* Address size. */ int address_size; error_code err = read_address_size (&sub_ctx, file->addr_64, &address_size, where); if (err != err_ok) retval = false; if (err == err_fatal) goto next; /* Segment size. */ uint8_t segment_size; if (!read_ctx_read_ubyte (&sub_ctx, &segment_size)) { wr_error (&where, ": can't read unit segment size.\n"); retval = false; goto next; } if (segment_size != 0) { wr_error (&where, ": dwarflint can't handle segment_size != 0.\n"); retval = false; goto next; } /* 7.20: The first tuple following the header in each set begins at an offset that is a multiple of the size of a single tuple (that is, twice the size of an address). The header is padded, if necessary, to the appropriate boundary. */ const uint8_t tuple_size = 2 * address_size; uint64_t off = read_ctx_get_offset (&sub_ctx); if ((off % tuple_size) != 0) { uint64_t noff = ((off / tuple_size) + 1) * tuple_size; for (uint64_t i = off; i < noff; ++i) { uint8_t c; if (!read_ctx_read_ubyte (&sub_ctx, &c)) { wr_error (&where, ": section ends after the header, " "but before the first entry.\n"); retval = false; goto next; } if (c != 0) wr_message (mc_impact_2 | mc_aranges | mc_header, &where, ": non-zero byte at 0x%" PRIx64 " in padding before the first entry.\n", read_ctx_get_offset (&sub_ctx)); } } assert ((read_ctx_get_offset (&sub_ctx) % tuple_size) == 0); while (!read_ctx_eof (&sub_ctx)) { /* We would like to report aranges the same way that readelf does. But readelf uses index of the arange in the array as returned by dwarf_getaranges, which sorts the aranges beforehand. We don't want to disturb the memory this way, the better to catch structural errors accurately. So report arange offset instead. If this becomes a problem, we will achieve this by two-pass analysis. */ where.set_arange (read_ctx_get_offset (&sub_ctx)); /* Record address. */ uint64_t address; ctx_offset = sub_ctx.ptr - ctx.begin; bool address_relocated = false; if (!read_ctx_read_var (&sub_ctx, address_size, &address)) { wr_error (&where, ": can't read address field.\n"); retval = false; goto next; } if ((rel = relocation_next (&sec->rel, ctx_offset, where, skip_mismatched))) { address_relocated = true; relocate_one (file, &sec->rel, rel, address_size, &address, where, rel_target::rel_address, NULL); } else if (file->ehdr.e_type == ET_REL && address != 0) wr_message (mc_impact_2 | mc_aranges | mc_reloc, &where, PRI_LACK_RELOCATION, "address field"); /* Record length. */ uint64_t length; if (!read_ctx_read_var (&sub_ctx, address_size, &length)) { wr_error (&where, ": can't read length field.\n"); retval = false; goto next; } if (address == 0 && length == 0 && !address_relocated) break; if (length == 0) /* DWARF 3 spec, 6.1.2 Lookup by Address: Each descriptor is a pair consisting of the beginning address [...], followed by the _non-zero_ length of that range. */ wr_error (&where, ": zero-length address range.\n"); /* Skip coverage analysis if we have errors. */ else if (retval && aranges_coverage != NULL) aranges_coverage_add (aranges_coverage, address, length, where); } if (sub_ctx.ptr != sub_ctx.end) { uint64_t start, end; section_locus wh (sec_aranges); if (read_check_zero_padding (&sub_ctx, &start, &end)) wr_message_padding_0 (mc_aranges, wh, start, end); else { wr_message_padding_n0 (mc_aranges | mc_error, wh, start, start + size); retval = false; } } goto next; } if (aranges_coverage != NULL) { compare_coverage (file, coverage, aranges_coverage, sec_aranges, "aranges"); delete aranges_coverage; } return retval; } check_debug_aranges::check_debug_aranges (checkstack &stack, dwarflint &lint) : _m_sec_aranges (lint.check (stack, _m_sec_aranges)) , _m_info (lint.toplev_check (stack, _m_info)) , _m_cu_coverage (lint.toplev_check (stack, _m_cu_coverage)) { coverage *cov = _m_cu_coverage != NULL ? &_m_cu_coverage->cov : NULL; if (!check_aranges_structural (&_m_sec_aranges->file, &_m_sec_aranges->sect, _m_info != NULL ? &_m_info->cus.front () : NULL, cov)) throw check_base::failed (); }