diff options
author | Djordje Todorovic <djordje.todorovic@rt-rk.com> | 2017-09-26 14:21:47 +0200 |
---|---|---|
committer | Mark Wielaard <mark@klomp.org> | 2017-10-06 14:41:37 +0200 |
commit | 20aa8539966069195ace5f909e6f5cf8b4e56e2c (patch) | |
tree | 11b61cc49b4d3b596b26eacc7f1f73f6edfc7296 | |
parent | 129765d3887ab0c7ce9ecb58eb836e8bc739239c (diff) | |
download | elfutils-20aa8539966069195ace5f909e6f5cf8b4e56e2c.tar.gz |
Add dwarflint/ from origin/dwarf branch
Signed-off-by: Djordje Todorovic <djordje.todorovic@rt-rk.com>
159 files changed, 18270 insertions, 0 deletions
diff --git a/dwarflint/Makefile.am b/dwarflint/Makefile.am new file mode 100644 index 00000000..e1a9c519 --- /dev/null +++ b/dwarflint/Makefile.am @@ -0,0 +1,209 @@ +## Process this file with automake to create Makefile.in +## +## Copyright (C) 1996-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 <http://www.gnu.org/licenses/>. +## +include $(top_srcdir)/config/eu.am +DEFS += $(YYDEBUG) -DDEBUGPRED=@DEBUGPRED@ \ + -DSRCDIR=\"$(shell cd $(srcdir);pwd)\" -DOBJDIR=\"$(shell pwd)\" +INCLUDES += -I$(srcdir)/../libelf -I$(srcdir)/../libebl \ + -I$(srcdir)/../libdw -I$(srcdir)/../libdwfl \ + -I$(srcdir)/../libasm + +AM_LDFLAGS = -Wl,-rpath-link,../libelf:../libdw + +no_mudflap.os = -fmudflap + +bin_PROGRAMS = dwarflint locstats +noinst_PROGRAMS = tests/test-coverage tests/test-wrap tests/test-all-dies-it + +dwarflint_SOURCES = \ + addr-record.cc addr-record.hh \ + all-dies-it.hh \ + check_registrar.hh check_registrar_i.hh \ + checkdescriptor.cc checkdescriptor.hh checkdescriptor_i.hh \ + checked_read.cc checked_read.hh \ + checkrule.cc checkrule.hh \ + checks.hh checks_i.hh \ + coverage.cc coverage.hh \ + cu_coverage.cc cu_coverage.hh cu_coverage_i.hh \ + die_locus.cc die_locus.hh \ + dwarf_2.cc dwarf_2.hh \ + dwarf_3.cc dwarf_3.hh \ + dwarf_4.cc dwarf_4.hh \ + dwarf_gnu.cc dwarf_gnu.hh \ + dwarf_mips.cc dwarf_mips.hh \ + dwarf_version-imp.cc dwarf_version-imp.hh \ + dwarf_version.cc dwarf_version.hh dwarf_version_i.hh \ + dwarflint.cc dwarflint.hh dwarflint_i.hh \ + elf_file.hh elf_file_i.hh \ + expected-at.cc expected.hh \ + files.cc files.hh \ + highlevel_check.cc highlevel_check.hh highlevel_check_i.hh \ + locus.cc locus.hh \ + main.cc \ + messages.cc messages.hh \ + misc.cc misc.hh \ + option.cc option.hh option_i.hh \ + pri.cc pri.hh \ + readctx.cc readctx.hh \ + reloc.cc reloc.hh \ + section_id.cc section_id.hh \ + sections.cc sections.hh sections_i.hh \ + wrap.cc wrap.hh \ + \ + check_debug_abbrev.cc check_debug_abbrev.hh check_debug_abbrev_i.hh \ + check_debug_aranges.cc check_debug_aranges.hh check_debug_aranges_i.hh \ + check_debug_info.cc check_debug_info.hh check_debug_info_i.hh \ + check_debug_line.cc check_debug_line.hh check_debug_line_i.hh \ + check_debug_loc_range.cc check_debug_loc_range.hh check_debug_loc_range_i.hh \ + check_debug_pub.cc check_debug_pub.hh \ + check_die_tree.cc check_die_tree.hh check_die_tree_i.hh \ + check_duplicate_DW_tag_variable.cc \ + check_dups_abstract_origin.cc \ + check_expected_trees.cc \ + check_matching_ranges.cc \ + check_nodebug.cc \ + check_range_out_of_scope.cc \ + check_self_referential_die.cc \ + check_linkage_external_die.cc \ + check_die_decl_call.cc \ + check_die_line_info.cc \ + lowlevel_checks.cc lowlevel_checks.hh \ + \ + ../src/dwarfstrings.c + +locstats_SOURCES = \ + locstats.cc \ + die_locus.cc die_locus.hh \ + files.cc files.hh \ + locus.cc locus.hh \ + option.cc option.hh option_i.hh \ + section_id.cc section_id.hh \ + pri.cc pri.hh + +tests_test_coverage_SOURCES = tests/test-coverage.cc coverage.cc pri.cc \ + ../src/dwarfstrings.c + +tests_test_wrap_SOURCES = tests/test-wrap.cc wrap.cc +tests_test_all_dies_it_SOURCES = tests/test-all-dies-it.cc + +EXTRA_TESTS = tests/run-debug_abbrev-duplicate-attribute.sh \ + tests/run-check_duplicate_DW_tag_variable.sh \ + tests/run-location-leaks.sh \ + tests/run-nodebug.sh \ + tests/run-check_range_out_of_scope.sh \ + tests/run-check_debug_info_refs.sh \ + tests/run-aranges_terminate_early.sh \ + tests/run-libdl-2.12.so.debug.sh \ + tests/run-test-all-dies-it.sh \ + tests/run-bad.sh \ + tests/run-check_self_referential_die.sh \ + tests/run-DW_AT_high_pc-relative.sh \ + tests/run-DW_AT_high_pc-below.sh \ + tests/run-DW_AT-later-version.sh \ + tests/run-upper.sh + +TESTS = $(EXTRA_TESTS) \ + tests/test-coverage \ + tests/test-wrap + +EXTRA_DIST = $(EXTRA_TESTS) \ + tests/debug_abbrev-duplicate-attribute.bz2 \ + tests/crc7.ko.debug.bz2 \ + tests/location-leaks.bz2 \ + tests/nodebug.bz2 \ + tests/check_range_out_of_scope-1.bz2 \ + tests/check_debug_info_refs-1.bz2 \ + tests/aranges_terminate_early.bz2 + tests/libdl-2.12.so.debug.bz2 \ + tests/hello.bad-1.bz2 \ + tests/hello.bad-3.bz2 \ + tests/empty-1.bz2 \ + tests/garbage-1.bz2 \ + tests/garbage-2.bz2 \ + tests/garbage-3.bz2 \ + tests/garbage-4.bz2 \ + tests/garbage-5.bz2 \ + tests/garbage-6.bz2 \ + tests/garbage-7.bz2 \ + tests/garbage-8.bz2 \ + tests/garbage-9.bz2 \ + tests/garbage-10.bz2 \ + tests/garbage-11.bz2 \ + tests/garbage-12.bz2 \ + tests/check_self_referential_die.bz2 \ + tests/DW_AT_high_pc-relative.bz2 \ + tests/DW_AT_high_pc-below.bz2 \ + tests/DW_AT-later-version.bz2 \ + tests/upper.bz2 + +installed_TESTS_ENVIRONMENT = libdir=$(DESTDIR)$(libdir) \ + bindir=$(DESTDIR)$(bindir) \ + $(top_srcdir)/tests/test-wrapper.sh \ + installed $(tests_rpath) \ + $(program_transform_name) +if STANDALONE +TESTS_ENVIRONMENT = $(installed_TESTS_ENVIRONMENT) +else !STANDALONE +TESTS_ENVIRONMENT = $(top_srcdir)/tests/test-wrapper.sh \ + ../libdw:../backends:../libelf:../libasm + +installcheck-local: + $(MAKE) $(AM_MAKEFLAGS) \ + TESTS_ENVIRONMENT='$(installed_TESTS_ENVIRONMENT)' check-TESTS +endif !STANDALONE + +if BUILD_STATIC +libasm = ../libasm/libasm.a +libdw = ../libdw/libdw.a $(zip_LIBS) $(libelf) $(libebl) -ldl +libelf = ../libelf/libelf.a +else +libasm = ../libasm/libasm.so +libdw = ../libdw/libdw.so +libelf = ../libelf/libelf.so +endif +libebl = ../libebl/libebl.a +libeu = ../lib/libeu.a + +# XXX later the C++ stuff will be in libdw.so directly +libdwpp = ../libdw/libdwpp.a $(libdw) + +dwarflint_LDADD = $(libebl) $(libelf) $(libdwpp) $(libeu) $(libmudflap) -ldl +locstats_LDADD = $(libebl) $(libelf) $(libdwpp) $(libeu) $(libmudflap) -ldl +tests_test_coverage_LDADD = $(libebl) $(libelf) $(libdwpp) $(libeu) $(libmudflap) -ldl +tests_test_all_dies_it_LDADD = $(libdwpp) + +installcheck-binPROGRAMS: $(bin_PROGRAMS) + bad=0; pid=$$$$; list="$(bin_PROGRAMS)"; for p in $$list; do \ + case ' $(AM_INSTALLCHECK_STD_OPTIONS_EXEMPT) ' in \ + *" $$p "* | *" $(srcdir)/$$p "*) continue;; \ + esac; \ + f=`echo "$$p" | \ + sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + for opt in --help --version; do \ + if LD_LIBRARY_PATH=$(DESTDIR)$(libdir) \ + $(DESTDIR)$(bindir)/$$f $$opt > c$${pid}_.out 2> c$${pid}_.err \ + && test -n "`cat c$${pid}_.out`" \ + && test -z "`cat c$${pid}_.err`"; then :; \ + else echo "$$f does not support $$opt" 1>&2; bad=1; fi; \ + done; \ + done; rm -f c$${pid}_.???; exit $$bad + +CLEANFILES += *.gconv + +MAINTAINERCLEANFILES = diff --git a/dwarflint/TODO b/dwarflint/TODO new file mode 100644 index 00000000..aa612026 --- /dev/null +++ b/dwarflint/TODO @@ -0,0 +1,161 @@ +-*-org-*- +* Generic DWARF support + We now have dwarf_version, form and attribute classes. These can be + (and are) set up to support DWARF 2, 3 and 4 separately, as well as + the MIPS extension (in a kinda-sorta way, since I've got no binaries + to check this). + + But there's still some code around that depends on per-form/per-attr + switches, namely check_debug_info.cc::reloc_target and the + reference-checking code in the same file. Maybe the way to do this + is to merge all the cl_*ptr into one class cl_pointer and have + dwarflint know that the attribute determines what it points to. + (Except that some forms determine the target themselves.) Then + declare at relevant attributes the pointer target (a section_id), if + any. I.e. type the forms more richly. + + So that's about the FORMs and ATs. But there's more, e.g. DW_OP_ + support. + +* low-level checks +** DW_TAG_type_unit + For each type defined in a compilation unit, a contribution may be + made to the .debug_types section of the object file. Each such + contribution consists of a type unit header (see Section 7.5.1.2) + followed by a DW_TAG_type_unit entry, together with its children. + +* high-level checks + +** DW_OP_GNU_implicit_pointer + http://www.mail-archive.com/elfutils-devel@lists.fedorahosted.org/msg00869.html + +** const values vs. addresses + http://www.mail-archive.com/elfutils-devel@lists.fedorahosted.org/msg00816.html + +** dwarflint --stats +*** mutability probably needs to take into account DW_OP_call* + https://fedorahosted.org/pipermail/elfutils-devel/2010-October/001628.html + +** expected trees/attributes + This is about the check_expected_trees check. All attributes are + marked optional. In future, we need to go through the standard, or + employ some other source of knowledge, and adjust the optionality + level. + + Also the approach we are taking now is no good. It ignores changes + in DWARF revisions and doesn't tackle the expected children case at + all. It seems that what we need is some sort of XPath-like + approach to matching sub-graphs. Each time one of the queries + triggers, a check would be done for expected "neighborhood" of the + node. Such a query might reach far from the original node, + spanning layers of parent/child or die/neighbor relationship. + +*** DW_AT_byte_size at DW_TAG_pointer_type + + That's from my conversation with Mark: + +<mjw> machatap: I was surprised to see all these DW_TAG_pointer_type and + DW_TAG_reference_type having an explicit DW_AT_byte_size + [2010-09-06 16:59] +<mjw> machatap: I see that you added the following note in dwarflint: + [2010-09-06 17:00] +<mjw> .optional (DW_AT_byte_size) // XXX added to reflect reality +<mjw> Any idea why reality is like that? +<machatap> mjw: yeah, the XXX meaning "we need to look into that" +<machatap> I'm afraid I added it there during the mass checks without also + putting it on the wiki or somewhere +<mjw> OK, so you also think that is strange. good. I might not be crazy after + all :) [2010-09-06 17:01] +<machatap> well, it's certainly not per the standard + +** DW_AT_location missing vs. optimized-out check + a variable/formal_parameter DIE should have some + location/declaration/const_value attr + https://fedorahosted.org/pipermail/elfutils-devel/2009-March/000179.html + +** DW_FORM_* basic sanity checks on attribute values + - cache min/max aggregate size per abbrev + - when variable-size, check .debug_info data sanity + + This is taken from wiki where it was put way back from some e-mail. + I don't even remember what that means anymore but perhaps that's to + checks stuff like -1s encoded as signed 0xffffffff etc. + +** .debug_frame/.eh_frame (CFI) + This wasn't even started yet. + +** .debug_loc opcodes +*** DW_OP_xderef, DW_OP_xderef_size + probably flag as unsupported. +*** DW_OP_call_{2,4} + check the operand matches DIE (put off till later) +*** DW_OP_call_ref + this deals with inter-DSO relationships, dwarflint in its current + one-file mode can't handle it. Put off till later +*** DW_OP_nop + are these even useful? + +* messages + +** streams vs fmtstring + We now use C++ streams in most places (there are remnants of C + printfs in places). But streams suck for localization. Personally + I'm not too enthusiastic about non-English bugreports, but rest of + elfutils is localized, and dwarflint should be too. I'm afraid + that means moving back to some sort of formatting strings. + +** filtering + + The current way of filtering is lacking. We want to filter + messages based on: + - severity (low, medium, high) + - category (e.g. unresolved reference) + - area (e.g. .debug_info) + - check in question (e.g. check_debug_info) + - the message in question (aka I don't want to see this particular + message at all) + + What's now there basically works, but it's not configurable from + command line, and it's awkward to use. Plus it stops about halfway + through, the bottom part of the stack needs to be implemented via + grepping, and that turns our messages into API. + + It seems there are at least two trees of abstractness (area, check + and message in question form one, category potentially another, + albeit perhaps degenerated) that we may want to filter + independently. E.g. I might want to filter out all + .debug_info/sev<1, or I might want to simply filter out sev<1 right + away. + +* quirks + + Some compilers produce files broken in various ways. Not to be + swamped with irrelevant known-broken stuff, we'd like dwarflint to + know about these "quirks" and be able to suppress them. I expect + there to be frequent additions to this "quirks table", so it should + be fairly easy to extend this. + + (Maybe quirk is simply a dwarf_version. After loading the CU DIE, + dwarflint would consult the quirk table and construct new + dwarf_version with appropriate exceptions.) + + The current option --tolerant will go away when this is implemented. + I don't think it even works anyway. + +* multi-file mode + + While dwarflint can check several files (meaning you can put several + file names to a command line), it treats each of them in isolation. + In theory these files can link to each other and form graphs, and + dwarflint should be able to check the whole graph. + +* failure tolerance + + We'd like dwarflint to do things like checking each CU the abbrev + table of which I was able to read. In fact dwarflint should check + even CUs that it has at least partial information about. It could + bail out as soon as it hits invalid abbrev, or abbrev with unknown + attribute. Even then it might give up its goal of validating + sibling references, and use them blindly to skip unknown portions. + Current check granularity makes this very awkward to express, I'll + have to rework how checks are defined and executed. diff --git a/dwarflint/addr-record.cc b/dwarflint/addr-record.cc new file mode 100644 index 00000000..59dfa006 --- /dev/null +++ b/dwarflint/addr-record.cc @@ -0,0 +1,60 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009,2010,2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "addr-record.hh" + +size_t +addr_record::find (uint64_t addr) const +{ + size_t a = 0; + size_t b = size (); + + while (a < b) + { + size_t i = (a + b) / 2; + uint64_t v = (*this)[i]; + + if (v > addr) + b = i; + else if (v < addr) + a = i + 1; + else + return i; + } + + return a; +} + +bool +addr_record::has_addr (uint64_t addr) const +{ + if (begin () == end () + || addr < front () + || addr > back ()) + return false; + + const_iterator it = begin () + find (addr); + return it != end () && *it == addr; +} + +void +addr_record::add (uint64_t addr) +{ + iterator it = begin () + find (addr); + if (it == end () || *it != addr) + insert (it, addr); +} diff --git a/dwarflint/addr-record.hh b/dwarflint/addr-record.hh new file mode 100644 index 00000000..a717d236 --- /dev/null +++ b/dwarflint/addr-record.hh @@ -0,0 +1,74 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009,2010,2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_ADDR_RECORD_H +#define DWARFLINT_ADDR_RECORD_H + +#include <stdlib.h> +#include <stdint.h> +#include <vector> + +#include "locus.hh" + +/// Address record is used to check that all DIE references actually +/// point to an existing die, not somewhere mid-DIE, where it just +/// happens to be interpretable as a DIE. This is stored as sorted +/// array for quick lookup and duplicate removal. +struct addr_record + : private std::vector<uint64_t> +{ + typedef std::vector<uint64_t> _super_t; + size_t find (uint64_t addr) const; + +public: + bool has_addr (uint64_t addr) const; + void add (uint64_t addr); +}; + +/// One reference for use in ref_record, parametrized by locus type. +template <class L> +struct ref_T +{ + uint64_t addr; // Referee address + L who; // Referrer + + ref_T () + : addr (-1) + {} + + ref_T (uint64_t a_addr, L const &a_who) + : addr (a_addr) + , who (a_who) + {} +}; + +/// Reference record is used to check validity of DIE references. +/// Unlike the above, this is not stored as sorted set, but simply as +/// an array of records, because duplicates are unlikely. +template <class L> +class ref_record_T + : private std::vector<ref_T<L> > +{ + typedef std::vector<ref_T<L> > _super_t; +public: + using _super_t::const_iterator; + using _super_t::begin; + using _super_t::end; + using _super_t::push_back; +}; + +#endif//DWARFLINT_ADDR_RECORD_H diff --git a/dwarflint/all-dies-it.hh b/dwarflint/all-dies-it.hh new file mode 100644 index 00000000..fe30057d --- /dev/null +++ b/dwarflint/all-dies-it.hh @@ -0,0 +1,142 @@ +/* Pedantic checking of DWARF files. + Copyright (C) 2009, 2010, 2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <vector> +#include <stdexcept> + +// Tree flattening iterator. It pre-order iterates all CUs in given +// dwarf file. +template <class T> +class all_dies_iterator + : public std::iterator<std::input_iterator_tag, + typename T::debug_info_entry> +{ + typedef typename T::debug_info_entry::children_type::const_iterator die_it_t; + typedef std::vector <std::pair <die_it_t, die_it_t> > die_it_stack_t; + + typename T::compile_units_type::const_iterator _m_cu_it, _m_cu_it_end; + die_it_t _m_die_it, _m_die_it_end; + die_it_stack_t _m_die_it_stack; + bool _m_atend; + + void start () + { + if (_m_cu_it == _m_cu_it_end) + _m_atend = true; + else + { + _m_die_it = die_it_t (*_m_cu_it); + _m_die_it_end = die_it_t (); + ++_m_cu_it; + assert (_m_die_it != _m_die_it_end); + } + } + +public: + // An end iterator. + all_dies_iterator () + : _m_atend (true) + {} + + explicit all_dies_iterator (T const &dw) + : _m_cu_it (dw.compile_units ().begin ()) + , _m_cu_it_end (dw.compile_units ().end ()) + , _m_atend (_m_cu_it == _m_cu_it_end) + { + if (!_m_atend) + start (); + } + + bool operator== (all_dies_iterator const &other) + { + return (_m_atend && other._m_atend) + || (_m_cu_it == other._m_cu_it + && _m_die_it == other._m_die_it + && _m_die_it_stack == other._m_die_it_stack); + } + + bool operator!= (all_dies_iterator const &other) + { + return !(*this == other); + } + + all_dies_iterator operator++ () // prefix + { + if (!_m_atend) + { + if (_m_die_it->has_children () + && _m_die_it->children ().begin () != _m_die_it->children ().end ()) + { + _m_die_it_stack.push_back (std::make_pair (_m_die_it, _m_die_it_end)); + _m_die_it_end = _m_die_it->children ().end (); + _m_die_it = _m_die_it->children ().begin (); + } + else + while (++_m_die_it == _m_die_it_end) + + { + if (_m_die_it_stack.size () == 0) + { + start (); + break; + } + _m_die_it = _m_die_it_stack.back ().first; + _m_die_it_end = _m_die_it_stack.back ().second; + _m_die_it_stack.pop_back (); + } + } + return *this; + } + + all_dies_iterator operator++ (int) // postfix + { + all_dies_iterator prev = *this; + ++*this; + return prev; + } + + typename T::debug_info_entry const &operator* () const + { + if (unlikely (_m_atend)) + throw std::runtime_error ("dereferencing end iterator"); + return *_m_die_it; + } + + std::vector<typename T::debug_info_entry> stack () const + { + std::vector<typename T::debug_info_entry> ret; + for (auto it = _m_die_it_stack.begin (); + it != _m_die_it_stack.end (); ++it) + ret.push_back (*it->first); + ret.push_back (*_m_die_it); + return ret; + } + + typename T::compile_unit cu () const + { + return *_m_cu_it; + } + + typename T::debug_info_entry const *operator-> () const + { + return &**this; + } +}; diff --git a/dwarflint/check_debug_abbrev.cc b/dwarflint/check_debug_abbrev.cc new file mode 100644 index 00000000..e7313230 --- /dev/null +++ b/dwarflint/check_debug_abbrev.cc @@ -0,0 +1,574 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009, 2010, 2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <dwarf.h> +#include <sstream> +#include <cassert> +#include <algorithm> + +#include "../libdw/c++/dwarf" + +#include "check_debug_info.hh" +#include "check_debug_abbrev.hh" +#include "pri.hh" +#include "dwarf_version.hh" +#include "sections.hh" +#include "checked_read.hh" +#include "messages.hh" +#include "misc.hh" + +char const * +locus_simple_fmt::abbr_offset_n () +{ + return "abbr. offset"; +} + +abbrev_attrib_locus::abbrev_attrib_locus (uint64_t abbr_offset, + uint64_t attr_offset, + int a_name) + : _m_abbr_offset (abbr_offset) + , _m_attr_offset (attr_offset) + , _m_name (a_name) +{} + +abbrev_attrib_locus::abbrev_attrib_locus (abbrev_attrib_locus const ©) + : _m_abbr_offset (copy._m_abbr_offset) + , _m_attr_offset (copy._m_attr_offset) + , _m_name (copy._m_name) +{} + +std::string +abbrev_attrib_locus::name () const +{ + return pri::attr_name (_m_name); +} + +void +abbrev_attrib_locus::set_name (int a_name) +{ + _m_name = a_name; +} + +abbrev_attrib_locus +abbrev_attrib_locus::non_symbolic () +{ + return abbrev_attrib_locus (_m_abbr_offset, _m_attr_offset); +} + +std::string +abbrev_attrib_locus::format (bool brief) const +{ + std::stringstream ss; + if (!brief) + ss << section_name[sec_abbrev] << ": "; + if (_m_name != -1) + ss << "abbr. 0x" << std::hex << _m_abbr_offset << ", attr. " << name (); + else + ss << "abbr. attribute 0x" << std::hex << _m_attr_offset; + return ss.str (); +} + +checkdescriptor const * +check_debug_abbrev::descriptor () +{ + static checkdescriptor cd + (checkdescriptor::create ("check_debug_abbrev") + .groups ("@low") + .schedule (false) + .description ( +"Checks for low-level structure of .debug_abbrev. In addition it " +"checks:\n" +" - that all abbreviation tables are non-empty\n" +" - that certain attribute forms match expectations (mainly those that " +"we have to work with in subsequent check passes. For example we " +"check that DW_AT_low_pc has a form of DW_FORM_{,ref_}addr)\n" +" - that all CUs that share an abbrev table are of the same DWARF " +"version\n" +" - that each abbrev table is used\n" +" - that abbrevs don't share abbrev codes\n" +" - that abbrev tags, attribute names and attribute forms are all known " +"(note that this assumes that elfutils know about all tags used in " +"practice. Be sure to build against recent-enough version)\n" +" - that the value of has_children is either 0 or 1\n" +" - that DW_AT_sibling isn't formed as DW_FORM_ref_addr, and that it " +"isn't present at childless abbrevs\n" +" - that attributes are not duplicated at abbrev\n" +" - that DW_AT_high_pc is never used without DW_AT_low_pc. If both are " +"used, that DW_AT_ranges isn't also used\n" +"This check generally requires CU headers to be readable, i.e. that the " +".debug_info section is roughly well-defined. If that isn't the case, " +"many checks will still be done, operating under assumption that what " +"we see is the latest DWARF format. This may render some checks " +"inaccurate.\n")); + return &cd; +} + +static reg<check_debug_abbrev> reg_debug_abbrev; + +abbrev * +abbrev_table::find_abbrev (uint64_t abbrev_code) const +{ + size_t a = 0; + size_t b = size; + struct abbrev *ab = NULL; + + while (a < b) + { + size_t i = (a + b) / 2; + ab = abbr + i; + + if (ab->code > abbrev_code) + b = i; + else if (ab->code < abbrev_code) + a = i + 1; + else + return ab; + } + + return NULL; +} + +namespace +{ + struct cmp_abbrev + { + bool operator () (abbrev const &a, abbrev const &b) const + { + return a.code < b.code; + } + }; + + void + complain (locus const &loc, int form_name, + bool indirect, char const *qualifier) + { + wr_error (loc) + << "attribute with " << qualifier << (indirect ? " indirect" : "") + << " form " << elfutils::dwarf::forms::identifier (form_name) + << '.' << std::endl; + } + + bool + check_no_abbreviations (check_debug_abbrev::abbrev_map const &abbrevs) + { + bool ret = abbrevs.begin () == abbrevs.end (); + + // It's not an error when the abbrev table contains no abbrevs. + // But since we got here, apparently there was a .debug_abbrev + // section with size of more than 0 bytes, which is wasteful. + if (ret) + wr_message (section_locus (sec_abbrev), + mc_abbrevs | mc_impact_1 | mc_acc_bloat) + << "no abbreviations." << std::endl; + return ret; + } + + check_debug_abbrev::abbrev_map + load_debug_abbrev (sec §, elf_file &file, + read_cu_headers *cu_headers) + { + check_debug_abbrev::abbrev_map abbrevs; + + read_ctx ctx; + read_ctx_init (&ctx, sect.data, file.other_byte_order); + + struct abbrev_table *section = NULL; + uint64_t first_attr_off = 0; + + // Tolerate failure here. + dwarf_version const *ver = NULL; + static dwarf_version const *latest_ver = dwarf_version::get_latest (); + + bool failed = false; + while (true) + { + /* If we get EOF at this point, either the CU was improperly + terminated, or there were no data to begin with. */ + if (read_ctx_eof (&ctx)) + { + if (!check_no_abbreviations (abbrevs)) + wr_error (section_locus (sec_abbrev)) + << "missing zero to mark end-of-table.\n"; + break; + } + + uint64_t abbr_off; + uint64_t abbr_code; + { + uint64_t prev_abbr_code = (uint64_t)-1; + uint64_t zero_seq_off = (uint64_t)-1; + + do + { + abbr_off = read_ctx_get_offset (&ctx); + + /* Abbreviation code. */ + if (!checked_read_uleb128 (&ctx, &abbr_code, + section_locus (sec_abbrev, abbr_off), + "abbrev code")) + throw check_base::failed (); + + /* Note: we generally can't tell the difference between + empty table and (excessive) padding. But NUL byte(s) + at the very beginning of section are almost certainly + the first case. */ + if (zero_seq_off == (uint64_t)-1 + && abbr_code == 0 + && (prev_abbr_code == 0 + || abbrevs.empty ())) + zero_seq_off = abbr_off; + + if (abbr_code != 0) + break; + else + section = NULL; + + prev_abbr_code = abbr_code; + } + while (!read_ctx_eof (&ctx) + /* On EOF, shift the offset so that beyond-EOF + end-position is printed for padding warning. + Necessary as our end position is exclusive. */ + || ((abbr_off += 1), false)); + + if (zero_seq_off != (uint64_t)-1) + wr_message_padding_0 (mc_abbrevs | mc_header, + section_locus (sec_abbrev), + zero_seq_off, abbr_off); + } + + if (read_ctx_eof (&ctx)) + { + /* It still could have been empty. */ + check_no_abbreviations (abbrevs); + break; + } + + abbrev_locus where (abbr_off); + + /* OK, we got some genuine abbreviation. See if we need to + allocate a new section. */ + if (section == NULL) + { + abbrev_table t; + section = &abbrevs.insert (std::make_pair (abbr_off, t)).first->second; + section->offset = abbr_off; + + // Find CU that uses this abbrev table, so that we know what + // version to validate against. + if (cu_headers != NULL) + { + ver = NULL; + cu_head const *other_head = NULL; + for (std::vector <cu_head>::const_iterator it + = cu_headers->cu_headers.begin (); + it != cu_headers->cu_headers.end (); ++it) + if (it->abbrev_offset == abbr_off) + { + section->used = true; + dwarf_version const *nver + = dwarf_version::get (it->version); + if (ver == NULL) + ver = nver; + else if (nver != ver) + { + wr_error (it->where) + << " and " << other_head->where << " both use " + << where << ", but each has a different version (" + << it->version << " vs. " << other_head->version + << ")." << std::endl; + + // Arbitrarily pick newer version. + if (it->version > other_head->version) + ver = nver; + } + + other_head = &*it; + } + + if (ver == NULL) + { + // This is hard error, we can't validate abbrev + // table without knowing what version to use. + wr_error (where) + << "abbreviation table is never used." << std::endl; + ver = dwarf_version::get_latest (); + } + } + else if (ver == NULL) // Only emit this once. + { + wr_error (section_locus (sec_info)) + << "couldn't load CU headers for processing .debug_abbrev; " + "assuming latest DWARF flavor." + << std::endl; + ver = latest_ver; + } + + assert (ver != NULL); + } + + abbrev *original = section->find_abbrev (abbr_code); + abbrev *cur; + abbrev fake (where); + if (unlikely (original != NULL)) + { + wr_error (where) << "duplicate abbrev code (first was at " + << original->where << ").\n"; + /* Don't actually save this abbrev if it's duplicate. */ + cur = &fake; + } + else + { + REALLOC (section, abbr); + cur = section->abbr + section->size++; + new (cur) abbrev (where); + } + + cur->code = abbr_code; + + /* Abbreviation tag. */ + uint64_t abbr_tag; + if (!checked_read_uleb128 (&ctx, &abbr_tag, where, "abbrev tag")) + throw check_base::failed (); + + if (abbr_tag > DW_TAG_hi_user) + { + wr_error (where) + << "invalid abbrev tag " << pri::hex (abbr_tag) + << '.' << std::endl; + throw check_base::failed (); + } + cur->tag = (typeof (cur->tag))abbr_tag; + + /* Abbreviation has_children. */ + uint8_t has_children; + if (!read_ctx_read_ubyte (&ctx, &has_children)) + { + wr_error (&where, ": can't read abbrev has_children.\n"); + throw check_base::failed (); + } + + if (has_children != DW_CHILDREN_no + && has_children != DW_CHILDREN_yes) + { + wr_error (where) + << "invalid has_children value " << pri::hex (cur->has_children) + << '.' << std::endl; + throw check_base::failed (); + } + cur->has_children = has_children == DW_CHILDREN_yes; + + bool null_attrib; + bool low_pc = false; + bool high_pc = false; + bool ranges = false; + std::map<unsigned, uint64_t> seen; + + do + { + uint64_t attr_off = read_ctx_get_offset (&ctx); + uint64_t attrib_name, attrib_form; + if (first_attr_off == 0) + first_attr_off = attr_off; + + /* Shift to match elfutils reporting. */ + attr_off -= first_attr_off; + abbrev_attrib_locus attr_locus (abbr_off, attr_off); + + /* Load attribute name and form. */ + if (!checked_read_uleb128 (&ctx, &attrib_name, attr_locus, + "attribute name")) + throw check_base::failed (); + + if (!checked_read_uleb128 (&ctx, &attrib_form, attr_locus, + "attribute form")) + throw check_base::failed (); + + /* Now if both are zero, this was the last attribute. */ + null_attrib = attrib_name == 0 && attrib_form == 0; + + REALLOC (cur, attribs); + + attr_locus.set_name (attrib_name); + struct abbrev_attrib *acur = cur->attribs + cur->size++; + new (acur) abbrev_attrib (); + acur->name = attrib_name; + acur->form = attrib_form; + acur->where = attr_locus; + + if (null_attrib) + break; + + /* Otherwise validate name and form. */ + if (attrib_name == 0) + { + wr_error (attr_locus.non_symbolic ()) + << "invalid attribute code 0." << std::endl; + // We can handle this, so keep going. But this is not + // kosher for high-level checks. + failed = true; + continue; + } + + attribute const *attribute = ver->get_attribute (attrib_name); + if (attribute == NULL) + { + // GCC commonly emits DWARF 2 with trivial extensions + // (such as attribute names) from newer versions. In + // GNU mode, don't even mind this. In non-gnu, emit + // warning. We explicitly don't do this for forms, + // where the consumer wouldn't know how to read or + // skip the datum. + attribute = latest_ver->get_attribute (attrib_name); + if (attribute == NULL) + // libdw should handle unknown attribute, as long as + // the form is kosher, so don't fail the check. + wr_message (attr_locus.non_symbolic (), + mc_abbrevs | mc_impact_1) + << "invalid or unknown name " << pri::hex (attrib_name) + << '.' << std::endl; + else if (opt_nognu) + wr_message (attr_locus, mc_abbrevs | mc_impact_1) + << "attribute from later DWARF version." + << std::endl; + } + + form const *form = check_debug_abbrev::check_form + (ver, attribute, attrib_form, attr_locus, false); + if (form == NULL) + { + // Error message has been emitted in check_form. + failed = true; + continue; + } + + std::pair<std::map<unsigned, uint64_t>::iterator, bool> inserted + = seen.insert (std::make_pair (attrib_name, attr_off)); + if (!inserted.second) + { + wr_error (attr_locus.non_symbolic ()) + << "duplicate attribute " << attr_locus.name () + << " (first was at " << pri::hex (inserted.first->second) + << ")." << std::endl; + + // I think we may allow such files for high-level + // consumption, so don't fail the check... + if (attrib_name == DW_AT_sibling) + // ... unless it's DW_AT_sibling. + failed = true; + } + + if (attrib_name == DW_AT_sibling && !cur->has_children) + wr_message (attr_locus, mc_die_rel | mc_acc_bloat | mc_impact_1) + << "superfluous DW_AT_sibling attribute at childless abbrev." + << std::endl; + if (attrib_name == DW_AT_ranges) + ranges = true; + else if (attrib_name == DW_AT_low_pc) + low_pc = true; + else if (attrib_name == DW_AT_high_pc) + high_pc = true; + } + while (!null_attrib); + + if (high_pc && !low_pc) + wr_error (where) + << "the abbrev has DW_AT_high_pc without also having DW_AT_low_pc." + << std::endl; + else if (high_pc && ranges) + wr_error (where) + << "the abbrev has DW_AT_high_pc & DW_AT_low_pc, " + << "but also has DW_AT_ranges." << std::endl; + } + + if (failed) + throw check_base::failed (); + + abbrev_table *last = NULL; + for (check_debug_abbrev::abbrev_map::iterator it = abbrevs.begin (); + it != abbrevs.end (); ++it) + { + std::sort (it->second.abbr, it->second.abbr + it->second.size, + cmp_abbrev ()); + if (last != NULL) + last->next = &it->second; + last = &it->second; + } + + return abbrevs; + } +} + +check_debug_abbrev::check_debug_abbrev (checkstack &stack, dwarflint &lint) + : _m_sec_abbr (lint.check (stack, _m_sec_abbr)) + , _m_cu_headers (lint.toplev_check (stack, _m_cu_headers)) + , abbrevs (load_debug_abbrev (_m_sec_abbr->sect, + _m_sec_abbr->file, + _m_cu_headers)) +{ +} + +form const * +check_debug_abbrev::check_form (dwarf_version const *ver, + attribute const *attribute, + int form_name, locus const &loc, bool indirect) +{ + form const *form = ver->get_form (form_name); + if (form == NULL) + { + wr_error (loc) + << "invalid form " << pri::hex (form_name) + << '.' << std::endl; + return NULL; + } + + if (attribute != NULL) + { + + int attrib_name = attribute->name (); + if (!ver->form_allowed (attribute, form)) + { + complain (loc, form_name, indirect, "invalid"); + return NULL; + } + else if (attrib_name == DW_AT_sibling + && sibling_form_suitable (ver, form) == sfs_long) + complain (loc, form_name, indirect, "unsuitable"); + } + + return form; +} + +check_debug_abbrev::~check_debug_abbrev () +{ + // xxx So using new[]/delete[] would be nicer (delete ignores + // const-ness), but I'm not dipping into that right now. Just cast + // away the const, we're in the dtor so what the heck. + abbrev_map &my_abbrevs = const_cast<abbrev_map &> (abbrevs); + + for (abbrev_map::iterator it = my_abbrevs.begin (); + it != my_abbrevs.end (); ++it) + { + for (size_t i = 0; i < it->second.size; ++i) + free (it->second.abbr[i].attribs); + free (it->second.abbr); + } +} diff --git a/dwarflint/check_debug_abbrev.hh b/dwarflint/check_debug_abbrev.hh new file mode 100644 index 00000000..ef5531ab --- /dev/null +++ b/dwarflint/check_debug_abbrev.hh @@ -0,0 +1,144 @@ +/* Low-level checking of .debug_abbrev. + 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_CHECK_DEBUG_ABBREV_HH +#define DWARFLINT_CHECK_DEBUG_ABBREV_HH + +#include "checks.hh" +#include "sections_i.hh" +#include "check_debug_info_i.hh" +#include "dwarf_version_i.hh" + +namespace locus_simple_fmt +{ + char const *abbr_offset_n (); +} + +typedef fixed_locus<sec_abbrev, + locus_simple_fmt::abbr_offset_n, + locus_simple_fmt::hex> abbrev_locus; + +class abbrev_attrib_locus + : public locus +{ + uint64_t _m_abbr_offset; + uint64_t _m_attr_offset; + int _m_name; + +public: + explicit abbrev_attrib_locus (uint64_t abbr_offset = -1, + uint64_t attr_offset = -1, + int name = -1); + + abbrev_attrib_locus (abbrev_attrib_locus const ©); + + abbrev_attrib_locus non_symbolic (); + + void set_name (int name); + std::string format (bool brief = false) const; + std::string name () const; +}; + +struct abbrev_attrib +{ + abbrev_attrib_locus where; + uint16_t name; + uint8_t form; + + abbrev_attrib () + : where () + , name (0) + , form (0) + {} +}; + +struct abbrev +{ + abbrev_locus where; + uint64_t code; + + /* Attributes. */ + abbrev_attrib *attribs; + size_t size; + size_t alloc; + + /* While ULEB128 can hold numbers > 32bit, these are not legal + values of many enum types. So just use as large type as + necessary to cover valid values. */ + uint16_t tag; + bool has_children; + + /* Whether some DIE uses this abbrev. */ + bool used; + + explicit abbrev (abbrev_locus const &loc) + : where (loc) + , code (0) + , attribs (0) + , size (0) + , alloc (0) + , tag (0) + , has_children (false) + , used (false) + {} +}; + +struct abbrev_table +{ + struct abbrev_table *next; + struct abbrev *abbr; + uint64_t offset; + size_t size; + size_t alloc; + bool used; /* There are CUs using this table. */ + + abbrev *find_abbrev (uint64_t abbrev_code) const; + + abbrev_table () + : next (NULL) + , abbr (NULL) + , offset (0) + , size (0) + , alloc (0) + , used (false) + {} +}; + +class check_debug_abbrev + : public check<check_debug_abbrev> +{ + section<sec_abbrev> *_m_sec_abbr; + read_cu_headers *_m_cu_headers; + +public: + static checkdescriptor const *descriptor (); + + // offset -> abbreviations + typedef std::map< ::Dwarf_Off, abbrev_table> abbrev_map; + abbrev_map const abbrevs; + + check_debug_abbrev (checkstack &stack, dwarflint &lint); + static form const *check_form (dwarf_version const *ver, + attribute const *attr, + int form_name, + locus const &loc, + bool indirect); + + ~check_debug_abbrev (); +}; + +#endif//DWARFLINT_CHECK_DEBUG_ABBREV_HH diff --git a/dwarflint/check_debug_abbrev_i.hh b/dwarflint/check_debug_abbrev_i.hh new file mode 100644 index 00000000..47c22088 --- /dev/null +++ b/dwarflint/check_debug_abbrev_i.hh @@ -0,0 +1 @@ +class check_debug_abbrev; diff --git a/dwarflint/check_debug_aranges.cc b/dwarflint/check_debug_aranges.cc new file mode 100644 index 00000000..8a503cef --- /dev/null +++ b/dwarflint/check_debug_aranges.cc @@ -0,0 +1,448 @@ +/* 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 <http://www.gnu.org/licenses/>. */ + +// xxx drop as soon as not necessary +#define __STDC_FORMAT_MACROS + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <inttypes.h> + +#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<check_debug_aranges> 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 (); +} diff --git a/dwarflint/check_debug_aranges.hh b/dwarflint/check_debug_aranges.hh new file mode 100644 index 00000000..7393b8d2 --- /dev/null +++ b/dwarflint/check_debug_aranges.hh @@ -0,0 +1,98 @@ +/* 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_CHECK_DEBUG_ARANGES_HH +#define DWARFLINT_CHECK_DEBUG_ARANGES_HH + +#include "checks.hh" +#include "sections_i.hh" +#include "check_debug_info_i.hh" +#include "cu_coverage_i.hh" + +namespace locus_simple_fmt +{ + char const *cudie_n (); +}; + +class cudie_locus + : public fixed_locus<sec_info, + locus_simple_fmt::cudie_n, + locus_simple_fmt::dec> +{ + typedef fixed_locus<sec_info, + locus_simple_fmt::cudie_n, + locus_simple_fmt::dec> _super_t; +public: + template <class T> + cudie_locus (T const &die) + : _super_t (die.offset ()) + {} + + cudie_locus (Dwarf_Off offset) + : _super_t (offset) + {} +}; + +class arange_locus + : public locus +{ + Dwarf_Off _m_table_offset; + Dwarf_Off _m_arange_offset; + locus const *_m_cudie_locus; + +public: + explicit arange_locus (Dwarf_Off table_offset = -1, + Dwarf_Off arange_offset = -1) + : _m_table_offset (table_offset) + , _m_arange_offset (arange_offset) + , _m_cudie_locus (NULL) + {} + + explicit arange_locus (locus const &cudie_locus) + : _m_table_offset (-1) + , _m_arange_offset (-1) + , _m_cudie_locus (&cudie_locus) + {} + + void + set_cudie (locus const *cudie_locus) + { + _m_cudie_locus = cudie_locus; + } + + void + set_arange (Dwarf_Off arange_offset) + { + _m_arange_offset = arange_offset; + } + + std::string format (bool brief = false) const; +}; + +class check_debug_aranges + : public check<check_debug_aranges> +{ + section<sec_aranges> *_m_sec_aranges; + check_debug_info *_m_info; + cu_coverage *_m_cu_coverage; + +public: + static checkdescriptor const *descriptor (); + check_debug_aranges (checkstack &stack, dwarflint &lint); +}; + +#endif//DWARFLINT_CHECK_DEBUG_ARANGES_HH diff --git a/dwarflint/check_debug_aranges_i.hh b/dwarflint/check_debug_aranges_i.hh new file mode 100644 index 00000000..0a698133 --- /dev/null +++ b/dwarflint/check_debug_aranges_i.hh @@ -0,0 +1 @@ +class check_debug_aranges; diff --git a/dwarflint/check_debug_info.cc b/dwarflint/check_debug_info.cc new file mode 100644 index 00000000..a7c51982 --- /dev/null +++ b/dwarflint/check_debug_info.cc @@ -0,0 +1,1261 @@ +/* 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 <http://www.gnu.org/licenses/>. */ + +#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; + } +} diff --git a/dwarflint/check_debug_info.hh b/dwarflint/check_debug_info.hh new file mode 100644 index 00000000..1377e102 --- /dev/null +++ b/dwarflint/check_debug_info.hh @@ -0,0 +1,161 @@ +/* Low-level checking of .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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_CHECK_DEBUG_INFO_HH +#define DWARFLINT_CHECK_DEBUG_INFO_HH + +#include <libdw.h> +#include "addr-record.hh" +#include "elf_file_i.hh" +#include "coverage.hh" +#include "checks.hh" +#include "check_debug_abbrev_i.hh" +#include "check_debug_line_i.hh" +#include "check_debug_aranges_i.hh" +#include "sections_i.hh" +#include "die_locus.hh" + +typedef ref_T<die_locus> ref; +typedef ref_record_T<die_locus> ref_record; + +struct cu_head +{ + Dwarf_Off offset; + Dwarf_Off size; // Size of this CU. + Dwarf_Off head_size; // Size from begin to 1st byte of CU. + Dwarf_Off total_size; // size + head_size + + int offset_size; // Offset size in this CU. + Dwarf_Off abbrev_offset; // Abbreviation section that this CU uses. + int version; // CU version + int address_size; // Address size in bytes on the target machine. + + cu_locus where; + + explicit cu_head (Dwarf_Off a_offset) + : offset (a_offset) + , size (0) + , head_size (0) + , total_size (0) + , offset_size (0) + , abbrev_offset (0) + , version (0) + , address_size (0) + , where (a_offset) + {} +}; + +struct cu +{ + struct cu *next; // For compatibility with C level. + // xxx will probably go away eventually + cu_head const *head; + uint64_t cudie_offset; + uint64_t low_pc; // DW_AT_low_pc value of CU DIE, -1 if not present. + ::ref stmt_list; + addr_record die_addrs; // Addresses where DIEs begin in this CU. + ref_record die_refs; // DIE references into other CUs from this CU. + ref_record loc_refs; // references into .debug_loc from this CU. + ref_record range_refs; // references into .debug_ranges from this CU. + ref_record decl_file_refs; // values of DW_AT_decl_file in this CU. + bool has_arange; // Whether we saw arange section pointing at this CU. + bool has_pubnames; // Likewise for pubnames. + bool has_pubtypes; // Likewise for pubtypes. + + cu () + : next (NULL) + , head (NULL) + , cudie_offset (0) + , low_pc (0) + , has_arange (false) + , has_pubnames (false) + , has_pubtypes (false) + {} +}; + +/** The pass for reading basic .debug_info data -- the layout of + sections and their headers. */ +class read_cu_headers + : public check<read_cu_headers> +{ + section<sec_info> *_m_sec_info; + +public: + static checkdescriptor const *descriptor (); + std::vector<cu_head> const cu_headers; + read_cu_headers (checkstack &stack, dwarflint &lint); +}; + +/** The pass for in-depth structural analysis of .debug_info. */ +class check_debug_info + : public check<check_debug_info> +{ + section<sec_info> *_m_sec_info; + section<sec_str> *_m_sec_str; + elf_file const &_m_file; + check_debug_abbrev *_m_abbrevs; + read_cu_headers *_m_cu_headers; + + // Abbreviation table with that offset had user(s) that failed + // validation. Check for unused abbrevs should be skipped. + std::vector< ::Dwarf_Off> _m_abbr_skip; + + // The check pass adds all low_pc/high_pc ranges loaded from DIE + // tree into this coverage structure. + coverage _m_cov; + + // If, during the check, we find any rangeptr-class attributes, we + // set need_ranges to true. cu_ranges pass then uses this as a hint + // whether to request .debug_ranges or not. + bool _m_need_ranges; + + bool check_cu_structural (struct read_ctx *ctx, + struct cu *const cu, + Elf_Data *strings, + struct coverage *strings_coverage, + struct relocation_data *reloc); + +public: + static checkdescriptor const *descriptor (); + + coverage const &cov () const { return _m_cov; } + bool need_ranges () const { return _m_need_ranges; } + + // This is where the loaded CUs are stored. + std::vector<cu> cus; + + check_debug_info (checkstack &stack, dwarflint &lint); + ~check_debug_info (); + + cu *find_cu (::Dwarf_Off offset); +}; + +/** Check pending references that need other sections to be validated + first. */ +class check_debug_info_refs + : public check<check_debug_info_refs> +{ + check_debug_info *_m_info; + check_debug_line *_m_line; + check_debug_aranges *_m_aranges; + +public: + static checkdescriptor const *descriptor (); + check_debug_info_refs (checkstack &stack, dwarflint &lint); +}; + +#endif//DWARFLINT_CHECK_DEBUG_INFO_HH diff --git a/dwarflint/check_debug_info_i.hh b/dwarflint/check_debug_info_i.hh new file mode 100644 index 00000000..ea388e39 --- /dev/null +++ b/dwarflint/check_debug_info_i.hh @@ -0,0 +1,5 @@ +struct cu_head; +struct cu; +class read_cu_headers; +class check_debug_info; +class check_debug_info_refs; diff --git a/dwarflint/check_debug_line.cc b/dwarflint/check_debug_line.cc new file mode 100644 index 00000000..34047e27 --- /dev/null +++ b/dwarflint/check_debug_line.cc @@ -0,0 +1,703 @@ +/* Low-level checking of .debug_line. + 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "check_debug_line.hh" +#include "check_debug_info.hh" +#include "sections.hh" +#include "pri.hh" +#include "checked_read.hh" +#include "messages.hh" +#include "misc.hh" + +#include <dwarf.h> +#include "../libdw/known-dwarf.h" +#include "../src/dwarfstrings.h" + +#include <sstream> + +checkdescriptor const * +check_debug_line::descriptor () +{ + static checkdescriptor cd + (checkdescriptor::create ("check_debug_line") + .groups ("@low") + .schedule (false) + .description ( +"Checks for low-level structure of .debug_line. In addition it\n" +"checks:\n" +" - for normalized values of certain attributes (such as that " +"default_is_stmt is 0 or 1, even though technically any non-zero " +"value is allowed).\n" +" - for valid setting of opcode base (i.e. non-zero) and any file" +"indices\n" +" - that all include directories and all files are used\n" +" - that files with absolute paths don't refer to include directories," +"and otherwise that the directory reference is valid\n" +" - that each used standard or extended opcode is known (note that this " +"assumes that elfutils know about all opcodes used in practice. Be " +"sure to build against recent-enough version).\n" +" - that the line number program is properly terminated with the " +"DW_LNE_end_sequence instruction and that it contains at least one " +"other instruction\n" +" - that relocations are valid. In ET_REL files that certain fields " +"are relocated\n" +"Furthermore, if .debug_info is valid, it is checked that each line " +"table is used by some CU.\n" +"TODOs:\n" +" - 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.\n" + )); + return &cd; +} + +static reg<check_debug_line> reg_debug_line; + +namespace +{ + struct include_directory_t + { + std::string name; + bool used; + }; + typedef std::vector<include_directory_t> include_directories_t; + + struct file_t + { + const char *name; + uint64_t dir_idx; + bool used; + }; + typedef std::vector<file_t> files_t; + + /* Directory index. */ + bool read_directory_index (include_directories_t &include_directories, + files_t &files, read_ctx *ctx, + const char *name, uint64_t *ptr, + locus const &loc, bool &retval) + { + size_t nfile = files.size () + 1; + if (!checked_read_uleb128 (ctx, ptr, + loc, "directory index")) + return false; + + if (*name == '/' && *ptr != 0) + wr_message (loc, mc_impact_2 | mc_line | mc_header) + << "file #" << nfile + << " has absolute pathname, but refers to directory != 0." + << std::endl; + + if (*ptr > include_directories.size ()) + /* Not >=, dirs are indexed from 1. */ + { + wr_message (loc, mc_impact_4 | mc_line | mc_header) + << "file #" << nfile + << " refers to directory #" << *ptr + << ", which wasn't defined." << std::endl; + + /* Consumer might choke on that. */ + retval = false; + } + else if (*ptr != 0) + include_directories[*ptr - 1].used = true; + return true; + } + + bool + use_file (files_t &files, uint64_t file_idx, + locus const &loc, char const *msg = "") + { + if (file_idx == 0 || file_idx > files.size ()) + { + wr_error (loc) + << msg << "invalid file index " << file_idx << '.' + << std::endl; + return false; + } + else + files[file_idx - 1].used = true; + return true; + } +} + +namespace +{ + char const * + table_n () + { + return "table"; + } + + typedef fixed_locus<sec_line, table_n, + locus_simple_fmt::dec> line_table_locus; +} + +check_debug_line::check_debug_line (checkstack &stack, dwarflint &lint) + : _m_sec (lint.check (stack, _m_sec)) + , _m_info (lint.toplev_check (stack, _m_info)) +{ + bool addr_64 = _m_sec->file.addr_64; + struct read_ctx ctx; + read_ctx_init (&ctx, _m_sec->sect.data, _m_sec->file.other_byte_order); + + // For violations that the high-level might not handle. + bool success = true; + + while (!read_ctx_eof (&ctx)) + { + uint64_t set_offset = read_ctx_get_offset (&ctx); + line_table_locus where (set_offset); + _m_line_tables.insert ((Dwarf_Off)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." << std::endl; + throw check_base::failed (); + } + if (!read_size_extra (&ctx, size32, &size, &offset_size, where)) + throw check_base::failed (); + + 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") << '.' << std::endl; + throw check_base::failed (); + } + 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." << std::endl; + skip: + success = 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." << std::endl; + goto skip; + } + const unsigned char *header_start = sub_ctx.ptr; + + /* 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." << std::endl; + 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." << std::endl; + 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 (where, mc_line | mc_impact_2 | mc_header) + << "default_is_stmt should be 0 or 1, not " + << default_is_stmt << '.' << std::endl; + + /* 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." << std::endl; + 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." << std::endl; + 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." << std::endl; + goto skip; + } + + /* Standard opcode lengths. */ + if (opcode_base == 0) + { + wr_error (where) << "opcode base set to 0." << std::endl; + 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 #" << i << '.' + << std::endl; + goto skip; + } + + include_directories_t 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 #" + << include_directories.size () + 1 // Numbered from 1. + << '.' << std::endl; + goto skip; + } + if (*name == 0) + break; + + include_directories.push_back ((include_directory_t){name, false}); + } + + /* File names. */ + files_t files; + while (1) + { + const char *name = read_ctx_read_str (&sub_ctx); + if (name == NULL) + { + wr_error (where) + << "can't read name of file #" + << files.size () + 1 // Numbered from 1. + << '.' << std::endl; + goto skip; + } + if (*name == 0) + break; + + uint64_t dir_idx; + if (!read_directory_index (include_directories, files, + &sub_ctx, name, &dir_idx, where, success)) + 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; + + files.push_back ((struct file_t){name, dir_idx, false}); + } + + /* Now that we have table of filenames, validate DW_AT_decl_file + references. We don't include filenames defined through + DW_LNE_define_file in consideration. */ + + if (_m_info != NULL) + { + bool found = false; + for (std::vector<cu>::const_iterator it = _m_info->cus.begin (); + it != _m_info->cus.end (); ++it) + if (it->stmt_list.addr == set_offset) + { + found = true; + for (ref_record::const_iterator + jt = it->decl_file_refs.begin (); + jt != it->decl_file_refs.end (); ++jt) + if (!use_file (files, jt->addr, jt->who)) + success = false; + } + if (!found) + wr_message (where, mc_line) + << "no CU uses this line table." << std::endl; + } + + const unsigned char *program_start = header_start + header_length; + if (header_length > (uint64_t)(sub_ctx.end - header_start) + || sub_ctx.ptr > program_start) + { + wr_error (where) + << "header claims that it has a size of " << header_length + << ", but in fact it has a size of " + << (sub_ctx.ptr - program_start + header_length) + << '.' << std::endl; + + /* Assume that the header lies, and what follows is in + fact line number program. */ + success = false; + } + else if (sub_ctx.ptr < program_start) + { + /* Skip the rest of the header. */ + uint64_t off_start, off_end; + if (read_check_zero_padding (&sub_ctx, &off_start, &off_end)) + wr_message_padding_0 (mc_line | mc_header, section_locus (sec_line), + off_start, off_end); + else + wr_message_padding_n0 + (mc_line | mc_header, section_locus (sec_line), + off_start, 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)) + { + section_locus op_where (sec_line, read_ctx_get_offset (&sub_ctx)); + uint8_t opcode; + if (!read_ctx_read_ubyte (&sub_ctx, &opcode)) + { + wr_error (op_where) << "can't read opcode." << std::endl; + 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, op_where, + "length of extended opcode")) + goto skip; + if (!read_ctx_need_data (&sub_ctx, skip_len)) + { + wr_error (op_where) + << "not enough data to read an opcode of length " + << skip_len << '.' << std::endl; + goto skip; + } + + const unsigned char *next = sub_ctx.ptr + skip_len; + if (!read_ctx_read_ubyte (&sub_ctx, &extended)) + { + wr_error (op_where) + << "can't read extended opcode." << std::endl; + 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, addr_64, &addr)) + { + wr_error (op_where) + << "can't read operand of DW_LNE_set_address." + << std::endl; + goto skip; + } + + struct relocation *rel; + if ((rel = relocation_next (&_m_sec->sect.rel, ctx_offset, + op_where, skip_mismatched))) + relocate_one (&_m_sec->file, &_m_sec->sect.rel, rel, + addr_64 ? 8 : 4, &addr, op_where, + rel_target::rel_address, NULL); + else if (_m_sec->file.ehdr.e_type == ET_REL) + { + wr_message (op_where, + mc_impact_2 | mc_line | mc_reloc) + << pri::lacks_relocation ("DW_LNE_set_address") + << '.' << std::endl; + + // Don't do the addr checking in this case. + break; + } + + if (addr == 0) + wr_message (op_where, mc_line | mc_impact_1) + << "DW_LNE_set_address with zero operand." + << std::endl; + break; + } + + case DW_LNE_set_discriminator: + { + /* XXX Is there anything interesting we should + check here? */ + uint64_t disc; + if (!checked_read_uleb128 (&sub_ctx, &disc, op_where, + "set_discriminator operand")) + goto skip; + + /* The discriminator is reset to zero on any + sequence change. So setting to zero is never + necessary. */ + if (disc == 0) + wr_message (op_where, mc_line | mc_impact_1) + << "DW_LNE_set_discriminator with zero operand." + << std::endl; + break; + } + + case DW_LNE_define_file: + { + const char *name; + if ((name = read_ctx_read_str (&sub_ctx)) == NULL) + { + wr_error (op_where) + << "can't read filename operand of DW_LNE_define_file." + << std::endl; + goto skip; + } + uint64_t dir_idx; + if (!read_directory_index (include_directories, + files, &sub_ctx, name, + &dir_idx, op_where, success)) + goto skip; + 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 (op_where, mc_impact_2 | mc_line) + << "unknown extended opcode 0x" + << std::hex << +extended << std::dec + << '.' << std::endl; + }; + }; + + if (sub_ctx.ptr > next) + { + wr_error (op_where) + << "opcode claims that it has a size of " << skip_len + << ", but in fact it has a size of " + << (skip_len + (next - sub_ctx.ptr)) << '.' << std::endl; + success = false; + } + else if (sub_ctx.ptr < next) + { + uint64_t off_start, off_end; + if (handled) + { + if (read_check_zero_padding (&sub_ctx, + &off_start, &off_end)) + wr_message_padding_0 + (mc_line, section_locus (sec_line), + off_start, off_end); + else + wr_message_padding_n0 + (mc_line, section_locus (sec_line), + off_start, next - sub_ctx.begin); + } + sub_ctx.ptr = next; + } + break; + } + + /* Standard opcodes that need validation or have + non-ULEB operands. */ + case DW_LNS_advance_line: + { + int64_t line_delta; + if (!checked_read_sleb128 (&sub_ctx, &line_delta, op_where, + "DW_LNS_advance_line operand")) + goto skip; + } + break; + + case DW_LNS_fixed_advance_pc: + { + uint16_t a; + if (!read_ctx_read_2ubyte (&sub_ctx, &a)) + { + wr_error (op_where) + << "can't read operand of DW_LNS_fixed_advance_pc." + << std::endl; + goto skip; + } + break; + } + + case DW_LNS_set_file: + { + uint64_t file_idx; + if (!checked_read_uleb128 (&sub_ctx, &file_idx, op_where, + "DW_LNS_set_file operand")) + goto skip; + if (!use_file (files, file_idx, op_where, "DW_LNS_set_file: ")) + success = 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 (op_where, mc_impact_2 | mc_line) + << "unknown standard opcode 0x" + << std::hex << +opcode << std::dec + << '.' << std::endl; + }; + }; + + 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 DW_LNE_%s", + i, dwarf_line_extended_opcode_string (extended)); + if (!checked_read_uleb128 (&sub_ctx, &operand, op_where, buf)) + goto skip; + } + + if (first_file) + { + if (!use_file (files, 1, op_where, + "initial value of `file' register: ")) + success = false; + 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[i].used) + wr_message (where, + mc_impact_3 | mc_acc_bloat | mc_line | mc_header) + << "the include #" << i + 1 + << " `" << include_directories[i].name + << "' is not used." << std::endl; + + // We can't do full analysis unless we know which DIEs refer to + // files. + if (_m_info != NULL) + { + bool useful = false; + + for (size_t i = 0; i < files.size (); ++i) + if (!files[i].used) + wr_message (where, + mc_impact_3 | mc_acc_bloat | mc_line | mc_header) + << "the file #" << i + 1 + << " `" << files[i].name << "' is not used." << std::endl; + else + useful = true; + + if (!seen_opcode && !useful) + wr_message (where, mc_line | mc_acc_bloat | mc_impact_3) + << "empty line number program and no references from .debug_info." + << std::endl; + } + + if (!terminated && seen_opcode) + wr_error (where) + << "sequence of opcodes not terminated with DW_LNE_end_sequence." + << std::endl; + else if (sub_ctx.ptr != sub_ctx.end) + { + uint64_t off_start, off_end; + if (read_check_zero_padding (&sub_ctx, &off_start, &off_end)) + wr_message_padding_0 + (mc_line, section_locus (sec_line), off_start, off_end); + else + wr_message_padding_n0 (mc_line, section_locus (sec_line), + off_start, sub_ctx.end - sub_ctx.begin); + } + } + + next: + if (!read_ctx_skip (&ctx, size)) + goto not_enough; + } + + if (success) + relocation_skip_rest (&_m_sec->sect.rel, section_locus (_m_sec->sect.id)); + else + throw check_base::failed (); +} + +bool +check_debug_line::has_line_table (Dwarf_Off off) const +{ + return _m_line_tables.find (off) != _m_line_tables.end (); +} diff --git a/dwarflint/check_debug_line.hh b/dwarflint/check_debug_line.hh new file mode 100644 index 00000000..41fde291 --- /dev/null +++ b/dwarflint/check_debug_line.hh @@ -0,0 +1,44 @@ +/* Low-level checking of .debug_line + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_CHECK_DEBUG_LINE_HH +#define DWARFLINT_CHECK_DEBUG_LINE_HH + +#include "check_debug_info_i.hh" +#include "sections_i.hh" +#include "checks.hh" + +#include "../libdw/libdw.h" +#include <set> + +class check_debug_line + : public check<check_debug_line> +{ + section<sec_line> *_m_sec; + check_debug_info *_m_info; + std::set<Dwarf_Off> _m_line_tables; + +public: + static checkdescriptor const *descriptor (); + check_debug_line (checkstack &stack, dwarflint &lint); + + std::set<Dwarf_Off> const &line_tables () const { return _m_line_tables; } + + bool has_line_table (Dwarf_Off off) const; +}; + +#endif//DWARFLINT_CHECK_DEBUG_LINE_HH diff --git a/dwarflint/check_debug_line_i.hh b/dwarflint/check_debug_line_i.hh new file mode 100644 index 00000000..5d9a1ff7 --- /dev/null +++ b/dwarflint/check_debug_line_i.hh @@ -0,0 +1 @@ +class check_debug_line; diff --git a/dwarflint/check_debug_loc_range.cc b/dwarflint/check_debug_loc_range.cc new file mode 100644 index 00000000..5f286226 --- /dev/null +++ b/dwarflint/check_debug_loc_range.cc @@ -0,0 +1,1041 @@ +/* Routines related to .debug_loc and .debug_range. + + 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 <http://www.gnu.org/licenses/>. */ + +// xxx drop as soon as not necessary +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <cassert> +#include <sstream> +#include <algorithm> + +#include "../libdw/c++/dwarf" +#include "../src/dwarf-opcodes.h" + +#include "elf_file.hh" +#include "check_debug_loc_range.hh" +#include "check_debug_info.hh" +#include "sections.hh" +#include "checked_read.hh" +#include "pri.hh" +#include "misc.hh" + +bool do_range_coverage = false; // currently no option + +global_opt<void_option> + opt_show_refs("\ +When validating .debug_loc and .debug_ranges, display information about \ +the DIE referring to the entry in consideration", "ref"); + +std::string +loc_range_locus::format (bool brief) const +{ + std::stringstream ss; + if (!brief) + ss << section_name[_m_sec] << ": "; + + if (_m_sec == sec_loc) + ss << "loclist"; + else + ss << "rangelist"; + + if (_m_offset != (Dwarf_Off)-1) + ss << " 0x" << std::hex << _m_offset; + + if (opt_show_refs) + ss << ", ref. by " << _m_refby.format (true); + + return ss.str (); +} + +checkdescriptor const * +check_debug_ranges::descriptor () +{ + static checkdescriptor cd + (checkdescriptor::create ("check_debug_ranges") + .groups ("@low") + .schedule (false) + .description ( +"Checks for low-level structure of .debug_ranges. In addition it " +"checks:\n" +" - for overlapping and dangling references from .debug_info\n" +" - that base address is set and that it actually changes the address\n" +" - that ranges have a positive size\n" +" - that there are no unreferenced holes in the section\n" +" - that relocations are valid. In ET_REL files that certain fields " +"are relocated\n" +" - neither or both of range start and range end are expected to be " +"relocated. It's expected that they are both relocated against the " +"same section.\n")); + return &cd; +} + +static reg<check_debug_ranges> reg_debug_ranges; + +checkdescriptor const * +check_debug_loc::descriptor () +{ + static checkdescriptor cd + (checkdescriptor::create ("check_debug_loc") + .groups ("@low") + .schedule (false) + .description ( +"Checks for low-level structure of .debug_loc. In addition it " +"makes the same checks as .debug_ranges. For location expressions " +"it further checks:\n" +" - that DW_OP_bra and DW_OP_skip argument is non-zero and doesn't " +"escape the expression. In addition it is required that the jump " +"ends on another instruction, not arbitrarily in the middle of the " +"byte stream, even if that position happened to be interpretable as " +"another well-defined instruction stream.\n" +" - on 32-bit machines it rejects DW_OP_const8u and DW_OP_const8s\n" +" - on 32-bit machines it checks that ULEB128-encoded arguments aren't " +"quantities that don't fit into 32 bits\n")); + return &cd; +} + +static reg<check_debug_loc> reg_debug_loc; + +namespace +{ + bool + coverage_map_init (struct coverage_map *coverage_map, + struct elf_file *elf, + Elf64_Xword mask, + Elf64_Xword warn_mask, + bool allow_overlap) + { + assert (coverage_map != NULL); + assert (elf != NULL); + + coverage_map->elf = elf; + coverage_map->allow_overlap = allow_overlap; + + for (size_t i = 1; i < elf->size; ++i) + { + struct sec *sec = elf->sec + i; + + bool normal = (sec->shdr.sh_flags & mask) == mask; + bool warn = (sec->shdr.sh_flags & warn_mask) == warn_mask; + if (normal || warn) + coverage_map->scos + .push_back (section_coverage (sec, !normal)); + } + + return true; + } + + struct coverage_map * + coverage_map_alloc_XA (struct elf_file *elf, bool allow_overlap) + { + coverage_map *ret = new coverage_map (); + if (!coverage_map_init (ret, elf, + SHF_EXECINSTR | SHF_ALLOC, + SHF_ALLOC, + allow_overlap)) + { + delete ret; + return NULL; + } + return ret; + } + + struct hole_env + { + locus const &loc; + uint64_t address; + uint64_t end; + }; + + bool + range_hole (uint64_t h_start, uint64_t h_length, void *xenv) + { + hole_env *env = (hole_env *)xenv; + char buf[128], buf2[128]; + assert (h_length != 0); + wr_error (&env->loc, + ": portion %s of the range %s " + "doesn't fall into any ALLOC section.\n", + range_fmt (buf, sizeof buf, + h_start + env->address, h_start + env->address + h_length), + range_fmt (buf2, sizeof buf2, env->address, env->end)); + return true; + } + + struct coverage_map_hole_info + { + struct elf_file *elf; + struct hole_info info; + }; + + /* begin is inclusive, end is exclusive. */ + bool + coverage_map_found_hole (uint64_t begin, uint64_t end, + struct section_coverage *sco, void *user) + { + struct coverage_map_hole_info *info = (struct coverage_map_hole_info *)user; + + const char *scnname = sco->sec->name; + + struct sec *sec = sco->sec; + GElf_Xword align = sec->shdr.sh_addralign; + + /* We don't expect some sections to be covered. But if they + are at least partially covered, we expect the same + coverage criteria as for .text. */ + if (!sco->hit + && ((sco->sec->shdr.sh_flags & SHF_EXECINSTR) == 0 + || strcmp (scnname, ".init") == 0 + || strcmp (scnname, ".fini") == 0 + || strcmp (scnname, ".plt") == 0)) + return true; + + /* For REL files, don't print addresses mangled by our layout. */ + uint64_t base = info->elf->ehdr.e_type == ET_REL ? 0 : sco->sec->shdr.sh_addr; + + /* If the hole is filled with NUL bytes, don't report it. But if we + get stripped debuginfo file, the data may not be available. In + that case don't report the hole, if it seems to be alignment + padding. */ + if (sco->sec->data->d_buf != NULL) + { + bool zeroes = true; + for (uint64_t j = begin; j < end; ++j) + if (((char *)sco->sec->data->d_buf)[j] != 0) + { + zeroes = false; + break; + } + if (zeroes) + return true; + } + else if (necessary_alignment (base + begin, end - begin, align)) + return true; + + char buf[128]; + wr_message (section_locus (info->info.section), + info->info.category | mc_acc_suboptimal | mc_impact_4) + << "addresses " << range_fmt (buf, sizeof buf, begin + base, end + base) + << " of section " << scnname << " are not covered.\n"; + return true; + } + + struct wrap_cb_arg + { + bool (*cb) (uint64_t begin, uint64_t end, + struct section_coverage *, void *); + section_coverage *sco; + void *user; + }; + + bool + unwrap_cb (uint64_t h_start, uint64_t h_length, void *user) + { + wrap_cb_arg *arg = (wrap_cb_arg *)user; + return (arg->cb) (h_start, h_start + h_length, arg->sco, arg->user); + } + + bool + coverage_map_find_holes (struct coverage_map *coverage_map, + bool (*cb) (uint64_t begin, uint64_t end, + struct section_coverage *, void *), + void *user) + { + for (size_t i = 0; i < coverage_map->size; ++i) + { + section_coverage *sco = &coverage_map->scos[i]; + wrap_cb_arg arg = {cb, sco, user}; + if (!sco->cov.find_holes (0, sco->sec->shdr.sh_size, unwrap_cb, &arg)) + return false; + } + + return true; + } + + void + coverage_map_add (struct coverage_map *coverage_map, + uint64_t address, + uint64_t length, + locus const &loc, + enum message_category cat) + { + bool found = false; + bool crosses_boundary = false; + bool overlap = false; + uint64_t end = address + length; + char buf[128]; // for messages + + /* This is for analyzing how much of the current range falls into + sections in coverage map. Whatever is left uncovered doesn't + fall anywhere and is reported. */ + coverage range_cov; + + for (size_t i = 0; i < coverage_map->size; ++i) + { + struct section_coverage *sco = &coverage_map->scos[i]; + GElf_Shdr *shdr = &sco->sec->shdr; + struct coverage *cov = &sco->cov; + + Elf64_Addr s_end = shdr->sh_addr + shdr->sh_size; + if (end <= shdr->sh_addr || address >= s_end) + /* no overlap */ + continue; + + if (found && !crosses_boundary) + { + /* While probably not an error, it's very suspicious. */ + wr_message (cat | mc_impact_2, &loc, + ": the range %s crosses section boundaries.\n", + range_fmt (buf, sizeof buf, address, end)); + crosses_boundary = true; + } + + found = true; + + if (length == 0) + /* Empty range. That means no actual coverage, and we can + also be sure that there are no more sections that this one + falls into. */ + break; + + uint64_t cov_begin + = address < shdr->sh_addr ? 0 : address - shdr->sh_addr; + uint64_t cov_end + = end < s_end ? end - shdr->sh_addr : shdr->sh_size; + assert (cov_begin < cov_end); + + uint64_t r_delta = shdr->sh_addr - address; + uint64_t r_cov_begin = cov_begin + r_delta; + uint64_t r_cov_end = cov_end + r_delta; + + if (!overlap && !coverage_map->allow_overlap + && cov->is_overlap (cov_begin, cov_end - cov_begin)) + { + /* Not a show stopper, this shouldn't derail high-level. */ + wr_message (loc, cat | mc_aranges | mc_impact_2 | mc_error) + << "the range " << range_fmt (buf, sizeof buf, address, end) + << " overlaps with another one." << std::endl; + overlap = true; + } + + if (sco->warn) + wr_message (cat | mc_impact_2, &loc, + ": the range %s covers section %s.\n", + range_fmt (buf, sizeof buf, address, end), sco->sec->name); + + /* Section coverage... */ + cov->add (cov_begin, cov_end - cov_begin); + sco->hit = true; + + /* And range coverage... */ + range_cov.add (r_cov_begin, r_cov_end - r_cov_begin); + } + + if (!found) + /* Not a show stopper. */ + wr_error (&loc, + ": couldn't find a section that the range %s covers.\n", + range_fmt (buf, sizeof buf, address, end)); + else if (length > 0) + { + hole_env env = {loc, address, end}; + range_cov.find_holes (0, length, range_hole, &env); + } + } + + bool + check_loc_or_range_ref (dwarf_version const *ver, + struct elf_file *file, + const struct read_ctx *parent_ctx, + struct cu *cu, + struct sec *sec, + struct coverage *coverage, + struct coverage_map *coverage_map, + struct coverage *pc_coverage, + uint64_t addr, + locus const &loc, + enum message_category cat) + { + char buf[128]; // messages + + assert (sec->id == sec_loc || sec->id == sec_ranges); + assert (cat == mc_loc || cat == mc_ranges); + assert ((sec->id == sec_loc) == (cat == mc_loc)); + assert (coverage != NULL); + + struct read_ctx ctx; + read_ctx_init (&ctx, parent_ctx->data, file->other_byte_order); + if (!read_ctx_skip (&ctx, addr)) + { + wr_error (&loc, ": invalid reference outside the section " + "%#" PRIx64 ", size only %#tx.\n", + addr, ctx.end - ctx.begin); + return false; + } + + bool retval = true; + bool contains_locations = sec->id == sec_loc; + + if (coverage->is_covered (addr, 1)) + { + wr_error (&loc, ": reference to %#" PRIx64 + " points into another location or range list.\n", addr); + retval = false; + } + + uint64_t escape = cu->head->address_size == 8 + ? (uint64_t)-1 : (uint64_t)(uint32_t)-1; + + bool overlap = false; + uint64_t base = cu->low_pc; + while (!read_ctx_eof (&ctx)) + { + uint64_t offset = read_ctx_get_offset (&ctx); + loc_range_locus where (sec->id, loc, offset); + +#define HAVE_OVERLAP \ + do { \ + wr_error (&where, ": range definitions overlap.\n"); \ + retval = false; \ + overlap = true; \ + } while (0) + + /* begin address */ + uint64_t begin_addr; + uint64_t begin_off = read_ctx_get_offset (&ctx); + GElf_Sym begin_symbol_mem, *begin_symbol = &begin_symbol_mem; + bool begin_relocated = false; + if (!overlap + && coverage->is_overlap (begin_off, cu->head->address_size)) + HAVE_OVERLAP; + + if (!read_ctx_read_offset (&ctx, cu->head->address_size == 8, &begin_addr)) + { + wr_error (&where, ": can't read address range beginning.\n"); + return false; + } + + struct relocation *rel; + if ((rel = relocation_next (&sec->rel, begin_off, + where, skip_mismatched))) + { + begin_relocated = true; + relocate_one (file, &sec->rel, rel, cu->head->address_size, + &begin_addr, where, rel_target::rel_value, + &begin_symbol); + } + + /* end address */ + uint64_t end_addr; + uint64_t end_off = read_ctx_get_offset (&ctx); + GElf_Sym end_symbol_mem, *end_symbol = &end_symbol_mem; + bool end_relocated = false; + if (!overlap + && coverage->is_overlap (end_off, cu->head->address_size)) + HAVE_OVERLAP; + + if (!read_ctx_read_offset (&ctx, cu->head->address_size == 8, + &end_addr)) + { + wr_error (&where, ": can't read address range ending.\n"); + return false; + } + + if ((rel = relocation_next (&sec->rel, end_off, + where, skip_mismatched))) + { + end_relocated = true; + relocate_one (file, &sec->rel, rel, cu->head->address_size, + &end_addr, where, rel_target::rel_value, &end_symbol); + if (begin_addr != escape) + { + if (!begin_relocated) + wr_message (cat | mc_impact_2 | mc_reloc, &where, + ": end of address range is relocated, but the beginning wasn't.\n"); + else + check_range_relocations (where, cat, file, + begin_symbol, end_symbol, + "begin and end address"); + } + } + else if (begin_relocated) + wr_message (cat | mc_impact_2 | mc_reloc, &where, + ": end of address range is not relocated, but the beginning was.\n"); + + bool done = false; + if (begin_addr == 0 && end_addr == 0 && !begin_relocated && !end_relocated) + done = true; + else if (begin_addr != escape) + { + if (base == (uint64_t)-1) + { + wr_error (&where, + ": address range with no base address set: %s.\n", + range_fmt (buf, sizeof buf, begin_addr, end_addr)); + /* This is not something that would derail high-level, + so carry on. */ + } + + if (end_addr < begin_addr) + wr_message (cat | mc_error, &where, ": has negative range %s.\n", + range_fmt (buf, sizeof buf, begin_addr, end_addr)); + else if (begin_addr == end_addr) + /* 2.6.6: A location list entry [...] whose beginning + and ending addresses are equal has no effect. */ + wr_message (cat | mc_acc_bloat | mc_impact_3, &where, + ": entry covers no range.\n"); + /* Skip coverage analysis if we have errors or have no base + (or just don't do coverage analysis at all). */ + else if (base < (uint64_t)-2 && retval + && (coverage_map != NULL || pc_coverage != NULL)) + { + uint64_t address = begin_addr + base; + uint64_t length = end_addr - begin_addr; + if (coverage_map != NULL) + coverage_map_add (coverage_map, address, length, where, cat); + if (pc_coverage != NULL) + pc_coverage->add (address, length); + } + + if (contains_locations) + { + /* location expression length */ + uint16_t len; + if (!overlap + && coverage->is_overlap (read_ctx_get_offset (&ctx), 2)) + HAVE_OVERLAP; + + if (!read_ctx_read_2ubyte (&ctx, &len)) + { + wr_error (where) + << "can't read length of location expression." + << std::endl; + return false; + } + + /* location expression itself */ + uint64_t expr_start = read_ctx_get_offset (&ctx); + if (!check_location_expression + (ver, *file, &ctx, cu, expr_start, &sec->rel, len, where)) + return false; + uint64_t expr_end = read_ctx_get_offset (&ctx); + if (!overlap + && coverage->is_overlap (expr_start, expr_end - expr_start)) + HAVE_OVERLAP; + + if (!read_ctx_skip (&ctx, len)) + { + /* "can't happen" */ + wr_error (&where, PRI_NOT_ENOUGH, "location expression"); + return false; + } + } + } + else + { + if (end_addr == base) + wr_message (cat | mc_acc_bloat | mc_impact_3, &where, + ": base address selection doesn't change base address" + " (%#" PRIx64 ").\n", base); + else + base = end_addr; + } +#undef HAVE_OVERLAP + + coverage->add (offset, read_ctx_get_offset (&ctx) - offset); + if (done) + break; + } + + return retval; + } + + struct ref_cu + { + ::ref ref; + ::cu *cu; + + bool + operator < (ref_cu const& other) const + { + return ref.addr < other.ref.addr; + } + }; + + bool + check_loc_or_range_structural (struct elf_file *file, + struct sec *sec, + struct cu *cu_chain, + struct coverage *pc_coverage) + { + assert (sec->id == sec_loc || sec->id == sec_ranges); + assert (cu_chain != NULL); + + struct read_ctx ctx; + read_ctx_init (&ctx, sec->data, file->other_byte_order); + + bool retval = true; + + /* For .debug_ranges, we optionally do ranges vs. ELF sections + coverage analysis. */ + // xxx this is a candidate for a separate check + struct coverage_map *coverage_map = NULL; + if (do_range_coverage && sec->id == sec_ranges + && (coverage_map + = coverage_map_alloc_XA (file, sec->id == sec_loc)) == NULL) + { + wr_error (section_locus (sec->id)) + << "couldn't read ELF, skipping coverage analysis." << std::endl; + retval = false; + } + + /* Overlap discovery. */ + struct coverage coverage; + + enum message_category cat = sec->id == sec_loc ? mc_loc : mc_ranges; + + { + /* Relocation checking in the followings assumes that all the + references are organized in monotonously increasing order. That + doesn't have to be the case. So merge all the references into + one sorted array. */ + typedef std::vector<ref_cu> ref_cu_vect; + ref_cu_vect refs; + for (struct cu *cu = cu_chain; cu != NULL; cu = cu->next) + { + ref_record *rec + = sec->id == sec_loc ? &cu->loc_refs : &cu->range_refs; + for (ref_record::const_iterator it = rec->begin (); + it != rec->end (); ++it) + { + ref_cu ref = {*it, cu}; + refs.push_back (ref); + } + } + std::sort (refs.begin (), refs.end ()); + + uint64_t last_off = 0; + for (ref_cu_vect::const_iterator it = refs.begin (); + it != refs.end (); ++it) + { + uint64_t off = it->ref.addr; + if (it != refs.begin ()) + { + if (off == last_off) + continue; + relocation_skip (&sec->rel, off, section_locus (sec->id), + skip_unref); + } + + // xxx right now this is just so that we can ver->get_form + // down the road, which is just a result of the way + // dwarf-opcodes encode operator operand types. But in the + // future, we'd like versions to support also queries for + // operators and their operands, so keep it. + dwarf_version const *ver + = dwarf_version::get (it->cu->head->version); + + /* XXX We pass cu_coverage down for all ranges. That means all + ranges get recorded, not only those belonging to CUs. + Perhaps that's undesirable. */ + if (!check_loc_or_range_ref (ver, file, &ctx, it->cu, sec, + &coverage, coverage_map, pc_coverage, + off, it->ref.who, cat)) + retval = false; + last_off = off; + } + } + + if (retval) + { + relocation_skip_rest (&sec->rel, section_locus (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 + chain. */ + struct hole_info hi = { + sec->id, cat, ctx.data->d_buf, cu_chain->head->address_size + }; + coverage.find_holes (0, ctx.data->d_size, found_hole, &hi); + + if (coverage_map) + { + struct coverage_map_hole_info cmhi = { + coverage_map->elf, {sec->id, cat, NULL, 0} + }; + coverage_map_find_holes (coverage_map, &coverage_map_found_hole, + &cmhi); + } + } + + delete coverage_map; + + return retval; + } +} + +section_coverage::section_coverage (struct sec *a_sec, bool a_warn) + : sec (a_sec) + , hit (false) + , warn (a_warn) +{ + assert (a_sec); +} + +check_debug_ranges::check_debug_ranges (checkstack &stack, dwarflint &lint) + : _m_sec_ranges (lint.check (stack, _m_sec_ranges)) + , _m_info (lint.check (stack, _m_info)) +{ + memset (&_m_cov, 0, sizeof (_m_cov)); + if (!::check_loc_or_range_structural (&_m_sec_ranges->file, + &_m_sec_ranges->sect, + &_m_info->cus.front (), + &_m_cov)) + throw check_base::failed (); +} + +check_debug_loc::check_debug_loc (checkstack &stack, dwarflint &lint) + : _m_sec_loc (lint.check (stack, _m_sec_loc)) + , _m_info (lint.check (stack, _m_info)) +{ + if (!::check_loc_or_range_structural (&_m_sec_loc->file, + &_m_sec_loc->sect, + &_m_info->cus.front (), + NULL)) + throw check_base::failed (); +} + +namespace +{ + /* Operands are passed back as attribute forms. In particular, + DW_FORM_dataX for X-byte operands, DW_FORM_[us]data for + ULEB128/SLEB128 operands, and DW_FORM_addr/DW_FORM_ref_addr + for 32b/64b operands. + If the opcode takes no operands, 0 is passed. + + Return value is false if we couldn't determine (i.e. invalid + opcode). + */ + + bool + get_location_opcode_operands (dwarf_version const *ver, + uint8_t opcode, + form const **f1p, + form const **f2p) + { + int op1, op2; + switch (opcode) + { +#define DW_OP_2(OPCODE, OP1, OP2) \ + case OPCODE: op1 = OP1; op2 = OP2; break; +#define DW_OP_1(OPCODE, OP1) DW_OP_2(OPCODE, OP1, 0) +#define DW_OP_0(OPCODE) DW_OP_2(OPCODE, 0, 0) + + DW_OP_OPERANDS + +#undef DEF_DW_OP_2 +#undef DEF_DW_OP_1 +#undef DEF_DW_OP_0 + default: + return false; + }; + +#define RETV(OP,P) \ + if (OP != 0) \ + { \ + form const *f = NULL; \ + f = ver->get_form (OP); \ + if (f == NULL) \ + return false; \ + *P = f; \ + } \ + else \ + *P = NULL; + + RETV (op1, f1p); + RETV (op2, f2p); + return true; + } + + static rel_target + reloc_target_loc (uint8_t opcode) + { + switch (opcode) + { + case DW_OP_call2: + case DW_OP_call4: + return sec_info; + + case DW_OP_addr: + return rel_target::rel_address; + + case DW_OP_call_ref: + assert (!"Can't handle call_ref!"); + }; + + std::cout << "XXX don't know how to handle opcode=" + << elfutils::dwarf::ops::name (opcode) << std::endl; + + return rel_target::rel_value; + } + + bool + op_read_form (struct elf_file const &file, + struct read_ctx *ctx, + struct cu *cu, + uint64_t init_off, + struct relocation_data *reloc, + int opcode, + form const *form, + uint64_t *valuep, + char const *str, + locus const &where) + { + if (form == NULL) + return true; + + uint64_t off = read_ctx_get_offset (ctx) + init_off; + + storage_class_t storclass = form->storage_class (); + assert (storclass != sc_string); + if (!read_generic_value (ctx, form->width (cu->head), storclass, + where, valuep, NULL)) + { + wr_error (where) + << "opcode \"" << elfutils::dwarf::ops::name (opcode) + << "\": can't read " << str << " (form \"" + << *form << "\")." << std::endl; + return false; + } + + /* For non-block forms, allow relocation of the datum. For block + form, allow relocation of block contents, but not the + block length). */ + + struct relocation *rel; + if ((rel = relocation_next (reloc, off, + where, skip_mismatched))) + { + if (storclass != sc_block) + relocate_one (&file, reloc, rel, + cu->head->address_size, valuep, where, + reloc_target_loc (opcode), NULL); + else + wr_error (where) << "relocation relocates a length field.\n"; + } + if (storclass == sc_block) + { + uint64_t off_block_end = read_ctx_get_offset (ctx) + init_off - 1; + relocation_next (reloc, off_block_end, where, skip_ok); + } + + return true; + } +} + +class locexpr_locus + : public locus +{ + uint64_t _m_offset; + locus const *_m_context; + +public: + explicit locexpr_locus (uint64_t offset, locus const *context) + : _m_offset (offset) + , _m_context (context) + {} + + std::string + format (bool) const + { + std::stringstream ss; + ss << _m_context + << " (location expression offset 0x" << std::hex << _m_offset << ")"; + return ss.str (); + } +}; + +bool +check_location_expression (dwarf_version const *ver, + elf_file const &file, + struct read_ctx *parent_ctx, + struct cu *cu, + uint64_t init_off, + struct relocation_data *reloc, + size_t length, + locus const &loc) +{ + struct read_ctx ctx; + if (!read_ctx_init_sub (&ctx, parent_ctx, parent_ctx->ptr, + parent_ctx->ptr + length)) + { + wr_error (&loc, PRI_NOT_ENOUGH, "location expression"); + return false; + } + + typedef ref_T<locexpr_locus> locexpr_ref; + typedef ref_record_T<locexpr_locus> locexpr_ref_record; + locexpr_ref_record oprefs; + addr_record opaddrs; + + while (!read_ctx_eof (&ctx)) + { + uint64_t opcode_off = read_ctx_get_offset (&ctx) + init_off; + locexpr_locus where (opcode_off, &loc); + opaddrs.add (opcode_off); + + uint8_t opcode; + if (!read_ctx_read_ubyte (&ctx, &opcode)) + { + wr_error (&where, ": can't read opcode.\n"); + break; + } + + form const *form1 = NULL; + form const *form2 = NULL; + if (!get_location_opcode_operands (ver, opcode, &form1, &form2)) + { + wr_error (where) + << "can't decode opcode \"" + << elfutils::dwarf::ops::name (opcode) << "\"." << std::endl; + break; + } + + uint64_t value1, value2; + if (!op_read_form (file, &ctx, cu, init_off, reloc, + opcode, form1, &value1, "1st operand", where) + || !op_read_form (file, &ctx, cu, init_off, reloc, + opcode, form2, &value2, "2st operand", where)) + goto out; + + switch (opcode) + { + case DW_OP_bra: + case DW_OP_skip: + { + int16_t skip = (uint16_t)value1; + + if (skip == 0) + wr_message (where, mc_loc | mc_acc_bloat | mc_impact_3) + << elfutils::dwarf::ops::name (opcode) + << " with skip 0." << std::endl; + else if (skip > 0 && !read_ctx_need_data (&ctx, (size_t)skip)) + wr_error (where) + << elfutils::dwarf::ops::name (opcode) + << " branches out of location expression." << std::endl; + /* Compare with the offset after the two-byte skip value. */ + else if (skip < 0 && ((uint64_t)-skip) > read_ctx_get_offset (&ctx)) + wr_error (where) + << elfutils::dwarf::ops::name (opcode) + << " branches before the beginning of location expression." + << std::endl; + else + { + uint64_t off_after = read_ctx_get_offset (&ctx) + init_off; + oprefs.push_back (locexpr_ref (off_after + skip, where)); + } + + break; + } + + case DW_OP_const8u: + case DW_OP_const8s: + if (cu->head->address_size == 4) + wr_error (where) + << elfutils::dwarf::ops::name (opcode) << " on 32-bit machine." + << std::endl; + break; + + default: + if (cu->head->address_size == 4 + && (opcode == DW_OP_constu + || opcode == DW_OP_consts + || opcode == DW_OP_deref_size + || opcode == DW_OP_plus_uconst) + && (value1 > (uint64_t)(uint32_t)-1)) + wr_message (where, mc_loc | mc_acc_bloat | mc_impact_3) + << elfutils::dwarf::ops::name (opcode) + << " with operand " << pri::hex (value1) + << " on a 32-bit machine." << std::endl; + } + } + + out: + for (locexpr_ref_record::const_iterator it = oprefs.begin (); + it != oprefs.end (); ++it) + if (!opaddrs.has_addr (it->addr)) + wr_error (it->who) << "unresolved reference to opcode at " + << pri::hex (it->addr) << ".\n"; + + return true; +} + +bool +found_hole (uint64_t start, uint64_t length, void *data) +{ + struct hole_info *info = (struct hole_info *)data; + bool all_zeroes = true; + for (uint64_t i = start; i < start + length; ++i) + if (((char*)info->data)[i] != 0) + { + all_zeroes = false; + break; + } + + uint64_t end = start + length; + if (all_zeroes) + { + /* Zero padding is valid, if it aligns on the bounds of + info->align bytes, and is not excessive. */ + if (info->align == 0 || info->align == 1 + || length > info->align // excessive + || end % info->align != 0 // doesn't actually align + || start % info->align == 0)// was already aligned + wr_message_padding_0 (info->category, section_locus (info->section), + start, end); + } + else + /* XXX: This actually lies when the unreferenced portion is + composed of sequences of zeroes and non-zeroes. */ + wr_message_padding_n0 (info->category, section_locus (info->section), + start, end); + + return true; +} + +void +check_range_relocations (locus const &loc, + enum message_category cat, + struct elf_file const *file, + GElf_Sym *begin_symbol, + GElf_Sym *end_symbol, + const char *description) +{ + if (begin_symbol != NULL + && end_symbol != NULL + && begin_symbol->st_shndx != end_symbol->st_shndx) + wr_message (cat | mc_impact_2 | mc_reloc, &loc, + ": %s relocated against different sections (%s and %s).\n", + description, + file->sec[begin_symbol->st_shndx].name, + file->sec[end_symbol->st_shndx].name); +} diff --git a/dwarflint/check_debug_loc_range.hh b/dwarflint/check_debug_loc_range.hh new file mode 100644 index 00000000..4fbc6f70 --- /dev/null +++ b/dwarflint/check_debug_loc_range.hh @@ -0,0 +1,112 @@ +/* Low-level checking of .debug_loc and .debug_range. + 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 <http://www.gnu.org/licenses/>. */ + +#include "checks.hh" +#include "sections_i.hh" +#include "check_debug_info_i.hh" +#include "messages.hh" +#include "coverage.hh" +#include "dwarf_version_i.hh" + +class loc_range_locus + : public locus +{ + locus const &_m_refby; + Dwarf_Off _m_offset; + section_id _m_sec; + +public: + loc_range_locus (section_id sec, locus const &refby, Dwarf_Off offset = -1) + : _m_refby (refby) + , _m_offset (offset) + , _m_sec ((assert (sec == sec_loc || sec == sec_ranges), sec)) + {} + + std::string format (bool brief) const; +}; + +struct section_coverage +{ + struct sec *sec; + struct coverage cov; + bool hit; /* true if COV is not pristine. */ + bool warn; /* dwarflint should emit a warning if a coverage appears + in this section */ + section_coverage (struct sec *a_sec, bool a_warn); +}; + +struct coverage_map +{ + struct elf_file *elf; + std::vector<section_coverage> scos; + size_t size; + size_t alloc; + bool allow_overlap; +}; + +class check_debug_ranges + : public check<check_debug_ranges> +{ + section<sec_ranges> *_m_sec_ranges; + check_debug_info *_m_info; + coverage _m_cov; + +public: + static checkdescriptor const *descriptor (); + + coverage const &cov () const { return _m_cov; } + check_debug_ranges (checkstack &stack, dwarflint &lint); +}; + +class check_debug_loc + : public check<check_debug_loc> +{ + section<sec_loc> *_m_sec_loc; + check_debug_info *_m_info; + +public: + static checkdescriptor const *descriptor (); + check_debug_loc (checkstack &stack, dwarflint &lint); +}; + +struct hole_info +{ + enum section_id section; + enum message_category category; + void *data; + unsigned align; +}; + +/* DATA has to be a pointer to an instance of struct hole_info. + DATA->data has to point at d_buf of section in question. */ +bool found_hole (uint64_t start, uint64_t length, void *data); + +bool check_location_expression (dwarf_version const *ver, + elf_file const &file, + struct read_ctx *parent_ctx, + struct cu *cu, + uint64_t init_off, + struct relocation_data *reloc, + size_t length, + locus const &loc); + +void check_range_relocations (locus const &loc, + enum message_category cat, + struct elf_file const *file, + GElf_Sym *begin_symbol, + GElf_Sym *end_symbol, + const char *description); diff --git a/dwarflint/check_debug_loc_range_i.hh b/dwarflint/check_debug_loc_range_i.hh new file mode 100644 index 00000000..a5926e12 --- /dev/null +++ b/dwarflint/check_debug_loc_range_i.hh @@ -0,0 +1,3 @@ +class check_debug_ranges; +class check_debug_loc; +struct hole_info; diff --git a/dwarflint/check_debug_pub.cc b/dwarflint/check_debug_pub.cc new file mode 100644 index 00000000..8ac39283 --- /dev/null +++ b/dwarflint/check_debug_pub.cc @@ -0,0 +1,260 @@ +/* Low-level checking of .debug_pub*. + 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "check_debug_pub.hh" +#include "check_debug_info.hh" +#include "sections.hh" +#include "pri.hh" +#include "checked_read.hh" +#include "messages.hh" +#include "misc.hh" + +namespace +{ + bool check_pub_structural (elf_file const &file, sec §, + check_debug_info *cus); +} + +template<section_id sec_id> +check_debug_pub<sec_id>::check_debug_pub (checkstack &stack, dwarflint &lint) + : _m_sec (lint.check (stack, _m_sec)) + , _m_file (_m_sec->file) + , _m_cus (lint.toplev_check (stack, _m_cus)) +{ + check_pub_structural (_m_file, _m_sec->sect, _m_cus); +} + + +checkdescriptor const * +check_debug_pubnames::descriptor () +{ + static checkdescriptor cd + (checkdescriptor::create ("check_debug_pubnames") + .groups ("@low") + .schedule (false) + .description ( +"Checks for low-level structure of .debug_pubnames. In addition it " +"checks:\n" +" - for garbage inside padding\n" +" - that relocations are valid. In ET_REL files that certain fields " +"are relocated\n" +"Furthermore, if .debug_info is valid, it is checked:\n" +" - that references point to actual CUs and DIEs\n" +" - that there's only one pub section per CU\n")); + return &cd; +} + +template check_debug_pub<sec_pubnames>::check_debug_pub (checkstack &stack, + dwarflint &lint); + +static reg<check_debug_pubnames> reg_debug_pubnames; + +checkdescriptor const * +check_debug_pubtypes::descriptor () +{ + static checkdescriptor cd + (checkdescriptor::create ("check_debug_pubtypes") + .groups ("@low") + .schedule (false) + .description ( +"Checks for low-level structure of .debug_pubtypes. In addition it " +"makes the same checks as check_debug_pubnames.\n")); + return &cd; +} + +template check_debug_pub<sec_pubtypes>::check_debug_pub (checkstack &stack, + dwarflint &lint); + +static reg<check_debug_pubtypes> reg_debug_pubtypes; + +namespace +{ + bool + check_pub_structural (elf_file const &file, sec §, + check_debug_info *cus) + { + struct read_ctx ctx; + read_ctx_init (&ctx, sect.data, file.other_byte_order); + bool retval = true; + + while (!read_ctx_eof (&ctx)) + { + enum section_id sid = sect.id; + section_locus where (sid, read_ctx_get_offset (&ctx)); + 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)) + goto not_enough; + sub_ctx.ptr = ctx.ptr; + + /* Version. */ + uint16_t version; + if (!read_ctx_read_2ubyte (&sub_ctx, &version)) + { + wr_error (&where, ": can't read set version.\n"); + retval = false; + goto next; + } + if (!supported_version (version, 1, where, 2)) + { + retval = false; + goto next; + } + + /* CU offset. */ + uint64_t cu_offset; /* Offset of related CU. */ + { + 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 (§.rel, ctx_offset, + where, skip_mismatched))) + relocate_one (&file, §.rel, rel, offset_size, + &cu_offset, where, sec_info, NULL); + else if (file.ehdr.e_type == ET_REL) + wr_message (mc_impact_2 | mc_pubtables | mc_reloc | mc_header, &where, + PRI_LACK_RELOCATION, "debug info offset"); + } + + struct cu *cu = NULL; + if (cus != NULL && (cu = cus->find_cu (cu_offset)) == NULL) + wr_error (where) + << "unresolved reference to " << pri::CU (cu_offset) + << '.' << std::endl; + // xxx this can be checked even without CU + if (cu != NULL) + { + //where.ref = &cu->head->where; + bool *has = sect.id == sec_pubnames + ? &cu->has_pubnames : &cu->has_pubtypes; + if (*has) + wr_message (mc_impact_2 | mc_pubtables | mc_header, &where, + ": there has already been section for this CU.\n"); + else + *has = true; + } + + /* Covered length. */ + uint64_t cu_len; + if (!read_ctx_read_offset (&sub_ctx, offset_size == 8, &cu_len)) + { + wr_error (&where, ": can't read covered length.\n"); + retval = false; + goto next; + } + if (cu != NULL && cu_len != cu->head->total_size) + { + wr_error (where) + << "the table covers length " << cu_len << " but CU has length " + << cu->head->total_size << '.' << std::endl; + retval = false; + goto next; + } + + /* Records... */ + while (!read_ctx_eof (&sub_ctx)) + { + section_locus rec_where (sect.id, sub_ctx.ptr - ctx.begin); + + uint64_t offset; + if (!read_ctx_read_offset (&sub_ctx, offset_size == 8, &offset)) + { + wr_error (&rec_where, ": can't read offset field.\n"); + retval = false; + goto next; + } + if (offset == 0) + break; + + if (cu != NULL + && !cu->die_addrs.has_addr (offset + cu->head->offset)) + { + wr_error (rec_where) + << "unresolved reference to " << pri::DIE (offset) + << '.' << std::endl; + retval = false; + goto next; + } + + // xxx read_ctx_read_str??? + uint8_t c; + do + if (!read_ctx_read_ubyte (&sub_ctx, &c)) + { + wr_error (&rec_where, ": can't read symbol name.\n"); + retval = false; + goto next; + } + while (c); + } + + if (sub_ctx.ptr != sub_ctx.end) + { + uint64_t off_start, off_end; + if (read_check_zero_padding (&sub_ctx, &off_start, &off_end)) + wr_message_padding_0 (mc_pubtables, section_locus (sect.id), + off_start, off_end); + else + { + wr_message_padding_n0 (mc_pubtables | mc_error, + section_locus (sect.id), + off_start, off_start + size); + retval = false; + } + } + } + + next: + if (read_ctx_skip (&ctx, size)) + continue; + + not_enough: + wr_error (&where, PRI_NOT_ENOUGH, "next set"); + return false; + } + + if (retval) + relocation_skip_rest (§.rel, section_locus (sect.id)); + + return retval; + } +} diff --git a/dwarflint/check_debug_pub.hh b/dwarflint/check_debug_pub.hh new file mode 100644 index 00000000..5ee6bc6d --- /dev/null +++ b/dwarflint/check_debug_pub.hh @@ -0,0 +1,61 @@ +/* Low-level checking of .debug_pub*. + Copyright (C) 2009 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_CHECK_DEBUG_PUB_HH +#define DWARFLINT_CHECK_DEBUG_PUB_HH + +#include "sections_i.hh" +#include "check_debug_info_i.hh" +#include "checks.hh" +#include "elf_file_i.hh" + +template<section_id sec_id> +class check_debug_pub + : public check<check_debug_pub<sec_id> > +{ +protected: + typedef section<sec_id> section_t; + section_t *_m_sec; + elf_file const &_m_file; + check_debug_info *_m_cus; + +public: + // instantiated in .cc for each subclass + check_debug_pub (checkstack &stack, dwarflint &lint); +}; + +struct check_debug_pubnames + : public check_debug_pub<sec_pubnames> +{ + static checkdescriptor const *descriptor (); + + check_debug_pubnames (checkstack &stack, dwarflint &lint) + : check_debug_pub<sec_pubnames> (stack, lint) + {} +}; + +struct check_debug_pubtypes + : public check_debug_pub<sec_pubtypes> +{ + static checkdescriptor const *descriptor (); + + check_debug_pubtypes (checkstack &stack, dwarflint &lint) + : check_debug_pub<sec_pubtypes> (stack, lint) + {} +}; + +#endif//DWARFLINT_CHECK_DEBUG_PUB_HH diff --git a/dwarflint/check_die_decl_call.cc b/dwarflint/check_die_decl_call.cc new file mode 100644 index 00000000..079d99e0 --- /dev/null +++ b/dwarflint/check_die_decl_call.cc @@ -0,0 +1,105 @@ +/* Check that decl or call file, line, column come in pairs. + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#include "check_die_tree.hh" +#include "pri.hh" +#include "messages.hh" + +using elfutils::dwarf; + +namespace +{ + class check_die_decl_call + : public die_check + { + public: + static checkdescriptor const *descriptor () + { + static checkdescriptor cd + (checkdescriptor::create ("check_die_decl_call") + .description ("Check that each decl or call attribute come in" + " file/line and column/line pairs.\n")); + return &cd; + } + + check_die_decl_call (highlevel_check_i *, checkstack &, dwarflint &) + { + // No state stored for this check. + } + + virtual void + die (all_dies_iterator<dwarf> const &it) + { + dwarf::debug_info_entry const &entry = *it; + dwarf::debug_info_entry::attributes_type attrs = entry.attributes (); + + // Make sure decl column and line, and file and line are paired. + dwarf::debug_info_entry::attributes_type::const_iterator + decl_column = attrs.find (DW_AT_decl_column); + dwarf::debug_info_entry::attributes_type::const_iterator + decl_line = attrs.find (DW_AT_decl_line); + dwarf::debug_info_entry::attributes_type::const_iterator + decl_file = attrs.find (DW_AT_decl_file); + + if (decl_column != attrs.end () && decl_line == attrs.end ()) + wr_message (die_locus (entry), mc_impact_3 | mc_acc_suboptimal) + .id (descriptor ()) + << elfutils::dwarf::tags::name (entry.tag ()) + << " has decl_column, but NOT decl_line" << std::endl; + + if (decl_line != attrs.end () && decl_file == attrs.end ()) + wr_message (die_locus (entry), mc_impact_3 | mc_acc_suboptimal) + .id (descriptor ()) + << elfutils::dwarf::tags::name (entry.tag ()) + << " has decl_line, but NOT decl_file" << std::endl; + + if (decl_file != attrs.end () && decl_line == attrs.end ()) + wr_message (die_locus (entry), mc_impact_3 | mc_acc_suboptimal) + .id (descriptor ()) + << elfutils::dwarf::tags::name (entry.tag ()) + << " has decl_file, but NOT decl_line" << std::endl; + + // Same for call variants. + dwarf::debug_info_entry::attributes_type::const_iterator + call_column = attrs.find (DW_AT_call_column); + dwarf::debug_info_entry::attributes_type::const_iterator + call_line = attrs.find (DW_AT_call_line); + dwarf::debug_info_entry::attributes_type::const_iterator + call_file = attrs.find (DW_AT_call_file); + + if (call_column != attrs.end () && call_line == attrs.end ()) + wr_message (die_locus (entry), mc_impact_3 | mc_acc_suboptimal) + .id (descriptor ()) + << elfutils::dwarf::tags::name (entry.tag ()) + << " has call_column, but NOT call_line" << std::endl; + + if (call_line != attrs.end () && call_file == attrs.end ()) + wr_message (die_locus (entry), mc_impact_3 | mc_acc_suboptimal) + .id (descriptor ()) + << elfutils::dwarf::tags::name (entry.tag ()) + << " has call_line, but NOT call_file" << std::endl; + + if (call_file != attrs.end () && call_line == attrs.end ()) + wr_message (die_locus (entry), mc_impact_3 | mc_acc_suboptimal) + .id (descriptor ()) + << elfutils::dwarf::tags::name (entry.tag ()) + << " has call_file, but NOT call_line" << std::endl; + } + }; + + reg_die_check<check_die_decl_call> reg; +} diff --git a/dwarflint/check_die_line_info.cc b/dwarflint/check_die_line_info.cc new file mode 100644 index 00000000..359976f0 --- /dev/null +++ b/dwarflint/check_die_line_info.cc @@ -0,0 +1,136 @@ +/* Check that every block that has an address is also in line info table. + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#include "check_die_tree.hh" +#include "pri.hh" +#include "messages.hh" + +using elfutils::dwarf; + +namespace +{ + class check_die_line_info + : public die_check + { + public: + static checkdescriptor const *descriptor () + { + static checkdescriptor cd + (checkdescriptor::create ("check_die_line_info") + .description ("Check that each code block start address " + "is also mentioned in the line table.\n")); + return &cd; + } + + check_die_line_info (highlevel_check_i *, checkstack &, dwarflint &) + { + // No state stored for this check. + } + + static bool is_code_block (dwarf::debug_info_entry const &entry) + { + int tag = entry.tag (); + switch (tag) + { + case DW_TAG_subprogram: + case DW_TAG_inlined_subroutine: + case DW_TAG_entry_point: + case DW_TAG_lexical_block: + case DW_TAG_label: + case DW_TAG_with_stmt: + case DW_TAG_try_block: + case DW_TAG_catch_block: + return true; + + default: + return false; + } + } + + static void check_die_pc (dwarf::debug_info_entry const &entry, + dwarf::attribute const &pc, + Dwarf_Addr addr) + { + dwarf::compile_unit cu = entry.compile_unit (); + dwarf::line_info_table line_info = cu.line_info (); + dwarf::line_table lines = line_info.lines (); + dwarf::line_table::const_iterator l = lines.find (addr); + + bool found = false; + while (l != lines.end ()) + { + dwarf::line_entry line = *l; + if (line.address () < addr) + { + l++; + continue; + } + else if (line.address () > addr) + { + // Ran past it... + break; + } + + found = true; + break; + } + + if (! found) + wr_message (die_locus (entry), mc_impact_3 | mc_acc_suboptimal) + .id (descriptor ()) + << elfutils::dwarf::tags::name (entry.tag ()) + << " " << dwarf::attributes::name (pc.first) << "=0x" + << std::hex << addr << std::dec + << ", NOT found in line table." << std::endl; + } + + virtual void + die (all_dies_iterator<dwarf> const &it) + { + dwarf::debug_info_entry const &entry = *it; + if (is_code_block (entry)) + { + dwarf::debug_info_entry::attributes_type attrs = entry.attributes (); + + dwarf::debug_info_entry::attributes_type::const_iterator + entry_pc = attrs.find (DW_AT_entry_pc); + if (entry_pc != attrs.end ()) + check_die_pc (entry, *entry_pc, (*entry_pc).second.address ()); + + dwarf::debug_info_entry::attributes_type::const_iterator + low_pc = attrs.find (DW_AT_low_pc); + if (low_pc != attrs.end ()) + check_die_pc (entry, *low_pc, (*low_pc).second.address ()); + + dwarf::debug_info_entry::attributes_type::const_iterator + at_ranges = attrs.find (DW_AT_ranges); + if (at_ranges != attrs.end ()) + { + dwarf::ranges ranges = entry.ranges (); + dwarf::ranges::const_iterator r = ranges.begin (); + while (r != ranges.end ()) + { + check_die_pc (entry, *at_ranges, (*r).first); + r++; + } + } + } + } + }; + + reg_die_check<check_die_line_info> reg; +} diff --git a/dwarflint/check_die_tree.cc b/dwarflint/check_die_tree.cc new file mode 100644 index 00000000..c9f0fb2a --- /dev/null +++ b/dwarflint/check_die_tree.cc @@ -0,0 +1,147 @@ +/* Pedantic checking of DWARF files + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#include "messages.hh" +#include "highlevel_check.hh" +#include "check_die_tree.hh" + +using namespace elfutils; + +namespace +{ + class die_check_registrar + : public check_registrar_T<die_check_item> + { + public: + friend class dwarflint; + void run (checkstack &stack, dwarflint &lint); + + static die_check_registrar * + inst () + { + static die_check_registrar inst; + return &inst; + } + }; +} + +void +check_die_tree::register_check (die_check_item *check) +{ + die_check_registrar::inst ()->push_back (check); +} + +class die_check_context + : protected std::vector<die_check *> +{ + typedef std::vector<die_check *> _super_t; + checkdescriptor const *_m_cd; + +public: + die_check_context (highlevel_check_i *check, + checkdescriptor const *cd, + dwarflint &lint, + die_check_registrar const ®istrar) + : _m_cd (cd) + { + // For per-DIE runs, we are only interested in limited context: + // the main iteration check, and the per-DIE check. This should + // be enough to decide whether to run the per-DIE check or not. + // We cannot use the original stack as a criterion, because the + // original check that tricked us into running is here, and the + // logic in should_check would then assume that we need to run + // everything. + checkstack stack; + stack.push_back (cd); + + for (die_check_registrar::const_iterator it = registrar.begin (); + it != registrar.end (); ++it) + { + stack.push_back ((*it)->descriptor ()); + popper p (stack); + if (lint.rules ().should_check (stack)) + push_back ((*it)->create (check, stack, lint)); + } + } + + void + error (all_dies_iterator<dwarf> const &a_d_it, + char const *reason = NULL) + { + std::string r; + if (reason) + { + r += ": "; + r += reason; + } + + wr_error (die_locus (*a_d_it)) + << "A check failed: " << (_m_cd->name () ?: "(nil)") + << r << std::endl; + } + + void + die (all_dies_iterator<dwarf> const &a_d_it) + { + for (iterator it = begin (); it != end (); ++it) + again: + try + { + (*it)->die (a_d_it); + } + catch (check_base::unscheduled &e) + { + // Turn the check off. + size_t pos = it - begin (); + delete *it; + erase (it); + it = begin () + pos; + if (it == end ()) + break; + goto again; + } + catch (check_base::failed &e) + { + // The check was supposed to emit an error message. + } + catch (std::exception &e) + { + error (a_d_it, e.what ()); + } + catch (...) + { + error (a_d_it); + } + } + + ~die_check_context () + { + for (iterator it = begin (); it != end (); ++it) + delete *it; + } +}; + +check_die_tree::check_die_tree (checkstack &stack, dwarflint &lint) + : highlevel_check<check_die_tree> (stack, lint) +{ + die_check_context ctx (this, descriptor (), lint, + *die_check_registrar::inst ()); + + for (all_dies_iterator<dwarf> it = all_dies_iterator<dwarf> (dw); + it != all_dies_iterator<dwarf> (); ++it) + ctx.die (it); +} diff --git a/dwarflint/check_die_tree.hh b/dwarflint/check_die_tree.hh new file mode 100644 index 00000000..f8967809 --- /dev/null +++ b/dwarflint/check_die_tree.hh @@ -0,0 +1,109 @@ +/* Pedantic checking of DWARF files + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifndef _CHECK_DIE_TREE_H_ +#define _CHECK_DIE_TREE_H_ + +#include "all-dies-it.hh" +#include "highlevel_check.hh" +#include "check_die_tree_i.hh" + +#include <c++/dwarf> + +struct die_check_item +{ + virtual checkdescriptor const *descriptor () const = 0; + virtual ~die_check_item () {} + virtual die_check *create (highlevel_check_i *check, + checkstack &stack, dwarflint &lint) = 0; +}; + +/// Top-level check that iterates over all DIEs in a file and +/// dispatches per-DIE checks on each one. Per-DIE checks are written +/// as subclasses of die_check (see below) and registered using +/// reg_die_check (see further below). +class check_die_tree + : public highlevel_check<check_die_tree> +{ +public: + static void register_check (die_check_item *check); + + static checkdescriptor const *descriptor () + { + static checkdescriptor cd + (checkdescriptor::create ("check_die_tree") + .hidden () + .description ("A pass over the DIE tree that dispatches to various per-DIE checks.\n")); + return &cd; + } + + check_die_tree (checkstack &stack, dwarflint &lint); +}; + +class die_check +{ +public: + virtual ~die_check () {} + virtual void die (all_dies_iterator<elfutils::dwarf> const &it) = 0; +}; + +template <class T> +struct reg_die_check + : public die_check_item +{ + reg_die_check () + { + check_die_tree::register_check (this); + } + + virtual die_check *create (highlevel_check_i *check, + checkstack &stack, dwarflint &lint) + { + return new T (check, stack, lint); + } + + virtual checkdescriptor const *descriptor () const + { + return T::descriptor (); + } + +private: + /// The top-level scheduler needs to see per-DIE checks as real + /// checks, which they are not. So the per-DIE registrar creates + /// this check stub that's here only to trick the check_die_tree to + /// run. check_die_tree then does the per-DIE check scheduling + /// itself, down in die_check_context. + class check_stub + : public highlevel_check<check_stub> + { + check_die_tree *_m_die_tree_check; + public: + static checkdescriptor const *descriptor () + { + return T::descriptor (); + } + + check_stub (checkstack &stack, dwarflint &lint) + : highlevel_check<check_stub> (stack, lint) + , _m_die_tree_check (lint.check (stack, _m_die_tree_check)) + {} + }; + + ::reg<check_stub> _m_reg_stub; +}; + +#endif /* _CHECK_DIE_TREE_H_ */ diff --git a/dwarflint/check_die_tree_i.hh b/dwarflint/check_die_tree_i.hh new file mode 100644 index 00000000..95b98e68 --- /dev/null +++ b/dwarflint/check_die_tree_i.hh @@ -0,0 +1 @@ +class check_die_tree; diff --git a/dwarflint/check_duplicate_DW_tag_variable.cc b/dwarflint/check_duplicate_DW_tag_variable.cc new file mode 100644 index 00000000..609760b8 --- /dev/null +++ b/dwarflint/check_duplicate_DW_tag_variable.cc @@ -0,0 +1,126 @@ +/* + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#include "check_die_tree.hh" +#include "pri.hh" +#include "messages.hh" +#include <map> + +using elfutils::dwarf; + +namespace +{ + class check_duplicate_DW_tag_variable + : public die_check + { + struct varinfo + { + dwarf::debug_info_entry::children_type::const_iterator decl, def; + + explicit varinfo (dwarf::debug_info_entry::children_type const &children) + : decl (children.end ()) + , def (children.end ()) + {} + + varinfo (varinfo const &other) + : decl (other.decl) + , def (other.def) + {} + }; + + public: + static checkdescriptor const *descriptor () { + static checkdescriptor cd + (checkdescriptor::create ("check_duplicate_DW_tag_variable") + .description ( +"Implements a check for two full DW_TAG_variable DIEs with the same " +"DW_AT_name value. This covers duplicate declaration, duplicate " +"definition and declaration with definition.\n" +" https://fedorahosted.org/pipermail/elfutils-devel/2010-July/001497.html\n" +" http://gcc.gnu.org/bugzilla/show_bug.cgi?id=39524\n")); + return &cd; + } + + check_duplicate_DW_tag_variable (highlevel_check_i *, + checkstack &, dwarflint &) {} + + virtual void + die (all_dies_iterator<dwarf> const &it) + { + dwarf::debug_info_entry::children_type const &children + = it->children (); + + typedef std::map<std::string, varinfo> variables_map; + variables_map variables; + + for (dwarf::debug_info_entry::children_type::const_iterator + jt = children.begin (); jt != children.end (); ++jt) + { + if (jt->tag () == DW_TAG_variable) + { + dwarf::debug_info_entry::attributes_type const & + attrs = jt->attributes (); + dwarf::debug_info_entry::attributes_type::const_iterator + at, et = attrs.end (); + if ((at = attrs.find (DW_AT_name)) == et) + continue; + char const *cname = (*at).second.identifier (); + + bool declaration = false; + if ((at = attrs.find (DW_AT_declaration)) != et) + declaration = (*at).second.flag (); + + std::string name (cname); + variables_map::iterator old = variables.find (name); + if (old == variables.end ()) + { + varinfo i (children); + if (declaration) + i.decl = jt; + else + i.def = jt; + variables.insert (std::make_pair (name, i)); + } + else + { + varinfo &i = old->second; + if ((declaration && i.decl != children.end ()) + || (!declaration && i.def != children.end ())) + wr_message (die_locus (*jt), mc_impact_3 | mc_die_other) + .id (descriptor ()) + << "Re" << (declaration ? "declaration" : "definition") + << " of variable '" << name << "', originally seen at " + << pri::ref (declaration ? *i.decl : *i.def) + << '.' << std::endl; + else + wr_message (die_locus (*jt), mc_impact_3 | mc_die_other) + .id (descriptor ()) + << "Found " + << (declaration ? "declaration" : "definition") + << " of variable '" << name + << "' whose " + << (declaration ? "definition" : "declaration") + << " was seen at " + << pri::ref (declaration ? *i.def : *i.decl) + << '.' << std::endl; + } + } + } + } + }; + reg_die_check<check_duplicate_DW_tag_variable> reg; +} diff --git a/dwarflint/check_dups_abstract_origin.cc b/dwarflint/check_dups_abstract_origin.cc new file mode 100644 index 00000000..f81d957a --- /dev/null +++ b/dwarflint/check_dups_abstract_origin.cc @@ -0,0 +1,152 @@ +/* Pedantic checking of DWARF files. + Copyright (C) 2009, 2010, 2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "check_die_tree.hh" +#include "pri.hh" +#include "messages.hh" +#include <map> + +using elfutils::dwarf; + +namespace +{ + class check_dups_abstract_origin + : public die_check + { + public: + static checkdescriptor const *descriptor () + { + static checkdescriptor cd + (checkdescriptor::create ("check_dups_abstract_origin") + .description ( +"If a given attribute name is present on a DIE, it is " +"suspicious if that attribute name appears on the DIE that's the " +"first DIE's DW_AT_abstract_origin or DW_AT_specification.\n" +" https://bugzilla.redhat.com/show_bug.cgi?id=527430\n")); + return &cd; + } + + bool + duplicate_ok (int tag, int at, int from, int ref_tag, bool same) + { + // A call site entry has a DW_AT_low_pc attribute which is the return + // address after the call and a DW_AT_abstract_origin that is a + // pointer to the reference it calls directly or indirectly. So + // both may be available also at the abstract_origin (with different + // values). + if (tag == DW_TAG_GNU_call_site + && (at == DW_AT_low_pc || at == DW_AT_abstract_origin) + && from == DW_AT_abstract_origin + && ! same) + return true; + + // A subprogram that has a concrete out-of-line instance might + // have an object_pointer different from the original variant + // of the subprogram. Similar for a subprogram specification, + // which may refer to the specification die of the object_pointer, + // while the instance of the subprogram will refer to the + // actual instance of the object_pointer die. + if (tag == DW_TAG_subprogram + && at == DW_AT_object_pointer + && (from == DW_AT_abstract_origin || from == DW_AT_specification) + && ref_tag == DW_TAG_subprogram + && ! same) + return true; + + // A subprogram can be defined outside the body of the enclosing + // class, then file and/or line attributes can differ. + if (tag == DW_TAG_subprogram + && from == DW_AT_specification + && (at == DW_AT_decl_line || at == DW_AT_decl_file) + && ref_tag == DW_TAG_subprogram + && ! same) + return true; + + // Same for a member variable can be defined outside the body of the + // enclosing class, then file and/or line attributes can differ. + if (tag == DW_TAG_variable + && from == DW_AT_specification + && (at == DW_AT_decl_line || at == DW_AT_decl_file) + && ref_tag == DW_TAG_member + && ! same) + return true; + + + return false; + } + + void + check_die_attr (dwarf::debug_info_entry const &entry, + dwarf::attribute const &attr) + { + std::map<unsigned int, dwarf::attr_value> m; + for (dwarf::debug_info_entry::attributes_type::const_iterator + at = entry.attributes ().begin (); + at != entry.attributes ().end (); ++at) + m.insert (std::make_pair ((*at).first, (*at).second)); + + dwarf::attr_value const &val = attr.second; + // xxx Referree can't be const&, gives memory errors. + dwarf::debug_info_entry referree = *val.reference (); + + std::map<unsigned int, dwarf::attr_value>::const_iterator at2; + for (dwarf::debug_info_entry::attributes_type::const_iterator + at = referree.attributes ().begin (); + at != referree.attributes ().end (); ++at) + if ((at2 = m.find ((*at).first)) != m.end () + && ! duplicate_ok (entry.tag (), at2->first, attr.first, + referree.tag (), at2->second == (*at).second)) + wr_message (die_locus (entry), mc_impact_3 | mc_acc_bloat | mc_die_rel) + .id (descriptor ()) + << dwarf::tags::name (entry.tag ()) + << " attribute " << dwarf::attributes::name (at2->first) + << " is duplicated at " << dwarf::attributes::name (attr.first) + << " (" << pri::ref (referree) << ")" + << (at2->second == (*at).second + ? "." : " with different value.") + << std::endl; + } + + explicit + check_dups_abstract_origin (highlevel_check_i *, checkstack &, dwarflint &) + { + // No state necessary. + } + + virtual void + die (all_dies_iterator<dwarf> const &it) + { + // Do we have DW_AT_abstract_origin or DW_AT_specification? + dwarf::debug_info_entry const &entry = *it; + for (dwarf::debug_info_entry::attributes_type::const_iterator + at = entry.attributes ().begin (); + at != entry.attributes ().end (); ++at) + if ((*at).first == DW_AT_abstract_origin + || (*at).first == DW_AT_specification) + { + assert ((*at).second.what_space () == dwarf::VS_reference); + check_die_attr (entry, *at); + } + } + }; + + reg_die_check<check_dups_abstract_origin> reg; +} diff --git a/dwarflint/check_expected_trees.cc b/dwarflint/check_expected_trees.cc new file mode 100644 index 00000000..dd22ddfa --- /dev/null +++ b/dwarflint/check_expected_trees.cc @@ -0,0 +1,202 @@ +/* + 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "../libdw/c++/dwarf-knowledge.cc" +#include "../libdw/c++/dwarf" + +#include "check_debug_info.hh" +#include "highlevel_check.hh" +#include "expected.hh" +#include "messages.hh" + +using elfutils::dwarf; + +namespace +{ + class check_expected_trees + : public highlevel_check<check_expected_trees> + { + public: + static checkdescriptor const *descriptor () { + static checkdescriptor cd + (checkdescriptor::create ("check_expected_trees") + .description ( +"Checks whether all DIEs have the right attributes and the right children.\n" +"Currently this is very much a work in progress.\n")); + return &cd; + } + + check_expected_trees (checkstack &stack, dwarflint &lint); + }; + + reg<check_expected_trees> reg_check_expected_trees; + + const expected_at_map expected_at; + //static const expected_children_map expected_children; + + struct name_extractor { + int operator () (dwarf::attribute const &at) { + return at.first; + } + } extract_name; + + std::ostream & + operator << (std::ostream &o, dwarf::value_space vs) + { + switch (vs) + { + case dwarf::VS_flag: return o << "flag"; + case dwarf::VS_dwarf_constant: return o << "dwarf_constant"; + case dwarf::VS_discr_list: return o << "discr_list"; + case dwarf::VS_reference: return o << "reference"; + case dwarf::VS_lineptr: return o << "lineptr"; + case dwarf::VS_macptr: return o << "macptr"; + case dwarf::VS_rangelistptr: return o << "rangelistptr"; + case dwarf::VS_identifier: return o << "identifier"; + case dwarf::VS_string: return o << "string"; + case dwarf::VS_source_file: return o << "source_file"; + case dwarf::VS_source_line: return o << "source_line"; + case dwarf::VS_source_column: return o << "source_column"; + case dwarf::VS_address: return o << "address"; + case dwarf::VS_constant: return o << "constant"; + case dwarf::VS_location: return o << "location"; + }; + + abort (); + } +} + +check_expected_trees::check_expected_trees (checkstack &stack, dwarflint &lint) + : highlevel_check<check_expected_trees> (stack, lint) +{ + lint.check <check_debug_info> (stack); + + try + { + struct + { + void operator () (dwarf::compile_unit const &cu, + dwarf::debug_info_entry const &parent) + { + die_locus where (parent); + + int parent_tag = parent.tag (); + + // Set of attributes of this DIE. + std::set <int> attributes; + std::transform (parent.attributes ().begin (), + parent.attributes ().end (), + std::inserter (attributes, attributes.end ()), + extract_name); + + // Attributes that we expect at this DIE. + expected_set::expectation_map const &expect + = expected_at.map (parent_tag); + + // Check missing attributes. + for (expected_set::expectation_map::const_iterator jt + = expect.begin (); jt != expect.end (); ++jt) + { + std::set <int>::iterator kt = attributes.find (jt->first); + char const *what = NULL; + if (kt == attributes.end ()) + switch (jt->second) + { + case opt_required: + what = " lacks required attribute "; + // FALL_THROUGH + + case opt_expected: + if (what == NULL) + what = " should contain attribute "; + wr_message (where, mc_impact_2 | mc_info) + << elfutils::dwarf::tags::name (parent_tag) << what + << elfutils::dwarf::attributes::name (jt->first) << '.' + << std::endl; + break; + + case opt_optional: + break; + }; + } + + // Check present attributes for expected-ness, and validate + // value space. + for (dwarf::debug_info_entry::attributes_type::const_iterator + jt = parent.attributes ().begin (), + jte = parent.attributes ().end (); + jt != jte; ++jt) + { + unsigned name = extract_name (*jt); + + expected_set::expectation_map::const_iterator + kt = expect.find (name); + if (kt == expect.end ()) + wr_message (where, mc_impact_3 | mc_info) + << ": DIE \"" << dwarf::tags::name (parent_tag) + << "\" has attribute \"" << dwarf::attributes::name (name) + << "\", which is not expected." << std::endl; + + try + { + unsigned exp_vs = expected_value_space (name, parent_tag); + dwarf::value_space vs = (*jt).second.what_space (); + if ((exp_vs & (1U << vs)) == 0) + wr_message (where, mc_impact_3 | mc_info) + << ": in DIE \"" << dwarf::tags::name (parent_tag) + << "\", attribute \"" << dwarf::attributes::name (name) + << "\" has value of unexpected type \"" << vs + << "\"." << std::endl; + } + // XXX more specific class when <dwarf> has it + catch (...) + { + wr_message (where, mc_impact_4 | mc_info | mc_error) + << ": in DIE \"" << dwarf::tags::name (parent_tag) + << "\", couldn't obtain type of attribute \"" + << dwarf::attributes::name (name) << "\"." + << std::endl; + } + } + + // Check children recursively. + dwarf::debug_info_entry::children_type const &children + = parent.children (); + for (dwarf::debug_info_entry::children_type::const_iterator + jt = children.begin (); jt != children.end (); ++jt) + (*this) (cu, *jt); + } + } recursively_validate; + + class dwarf::compile_units_type const &cus = dw.compile_units (); + for (dwarf::compile_units_type::const_iterator it = cus.begin (); + it != cus.end (); ++it) + recursively_validate (*it, *it); + } + // XXX more specific class when <dwarf> has it + catch (std::runtime_error &exc) + { + wr_error (section_locus (sec_info)) + << "Exception while checking expected trees: " << exc.what () + << std::endl; + throw check_base::failed (); + } +} diff --git a/dwarflint/check_linkage_external_die.cc b/dwarflint/check_linkage_external_die.cc new file mode 100644 index 00000000..4170f9b2 --- /dev/null +++ b/dwarflint/check_linkage_external_die.cc @@ -0,0 +1,171 @@ +/* Check that every die that has a linkage_name is also external. + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#include "check_die_tree.hh" +#include "pri.hh" +#include "messages.hh" + + +#include "../libelf/gelf.h" +#include "../libdw/libdw.h" + +using elfutils::dwarf; + +namespace +{ + class check_linkage_external_die + : public die_check + { + private: + std::map<std::string, bool> _m_symbols; + + public: + static checkdescriptor const *descriptor () + { + static checkdescriptor cd + (checkdescriptor::create ("check_linkage_external_die") + .description ("Check that each DIE that has a linkage_name " + "also has an external attribute.\n")); + return &cd; + } + + check_linkage_external_die (highlevel_check_i *check, + checkstack &, dwarflint &) + { + // Extract all symbol table names for objects and functions + // and store whether they are global or not in _m_symbols. + Dwarf *dwarf = check->c_dw; + Elf *elf = dwarf_getelf (dwarf); + Elf_Scn *scn = NULL; + while ((scn = elf_nextscn (elf, scn)) != NULL) + { + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); + if (shdr != NULL && (shdr->sh_type == SHT_DYNSYM + || shdr->sh_type == SHT_SYMTAB)) + { + Elf_Data *data = elf_getdata (scn, NULL); + size_t shstrndx; + elf_getshdrstrndx (elf, &shstrndx); + unsigned int syms = shdr->sh_size / shdr->sh_entsize; + for (unsigned int cnt = 0; cnt < syms; ++cnt) + { + GElf_Sym sym_mem; + GElf_Sym *sym = gelf_getsym (data, cnt, &sym_mem); + if (sym != NULL + && (GELF_ST_TYPE (sym->st_info) == STT_OBJECT + || GELF_ST_TYPE (sym->st_info) == STT_FUNC)) + { + const char *name; + name = elf_strptr (elf, shdr->sh_link, sym->st_name); + if (name != NULL) + { + // Regard anything not explicitly marked as local + // a global symbol, it could be STB_GLOBAL, + // STB_WEAK, STB_GNU_UNIQUE, ... + unsigned int binding = GELF_ST_BIND (sym->st_info); + bool global = binding != STB_LOCAL; + using namespace std; + _m_symbols.insert (pair<string, bool> + (string (name), global)); + } + } + } + } + } + } + + static bool is_external (all_dies_iterator<dwarf> const &it) + { + dwarf::debug_info_entry::attributes_type attrs = (*it).attributes (); + dwarf::debug_info_entry::attributes_type::const_iterator external + = attrs.find_integrate (DW_AT_external); + + return external != attrs.end () && (*external).second.flag (); + } + + virtual void + die (all_dies_iterator<dwarf> const &it) + { + dwarf::debug_info_entry const &entry = *it; + dwarf::debug_info_entry::attributes_type attrs = entry.attributes (); + dwarf::debug_info_entry::attributes_type::const_iterator linkage_name + = attrs.find (DW_AT_linkage_name); + if (linkage_name == attrs.end ()) + linkage_name = attrs.find (DW_AT_MIPS_linkage_name); + if (linkage_name != attrs.end ()) + { + using namespace std; + const char *name = (*linkage_name).second.string (); + map<string, bool>::iterator s = _m_symbols.find (string (name)); + if (s == _m_symbols.end ()) + { + // No symbol in table, OK, if not a defining or const object. + // GNU extension, anonymous structs, enums and unions can + // have a linkage_name. + if (attrs.find (DW_AT_declaration) == attrs.end () + && attrs.find (DW_AT_const_value) == attrs.end () + && ((entry.tag () != DW_TAG_structure_type + && entry.tag () != DW_TAG_enumeration_type + && entry.tag () != DW_TAG_union_type) + || attrs.find (DW_AT_name) != attrs.end ())) + { + wr_message (die_locus (entry), + mc_impact_3 | mc_acc_suboptimal | mc_die_other) + .id (descriptor ()) + << elfutils::dwarf::tags::name (entry.tag ()) + << " has linkage_name attribute `" + << name << "', which is not in string table," + << " but DIE is not marked as a declaration" + << " or const value." + << std::endl; + } + } + else if ((*s).second == false) + { + // Local symbol in table, OK if not a defining object + // and marked external. Which means it comes from an + // external symbol table. + if (attrs.find (DW_AT_declaration) == attrs.end () + && is_external (it)) + { + wr_message (die_locus (entry), + mc_impact_3 | mc_acc_suboptimal | mc_die_other) + .id (descriptor ()) + << elfutils::dwarf::tags::name (entry.tag ()) + << " has linkage_name attribute `" + << name << "', which is a local symbol." + << std::endl; + } + } + else if (! is_external (it)) + { + // Global symbol in symbol table, not marked external. + // Always bad. + wr_message (die_locus (entry), + mc_impact_3 | mc_acc_suboptimal | mc_die_other) + .id (descriptor ()) + << elfutils::dwarf::tags::name (entry.tag ()) + << " has linkage_name attribute, but no external attribute." + << std::endl; + } + } + } + }; + + reg_die_check<check_linkage_external_die> reg; +} diff --git a/dwarflint/check_matching_ranges.cc b/dwarflint/check_matching_ranges.cc new file mode 100644 index 00000000..972d0b67 --- /dev/null +++ b/dwarflint/check_matching_ranges.cc @@ -0,0 +1,108 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009,2010,2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "highlevel_check.hh" +#include "check_debug_loc_range.hh" +#include "check_debug_aranges.hh" + +using elfutils::dwarf; + +namespace +{ + class check_matching_ranges + : public highlevel_check<check_matching_ranges> + { + public: + static checkdescriptor const *descriptor () { + static checkdescriptor cd + (checkdescriptor::create ("check_matching_ranges") + .description ( +"Check that the ranges in .debug_aranges and .debug_ranges match.\n")); + return &cd; + } + + check_matching_ranges (checkstack &stack, dwarflint &lint); + }; + + reg<check_matching_ranges> reg_matching_ranges; +} + +check_matching_ranges::check_matching_ranges (checkstack &stack, + dwarflint &lint) + : highlevel_check<check_matching_ranges> (stack, lint) +{ + lint.check<check_debug_ranges> (stack); + lint.check<check_debug_aranges> (stack); + + try + { + char buf[128]; + const dwarf::aranges_map &aranges = dw.aranges (); + for (dwarf::aranges_map::const_iterator i = aranges.begin (); + i != aranges.end (); ++i) + { + const dwarf::compile_unit &cu = i->first; + cudie_locus where_ref (cu); + loc_range_locus where_r (sec_ranges, where_ref); + arange_locus where_ar (where_ref); + + std::set<dwarf::ranges::key_type> + cu_aranges = i->second, + cu_ranges = cu.ranges (); + + typedef std::vector <dwarf::arange_list::value_type> + range_vec; + range_vec missing; + std::back_insert_iterator <range_vec> i_missing (missing); + + std::set_difference (cu_aranges.begin (), cu_aranges.end (), + cu_ranges.begin (), cu_ranges.end (), + i_missing); + + for (range_vec::iterator it = missing.begin (); + it != missing.end (); ++it) + wr_message (where_r, mc_ranges | mc_aranges | mc_impact_3) + << "missing range " + << range_fmt (buf, sizeof buf, it->first, it->second) + << ", present in .debug_aranges." << std::endl; + + missing.clear (); + std::set_difference (cu_ranges.begin (), cu_ranges.end (), + cu_aranges.begin (), cu_aranges.end (), + i_missing); + + for (range_vec::iterator it = missing.begin (); + it != missing.end (); ++it) + wr_message (where_ar, mc_ranges | mc_aranges | mc_impact_3) + << "missing range " + << range_fmt (buf, sizeof buf, it->first, it->second) + << ", present in .debug_ranges." << std::endl; + } + } + // XXX more specific class when <dwarf> has it + catch (std::runtime_error &exc) + { + wr_error (section_locus (sec_info)) + << "Exception while checking matching ranges: " << exc.what () + << std::endl; + throw check_base::failed (); + } +} diff --git a/dwarflint/check_nodebug.cc b/dwarflint/check_nodebug.cc new file mode 100644 index 00000000..19455a59 --- /dev/null +++ b/dwarflint/check_nodebug.cc @@ -0,0 +1,73 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009,2010,2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "checks.hh" +#include "messages.hh" +#include "sections.hh" +#include "option.hh" + +static void_option ignore_missing + ("Don't complain if files have no DWARF at all", + "nodebug:ignore", 'i'); + +class check_nodebug + : public check<check_nodebug> +{ +public: + static checkdescriptor const *descriptor () + { + static checkdescriptor cd + (checkdescriptor::create ("check_nodebug") + .groups ("@low") + .option (ignore_missing) + .description ( +"Checks that there are at least essential debuginfo sections present " +"in the ELF file.\n")); + return &cd; + } + + check_nodebug (checkstack &stack, dwarflint &lint); + +private: + void not_available (section_id sec_id) + { + wr_error (section_locus (sec_id)) + << "data not found." << std::endl; + } + + template <section_id sec_id> + void request (checkstack &stack, dwarflint &lint) + { + if (lint.toplev_check<section<sec_id> > (stack) == NULL) + not_available (sec_id); + } + +}; + +static reg<check_nodebug> reg_nodebug; + +check_nodebug::check_nodebug (checkstack &stack, dwarflint &lint) +{ + if (ignore_missing) + return; + + // We demand .debug_info and .debug_abbrev, the rest is optional. + // Presence of the other sections is (or should be) requested if + // there are pending references from .debug_info. + request<sec_abbrev> (stack, lint); + request<sec_info> (stack, lint); +} diff --git a/dwarflint/check_range_out_of_scope.cc b/dwarflint/check_range_out_of_scope.cc new file mode 100644 index 00000000..f60f18f3 --- /dev/null +++ b/dwarflint/check_range_out_of_scope.cc @@ -0,0 +1,233 @@ +/* Check whether PC ranges reported at DIE fall into the containing scope. + 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "highlevel_check.hh" +#include "coverage.hh" +#include "pri.hh" +#include "check_debug_loc_range.hh" + +using elfutils::dwarf; + +namespace +{ + class check_range_out_of_scope + : public highlevel_check<check_range_out_of_scope> + { + typedef std::vector<std::pair< ::Dwarf_Addr, ::Dwarf_Addr> > + ranges_t; + + static void recursively_validate (dwarf::compile_unit const &cu, + dwarf::debug_info_entry const &die, + ranges_t const &ranges, + locus const &wh_parent); + + public: + static checkdescriptor const *descriptor () { + static checkdescriptor cd + (checkdescriptor::create ("check_range_out_of_scope") + .description ( +"Check whether PC ranges reported at DIEs fall into the containing scope.\n")); + return &cd; + } + + check_range_out_of_scope (checkstack &stack, dwarflint &lint); + }; + + // Register the check. + reg<check_range_out_of_scope> reg_range_out_of_scope; + const ::Dwarf_Addr noaddr = -1; +} + +check_range_out_of_scope::check_range_out_of_scope (checkstack &stack, dwarflint &lint) + : highlevel_check<check_range_out_of_scope> (stack, lint) +{ + try + { + class dwarf::compile_units_type const &cus = dw.compile_units (); + ranges_t r; + r.push_back (std::make_pair (0, -1)); + section_locus wh (sec_info); + for (dwarf::compile_units_type::const_iterator it = cus.begin (); + it != cus.end (); ++it) + recursively_validate (*it, *it, r, wh); + } + // XXX more specific class when <dwarf> has it + catch (std::runtime_error &exc) + { + wr_error (section_locus (sec_info)) + << "Exception while checking ranges out of scope: " << exc.what () + << std::endl; + throw check_base::failed (); + } +} + +void +check_range_out_of_scope::recursively_validate + (dwarf::compile_unit const &cu, + dwarf::debug_info_entry const &die, + ranges_t const &ranges, + locus const &wh_parent) +{ + die_locus wh (die); + + ::Dwarf_Addr low_pc = 0; + ::Dwarf_Addr high_pc = ::noaddr; + ranges_t my_ranges; + for (dwarf::debug_info_entry::attributes_type::const_iterator + at = die.attributes ().begin (); + at != die.attributes ().end (); ++at) + { + dwarf::attr_value const &value = (*at).second; + dwarf::value_space vs = value.what_space (); + if ((*at).first == DW_AT_low_pc) + low_pc = value.address (); + else if ((*at).first == DW_AT_high_pc) + high_pc = value.address (); + else if (vs == dwarf::VS_rangelistptr) + for (dwarf::range_list::const_iterator + it = value.ranges ().begin (); + it != value.ranges ().end (); ++it) + my_ranges.push_back (*it); + } + + if (low_pc != 0 || high_pc != ::noaddr) + { + // Simultaneous appearance of both low_pc/high_pc pair + // and rangelist pointer is forbidden by 3.1.1 #1. + // Presence of low_pc on itself is OK on compile_unit + // and partial_unit DIEs, otherwise it serves the same + // purpose as low_pc/high_pc pair that covers one + // address point. + + if (high_pc == ::noaddr + && die.tag () != DW_TAG_compile_unit + && die.tag () != DW_TAG_partial_unit) + high_pc = low_pc + 1; + + if (high_pc != ::noaddr) + { + if (my_ranges.size () != 0) + wr_message (wh, mc_impact_4 | mc_info | mc_error) + << "both low_pc/high_pc pair and ranges present." + << std::endl; + else + my_ranges.push_back (std::make_pair (low_pc, high_pc)); + } + } + + // If my_ranges is non-empty, check that it's a subset of + // ranges. + if (my_ranges.size () != 0) + { + // xxx Extract this logic to some table. + switch (die.tag ()) + { + /* These PC-ful DIEs should be wholly contained by + PC-ful parental DIE. */ + case DW_TAG_inlined_subroutine: + case DW_TAG_lexical_block: + case DW_TAG_entry_point: + case DW_TAG_label: + case DW_TAG_with_stmt: + case DW_TAG_try_block: + case DW_TAG_catch_block: + { + coverage cov1; + for (ranges_t::const_iterator it = my_ranges.begin (); + it != my_ranges.end (); ++it) + cov1.add ((*it).first, (*it).second - (*it).first); + + coverage cov2; + for (ranges_t::const_iterator it = ranges.begin (); + it != ranges.end (); ++it) + cov2.add ((*it).first, (*it).second - (*it).first); + + coverage result = cov1 - cov2; + + if (!result.empty ()) + { + wr_message (wh, mc_error).id (descriptor ()) + << "PC range " << cov::format_ranges (cov1) + << " is not a sub-range of containing scope." + << std::endl; + + wr_message (wh_parent, mc_error).when_prev () + << "in this context: " << cov::format_ranges (cov2) + << std::endl; + } + } + } + } + + // xxx building the coverage for each die is a waste of time + ranges_t const &use_ranges + = my_ranges.size () > 0 ? my_ranges : ranges; + coverage cov; + + for (ranges_t::const_iterator it = use_ranges.begin (); + it != use_ranges.end (); ++it) + cov.add ((*it).first, (*it).second - (*it).first); + + // Now finally look for location attributes and check that + // _their_ PCs form a subset of ranges of this DIE. + for (dwarf::debug_info_entry::attributes_type::const_iterator + at = die.attributes ().begin (); + at != die.attributes ().end (); ++at) + { + dwarf::attr_value const &value = (*at).second; + dwarf::value_space vs = value.what_space (); + + if (vs == dwarf::VS_location) + { + dwarf::location_attr const &loc = value.location (); + if (loc.is_list ()) + { + bool runoff = false; + for (dwarf::location_attr::const_iterator + lt = loc.begin (); lt != loc.end (); ++lt) + { + ::Dwarf_Addr start = (*lt).first.first; //1st insn + ::Dwarf_Addr end = (*lt).first.second; //1st past end + ::Dwarf_Addr length = end - start; + if (length > 0 // skip empty ranges + && !cov.is_covered (start, length)) + wr_message (wh, mc_error) + .id (descriptor (), runoff) + << "attribute `" + << elfutils::dwarf::attributes::name ((*at).first) + << "': PC range " << pri::range (start, end) + << " outside containing scope." << std::endl; + } + wr_message (wh_parent, mc_error) + .when (runoff) + << "in this context: " << cov::format_ranges (cov) + << '.' << std::endl; + } + } + } + + // Check children recursively. + for (dwarf::debug_info_entry::children_type::const_iterator + jt = die.children ().begin (); + jt != die.children ().end (); ++jt) + recursively_validate (cu, *jt, use_ranges, + my_ranges.size () > 0 ? wh : wh_parent); +} diff --git a/dwarflint/check_registrar.hh b/dwarflint/check_registrar.hh new file mode 100644 index 00000000..b254b018 --- /dev/null +++ b/dwarflint/check_registrar.hh @@ -0,0 +1,57 @@ +/* Pedantic checking of DWARF files + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifndef _CHECK_REGISTRAR_H_ +#define _CHECK_REGISTRAR_H_ + +#include "checkdescriptor_i.hh" + +#include <vector> +#include <set> +#include <iostream> + +namespace check_registrar_aux +{ + bool be_verbose (); + void list_one_check (checkdescriptor const &cd); +} + +template <class Item> +class check_registrar_T + : protected std::vector<Item *> +{ + typedef std::vector<Item *> _super_t; +public: + + using _super_t::push_back; + using _super_t::const_iterator; + using _super_t::begin; + using _super_t::end; + + typedef std::vector<checkdescriptor const *> checkdescriptors_t; + + checkdescriptors_t + get_descriptors () const + { + checkdescriptors_t ret; + for (typename _super_t::const_iterator it = begin (); it != end (); ++it) + ret.push_back ((*it)->descriptor ()); + return ret; + } +}; + +#endif /* _CHECK_REGISTRAR_H_ */ diff --git a/dwarflint/check_registrar_i.hh b/dwarflint/check_registrar_i.hh new file mode 100644 index 00000000..6e84c911 --- /dev/null +++ b/dwarflint/check_registrar_i.hh @@ -0,0 +1,2 @@ +//-*-c++-*- +struct check_registrar; diff --git a/dwarflint/check_self_referential_die.cc b/dwarflint/check_self_referential_die.cc new file mode 100644 index 00000000..48a6dca5 --- /dev/null +++ b/dwarflint/check_self_referential_die.cc @@ -0,0 +1,72 @@ +/* Check for DIEs with attributes referencing the DIE itself. + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#include "check_die_tree.hh" +#include "pri.hh" +#include "messages.hh" + +using elfutils::dwarf; + +namespace +{ + class check_self_referential_die + : public die_check + { + public: + static checkdescriptor const *descriptor () + { + static checkdescriptor cd + (checkdescriptor::create ("check_self_referential_die") + .description ( +"A reference attribute referencing the DIE itself is suspicious.\n" +"One example is a DW_AT_containing_type pointing to itself.\n" +" https://fedorahosted.org/pipermail/elfutils-devel/2011-February/001794.html\n" + )); + return &cd; + } + + check_self_referential_die (highlevel_check_i *, checkstack &, dwarflint &) + { + // We don't keep any state for this die check. + } + + virtual void + die (all_dies_iterator<dwarf> const &it) + { + dwarf::debug_info_entry const &entry = *it; + for (dwarf::debug_info_entry::attributes_type::const_iterator + at = entry.attributes ().begin (); + at != entry.attributes ().end (); ++at) + { + dwarf::attr_value const &val = (*at).second; + if (val.what_space () == dwarf::VS_reference) + { + dwarf::debug_info_entry ref = *val.reference (); + if (ref.identity () == entry.identity ()) + wr_message (die_locus (entry), + mc_impact_3 | mc_acc_suboptimal | mc_die_rel) + .id (descriptor ()) + << dwarf::tags::name (entry.tag ()) + << " attribute " << dwarf::attributes::name ((*at).first) + << " references DIE itself." << std::endl; + } + } + } + }; + + reg_die_check<check_self_referential_die> reg; +} diff --git a/dwarflint/checkdescriptor.cc b/dwarflint/checkdescriptor.cc new file mode 100644 index 00000000..99ce4db4 --- /dev/null +++ b/dwarflint/checkdescriptor.cc @@ -0,0 +1,125 @@ +/* + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#include "checkdescriptor.hh" +#include "wrap.hh" +#include <sstream> +#include <cassert> + +std::ostream & +operator << (std::ostream &o, checkgroups const &groups) +{ + o << '['; + for (checkgroups::const_iterator it = groups.begin (); + it != groups.end (); ++it) + { + if (it != groups.begin ()) + o << ','; + o << *it; + } + o << ']'; + return o; +} + +checkdescriptor::create::create (char const *name) + : _m_name (name) + , _m_description (NULL) + , _m_hidden (false) + , _m_schedule (true) +{} + +checkdescriptor::create::create (checkdescriptor const &base) + : _m_groups (base.groups ()) + , _m_name (base.name ()) + , _m_description (base.description ()) + , _m_hidden (base.hidden ()) + , _m_schedule (base.schedule ()) + , _m_opts (base.opts ()) +{} + +checkdescriptor::create & +checkdescriptor::create::groups (char const *a_groups) +{ + std::stringstream ss (a_groups); + std::string group; + while (ss >> group) + _m_groups.insert (group); + return *this; +} + +checkdescriptor::checkdescriptor () + : _m_name (NULL) + , _m_description (NULL) + , _m_groups () + , _m_hidden (false) + , _m_schedule (true) + , _m_opts () +{} + +checkdescriptor::checkdescriptor (create const &c) + : _m_name (c._m_name) + , _m_description (c._m_description) + , _m_groups (c._m_groups) + , _m_hidden (c._m_hidden) + , _m_schedule (c._m_schedule) + , _m_opts (c._m_opts) +{} + +bool +checkdescriptor::in_group (std::string const &group) const +{ + return _m_groups.find (group) != _m_groups.end (); +} + +void +checkdescriptor::list (bool verbose) const +{ + const size_t columns = 70; + + if (verbose) + std::cout << "=== " << name () << " ==="; + else + std::cout << name (); + + checkgroups const &g = groups (); + if (!g.empty ()) + { + if (verbose) + std::cout << std::endl << "groups: "; + else + std::cout << ' '; + std::cout << g; + } + std::cout << std::endl; + + if (verbose) + { + char const *desc = description (); + if (desc != NULL) + std::cout << wrap_str (desc, columns).join (); + + options const &o = opts (); + if (!o.empty ()) + { + std::cout << "recognized options:" << std::endl; + argp a = o.build_argp (); + argp_help (&a, stdout, ARGP_HELP_LONG, NULL); + } + + std::cout << std::endl; + } +} diff --git a/dwarflint/checkdescriptor.hh b/dwarflint/checkdescriptor.hh new file mode 100644 index 00000000..556eb972 --- /dev/null +++ b/dwarflint/checkdescriptor.hh @@ -0,0 +1,100 @@ +/* + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_CHECKDESCRIPTOR_HH +#define DWARFLINT_CHECKDESCRIPTOR_HH + +#include <set> +#include <string> +#include <iosfwd> +#include <vector> + +#include "option.hh" + +struct checkgroups + : public std::set<std::string> +{}; +std::ostream &operator << (std::ostream &o, checkgroups const &groups); + +struct checkdescriptor +{ + class create + { + friend class checkdescriptor; + checkgroups _m_groups; + char const *const _m_name; + char const *_m_description; + bool _m_hidden; + bool _m_schedule; + options _m_opts; + + public: + create (char const *name = NULL); + create (checkdescriptor const &base); ///< For construction of overrides. + create &groups (char const *name); + + create &description (char const *d) + { + _m_description = d; + return *this; + } + + create hidden () + { + _m_hidden = true; + return *this; + } + + create option (option_i &opt) + { + _m_opts.add (&opt); + return *this; + } + + create schedule (bool whether) + { + _m_schedule = whether; + return *this; + } + }; + + checkdescriptor (); + checkdescriptor (create const &c); + + char const *name () const { return _m_name; } + char const *description () const { return _m_description; } + + checkgroups const &groups () const { return _m_groups; } + bool in_group (std::string const &group) const; + + bool hidden () const { return _m_hidden; } + bool schedule () const { return _m_schedule; } + + options const &opts () const { return _m_opts; } + + void list (bool verbose) const; + +private: + char const *const _m_name; + char const *const _m_description; + checkgroups const _m_groups; + bool const _m_hidden; + bool const _m_schedule; + options const _m_opts; +}; + +#endif//DWARFLINT_CHECKDESCRIPTOR_HH diff --git a/dwarflint/checkdescriptor_i.hh b/dwarflint/checkdescriptor_i.hh new file mode 100644 index 00000000..052fe1bd --- /dev/null +++ b/dwarflint/checkdescriptor_i.hh @@ -0,0 +1 @@ +struct checkdescriptor; diff --git a/dwarflint/checked_read.cc b/dwarflint/checked_read.cc new file mode 100644 index 00000000..48b15359 --- /dev/null +++ b/dwarflint/checked_read.cc @@ -0,0 +1,209 @@ +/* Pedantic checking of DWARF files + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <dwarf.h> +#include <cassert> + +#include "checked_read.hh" +#include "messages.hh" +#include "misc.hh" + +bool +read_size_extra (struct read_ctx *ctx, uint32_t size32, uint64_t *sizep, + int *offset_sizep, locus const &loc) +{ + if (size32 == DWARF3_LENGTH_64_BIT) + { + if (!read_ctx_read_8ubyte (ctx, sizep)) + { + wr_error (loc) << "can't read 64bit CU length.\n"; + return false; + } + + *offset_sizep = 8; + } + else if (size32 >= DWARF3_LENGTH_MIN_ESCAPE_CODE) + { + wr_error (loc) + << "unrecognized CU length escape value: " << size32 << ".\n"; + return false; + } + else + { + *sizep = size32; + *offset_sizep = 4; + } + + return true; +} + +error_code +read_address_size (struct read_ctx *ctx, bool addr_64, + int *address_sizep, locus const &loc) +{ + uint8_t address_size; + if (!read_ctx_read_ubyte (ctx, &address_size)) + { + wr_error (loc) << "can't read address size.\n"; + return err_fatal; + } + + error_code ret = err_ok; + if (address_size != 4 && address_size != 8) + { + /* Keep going. Deduce the address size from ELF header, and try + to parse it anyway. */ + wr_error (loc) << "invalid address size: " << (int)address_size + << " (only 4 or 8 allowed).\n"; + address_size = addr_64 ? 8 : 4; + ret = err_nohl; + } + else if ((address_size == 8) != addr_64) + { + /* Keep going, we may still be able to parse it. */ + wr_error (loc) << "CU reports address size of " << address_size + << " in " << (addr_64 ? 64 : 32) << "-bit ELF.\n"; + ret = err_nohl; + } + + *address_sizep = address_size; + return ret; +} + +bool +checked_read_uleb128 (read_ctx *ctx, uint64_t *ret, + locus const &loc, const char *what) +{ + const unsigned char *ptr = ctx->ptr; + int st = read_ctx_read_uleb128 (ctx, ret); + if (st < 0) + wr_error (loc) << "can't read " << what << ".\n"; + else if (st > 0) + { + char buf[19]; // 16 hexa digits, "0x", terminating zero + sprintf (buf, "%#" PRIx64, *ret); + wr_format_leb128_message (loc, what, buf, ptr, ctx->ptr); + } + return st >= 0; +} + +bool +checked_read_sleb128 (read_ctx *ctx, int64_t *ret, + locus const &loc, const char *what) +{ + const unsigned char *ptr = ctx->ptr; + int st = read_ctx_read_sleb128 (ctx, ret); + if (st < 0) + wr_error (loc) << "can't read " << what << ".\n"; + else if (st > 0) + { + char buf[20]; // sign, "0x", 16 hexa digits, terminating zero + int64_t val = *ret; + sprintf (buf, "%s%#" PRIx64, val < 0 ? "-" : "", val < 0 ? -val : val); + wr_format_leb128_message (loc, what, buf, ptr, ctx->ptr); + } + return st >= 0; +} + +bool +checked_read_leb128 (read_ctx *ctx, form_width_t width, uint64_t *ret, + locus const &loc, const char *what) +{ + assert (width == fw_sleb || width == fw_uleb); + if (width == fw_sleb) + { + int64_t svalue; + if (!checked_read_sleb128 (ctx, &svalue, loc, what)) + return false; + *ret = (uint64_t) svalue; + return true; + } + else + return checked_read_uleb128 (ctx, ret, loc, what); +} + +bool +read_sc_value (uint64_t *valuep, form_width_t width, + read_ctx *ctx, locus const &loc) +{ + switch (width) + { + case fw_0: + *valuep = 1; + return true; + + case fw_1: + case fw_2: + case fw_4: + case fw_8: + return read_ctx_read_var (ctx, width, valuep); + + case fw_uleb: + case fw_sleb: + return checked_read_leb128 (ctx, width, valuep, + loc, "attribute value"); + + case fw_unknown: + ; + } + UNREACHABLE; +} + +bool +read_generic_value (read_ctx *ctx, + form_width_t width, storage_class_t storclass, + locus const &loc, uint64_t *valuep, read_ctx *blockp) +{ + uint64_t value; + if (storclass == sc_value + || storclass == sc_block) + { + if (!read_sc_value (&value, width, ctx, loc)) + return false; + if (valuep != NULL) + *valuep = value; + if (storclass == sc_value) + return true; + } + + unsigned char const *start = ctx->ptr; + if (storclass == sc_string) + { + if (!read_ctx_read_str (ctx)) + return false; + } + else if (storclass == sc_block) + { + if (!read_ctx_skip (ctx, value)) + return false; + } + + if (blockp != NULL) + { + if (!read_ctx_init_sub (blockp, ctx, start, ctx->ptr)) + return false; + } + + return true; +} diff --git a/dwarflint/checked_read.hh b/dwarflint/checked_read.hh new file mode 100644 index 00000000..c2b19e82 --- /dev/null +++ b/dwarflint/checked_read.hh @@ -0,0 +1,64 @@ +/* Pedantic checking of DWARF files + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_CHECKED_READ_HH +#define DWARFLINT_CHECKED_READ_HH + +#include "readctx.hh" +#include "locus.hh" +#include "dwarf_version.hh" + +enum error_code + { + err_ok, ///< The operation passed. + err_fatal, ///< The operation ended in unrecoverable error. + err_nohl, ///< There was an error, but low-level checks may continue. + }; + +bool read_size_extra (read_ctx *ctx, uint32_t size32, uint64_t *sizep, + int *offset_sizep, locus const &loc); + +/// Read address size and return it via address_sizep and return 0. +/// Address size may be 4 or 8; for other values it's set depending or +/// addr_64, and err_nohl is returned. +error_code read_address_size (read_ctx *ctx, bool addr_64, + int *address_sizep, locus const &loc); + +bool checked_read_uleb128 (read_ctx *ctx, uint64_t *ret, + locus const &loc, const char *what); + +bool checked_read_sleb128 (read_ctx *ctx, int64_t *ret, + locus const &loc, const char *what); + +bool checked_read_leb128 (read_ctx *ctx, form_width_t width, uint64_t *ret, + locus const &loc, const char *what); + +/// Read value depending on the form width and storage class. +bool read_sc_value (uint64_t *valuep, form_width_t width, + read_ctx *ctx, locus const &loc); + +/// Read value depending on the form width and storage class. +/// Value is returned via VALUEP, if that is non-NULL; for block +/// forms, the value is block length. Block context is returned via +/// BLOCKP, in non-NULL; for string class, the block is the string +/// itself. +bool read_generic_value (read_ctx *ctx, + form_width_t width, storage_class_t storclass, + locus const &loc, uint64_t *valuep, + read_ctx *blockp); + +#endif//DWARFLINT_CHECKED_READ_HH diff --git a/dwarflint/checkrule.cc b/dwarflint/checkrule.cc new file mode 100644 index 00000000..7f14d726 --- /dev/null +++ b/dwarflint/checkrule.cc @@ -0,0 +1,82 @@ +/* + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#include "checkrule.hh" +#include "checkdescriptor.hh" +#include "dwarflint.hh" +#include <cassert> + +checkrule::checkrule (std::string const &a_name, action_t an_action) + : _m_name (a_name) + , _m_action (an_action) + , _m_used (false) +{ +} + +checkrule_internal::checkrule_internal (std::string const &a_name, + action_t an_action) + : checkrule (a_name, an_action) +{ + mark_used (); +} + +namespace +{ + bool + rule_matches (std::string const &name, + checkdescriptor const &cd) + { + if (name == "@all") + return true; + if (name == "@none") + return false; + if (name == cd.name ()) + return true; + return cd.in_group (name); + } +} + +bool +checkrules::should_check (checkstack const &stack) const +{ + // We always allow scheduling hidden checks. Those are service + // routines that the user doesn't even see it the list of checks. + assert (!stack.empty ()); + if (stack.back ()->hidden ()) + return true; + + bool should = false; + for (const_iterator it = begin (); it != end (); ++it) + { + std::string const &rule_name = it->name (); + bool nflag = it->action () == checkrule::request; + if (nflag == should && it->used ()) + continue; + + for (checkstack::const_iterator jt = stack.begin (); + jt != stack.end (); ++jt) + if (rule_matches (rule_name, **jt)) + { + it->mark_used (); + //std::cout << " rule: " << rule_name << " " << nflag << std::endl; + should = nflag; + break; + } + } + + return should; +} diff --git a/dwarflint/checkrule.hh b/dwarflint/checkrule.hh new file mode 100644 index 00000000..7c536d98 --- /dev/null +++ b/dwarflint/checkrule.hh @@ -0,0 +1,62 @@ +/* + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_CHECKRULE_HH +#define DWARFLINT_CHECKRULE_HH + +#include <vector> +#include <string> +#include "dwarflint_i.hh" + +struct checkrule +{ + enum action_t + { + forbid, + request, + }; + +private: + std::string _m_name; + action_t _m_action; + mutable bool _m_used; + +public: + checkrule (std::string const &name, action_t action); + + std::string const &name () const { return _m_name; } + action_t action () const { return _m_action; } + bool used () const { return _m_used; } + void mark_used () const { _m_used = true; } +}; + +// These are like normal rules, but they are initially marked as used +// so as not to be warned about. +struct checkrule_internal + : public checkrule +{ + checkrule_internal (std::string const &name, action_t action); +}; + +class checkrules + : public std::vector<checkrule> +{ +public: + bool should_check (checkstack const &stack) const; +}; + +#endif//DWARFLINT_CHECKRULE_HH diff --git a/dwarflint/checks.hh b/dwarflint/checks.hh new file mode 100644 index 00000000..7039b633 --- /dev/null +++ b/dwarflint/checks.hh @@ -0,0 +1,99 @@ +/* + 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_CHECKS_HH +#define DWARFLINT_CHECKS_HH + +#include "locus.hh" +#include "dwarflint.hh" +#include "checkdescriptor.hh" +#include "messages.hh" +#include "check_registrar.hh" + +#include <string> +#include <cassert> + +struct check_base +{ + struct failed {}; + struct unscheduled: public failed {}; + virtual ~check_base () {} +}; + +template<class T> +class check + : public check_base +{ +private: + template <class X> + friend X *dwarflint::check (checkstack &stack); + static void const *key () + { + return reinterpret_cast <void const *> (&key); + } +}; + +class popper +{ + checkstack &_m_guard_stack; + +public: + popper (checkstack &guard_stack) + : _m_guard_stack (guard_stack) + {} + + ~popper () + { + _m_guard_stack.pop_back (); + } +}; + +template <class T> +inline T * +dwarflint::toplev_check (checkstack &stack, T *) +{ + try + { + return check<T> (stack); + } + catch (check_base::failed const &f) + { + return NULL; + } +} + +template <class T> +struct reg + : public main_check_item +{ + reg () + { + dwarflint::main_registrar ()->push_back (this); + } + + virtual void run (checkstack &stack, dwarflint &lint) + { + lint.toplev_check <T> (stack); + } + + virtual checkdescriptor const *descriptor () const + { + return T::descriptor (); + } +}; + +#endif//DWARFLINT_CHECKS_HH diff --git a/dwarflint/checks_i.hh b/dwarflint/checks_i.hh new file mode 100644 index 00000000..2d405382 --- /dev/null +++ b/dwarflint/checks_i.hh @@ -0,0 +1,2 @@ +struct check_base; +class die_check; diff --git a/dwarflint/coverage.cc b/dwarflint/coverage.cc new file mode 100644 index 00000000..9ce6954c --- /dev/null +++ b/dwarflint/coverage.cc @@ -0,0 +1,367 @@ +/* Implementation of coverage analysis. + + Copyright (C) 2008, 2009, 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "coverage.hh" +#include "pri.hh" + +#include <stdbool.h> +#include <assert.h> +#include <string.h> +#include <inttypes.h> + +coverage::const_iterator +coverage::find (uint64_t start) const +{ + assert (!empty ()); + + size_t a = 0; + size_t b = size (); + + while (a < b) + { + size_t i = (a + b) / 2; + cov_range const &r = at (i); + + if (r.start > start) + b = i; + else if (r.start < start) + a = i + 1; + else + return begin () + i; + } + + return begin () + a; +} + +coverage::iterator +coverage::find (uint64_t start) +{ + const_iterator it = const_cast<coverage const *> (this)->find (start); + return begin () + (it - begin ()); +} + +void +coverage::add (uint64_t start, uint64_t length) +{ + cov_range nr = (struct cov_range){start, length}; + if (empty ()) + { + push_back (nr); + return; + } + + iterator r_i = find (start); + + cov_range *to_insert = &nr; + cov_range *coalesce = &nr; + + // Coalesce with previous range? + if (r_i > begin ()) + { + iterator p_i = r_i - 1; + if (coalesce->start <= p_i->start + p_i->length) + { + uint64_t coalesce_end = coalesce->start + coalesce->length; + if (coalesce_end > p_i->start + p_i->length) + { + p_i->length = coalesce_end - p_i->start; + coalesce = &*p_i; + } + else + coalesce = NULL; + to_insert = NULL; + } + } + + // Coalesce with one or more following ranges? + if (coalesce != NULL && r_i != end ()) + { + iterator p_i = r_i; + while (p_i != end () + && coalesce->start + coalesce->length >= p_i->start) + { + uint64_t p_end = p_i->start + p_i->length; + if (p_end > coalesce->start + coalesce->length) + coalesce->length = p_end - coalesce->start; + if (to_insert != NULL) + { + *p_i = *to_insert; + to_insert = NULL; + coalesce = &*p_i; + assert (p_i == r_i); + ++r_i; // keep this element + } + ++p_i; + } + if (p_i > r_i) + erase (r_i, p_i); + } + + if (to_insert != NULL) + { + size_t idx = r_i - begin (); + insert (begin () + idx, *to_insert); + } +} + +bool +coverage::remove (uint64_t start, + uint64_t length) +{ + uint64_t a_end = start + length; + if (empty () || start == a_end) + return false; + + iterator r_i = find (start); + iterator erase_begin_i = end (); + iterator erase_end_i = r_i; // end exclusive + bool overlap = false; + + // Cut from previous range? + if (r_i > begin ()) + { + iterator p_i = r_i - 1; + if (start < p_i->start + p_i->length) + { + uint64_t r_end = p_i->start + p_i->length; + // Do we cut the beginning of the range? + if (start == p_i->start) + p_i->length = a_end >= r_end ? 0 : r_end - a_end; + else + { + p_i->length = start - p_i->start; + // Do we shoot a hole in that range? + if (a_end < r_end) + { + add (a_end, r_end - a_end); + return true; + } + } + + overlap = true; + if (p_i->length == 0) + erase_begin_i = p_i; + } + } + + if (erase_begin_i == end ()) + erase_begin_i = r_i; + + // Cut from next range? + while (r_i < end () && r_i->start < a_end) + { + overlap = true; + if (a_end >= r_i->start + r_i->length) + { + ++erase_end_i; + ++r_i; + } + else + { + uint64_t end0 = r_i->start + r_i->length; + r_i->length = end0 - a_end; + r_i->start = a_end; + assert (end0 == r_i->start + r_i->length); + } + } + + // Did we cut out anything completely? + if (erase_end_i > erase_begin_i) + erase (erase_begin_i, erase_end_i); + + return overlap; +} + +bool +coverage::is_covered (uint64_t start, uint64_t length) const +{ + if (empty ()) + return false; + + const_iterator r_i = find (start); + uint64_t a_end = start + length; + if (r_i < end ()) + if (start >= r_i->start) + return a_end <= r_i->start + r_i->length; + + if (r_i > begin ()) + { + --r_i; + return a_end <= r_i->start + r_i->length; + } + + return false; +} + +char * +range_fmt (char *buf, size_t buf_size, uint64_t start, uint64_t end) +{ + std::stringstream ss; + ss << pri::range (start, end); + std::string s = ss.str (); + strncpy (buf, s.c_str (), buf_size); + return buf; +} + +namespace +{ + bool overlaps (uint64_t start, uint64_t end, cov_range const &r) + { + return (start >= r.start && start < r.start + r.length) + || (end > r.start && end <= r.start + r.length) + || (start < r.start && end > r.start + r.length); + } +} + +bool +coverage::is_overlap (uint64_t start, uint64_t length) const +{ + if (empty ()) + return false; + if (length == 0) + return is_covered (start, length); + + uint64_t a_end = start + length; + const_iterator r_i = find (start); + + if (r_i < end () && overlaps (start, a_end, *r_i)) + return true; + + if (r_i > begin ()) + return overlaps (start, a_end, *--r_i); + + return false; +} + +bool +coverage::find_holes (uint64_t start, uint64_t length, + bool (*hole)(uint64_t start, uint64_t length, + void *user_data), + void *user_data) const +{ + if (length == 0) + return true; + + if (empty ()) + return hole (start, length, user_data); + + if (start < front ().start) + if (!hole (start, front ().start - start, user_data)) + return false; + + for (size_t i = 0; i < size () - 1; ++i) + { + uint64_t end_i = at (i).end (); + if (!hole (end_i, at (i+1).start - end_i, user_data)) + return false; + } + + if (start + length > back ().end ()) + { + uint64_t end_last = back ().end (); + return hole (end_last, start + length - end_last, user_data); + } + + return true; +} + +bool +coverage::find_ranges (bool (*cb)(uint64_t start, uint64_t length, void *data), + void *user_data) const +{ + for (const_iterator it = begin (); it != end (); ++it) + if (!cb (it->start, it->length, user_data)) + return false; + + return true; +} + +void +coverage::add_all (coverage const &other) +{ + for (size_t i = 0; i < other.size (); ++i) + add (other[i].start, other[i].length); +} + +bool +coverage::remove_all (coverage const &other) +{ + bool ret = false; + for (size_t i = 0; i < other.size (); ++i) + if (remove (other[i].start, other[i].length)) + ret = true; + return ret; +} + +coverage +coverage::operator+ (coverage const &rhs) const +{ + coverage ret = *this; + ret.add_all (rhs); + return ret; +} + +coverage +coverage::operator- (coverage const &rhs) const +{ + coverage ret = *this; + ret.remove_all (rhs); + return ret; +} + + +bool +cov::_format_base::fmt (uint64_t start, uint64_t length) +{ + if (_m_seen) + _m_os << _m_delim; + _m_os << pri::range (start, start + length); + _m_seen = true; + return true; +} + +bool +cov::_format_base::wrap_fmt (uint64_t start, uint64_t length, void *data) +{ + _format_base *self = static_cast <_format_base *> (data); + return self->fmt (start, length); +} + +cov::_format_base::_format_base (std::string const &delim) + : _m_delim (delim), + _m_seen (false) +{} + +cov::format_ranges::format_ranges (coverage const &cov, + std::string const &delim) + : _format_base (delim) +{ + cov.find_ranges (&wrap_fmt, this); +} + +cov::format_holes::format_holes (coverage const &cov, + uint64_t start, uint64_t length, + std::string const &delim) + : _format_base (delim) +{ + cov.find_holes (start, length, &wrap_fmt, this); +} diff --git a/dwarflint/coverage.hh b/dwarflint/coverage.hh new file mode 100644 index 00000000..7d8fe285 --- /dev/null +++ b/dwarflint/coverage.hh @@ -0,0 +1,142 @@ +/* Coverage analysis, C++ support. + + Copyright (C) 2008, 2009, 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_COVERAGE_HH +#define DWARFLINT_COVERAGE_HH + +#include <string> +#include <sstream> +#include <vector> +#include <cstdint> + +/* Functions and data structures for handling of address range + coverage. We use that to find holes of unused bytes in DWARF + string table. */ + +struct cov_range +{ + uint64_t start; + uint64_t length; + + uint64_t end () const { return start + length; } + + bool operator== (cov_range const &rhs) const + { + return start == rhs.start + && length == rhs.length; + } +}; + +struct coverage + : private std::vector<cov_range> +{ + iterator find (uint64_t start); + const_iterator find (uint64_t start) const; + +public: + using std::vector<cov_range>::front; + using std::vector<cov_range>::back; + using std::vector<cov_range>::size; + using std::vector<cov_range>::empty; + + void add (uint64_t start, uint64_t length); + + /// Returns true if something was actually removed, false if whole + /// range falls into hole in coverage. + bool remove (uint64_t start, uint64_t length); + + void add_all (coverage const &other); + + // Returns true if something was actually removed, false if whole + // range falls into hole in coverage. + bool remove_all (coverage const &other); + + bool find_ranges (bool (*cb)(uint64_t start, uint64_t length, void *data), + void *data) const; + + /// Returns true if whole range ADDRESS/LENGTH is covered by COV. + /// If LENGTH is zero, it's checked that the address is inside or at + /// the edge of covered range, or that there is a zero-length range + /// at that address. + bool is_covered (uint64_t start, uint64_t length) const; + + /// Returns true if at least some of the range ADDRESS/LENGTH is + /// covered by COV. Zero-LENGTH range never overlaps. */ + bool is_overlap (uint64_t start, uint64_t length) const; + + bool find_holes (uint64_t start, uint64_t length, + bool (*cb)(uint64_t start, uint64_t length, void *data), + void *data) const; + + coverage operator+ (coverage const &rhs) const; + coverage operator- (coverage const &rhs) const; + bool operator== (coverage const &rhs) const + { + return static_cast<std::vector<cov_range> > (rhs) == *this; + } +}; + +char *range_fmt (char *buf, size_t buf_size, + uint64_t start, uint64_t end); + +namespace cov +{ + class _format_base + { + protected: + std::string const &_m_delim; + std::ostringstream _m_os; + bool _m_seen; + + inline bool fmt (uint64_t start, uint64_t length); + static bool wrap_fmt (uint64_t start, uint64_t length, void *data); + _format_base (std::string const &delim); + + public: + inline operator std::string () const + { + return _m_os.str (); + } + }; + + struct format_ranges + : public _format_base + { + format_ranges (coverage const &cov, std::string const &delim = ", "); + }; + + struct format_holes + : public _format_base + { + format_holes (coverage const &cov, uint64_t start, uint64_t length, + std::string const &delim = ", "); + }; +} + +inline std::ostream & +operator << (std::ostream &os, cov::format_ranges const &obj) +{ + return os << std::string (obj); +} + +inline std::ostream & +operator << (std::ostream &os, cov::format_holes const &obj) +{ + return os << std::string (obj); +} +#endif//DWARFLINT_COVERAGE_HH diff --git a/dwarflint/cu_coverage.cc b/dwarflint/cu_coverage.cc new file mode 100644 index 00000000..fd941786 --- /dev/null +++ b/dwarflint/cu_coverage.cc @@ -0,0 +1,42 @@ +/* Pedantic checking of DWARF files + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "cu_coverage.hh" +#include "check_debug_info.hh" +#include "check_debug_loc_range.hh" +#include <cstring> + +checkdescriptor const * +cu_coverage::descriptor () +{ + static checkdescriptor cd + (checkdescriptor::create ("cu_coverage") + .hidden ()); + return &cd; +} + +cu_coverage::cu_coverage (checkstack &stack, dwarflint &lint) + : _m_info (lint.check (stack, _m_info)) + , _m_ranges (lint.check_if (_m_info->need_ranges (), stack, _m_ranges)) + , cov (_m_info->cov () + + (_m_ranges != NULL ? _m_ranges->cov () : coverage ())) +{ +} diff --git a/dwarflint/cu_coverage.hh b/dwarflint/cu_coverage.hh new file mode 100644 index 00000000..c7a5b976 --- /dev/null +++ b/dwarflint/cu_coverage.hh @@ -0,0 +1,41 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_CU_COVERAGE_HH +#define DWARFLINT_CU_COVERAGE_HH + +#include "check_debug_info_i.hh" +#include "check_debug_loc_range_i.hh" +#include "coverage.hh" +#include "checks.hh" + +/** The pass for finalizing cu_coverage. */ +class cu_coverage + : public check<cu_coverage> +{ + check_debug_info *_m_info; + check_debug_ranges *_m_ranges; + +public: + static checkdescriptor const *descriptor (); + + coverage cov; + + cu_coverage (checkstack &stack, dwarflint &lint); +}; + +#endif//DWARFLINT_CU_COVERAGE_HH diff --git a/dwarflint/cu_coverage_i.hh b/dwarflint/cu_coverage_i.hh new file mode 100644 index 00000000..01d567f3 --- /dev/null +++ b/dwarflint/cu_coverage_i.hh @@ -0,0 +1 @@ +class cu_coverage; diff --git a/dwarflint/die_locus.cc b/dwarflint/die_locus.cc new file mode 100644 index 00000000..a6a1667c --- /dev/null +++ b/dwarflint/die_locus.cc @@ -0,0 +1,45 @@ +/* + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "die_locus.hh" +#include "pri.hh" + +char const * +locus_simple_fmt::cu_n () +{ + return "CU"; +} + +std::string +die_locus::format (bool brief) const +{ + std::stringstream ss; + if (!brief) + ss << section_name[sec_info] << ": "; + + ss << "DIE 0x" << std::hex << _m_offset; + + if (_m_attrib_name != -1) + ss << ", attr. " << pri::attr_name (_m_attrib_name); + + return ss.str (); +} + diff --git a/dwarflint/die_locus.hh b/dwarflint/die_locus.hh new file mode 100644 index 00000000..bff85bea --- /dev/null +++ b/dwarflint/die_locus.hh @@ -0,0 +1,60 @@ +/* + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifndef _DWARFLINT_DIE_LOCUS_H_ +#define _DWARFLINT_DIE_LOCUS_H_ + +#include "locus.hh" +#include "../libdw/c++/dwarf" + +namespace locus_simple_fmt +{ + char const *cu_n (); +}; + +typedef fixed_locus<sec_info, + locus_simple_fmt::cu_n, + locus_simple_fmt::dec> cu_locus; + +class die_locus + : public locus +{ + Dwarf_Off _m_offset; + int _m_attrib_name; + +public: + explicit die_locus (Dwarf_Off offset = -1, int attrib_name = -1) + : _m_offset (offset) + , _m_attrib_name (attrib_name) + {} + + template <class T> + explicit die_locus (T const &die, int attrib_name = -1) + : _m_offset (die.offset ()) + , _m_attrib_name (attrib_name) + {} + + void + set_attrib_name (int attrib_name) + { + _m_attrib_name = attrib_name; + } + + std::string format (bool brief = false) const; +}; + +#endif /* _DWARFLINT_DIE_LOCUS_H_ */ diff --git a/dwarflint/dwarf_2.cc b/dwarflint/dwarf_2.cc new file mode 100644 index 00000000..c73898e0 --- /dev/null +++ b/dwarflint/dwarf_2.cc @@ -0,0 +1,169 @@ +/* Pedantic checking of DWARF files + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#include "dwarf_version-imp.hh" +#include "dwarf_2.hh" +#include "../libdw/dwarf.h" + +namespace +{ + typedef preset_attribute<cl_reference, cl_constant> const_or_ref_attribute; + + struct dwarf_2_attributes + : public attribute_table + { + dwarf_2_attributes () + { + // Note about location descriptions in DWARF 2 (and 3). In + // DWARF 2, location expressions can have classes of cl_constant + // or cl_block. But we need to tell those block expressions + // from any old block attribute to validate the expression, and + // those constants from any old number to validate the + // reference. So we retrofit all the DW_FORM_block* forms and + // appropriate attributes with cl_exprloc form DWARF 4 and + // cl_loclistptr (even though in DWARF 4 it's actually only + // DW_FORM_exprloc that has this class). + // + // Similarly with cl_lineptr and cl_macptr. cl_rangelistptr + // wasn't introduced until DWARF 3. + + add (ref_attribute (DW_AT_sibling)); + add (location_attribute (DW_AT_location)); + add (string_attribute (DW_AT_name)); + add (const_attribute (DW_AT_ordering)); + add (const_attribute (DW_AT_byte_size)); + add (const_attribute (DW_AT_bit_offset)); + add (const_attribute (DW_AT_bit_size)); + add (attribute (DW_AT_stmt_list, cl_lineptr)); + add (addr_attribute (DW_AT_low_pc)); + add (addr_attribute (DW_AT_high_pc)); + add (const_attribute (DW_AT_language)); + add (ref_attribute (DW_AT_discr)); + add (const_attribute (DW_AT_discr_value)); + add (const_attribute (DW_AT_visibility)); + add (ref_attribute (DW_AT_import)); + add (location_attribute (DW_AT_string_length)); + add (ref_attribute (DW_AT_common_reference)); + add (string_attribute (DW_AT_comp_dir)); + add (attribute (DW_AT_const_value, + dw_class_set (cl_string, cl_constant, cl_block))); + add (ref_attribute (DW_AT_containing_type)); + add (ref_attribute (DW_AT_default_value)); + add (const_attribute (DW_AT_inline)); + add (flag_attribute (DW_AT_is_optional)); + add (const_or_ref_attribute (DW_AT_lower_bound)); + add (string_attribute (DW_AT_producer)); + add (flag_attribute (DW_AT_prototyped)); + add (location_attribute (DW_AT_return_addr)); + add (const_attribute (DW_AT_start_scope)); + add (const_attribute (DW_AT_bit_stride)); + add (const_or_ref_attribute (DW_AT_upper_bound)); + add (ref_attribute (DW_AT_abstract_origin)); + add (const_attribute (DW_AT_accessibility)); + add (const_attribute (DW_AT_address_class)); + add (flag_attribute (DW_AT_artificial)); + add (ref_attribute (DW_AT_base_types)); + add (const_attribute (DW_AT_calling_convention)); + add (const_or_ref_attribute (DW_AT_count)); + add (static_location_attribute (DW_AT_data_member_location)); + add (const_attribute (DW_AT_decl_column)); + add (const_attribute (DW_AT_decl_file)); + add (const_attribute (DW_AT_decl_line)); + add (flag_attribute (DW_AT_declaration)); + add (block_attribute (DW_AT_discr_list)); + add (const_attribute (DW_AT_encoding)); + add (flag_attribute (DW_AT_external)); + add (location_attribute (DW_AT_frame_base)); + add (ref_attribute (DW_AT_friend)); + add (const_attribute (DW_AT_identifier_case)); + add (attribute (DW_AT_macro_info, cl_macptr)); + add (block_attribute (DW_AT_namelist_item)); + add (ref_attribute (DW_AT_priority)); + add (location_attribute (DW_AT_segment)); + add (ref_attribute (DW_AT_specification)); + add (location_attribute (DW_AT_static_link)); + add (ref_attribute (DW_AT_type)); + add (location_attribute (DW_AT_use_location)); + add (flag_attribute (DW_AT_variable_parameter)); + add (const_attribute (DW_AT_virtuality)); + add (static_location_attribute (DW_AT_vtable_elem_location)); + } + }; + + struct exprloc_form + : public preset_form<sc_block, cl_exprloc, cl_block> + { + exprloc_form (int a_name, form_width_t a_width) + : preset_form<sc_block, cl_exprloc, cl_block> (a_name, a_width) + {} + }; + + struct dwarf_2_forms + : public form_table + { + dwarf_2_forms () + { + add (exprloc_form (DW_FORM_block, fw_uleb)); + add (exprloc_form (DW_FORM_block1, fw_1)); + add (exprloc_form (DW_FORM_block2, fw_2)); + add (exprloc_form (DW_FORM_block4, fw_4)); + + // These constant forms can in theory, in legal DWARF 2, + // represent various pointers. + typedef preset_form<sc_value, + cl_constant, cl_lineptr, cl_loclistptr, + cl_macptr> dw2_data_form; + + add (dw2_data_form (DW_FORM_data1, fw_1)); + add (dw2_data_form (DW_FORM_data2, fw_2)); + add (dw2_data_form (DW_FORM_data4, fw_4)); + add (dw2_data_form (DW_FORM_data8, fw_8)); + add (dw2_data_form (DW_FORM_sdata, fw_sleb)); + add (dw2_data_form (DW_FORM_udata, fw_uleb)); + + add (flag_form (DW_FORM_flag, fw_1)); + + add (ref_form (DW_FORM_ref1, fw_1)); + add (ref_form (DW_FORM_ref2, fw_2)); + add (ref_form (DW_FORM_ref4, fw_4)); + add (ref_form (DW_FORM_ref8, fw_8, fb_64)); + add (ref_form (DW_FORM_ref_udata, fw_uleb)); + + add (string_form (DW_FORM_string)); + add (offset_form (DW_FORM_strp, cl_string)); + add (address_form (DW_FORM_addr, cl_address)); + add (address_form (DW_FORM_ref_addr, cl_reference)); + + add (form (DW_FORM_indirect, cl_indirect, fw_uleb, sc_value)); + } + }; + + struct dwarf_2_t + : public std_dwarf + { + dwarf_2_t () + : std_dwarf (dwarf_2_attributes (), dwarf_2_forms ()) + {} + }; +} + +dwarf_version const * +dwarf_2 () +{ + static dwarf_2_t dw; + return &dw; +} diff --git a/dwarflint/dwarf_2.hh b/dwarflint/dwarf_2.hh new file mode 100644 index 00000000..84f11f26 --- /dev/null +++ b/dwarflint/dwarf_2.hh @@ -0,0 +1,26 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_DWARF_2_HH +#define DWARFLINT_DWARF_2_HH + +#include "dwarf_version_i.hh" + +dwarf_version const *dwarf_2 (); + +#endif//DWARFLINT_DWARF_2_HH diff --git a/dwarflint/dwarf_3.cc b/dwarflint/dwarf_3.cc new file mode 100644 index 00000000..913029c3 --- /dev/null +++ b/dwarflint/dwarf_3.cc @@ -0,0 +1,141 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#include "dwarf_version-imp.hh" +#include "dwarf_2.hh" +#include "dwarf_3.hh" +#include "../libdw/dwarf.h" +#include <cassert> + +namespace +{ + struct dwarf_3_attributes + : public attribute_table + { + dwarf_3_attributes () + { + add (location_attribute (DW_AT_location)); + add (dynval_attribute (DW_AT_byte_size)); + add (dynval_attribute (DW_AT_bit_offset)); + add (dynval_attribute (DW_AT_bit_size)); + add (location_attribute (DW_AT_string_length)); + add (dynval_attribute (DW_AT_lower_bound)); + add (location_attribute (DW_AT_return_addr)); + + // Note, DWARF 3 claims only a const class for DW_AT_bit_stride, + // but from 2.19 it's clear that this is an omission. + add (dynval_attribute (DW_AT_bit_stride)); + + add (dynval_attribute (DW_AT_upper_bound)); + add (dynval_attribute (DW_AT_count)); + add (attribute (DW_AT_data_member_location, + dw_class_set (cl_exprloc, cl_constant, cl_loclistptr))); + add (location_attribute (DW_AT_frame_base)); + add (location_attribute (DW_AT_segment)); + add (location_attribute (DW_AT_static_link)); + add (location_attribute (DW_AT_use_location)); + add (location_attribute (DW_AT_vtable_elem_location)); + add (dynval_attribute (DW_AT_allocated)); + add (dynval_attribute (DW_AT_associated)); + add (attribute (DW_AT_data_location, cl_exprloc)); + add (dynval_attribute (DW_AT_byte_stride)); + add (addr_attribute (DW_AT_entry_pc)); + add (flag_attribute (DW_AT_use_UTF8)); + add (ref_attribute (DW_AT_extension)); + add (attribute (DW_AT_ranges, cl_rangelistptr)); + add (attribute (DW_AT_trampoline, + dw_class_set (cl_address, cl_flag, + cl_reference, cl_string))); + add (const_attribute (DW_AT_call_column)); + add (const_attribute (DW_AT_call_file)); + add (const_attribute (DW_AT_call_line)); + add (string_attribute (DW_AT_description)); + add (const_attribute (DW_AT_binary_scale)); + add (const_attribute (DW_AT_decimal_scale)); + add (ref_attribute (DW_AT_small)); + add (const_attribute (DW_AT_decimal_sign)); + add (const_attribute (DW_AT_digit_count)); + add (string_attribute (DW_AT_picture_string)); + add (flag_attribute (DW_AT_mutable)); + add (flag_attribute (DW_AT_threads_scaled)); + add (flag_attribute (DW_AT_explicit)); + add (ref_attribute (DW_AT_object_pointer)); + add (const_attribute (DW_AT_endianity)); + add (flag_attribute (DW_AT_elemental)); + add (flag_attribute (DW_AT_pure)); + add (flag_attribute (DW_AT_recursive)); + } + }; + + struct dwarf_3_forms + : public form_table + { + dwarf_3_forms () + { + add (offset_form (DW_FORM_ref_addr, cl_reference)); + + // In DWARF 2 we made all the const forms into various cl_*ptr, + // since that's how the standard was worded: it allowed + // DW_AT_location to have any constant form. In DWARF 3, only + // data4 and data8 are like this. In addition, these two can + // also be cl_rangelistptr. + typedef preset_form<sc_value, + cl_constant, cl_lineptr, cl_loclistptr, + cl_macptr, cl_rangelistptr> dw3_data_form; + + add (const_form (DW_FORM_data1, fw_1)); + add (const_form (DW_FORM_data2, fw_2)); + add (dw3_data_form (DW_FORM_data4, fw_4)); + add (dw3_data_form (DW_FORM_data8, fw_8)); + add (const_form (DW_FORM_sdata, fw_sleb)); + add (const_form (DW_FORM_udata, fw_uleb)); + } + }; + + struct dwarf_3_ext_t + : public std_dwarf + { + dwarf_3_ext_t () + : std_dwarf (dwarf_3_attributes (), dwarf_3_forms ()) + {} + + dw_class + ambiguous_class (__attribute__ ((unused)) form const *form, + attribute const *attribute, + dw_class_set const &candidates) const + { + assert (attribute->name () == DW_AT_data_member_location); + assert (candidates == dw_class_set (cl_constant, cl_loclistptr)); + return cl_loclistptr; + } + }; +} + +dwarf_version const * +dwarf_3_ext () +{ + static dwarf_3_ext_t dw; + return &dw; +} + +dwarf_version const * +dwarf_3 () +{ + static dwarf_version const *dw = + dwarf_version::extend (dwarf_2 (), dwarf_3_ext ()); + return dw; +} diff --git a/dwarflint/dwarf_3.hh b/dwarflint/dwarf_3.hh new file mode 100644 index 00000000..c3e017a4 --- /dev/null +++ b/dwarflint/dwarf_3.hh @@ -0,0 +1,30 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_DWARF_3_HH +#define DWARFLINT_DWARF_3_HH + +#include "dwarf_version_i.hh" + +/// Pure DWARF 3 extension. +dwarf_version const *dwarf_3_ext (); + +/// DWARF 3 and below. +dwarf_version const *dwarf_3 (); + +#endif//DWARFLINT_DWARF_3_HH diff --git a/dwarflint/dwarf_4.cc b/dwarflint/dwarf_4.cc new file mode 100644 index 00000000..e398f769 --- /dev/null +++ b/dwarflint/dwarf_4.cc @@ -0,0 +1,97 @@ +/* Pedantic checking of DWARF files + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#include "dwarf_version-imp.hh" +#include "dwarf_3.hh" +#include "dwarf_4.hh" +#include "../libdw/dwarf.h" + +namespace +{ + struct dwarf_4_attributes + : public attribute_table + { + dwarf_4_attributes () + { + add (attribute (DW_AT_high_pc, dw_class_set (cl_address, cl_constant))); + add (ref_attribute (DW_AT_namelist_item)); + add (ref_attribute (DW_AT_signature)); + add (flag_attribute (DW_AT_main_subprogram)); + add (const_attribute (DW_AT_data_bit_offset)); + add (flag_attribute (DW_AT_const_expr)); + add (flag_attribute (DW_AT_enum_class)); + add (string_attribute (DW_AT_linkage_name)); + } + }; + + struct exprloc_form + : public preset_form<sc_block, cl_exprloc> + { + exprloc_form (int a_name) + : preset_form<sc_block, cl_exprloc> (a_name, fw_uleb) + {} + }; + + struct dwarf_4_forms + : public form_table + { + dwarf_4_forms () + { + add (const_form (DW_FORM_data4, fw_4)); + add (const_form (DW_FORM_data8, fw_8)); + add (offset_form (DW_FORM_sec_offset, + dw_class_set (cl_lineptr, cl_loclistptr, + cl_macptr, cl_rangelistptr))); + add (exprloc_form (DW_FORM_exprloc)); + add (flag_form (DW_FORM_flag_present, fw_0)); + + // http://wiki.dwarfstd.org/index.php?title=COMDAT_Type_Sections + add (ref_form (DW_FORM_ref_sig8, fw_8)); + + // In DWARF 2 we claim that blocks are exprloc forms (see + // comment there). Revert back to pure blocks now that we have + // proper support for cl_exprloc. + add (block_form (DW_FORM_block, fw_uleb)); + add (block_form (DW_FORM_block1, fw_1)); + add (block_form (DW_FORM_block2, fw_2)); + add (block_form (DW_FORM_block4, fw_4)); + } + }; + + struct dwarf_4_ext_t + : public std_dwarf + { + dwarf_4_ext_t () + : std_dwarf (dwarf_4_attributes (), dwarf_4_forms ()) + {} + }; +} + +dwarf_version const * +dwarf_4_ext () +{ + static dwarf_4_ext_t dw; + return &dw; +} + +dwarf_version const * +dwarf_4 () +{ + static dwarf_version const *dw = + dwarf_version::extend (dwarf_3 (), dwarf_4_ext ()); + return dw; +} diff --git a/dwarflint/dwarf_4.hh b/dwarflint/dwarf_4.hh new file mode 100644 index 00000000..50d32c9a --- /dev/null +++ b/dwarflint/dwarf_4.hh @@ -0,0 +1,30 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_DWARF_4_HH +#define DWARFLINT_DWARF_4_HH + +#include "dwarf_version_i.hh" + +/// Pure DWARF 4 extension. +dwarf_version const *dwarf_4_ext (); + +/// DWARF 4 and below. +dwarf_version const *dwarf_4 (); + +#endif//DWARFLINT_DWARF_4_HH diff --git a/dwarflint/dwarf_gnu.cc b/dwarflint/dwarf_gnu.cc new file mode 100644 index 00000000..97f28d05 --- /dev/null +++ b/dwarflint/dwarf_gnu.cc @@ -0,0 +1,117 @@ +/* Pedantic checking of DWARF files + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#include "dwarf_version-imp.hh" +#include "../libdw/dwarf.h" + +namespace +{ + struct dwarf_gnu_attributes + : public attribute_table + { + void unused (__attribute__ ((unused)) attribute const &attrib) const {} + dwarf_gnu_attributes () + { + // It's rather hard to find any reference on some of the GNU + // extensions. So we simply mark these with unused. + + // The MIPS documentation claims that these are "apparently only + // output in DWARF1, not DWARF2". I found nothing particular + // about how these are used. + unused (const_attribute (DW_AT_sf_names)); + unused (const_attribute (DW_AT_src_info)); + unused (const_attribute (DW_AT_mac_info)); + unused (const_attribute (DW_AT_src_coords)); + unused (const_attribute (DW_AT_body_begin)); + unused (const_attribute (DW_AT_body_end)); + + add (flag_attribute (DW_AT_GNU_vector)); + + // xxx these are glass cl_GNU_mutexlistptr. data4 and data8 are + // supposed to have this class. So how do we smuggle this class + // to whatever DW_FORM_data4 and DW_FORM_data8 have in current + // version? For now, just claim it's plain old constant. + // http://gcc.gnu.org/wiki/ThreadSafetyAnnotationsInDWARF + add (const_attribute (DW_AT_GNU_guarded_by)); + add (const_attribute (DW_AT_GNU_pt_guarded_by)); + add (const_attribute (DW_AT_GNU_guarded)); + add (const_attribute (DW_AT_GNU_pt_guarded)); + add (const_attribute (DW_AT_GNU_locks_excluded)); + add (const_attribute (DW_AT_GNU_exclusive_locks_required)); + add (const_attribute (DW_AT_GNU_shared_locks_required)); + + // Contains a shallower 8-byte signature of the type described + // in the type unit. This is nominally a const_attribute, but + // we do the checking ourselves in form_allowed. + // http://gcc.gnu.org/wiki/DwarfSeparateTypeInfo + add (const_attribute (DW_AT_GNU_odr_signature)); + + // http://gcc.gnu.org/wiki/TemplateParmsDwarf + add (string_attribute (DW_AT_GNU_template_name)); + + // GNU extensions for representation of call sites + // http://www.dwarfstd.org/ShowIssue.php?issue=100909.2 + add (attribute (DW_AT_GNU_call_site_value, cl_exprloc)); + add (attribute (DW_AT_GNU_call_site_data_value, cl_exprloc)); + add (attribute (DW_AT_GNU_call_site_target, cl_exprloc)); + add (attribute (DW_AT_GNU_call_site_target_clobbered, cl_exprloc)); + add (flag_attribute (DW_AT_GNU_tail_call)); + add (flag_attribute (DW_AT_GNU_all_tail_call_sites)); + add (flag_attribute (DW_AT_GNU_all_call_sites)); + add (flag_attribute (DW_AT_GNU_all_source_call_sites)); + } + }; + + struct dwarf_gnu_ext_t + : public std_dwarf + { + dwarf_gnu_ext_t () + : std_dwarf (dwarf_gnu_attributes (), form_table ()) + {} + + virtual bool + form_allowed (attribute const *attr, form const *form) const + { + // Without -gstrict-dwarf gcc allows usage of attributes from + // later versions. One strange case is DW_AT_ranges in version 2 + // since that version doesn't actually define a rangelistptr + // class. So we just allow data4 or data8 here. + if (attr->name () == DW_AT_ranges) + { + form_width_t width = form->width (NULL); + return (form->classes ()[cl_constant] + && (width == fw_4 || width == fw_8)); + } + + // upper_bound is allowed to also be a block (dwarf3 in dwarf2). + if (attr->name () == DW_AT_upper_bound) + return form->classes ()[cl_block]; + + if (attr->name () == DW_AT_GNU_odr_signature) + return form->classes ()[cl_constant] && form->width (NULL) == fw_8; + else + return std_dwarf::form_allowed (attr, form); + } + }; +} + +dwarf_version const * +dwarf_gnu_ext () +{ + static dwarf_gnu_ext_t dw; + return &dw; +} diff --git a/dwarflint/dwarf_gnu.hh b/dwarflint/dwarf_gnu.hh new file mode 100644 index 00000000..1481c8cd --- /dev/null +++ b/dwarflint/dwarf_gnu.hh @@ -0,0 +1,26 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_DWARF_GNU_HH +#define DWARFLINT_DWARF_GNU_HH + +#include "dwarf_version_i.hh" + +dwarf_version const *dwarf_gnu_ext (); + +#endif//DWARFLINT_DWARF_GNU_HH diff --git a/dwarflint/dwarf_mips.cc b/dwarflint/dwarf_mips.cc new file mode 100644 index 00000000..43863335 --- /dev/null +++ b/dwarflint/dwarf_mips.cc @@ -0,0 +1,88 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#include "dwarf_version-imp.hh" +#include "../libdw/dwarf.h" + +namespace +{ + struct dwarf_mips_attributes + : public attribute_table + { + void unused (__attribute__ ((unused)) attribute const &attrib) const {} + dwarf_mips_attributes () + { + // Most of these are really just sketched, since we don't emit + // them anyway. For those in need, the documentation is in + // mips_extensions.pdf that's installed by libdwarf-devel in + // Fedora. According to that document, some forms were never + // even emitted. Those are marked as unused and not added. + // Their class is arbitrarily chosen as cl_constant. + + add (const_attribute (DW_AT_MIPS_fde)); + unused (const_attribute (DW_AT_MIPS_loop_begin)); + unused (const_attribute (DW_AT_MIPS_tail_loop_begin)); + unused (const_attribute (DW_AT_MIPS_epilog_begin)); + unused (const_attribute (DW_AT_MIPS_loop_unroll_factor)); + unused (const_attribute (DW_AT_MIPS_software_pipeline_depth)); + add (string_attribute (DW_AT_MIPS_linkage_name)); + + // [section 8.10] If DW_AT_MIPS_stride is present, the attribute + // contains a reference to a DIE which describes the location + // holding the stride, and the DW_AT_stride_size field of + // DW_TAG_array_type is ignored if present. The value of the + // stride is the number of 4 byte words between elements along + // that axis. + add (ref_attribute (DW_AT_MIPS_stride)); + + add (string_attribute (DW_AT_MIPS_abstract_name)); + + // xxx in addition, this is supposed to be CU-local reference, + // similarly to the DW_AT_sibling. An opportunity to generalize + // sibling_form_suitable. + add (ref_attribute (DW_AT_MIPS_clone_origin)); + + add (flag_attribute (DW_AT_MIPS_has_inlines)); + + // The documentation is unclear on what form these should take. + // I'm making them the same as DW_AT_byte_stride in DWARF2, in + // hopes that that's what they are supposed to be. + add (const_attribute (DW_AT_MIPS_stride_byte)); + add (const_attribute (DW_AT_MIPS_stride_elem)); + + add (ref_attribute (DW_AT_MIPS_ptr_dopetype)); + add (ref_attribute (DW_AT_MIPS_allocatable_dopetype)); + add (ref_attribute (DW_AT_MIPS_assumed_shape_dopetype)); + add (flag_attribute (DW_AT_MIPS_assumed_size)); + } + }; + + struct dwarf_mips_ext_t + : public std_dwarf + { + dwarf_mips_ext_t () + : std_dwarf (dwarf_mips_attributes (), form_table ()) + {} + }; +} + +dwarf_version const * +dwarf_mips_ext () +{ + static dwarf_mips_ext_t dw; + return &dw; +} diff --git a/dwarflint/dwarf_mips.hh b/dwarflint/dwarf_mips.hh new file mode 100644 index 00000000..0ec678fb --- /dev/null +++ b/dwarflint/dwarf_mips.hh @@ -0,0 +1,26 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_DWARF_MIPS_HH +#define DWARFLINT_DWARF_MIPS_HH + +#include "dwarf_version_i.hh" + +dwarf_version const *dwarf_mips_ext (); + +#endif//DWARFLINT_DWARF_MIPS_HH diff --git a/dwarflint/dwarf_version-imp.cc b/dwarflint/dwarf_version-imp.cc new file mode 100644 index 00000000..b4b20b56 --- /dev/null +++ b/dwarflint/dwarf_version-imp.cc @@ -0,0 +1,69 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#include "dwarf_version-imp.hh" + +template <class T> +void +dwver_index_table<T>::add (T const &emt) +{ + _m_table.insert (std::make_pair (emt.name (), emt)); +} + +template <class T> +T const * +dwver_index_table<T>::get (int f) const +{ + typename _table_t::const_iterator it = _m_table.find (f); + if (it != _m_table.end ()) + return &it->second; + else + return NULL; +} + +template class dwver_index_table<form>; +template class dwver_index_table<attribute>; + +offset_form::offset_form (int a_name, dw_class_set a_classes) + : form (a_name, a_classes, fw_offset, sc_value) +{} + +address_form::address_form (int a_name, dw_class_set a_classes) + : form (a_name, a_classes, fw_address, sc_value) +{} + +string_form::string_form (int a_name) + : preset_form<sc_string, cl_string> (a_name, fw_unknown) +{} + +std_dwarf::std_dwarf (attribute_table const &attrtab, + form_table const &formtab) + : _m_attrtab (attrtab) + , _m_formtab (formtab) +{} + +form const * +std_dwarf::get_form (int form_name) const +{ + return _m_formtab.get (form_name); +} + +attribute const * +std_dwarf::get_attribute (int attribute_name) const +{ + return _m_attrtab.get (attribute_name); +} diff --git a/dwarflint/dwarf_version-imp.hh b/dwarflint/dwarf_version-imp.hh new file mode 100644 index 00000000..8c170841 --- /dev/null +++ b/dwarflint/dwarf_version-imp.hh @@ -0,0 +1,114 @@ +/* Pedantic checking of DWARF files + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_DWARF_VERSION_IMP_HH +#define DWARFLINT_DWARF_VERSION_IMP_HH + +#include "dwarf_version.hh" +#include <map> + +template <class T> +class dwver_index_table +{ + typedef std::map<int, T> _table_t; + _table_t _m_table; + +protected: + void add (T const &emt); + +public: + T const *get (int name) const; +}; + +typedef dwver_index_table<form> form_table; +typedef dwver_index_table<attribute> attribute_table; + +template<storage_class_t StorClass, dw_class... Classes> +struct preset_form + : public form +{ + preset_form (int a_name, form_width_t a_width, + form_bitness_t a_bitness = fb_any) + : form (a_name, dw_class_set (Classes...), a_width, StorClass, a_bitness) + {} +}; + +template<dw_class... Classes> +struct preset_attribute + : public attribute +{ + preset_attribute (int a_name) + : attribute (a_name, dw_class_set (Classes...)) + {} +}; + + +struct offset_form + : public form +{ + offset_form (int a_name, dw_class_set a_classes); +}; + +struct address_form + : public form +{ + address_form (int a_name, dw_class_set a_classes); +}; + +struct string_form + : public preset_form<sc_string, cl_string> +{ + string_form (int a_name); +}; + +typedef preset_form<sc_block, cl_block> block_form; +typedef preset_form<sc_value, cl_constant> const_form; +typedef preset_form<sc_value, cl_reference> ref_form; +typedef preset_form<sc_value, cl_flag> flag_form; + +typedef preset_attribute<cl_constant> const_attribute; +typedef preset_attribute<cl_reference> ref_attribute; +typedef preset_attribute<cl_address> addr_attribute; +typedef preset_attribute<cl_string> string_attribute; +typedef preset_attribute<cl_flag> flag_attribute; +typedef preset_attribute<cl_block> block_attribute; + +// [DWARF 3, DWARF 4, section 2.19]: attributes that [...] specify a +// property [...] that is an integer value, where the value may be +// known during compilation or may be computed dynamically during +// execution. +typedef preset_attribute<cl_constant, cl_exprloc, + cl_reference> dynval_attribute; + +typedef preset_attribute<cl_exprloc, cl_loclistptr> location_attribute; +typedef preset_attribute<cl_exprloc, cl_reference> static_location_attribute; + +class std_dwarf + : public dwarf_version +{ + attribute_table const _m_attrtab; + form_table const _m_formtab; + +public: + std_dwarf (attribute_table const &attrtab, + form_table const &formtab); + + form const *get_form (int form_name) const; + attribute const *get_attribute (int attribute_name) const; +}; + +#endif//DWARFLINT_DWARF_VERSION_IMP_HH diff --git a/dwarflint/dwarf_version.cc b/dwarflint/dwarf_version.cc new file mode 100644 index 00000000..57fd4b4b --- /dev/null +++ b/dwarflint/dwarf_version.cc @@ -0,0 +1,293 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009,2010,2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +// The tables here capture attribute/allowed forms depending on DWARF +// version. Apart from standardized DWARF formats, e.g. DWARF3+GNU is +// a version of its own. + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <map> +#include <cassert> +#include <string.h> + +#include "../libdw/c++/dwarf" + +#include "dwarf_version.hh" +#include "dwarf_2.hh" +#include "dwarf_3.hh" +#include "dwarf_4.hh" +#include "dwarf_gnu.hh" +#include "dwarf_mips.hh" +#include "check_debug_info.hh" + +global_opt<void_option> + opt_nognu ("Don't use GNU extension.", "nognu"); + +dw_class_set::dw_class_set (dw_class a, dw_class b, dw_class c, + dw_class d, dw_class e) +{ +#define ADD(V) if (V != max_dw_class) (*this)[V] = true + ADD (a); + ADD (b); + ADD (c); + ADD (d); + ADD (e); +#undef ADD +} + +form::form (int a_name, dw_class_set a_classes, + form_width_t a_width, storage_class_t a_storclass, + form_bitness_t a_bitness) + : _m_name (a_name) + , _m_classes (a_classes) + , _m_width (a_width) + , _m_storclass (a_storclass) + , _m_bitness (a_bitness) +{} + +form::form (int a_name, dw_class_set a_classes, + form_width_special_t a_width, storage_class_t a_storclass, + form_bitness_t a_bitness) + : _m_name (a_name) + , _m_classes (a_classes) + , _m_width (a_width) + , _m_storclass (a_storclass) + , _m_bitness (a_bitness) +{} + +dw_class +dwarf_version::form_class (form const *form, attribute const *attribute) const +{ + assert (form != NULL); + assert (attribute != NULL); + dw_class_set result = form->classes (); + result &= attribute->classes (); + if (result.count () > 1) + { + dw_class ret = this->ambiguous_class (form, attribute, result); + assert (ret < max_dw_class); + assert (result[ret]); + return ret; + } + else if (result.count () == 1) + return static_cast<dw_class> (ffsl (result.to_ulong ()) - 1); + else + return max_dw_class; +} + +form_width_t +form::width (cu_head const *cu_head) const +{ + switch (_m_width) + { + case fw_offset: + case fw_address: + if (unlikely (cu_head == NULL)) + return fw_unknown; + if (_m_width == fw_offset) + return static_cast<form_width_t> (cu_head->offset_size); + else + return static_cast<form_width_t> (cu_head->address_size); + + default: + return static_cast<form_width_t> (_m_width); + } +} + +std::ostream & +operator << (std::ostream &os, form const &obj) +{ + return os << elfutils::dwarf::forms::identifier (obj.name ()); +} + +namespace +{ + dw_class_set + include_indirect (dw_class_set a_classes) + { + a_classes.set (cl_indirect); + return a_classes; + } +} + +attribute::attribute (int a_name, dw_class_set const &a_classes) + : _m_name (a_name) + , _m_classes (include_indirect (a_classes)) +{} + +std::ostream & +operator << (std::ostream &os, attribute const &obj) +{ + return os << elfutils::dwarf::attributes::identifier (obj.name ()); +} + + +bool +dwarf_version::form_allowed (int form) const +{ + return get_form (form) != NULL; +} + +bool +dwarf_version::form_allowed (attribute const *attr, form const *form) const +{ + dw_class_set const &attr_classes = attr->classes (); + dw_class_set const &form_classes = form->classes (); + return (attr_classes & form_classes).any (); +} + +sibling_form_suitable_t +sibling_form_suitable (dwarf_version const *ver, form const *form) +{ + if (!ver->form_allowed (ver->get_attribute (DW_AT_sibling), form)) + return sfs_invalid; + else if (form->name () == DW_FORM_ref_addr) + return sfs_long; + else + return sfs_ok; +} + +namespace +{ + class dwarf_version_union + : public dwarf_version + { + dwarf_version const *_m_source; + dwarf_version const *_m_extension; + + public: + dwarf_version_union (dwarf_version const *source, + dwarf_version const *extension) + : _m_source (source) + , _m_extension (extension) + { + } + + template<class T> + T const * + lookfor (int name, T const*(dwarf_version::*getter) (int) const) const + { + if (T const *emt = (_m_extension->*getter) (name)) + return emt; + else + return (_m_source->*getter) (name); + } + + form const * + get_form (int form_name) const + { + return lookfor (form_name, &dwarf_version::get_form); + } + + attribute const * + get_attribute (int attribute_name) const + { + return lookfor (attribute_name, &dwarf_version::get_attribute); + } + + dw_class + ambiguous_class (form const *form, + attribute const *attribute, + dw_class_set const &candidates) const + { + dw_class ret = _m_extension->ambiguous_class (form, attribute, candidates); + if (ret == max_dw_class) + ret = _m_source->ambiguous_class (form, attribute, candidates); + return ret; + } + + bool + form_allowed (attribute const *attr, form const *form) const + { + // In GNU mode any combination of new attribute/old form goes, + // in strict mode only the latest. + if (opt_nognu) + return _m_extension->form_allowed (attr, form); + else + return (_m_source->form_allowed (attr, form) + || _m_extension->form_allowed (attr, form)); + } + }; +} + +dwarf_version const * +dwarf_version::extend (dwarf_version const *source, + dwarf_version const *extension) +{ + assert (source != NULL); + assert (extension != NULL); + // this leaks, but we don't really care. These objects have to live + // the whole execution time anyway. + return new dwarf_version_union (source, extension); +} + +namespace +{ + dwarf_version const *get_ext () + { + // xxx The GNU toolchain commonly uses DW_AT_MIPS_linkage_name, + // which is part of the MIPS extensions. So that's what we + // return. I wonder how to solve this "right". We cannot simply + // request DW_AT_producer/DW_AT_language values here, since we + // need the version to know how to read these attributes in the + // first place. + + if (opt_nognu) + return dwarf_mips_ext (); + else + return dwarf_version::extend (dwarf_mips_ext (), dwarf_gnu_ext ()); + } +} + +dwarf_version const * +dwarf_version::get (unsigned version) +{ + static dwarf_version const *ext = get_ext (); + + switch (version) + { + case 2: + { + static dwarf_version const *dw = extend (dwarf_2 (), ext); + return dw; + } + + case 3: + { + static dwarf_version const *dw = extend (dwarf_3 (), ext); + return dw; + } + + case 4: + { + static dwarf_version const *dw = extend (dwarf_4 (), ext); + return dw; + } + + default: + return NULL; + }; +} + +dwarf_version const * +dwarf_version::get_latest () +{ + return get (4); +} diff --git a/dwarflint/dwarf_version.hh b/dwarflint/dwarf_version.hh new file mode 100644 index 00000000..177743a1 --- /dev/null +++ b/dwarflint/dwarf_version.hh @@ -0,0 +1,240 @@ +/* Dwarf version tables. + + 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_DWARF_VERSION_HH +#define DWARFLINT_DWARF_VERSION_HH + +#include <bitset> +#include <iosfwd> +#include "check_debug_info_i.hh" +#include "dwarf_version_i.hh" +#include "option.hh" + +extern global_opt<void_option> opt_nognu; + +enum dw_class + { + cl_indirect, + cl_address, + cl_block, + cl_constant, + cl_exprloc, + cl_flag, + cl_reference, + cl_string, + cl_loclistptr, + cl_lineptr, + cl_macptr, + cl_rangelistptr, + max_dw_class + }; + +class dw_class_set + : public std::bitset<max_dw_class> +{ +public: + dw_class_set (dw_class a = max_dw_class, dw_class b = max_dw_class, + dw_class c = max_dw_class, dw_class d = max_dw_class, + dw_class e = max_dw_class); +}; + +enum form_width_t + { + fw_0 = 0, + fw_1 = 1, + fw_2 = 2, + fw_4 = 4, + fw_8 = 8, + fw_sleb, + fw_uleb, + fw_unknown, + }; + +/// Special forms for use in DWARF tables. These never leak out to +/// the user of dwarf_version. +enum form_width_special_t + { + fw_offset = fw_unknown + 1, + fw_address, + }; + +enum storage_class_t + { + sc_value, + sc_block, + sc_string, + }; + +enum form_bitness_t + { + fb_any, ///< Form is allowed in all CUs + fb_32, ///< Form is allowed only in 32-bit CUs + fb_64, ///< Form is allowed only in 64-bit CUs + }; + +class form +{ + int const _m_name; + dw_class_set const _m_classes; + int const _m_width; + storage_class_t const _m_storclass; + form_bitness_t _m_bitness; + +public: + form (int name, dw_class_set classes, + form_width_t width, storage_class_t storclass, + form_bitness_t bitness = fb_any); + + form (int name, dw_class_set classes, + form_width_special_t width, storage_class_t storclass, + form_bitness_t bitness = fb_any); + + int + name () const + { + return _m_name; + } + + /// Answer set of DWARF classes that this form can have. + dw_class_set const & + classes () const + { + return _m_classes; + } + + /// Return width of data stored with given form. CU may be NULL if + /// you are sure that the form size doesn't depend on bitness of + /// address_size or offset_size. + /// + /// Forms for which width makes no sense (namely those in the + /// storage class of sc_string) get fw_unknown. Unknown forms get + /// an assert. + /// + /// Return value is never fw_offset or fw_address. These get + /// resolved to fw_4 or fw_8 depending on corresponding value in + /// CU_HEAD. + form_width_t width (cu_head const *cu_head) const; + + /// Return storage class of given form. Closely related to width. + storage_class_t + storage_class () const + { + return _m_storclass; + } + + form_bitness_t + bitness () const + { + return _m_bitness; + } +}; +std::ostream &operator << (std::ostream &os, form const &obj); + + +class attribute +{ + int const _m_name; + dw_class_set const _m_classes; + +public: + /// NB this ctor automatically adds cl_indirect to a_classes. + attribute (int a_name, dw_class_set const &a_classes); + + int + name () const + { + return _m_name; + } + + /// Answer set of DWARF classes that this form can have. + dw_class_set const & + classes () const + { + return _m_classes; + } +}; +std::ostream &operator << (std::ostream &os, attribute const &obj); + +class dwarf_version +{ +public: + /// Return form object for given form name. Return NULL for unknown + /// forms. + virtual form const *get_form (int form_name) const = 0; + + /// Return attribute object for given attribute name. Return NULL + /// for unknown attributes; + virtual attribute const *get_attribute (int attribute_name) const = 0; + + /// If more than one class ends up as a candidate after the request + /// to form_class, this function is called to resolve the ambiguity. + virtual dw_class + ambiguous_class (__attribute__ ((unused)) form const *form, + __attribute__ ((unused)) attribute const *attribute, + __attribute__ ((unused)) dw_class_set const &candidates) + const + { + return max_dw_class; // = we don't know. This will assert back in caller. + } + + /// Shortcut for get_form (form_name) != NULL. + bool form_allowed (int form_name) const; + + /// Figure out whether, in given DWARF version, given attribute is + /// allowed to have given form. + virtual bool form_allowed (attribute const *attr, form const *form) const + __attribute__ ((nonnull (1, 2))); + + /// Answer a class of FORM given ATTRIBUTE as a context. If there's + /// exactly one candidate class, that's the one answered. If + /// there's more, ambiguous_class is called to resolve the + /// ambiguity. If there's no candidate, then the request is + /// invalid, you must validate the form via form_allowed before + /// calling this. + dw_class form_class (form const *form, attribute const *attribute) const; + + + /// Return dwarf_version object for given DWARF version. + static dwarf_version const *get (unsigned version) + __attribute__ ((pure)); + + /// Return dwarf_version object for latest supported DWARF version. + static dwarf_version const *get_latest () + __attribute__ ((pure)); + + /// Return dwarf_version that represents SOURCE extended with + /// EXTENSION. Currently this probably has no use, but one obvious + /// candidate usage is representing GNU extensions over core DWARF. + /// Extension can contain overrides of the source dwarf_version + /// object, and these overrides take precedence. + static dwarf_version const *extend (dwarf_version const *source, + dwarf_version const *extension); +}; + +/// Check that the form is suitable for the DW_AT_sibling attribute. +enum sibling_form_suitable_t + { + sfs_ok, ///< This form is OK for DW_AT_sibling + sfs_long, ///< Global reference form, unnecessary for DW_AT_sibling + sfs_invalid, ///< This form isn't allowed at DW_AT_sibling + }; +sibling_form_suitable_t sibling_form_suitable (dwarf_version const *ver, + form const *form) + __attribute__ ((nonnull (1, 2))); + +#endif//DWARFLINT_DWARF_VERSION_HH diff --git a/dwarflint/dwarf_version_i.hh b/dwarflint/dwarf_version_i.hh new file mode 100644 index 00000000..b1a54b5b --- /dev/null +++ b/dwarflint/dwarf_version_i.hh @@ -0,0 +1,3 @@ +class form; +class attribute; +class dwarf_version; diff --git a/dwarflint/dwarflint.cc b/dwarflint/dwarflint.cc new file mode 100644 index 00000000..8067bf41 --- /dev/null +++ b/dwarflint/dwarflint.cc @@ -0,0 +1,245 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2008,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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "dwarflint.hh" +#include "messages.hh" +#include "checks.hh" +#include "check_registrar.hh" +#include "files.hh" +#include "main.hh" + +#include <fcntl.h> +#include <cstring> +#include <cerrno> +#include <stdexcept> + +std::ostream & +operator << (std::ostream &o, checkstack const &stack) +{ + o << "{"; + for (checkstack::const_iterator it = stack.begin (); + it != stack.end (); ++it) + { + if (it != stack.begin ()) + o << ','; + o << (*it)->name (); + } + o << "}"; + return o; +} + +void +main_check_registrar::run (dwarflint &lint) +{ + for (const_iterator it = begin (); it != end (); ++it) + if ((*it)->descriptor ()->schedule ()) + { + checkstack stack; + (*it)->run (stack, lint); + } +} + +dwarflint::dwarflint (char const *a_fname, checkrules const &a_rules) + : _m_fname (a_fname) + , _m_fd (files::open (_m_fname)) + , _m_rules (a_rules) +{ + main_registrar ()->run (*this); +} + +dwarflint::~dwarflint () +{ + if (close (_m_fd) < 0) + // Not that we can do anything about it... + wr_error () << "Couldn't close the file " << _m_fname << ": " + << strerror (errno) << "." << std::endl; + for (check_map::const_iterator it = _m_checks.begin (); + it != _m_checks.end (); ++it) + delete it->second; +} + +void *const dwarflint::marker = (void *)-1; + +void * +dwarflint::find_check (void const *key) +{ + check_map::const_iterator it = _m_checks.find (key); + + if (it != _m_checks.end ()) + { + void *c = it->second; + + // We already tried to do the check, but failed. + if (c == NULL) + throw check_base::failed (); + else + // Recursive dependency! + assert (c != marker); + + return c; + } + + return NULL; +} + +main_check_registrar * +dwarflint::main_registrar () +{ + static main_check_registrar inst; + return &inst; +} + +namespace +{ + bool + be_verbose () + { + // We can hopefully assume that the option doesn't change during + // execution, so we can simply cache it this was. + static bool be_verbose = opt_list_checks.value () == "full"; + return be_verbose; + } + + template <class T> + void + list_part_checks (T const &descriptors) + { + for (typename T::const_iterator it = descriptors.begin (); + it != descriptors.end (); ++it) + if (!(*it)->hidden ()) + (*it)->list (be_verbose ()); + } +} + +void +dwarflint::list_checks () +{ + list_part_checks (dwarflint::main_registrar ()->get_descriptors ()); + + if (!be_verbose ()) + std::cout + << "Use --list-checks=full to get more detailed description." + << std::endl; +} + +static global_opt<void_option> show_progress + ("Print out checks as they are performed, their context and result.", + "show-progress"); + +namespace +{ + struct reporter + { + checkstack const &stack; + checkdescriptor const &cd; + + reporter (checkstack const &s, checkdescriptor const &a_cd); + void operator () (char const *what, bool ext = false); + }; + + reporter::reporter (checkstack const &s, checkdescriptor const &a_cd) + : stack (s) + , cd (a_cd) + { + (*this) ("...", true); + } + + void + reporter::operator () (char const *what, bool ext) + { + if (!show_progress) + return; + + if (false) + for (size_t i = 0; i < stack.size (); ++i) + std::cout << ' '; + + std::cout << cd.name () << ' ' << what; + if (ext) + std::cout << ' ' << cd.groups () << ' ' << stack; + std::cout << std::endl; + } +} + +void * +dwarflint::dispatch_check (checkstack &stack, + checkdescriptor const &cd, + void const *key, + check_base *(* create) (checkstack &, dwarflint &)) +{ + // Put a marker there indicating that we are trying to satisfy + // that dependency. + bool inserted + = _m_checks.insert (std::make_pair (key, (check_base *)marker)).second; + assert (inserted || !"duplicate key"); + +#define FAIL \ + /* Put the anchor in the table. */ \ + _m_checks[key] = NULL; \ + report ("FAIL") + + reporter report (stack, cd); + try + { + stack.push_back (&cd); + popper p (stack); + + if (!_m_rules.should_check (stack)) + throw check_base::unscheduled (); + + // Now do the check. + check_base *c = create (stack, *this); + + // On success, put the actual check object there instead of the + // marker. + _m_checks[key] = c; + report ("done"); + return c; + } + catch (check_base::unscheduled &e) + { + report ("skipped"); + _m_checks.erase (key); + throw; + } + catch (check_base::failed &e) + { + // We can assume that the check emitted error message. + FAIL; + throw; + } + catch (std::exception &e) + { + wr_error () << "A check failed: " << (cd.name () ?: "(nil)") << ": " + << e.what () << std::endl; + FAIL; + throw check_base::failed (); + } + catch (...) + { + wr_error () << "A check failed: " << (cd.name () ?: "(nil)") << "." + << std::endl; + FAIL; + throw check_base::failed (); + } + +#undef FAIL +} diff --git a/dwarflint/dwarflint.hh b/dwarflint/dwarflint.hh new file mode 100644 index 00000000..6b828834 --- /dev/null +++ b/dwarflint/dwarflint.hh @@ -0,0 +1,136 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009,2010,2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_HH +#define DWARFLINT_HH + +#include <map> +#include <vector> +#include <stdexcept> +#include <iosfwd> + +#include "../libelf/libelf.h" +#include "checks_i.hh" +#include "checkdescriptor_i.hh" +#include "checkrule.hh" +#include "check_registrar.hh" +#include "dwarflint_i.hh" +#include "highlevel_check_i.hh" + +// Classes for full-blown check passes. +struct main_check_item +{ + virtual checkdescriptor const *descriptor () const = 0; + virtual ~main_check_item () {} + virtual void run (checkstack &stack, dwarflint &lint) = 0; +}; + +class main_check_registrar + : public check_registrar_T<main_check_item> +{ +public: + friend class dwarflint; + void run (dwarflint &lint); +}; + +class checkstack + : public std::vector <checkdescriptor const *> +{}; +std::ostream &operator << (std::ostream &o, checkstack const &stack); + + +class dwarflint +{ + typedef std::map <void const *, class check_base *> check_map; + check_map _m_checks; + char const *_m_fname; + int _m_fd; + checkrules const &_m_rules; + + static void *const marker; + + // Return a pointer to check, or NULL if the check hasn't been done + // yet. Throws check_base::failed if the check was requested + // earlier but failed, or aborts program via assertion if recursion + // was detected. + void *find_check (void const *key); + + template <class T> + static check_base * + create_check_object (checkstack &stack, dwarflint &lint) + { + return new T (stack, lint); + } + + void *dispatch_check (checkstack &stack, + checkdescriptor const &cd, + void const *key, + check_base *(* create) (checkstack &, dwarflint &)); + +public: + dwarflint (char const *fname, checkrules const &rules); + ~dwarflint (); + int fd () { return _m_fd; } + char const *fname () { return _m_fname; } + + template <class T> + T * + check (checkstack &stack) + { + void const *key = T::key (); + T *c = static_cast <T *> (find_check (key)); + checkdescriptor const &cd = *T::descriptor (); + + if (c == NULL) + c = (T *)dispatch_check (stack, cd, key, &create_check_object<T>); + + return c; + } + + template <class T> + T * + check (checkstack &stack, T *) + { + return check<T> (stack); + } + + template <class T> + T *toplev_check (checkstack &stack, T *fake = NULL); + + template <class T> + T * + check_if (bool whether, checkstack &stack, + __attribute__ ((unused)) T *fake = NULL) + { + if (whether) + return check<T> (stack); + else + return NULL; + } + + checkrules const & + rules () const + { + return _m_rules; + } + + static main_check_registrar *main_registrar (); + + static void list_checks (); +}; + +#endif//DWARFLINT_HH diff --git a/dwarflint/dwarflint_i.hh b/dwarflint/dwarflint_i.hh new file mode 100644 index 00000000..9769d6bd --- /dev/null +++ b/dwarflint/dwarflint_i.hh @@ -0,0 +1,3 @@ +class checkstack; +class dwarflint; +class main_check_registrar; diff --git a/dwarflint/elf_file.hh b/dwarflint/elf_file.hh new file mode 100644 index 00000000..34fb7c4a --- /dev/null +++ b/dwarflint/elf_file.hh @@ -0,0 +1,62 @@ +/* Pedantic checking of DWARF files. + Copyright (C) 2008, 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_ELF_FILE_HH +#define DWARFLINT_ELF_FILE_HH + +#include "../libdw/libdw.h" +#include "../libebl/libebl.h" +#include "reloc.hh" + +struct sec +{ + GElf_Shdr shdr; + struct relocation_data rel; + Elf_Scn *scn; + const char *name; + + Elf_Data *data; /* May be NULL if data in this section are + missing or not substantial. */ + enum section_id id; + + sec () + : scn (NULL) + , name (NULL) + , data (NULL) + , id (sec_invalid) + {} +}; + +struct elf_file +{ + GElf_Ehdr ehdr; /* Header of underlying Elf. */ + Elf *elf; + Ebl *ebl; + + struct sec *sec; /* Array of sections. */ + size_t size; + size_t alloc; + + /* Pointers into SEC above. Maps section_id to section. */ + struct sec *debugsec[count_debuginfo_sections]; + + bool addr_64; /* True if it's 64-bit Elf. */ + bool other_byte_order; /* True if the file has a byte order + different from the host. */ +}; + +#endif/*DWARFLINT_ELF_FILE_HH*/ diff --git a/dwarflint/elf_file_i.hh b/dwarflint/elf_file_i.hh new file mode 100644 index 00000000..f1daaf0e --- /dev/null +++ b/dwarflint/elf_file_i.hh @@ -0,0 +1,4 @@ +struct sec; +struct elf_file; +struct abbrev_table; +struct cu; diff --git a/dwarflint/expected-at.cc b/dwarflint/expected-at.cc new file mode 100644 index 00000000..2811130f --- /dev/null +++ b/dwarflint/expected-at.cc @@ -0,0 +1,868 @@ +/* Pedantic checking of DWARF files. + Copyright (C) 2009, 2011 Red Hat, Inc. + This file is part of elfutils. + Written by Petr Machata <pmachata@redhat.com>, 2009. + + 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 <http://www.gnu.org/licenses/>. */ + +#include <config.h> +#include "expected.hh" +#include "../libdw/dwarf.h" + +expected_at_map::expected_at_map () +{ + std::set <int> at_set_decl; + at_set_decl.insert (DW_AT_decl_column); + at_set_decl.insert (DW_AT_decl_file); + at_set_decl.insert (DW_AT_decl_line); + + std::set <int> at_linkage_name; + at_linkage_name.insert (DW_AT_MIPS_linkage_name); + at_linkage_name.insert (DW_AT_linkage_name); + + m_map [DW_TAG_access_declaration] + .optional (at_set_decl) + .optional (DW_AT_accessibility) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + ; + + m_map[DW_TAG_array_type] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_bit_size) + .optional (DW_AT_bit_stride) + .optional (DW_AT_byte_size) + .optional (DW_AT_data_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_ordering) + .optional (DW_AT_sibling) + .optional (DW_AT_specification) + .optional (DW_AT_start_scope) + .optional (DW_AT_type) + .optional (DW_AT_visibility) + .optional (DW_AT_GNU_vector) + ; + + m_map [DW_TAG_base_type] + .optional (at_set_decl) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_binary_scale) + .optional (DW_AT_bit_offset) + .optional (DW_AT_bit_size) + .optional (DW_AT_byte_size) + .optional (DW_AT_data_bit_offset) + .optional (DW_AT_data_location) + .optional (DW_AT_decimal_scale) + .optional (DW_AT_decimal_sign) + .optional (DW_AT_description) + .optional (DW_AT_digit_count) + .optional (DW_AT_encoding) + .optional (DW_AT_endianity) + .optional (DW_AT_name) + .optional (DW_AT_picture_string) + .optional (DW_AT_sibling) + .optional (DW_AT_small) + ; + + m_map [DW_TAG_catch_block] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_high_pc) + .optional (DW_AT_low_pc) + .optional (DW_AT_ranges) + .optional (DW_AT_segment) + .optional (DW_AT_sibling) + ; + + m_map [DW_TAG_class_type] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_bit_size) + .optional (DW_AT_byte_size) + .optional (DW_AT_data_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_signature) + .optional (DW_AT_specification) + .optional (DW_AT_start_scope) + .optional (DW_AT_visibility) + .optional (DW_AT_containing_type) // XXX added to reflect reality + ; + + m_map [DW_TAG_common_block] + .optional (at_set_decl) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (at_linkage_name) + .optional (DW_AT_location) + .optional (DW_AT_name) + .optional (DW_AT_segment) + .optional (DW_AT_sibling) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_common_inclusion] + .optional (at_set_decl) + .optional (DW_AT_common_reference) + .optional (DW_AT_declaration) + .optional (DW_AT_sibling) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_compile_unit] + .optional (DW_AT_base_types) + .optional (DW_AT_comp_dir) + .optional (DW_AT_identifier_case) + .optional (DW_AT_high_pc) + .optional (DW_AT_language) + .optional (DW_AT_low_pc) + .optional (DW_AT_macro_info) + .optional (DW_AT_main_subprogram) + .optional (DW_AT_name) + .optional (DW_AT_producer) + .optional (DW_AT_ranges) + .optional (DW_AT_segment) + .optional (DW_AT_stmt_list) + .optional (DW_AT_use_UTF8) + .optional (DW_AT_entry_pc) // XXX added to reflect reality + ; + + m_map [DW_TAG_condition] + .optional (at_set_decl) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + ; + + m_map [DW_TAG_const_type] + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_data_location) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + ; + + m_map [DW_TAG_constant] + .optional (at_set_decl) + .optional (DW_AT_accessibility) + .optional (DW_AT_const_value) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_endianity) + .optional (DW_AT_external) + .optional (at_linkage_name) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_start_scope) + .optional (DW_AT_type) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_dwarf_procedure] + .optional (DW_AT_location) + ; + + m_map [DW_TAG_entry_point] + .optional (at_set_decl) + .optional (DW_AT_address_class) + .optional (DW_AT_description) + .optional (DW_AT_frame_base) + .optional (at_linkage_name) + .optional (DW_AT_low_pc) + .optional (DW_AT_name) + .optional (DW_AT_return_addr) + .optional (DW_AT_segment) + .optional (DW_AT_sibling) + .optional (DW_AT_static_link) + .optional (DW_AT_type) + .optional (DW_AT_GNU_all_tail_call_sites) + .optional (DW_AT_GNU_all_call_sites) + .optional (DW_AT_GNU_all_source_call_sites) + ; + + m_map [DW_TAG_enumeration_type] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_bit_size) + .optional (DW_AT_bit_stride) + .optional (DW_AT_byte_size) + .optional (DW_AT_byte_stride) + .optional (DW_AT_data_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_signature) + .optional (DW_AT_specification) + .optional (DW_AT_start_scope) + .optional (DW_AT_type) + .optional (DW_AT_visibility) + .optional (at_linkage_name) // GNU extension for anonymous typedef enums. + ; + + m_map [DW_TAG_enumerator] + .optional (at_set_decl) + .optional (DW_AT_const_value) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + ; + + m_map [DW_TAG_file_type] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_bit_size) + .optional (DW_AT_byte_size) + .optional (DW_AT_data_location) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_start_scope) + .optional (DW_AT_type) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_formal_parameter] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_artificial) + .optional (DW_AT_const_value) + .optional (DW_AT_default_value) + .optional (DW_AT_description) + .optional (DW_AT_endianity) + .optional (DW_AT_is_optional) + .optional (DW_AT_location) + .optional (DW_AT_name) + .optional (DW_AT_segment) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + .optional (DW_AT_variable_parameter) + ; + + m_map [DW_TAG_friend] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_friend) + .optional (DW_AT_sibling) + ; + + m_map [DW_TAG_imported_declaration] + .optional (at_set_decl) + .optional (DW_AT_accessibility) + .optional (DW_AT_description) + .optional (DW_AT_import) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_start_scope) + ; + + m_map [DW_TAG_imported_module] + .optional (at_set_decl) + .optional (DW_AT_import) + .optional (DW_AT_sibling) + .optional (DW_AT_start_scope) + ; + + m_map [DW_TAG_imported_unit] + .required (DW_AT_import) + ; + + m_map [DW_TAG_inheritance] + .optional (at_set_decl) + .optional (DW_AT_accessibility) + .optional (DW_AT_data_member_location) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + .optional (DW_AT_virtuality) + ; + + m_map [DW_TAG_inlined_subroutine] + .optional (DW_AT_abstract_origin) + .optional (DW_AT_call_column) + .optional (DW_AT_call_file) + .optional (DW_AT_call_line) + .optional (DW_AT_const_expr) + .optional (DW_AT_entry_pc) + .optional (DW_AT_high_pc) + .optional (DW_AT_low_pc) + .optional (DW_AT_ranges) + .optional (DW_AT_return_addr) + .optional (DW_AT_segment) + .optional (DW_AT_sibling) + .optional (DW_AT_start_scope) + .optional (DW_AT_trampoline) + ; + + m_map [DW_TAG_interface_type] + .optional (at_set_decl) + .optional (DW_AT_accessibility) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_start_scope) + ; + + m_map [DW_TAG_label] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_description) + .optional (DW_AT_low_pc) + .optional (DW_AT_name) + .optional (DW_AT_segment) + .optional (DW_AT_start_scope) + .optional (DW_AT_sibling) + ; + + m_map [DW_TAG_lexical_block] + .optional (DW_AT_abstract_origin) + .optional (DW_AT_description) + .optional (DW_AT_high_pc) + .optional (DW_AT_low_pc) + .optional (DW_AT_name) + .optional (DW_AT_ranges) + .optional (DW_AT_segment) + .optional (DW_AT_sibling) + ; + + // At one time gcc did emit at_linkage_name for members, but that + // has been corrected: + // http://gcc.gnu.org/ml/gcc-patches/2010-06/msg01713.html + m_map [DW_TAG_member] + .optional (at_set_decl) + .optional (DW_AT_accessibility) + .optional (DW_AT_bit_offset) + .optional (DW_AT_bit_size) + .optional (DW_AT_byte_size) + .optional (DW_AT_data_bit_offset) + .optional (DW_AT_data_member_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_mutable) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_module] + .optional (at_set_decl) + .optional (DW_AT_accessibility) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_entry_pc) + .optional (DW_AT_high_pc) + .optional (DW_AT_low_pc) + .optional (DW_AT_name) + .optional (DW_AT_priority) + .optional (DW_AT_ranges) + .optional (DW_AT_segment) + .optional (DW_AT_sibling) + .optional (DW_AT_specification) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_namelist] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_declaration) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_namelist_item] + .optional (at_set_decl) + .optional (DW_AT_namelist_item) + .optional (DW_AT_sibling) + ; + + m_map [DW_TAG_namespace] + .optional (at_set_decl) + .optional (DW_AT_description) + .optional (DW_AT_extension) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_start_scope) + ; + + m_map [DW_TAG_packed_type] + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_data_location) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + ; + + m_map [DW_TAG_partial_unit] + .optional (DW_AT_base_types) + .optional (DW_AT_comp_dir) + .optional (DW_AT_description) + .optional (DW_AT_identifier_case) + .optional (DW_AT_high_pc) + .optional (DW_AT_language) + .optional (DW_AT_low_pc) + .optional (DW_AT_macro_info) + .optional (DW_AT_name) + .optional (DW_AT_producer) + .optional (DW_AT_ranges) + .optional (DW_AT_segment) + .optional (DW_AT_stmt_list) + .optional (DW_AT_use_UTF8) + ; + + m_map [DW_TAG_pointer_type] + .optional (DW_AT_address_class) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_data_location) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + .optional (DW_AT_byte_size) // XXX added to reflect reality + ; + + m_map [DW_TAG_ptr_to_member_type] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_address_class) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_containing_type) + .optional (DW_AT_data_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + .optional (DW_AT_use_location) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_reference_type] + .optional (DW_AT_address_class) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_data_location) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + .optional (DW_AT_byte_size) // XXX added to reflect reality + ; + + m_map [DW_TAG_restrict_type] + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_data_location) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + ; + + m_map [DW_TAG_rvalue_reference_type] + .optional (at_set_decl) + .optional (DW_AT_address_class) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_data_location) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + ; + + m_map [DW_TAG_set_type] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_bit_size) + .optional (DW_AT_byte_size) + .optional (DW_AT_data_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_start_scope) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_shared_type] + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_count) + .optional (DW_AT_data_location) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + ; + + m_map [DW_TAG_string_type] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_bit_size) + .optional (DW_AT_byte_size) + .optional (DW_AT_data_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_start_scope) + .optional (DW_AT_string_length) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_structure_type] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_bit_size) + .optional (DW_AT_byte_size) + .optional (DW_AT_data_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_signature) + .optional (DW_AT_specification) + .optional (DW_AT_start_scope) + .optional (DW_AT_visibility) + .optional (DW_AT_containing_type) // XXX added to reflect reality + .optional (at_linkage_name) // GNU extension for anonymous typedef structs. + ; + + m_map [DW_TAG_subprogram] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_address_class) + .optional (DW_AT_artificial) + .optional (DW_AT_calling_convention) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_elemental) + .optional (DW_AT_entry_pc) + .optional (DW_AT_explicit) + .optional (DW_AT_external) + .optional (DW_AT_frame_base) + .optional (DW_AT_high_pc) + .optional (DW_AT_inline) + .optional (at_linkage_name) + .optional (DW_AT_low_pc) + .optional (DW_AT_name) + .optional (DW_AT_object_pointer) + .optional (DW_AT_prototyped) + .optional (DW_AT_pure) + .optional (DW_AT_ranges) + .optional (DW_AT_recursive) + .optional (DW_AT_return_addr) + .optional (DW_AT_segment) + .optional (DW_AT_sibling) + .optional (DW_AT_specification) + .optional (DW_AT_start_scope) + .optional (DW_AT_static_link) + .optional (DW_AT_trampoline) + .optional (DW_AT_type) + .optional (DW_AT_visibility) + .optional (DW_AT_virtuality) + .optional (DW_AT_vtable_elem_location) + .optional (DW_AT_containing_type) // XXX added to reflect reality + .optional (DW_AT_GNU_all_tail_call_sites) + .optional (DW_AT_GNU_all_call_sites) + .optional (DW_AT_GNU_all_source_call_sites) + ; + + m_map [DW_TAG_subrange_type] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_bit_size) + .optional (DW_AT_bit_stride) + .optional (DW_AT_byte_size) + .optional (DW_AT_byte_stride) + .optional (DW_AT_count) + .optional (DW_AT_data_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_lower_bound) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_threads_scaled) + .optional (DW_AT_type) + .optional (DW_AT_upper_bound) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_subroutine_type] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_address_class) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_data_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_prototyped) + .optional (DW_AT_sibling) + .optional (DW_AT_start_scope) + .optional (DW_AT_type) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_template_alias] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_data_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_signature) + .optional (DW_AT_start_scope) + .optional (DW_AT_type) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_template_type_parameter] + .optional (at_set_decl) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + ; + + m_map [DW_TAG_template_value_parameter ] + .optional (at_set_decl) + .optional (DW_AT_const_value) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + ; + + m_map [DW_TAG_thrown_type] + .optional (at_set_decl) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_data_location) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + ; + + m_map [DW_TAG_try_block] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_high_pc) + .optional (DW_AT_low_pc) + .optional (DW_AT_ranges) + .optional (DW_AT_segment) + .optional (DW_AT_sibling) + ; + + m_map [DW_TAG_typedef] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_data_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_start_scope) + .optional (DW_AT_type) + .optional (DW_AT_visibility) + ; + + m_map [DW_TAG_type_unit] + .optional (DW_AT_language) + ; + + m_map [DW_TAG_union_type] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_bit_size) + .optional (DW_AT_byte_size) + .optional (DW_AT_data_location) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_signature) + .optional (DW_AT_specification) + .optional (DW_AT_start_scope) + .optional (DW_AT_visibility) + .optional (at_linkage_name) // GNU extension for anonymous typedef unions. + ; + + m_map [DW_TAG_unspecified_parameters] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_artificial) + .optional (DW_AT_sibling) + ; + + m_map [DW_TAG_unspecified_type] + .optional (at_set_decl) + .optional (DW_AT_description) + .optional (DW_AT_name) + ; + + m_map [DW_TAG_variable] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_const_expr) + .optional (DW_AT_const_value) + .optional (DW_AT_declaration) + .optional (DW_AT_description) + .optional (DW_AT_endianity) + .optional (DW_AT_external) + .optional (at_linkage_name) + .optional (DW_AT_location) + .optional (DW_AT_name) + .optional (DW_AT_segment) + .optional (DW_AT_sibling) + .optional (DW_AT_specification) + .optional (DW_AT_start_scope) + .optional (DW_AT_type) + .optional (DW_AT_visibility) + .optional (DW_AT_artificial) // XXX added to reflect reality + ; + + m_map [DW_TAG_variant] + .optional (at_set_decl) + .optional (DW_AT_accessibility) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_declaration) + .optional (DW_AT_discr_list) + .optional (DW_AT_discr_value) + .optional (DW_AT_sibling) + ; + + m_map [DW_TAG_variant_part] + .optional (at_set_decl) + .optional (DW_AT_abstract_origin) + .optional (DW_AT_accessibility) + .optional (DW_AT_declaration) + .optional (DW_AT_discr) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + ; + + m_map [DW_TAG_volatile_type] + .optional (DW_AT_allocated) + .optional (DW_AT_associated) + .optional (DW_AT_data_location) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + ; + + m_map [DW_TAG_with_stmt] + .optional (DW_AT_accessibility) + .optional (DW_AT_address_class) + .optional (DW_AT_declaration) + .optional (DW_AT_high_pc) + .optional (DW_AT_location) + .optional (DW_AT_low_pc) + .optional (DW_AT_ranges) + .optional (DW_AT_segment) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + .optional (DW_AT_visibility) + ; + + // http://gcc.gnu.org/wiki/summit2010?action=AttachFile&do=get&target=jelinek.pdf + m_map [DW_TAG_GNU_call_site] + .optional (DW_AT_abstract_origin) + .optional (DW_AT_call_column) + .optional (DW_AT_call_file) + .optional (DW_AT_call_line) + .optional (DW_AT_GNU_call_site_target) + .optional (DW_AT_GNU_call_site_target_clobbered) + .required (DW_AT_low_pc) + .optional (DW_AT_sibling) + .optional (DW_AT_GNU_tail_call) + .optional (DW_AT_type) + ; + + m_map [DW_TAG_GNU_call_site_parameter] + .optional (DW_AT_abstract_origin) + .optional (DW_AT_GNU_call_site_data_value) + .optional (DW_AT_GNU_call_site_value) + .optional (DW_AT_data_location) + .required (DW_AT_location) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + .optional (DW_AT_type) + ; + + // http://gcc.gnu.org/wiki/TemplateParmsDwarf + m_map [DW_TAG_GNU_template_template_param] + .required (DW_AT_name) + .required (DW_AT_GNU_template_name) + ; + + m_map [DW_TAG_GNU_template_parameter_pack] + .optional (at_set_decl) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + ; + + m_map [DW_TAG_GNU_formal_parameter_pack] + .optional (at_set_decl) + .optional (DW_AT_name) + .optional (DW_AT_sibling) + ; + +} diff --git a/dwarflint/expected.hh b/dwarflint/expected.hh new file mode 100644 index 00000000..cf1b3c5b --- /dev/null +++ b/dwarflint/expected.hh @@ -0,0 +1,102 @@ +/* Pedantic checking of DWARF files. + Copyright (C) 2009 Red Hat, Inc. + This file is part of elfutils. + Written by Petr Machata <pmachata@redhat.com>, 2009. + + 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 <http://www.gnu.org/licenses/>. */ + +#include <map> +#include <set> +#include <stdexcept> +#include <sstream> +#include <cassert> + +#include "../libdw/c++/dwarf" + +enum optionality +{ + opt_optional = 0, // may or may not be present + opt_required, // bogus if missing + opt_expected, // suspicious if missing +}; + +template <class T> +std::string +string_of (T x) +{ + std::ostringstream o; + o << x; + return o.str(); +} + +struct expected_set +{ + typedef std::map <int, optionality> expectation_map; + +private: + expectation_map m_map; + +public: +#define DEF_FILLER(WHAT) \ + expected_set &WHAT (int attribute) \ + { \ + assert (m_map.find (attribute) == m_map.end ()); \ + m_map.insert (std::make_pair (attribute, opt_##WHAT)); \ + return *this; \ + } \ + expected_set &WHAT (std::set <int> const &attributes) \ + { \ + for (std::set <int>::const_iterator it = attributes.begin (); \ + it != attributes.end (); ++it) \ + WHAT (*it); \ + return *this; \ + } + + DEF_FILLER (required) + DEF_FILLER (expected) + DEF_FILLER (optional) +#undef DEF_FILLER + + expectation_map const &map () const + { + return m_map; + } +}; + +class expected_map +{ + typedef std::map <int, expected_set> expected_map_t; + +protected: + expected_map_t m_map; + expected_map () {} + +public: + expected_set::expectation_map const &map (int tag) const + { + expected_map_t::const_iterator it = m_map.find (tag); + if (it == m_map.end ()) + throw std::runtime_error ("Unknown tag " + + elfutils::dwarf::tags::identifier (tag)); + return it->second.map (); + } +}; + +struct expected_at_map + : public expected_map +{ + expected_at_map (); +}; diff --git a/dwarflint/files.cc b/dwarflint/files.cc new file mode 100644 index 00000000..fb74a0e4 --- /dev/null +++ b/dwarflint/files.cc @@ -0,0 +1,138 @@ +/* Pedantic checking of DWARF files. + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include "files.hh" +#include "messages.hh" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sstream> +#include <cstring> +#include <cerrno> +#include <stdexcept> + +namespace +{ + inline bool failed (void *ptr) { return ptr == NULL; } + inline bool failed (int i) { return i < 0; } + + template <class T> + inline T + throw_if_failed (T x, char const *msg, + char const *(*errmsgcb) (int) = NULL) + { + if (unlikely (failed (x))) + { + std::stringstream ss; + ss << msg; + if (errmsgcb != NULL) + ss << ": " << errmsgcb (-1); + throw std::runtime_error (ss.str ()); + } + return x; + } + + char const * + mystrerror (int i) + { + if (i == -1) + i = errno; + return strerror (i); + } +} + +int +files::open (char const *fname) +{ + int fd = ::open (fname, O_RDONLY); + if (fd == -1) + { + std::stringstream ss; + ss << "Cannot open input file: " << strerror (errno) << "."; + throw std::runtime_error (ss.str ()); + } + + return fd; +} + +Dwfl * +files::open_dwfl () +{ + static class my_callbacks + : public Dwfl_Callbacks + { + // Stub libdwfl callback, only the ELF handle already open is ever used. + static int + find_no_debuginfo (Dwfl_Module *mod __attribute__ ((unused)), + void **userdata __attribute__ ((unused)), + const char *modname __attribute__ ((unused)), + Dwarf_Addr base __attribute__ ((unused)), + const char *file_name __attribute__ ((unused)), + const char *debuglink_file __attribute__ ((unused)), + GElf_Word debuglink_crc __attribute__ ((unused)), + char **debuginfo_file_name __attribute__ ((unused))) + { + return -1; + } + + public: + my_callbacks () + { + section_address = dwfl_offline_section_address; + find_debuginfo = find_no_debuginfo; + } + } cbs; + + return throw_if_failed (dwfl_begin (&cbs), + "Couldn't initialize DWFL"); +} + +Dwarf * +files::open_dwarf (Dwfl *dwfl, char const *fname, int fd) +{ + dwfl_report_begin (dwfl); + + // Dup FD for dwfl to consume. + int dwfl_fd + = throw_if_failed (dup (fd), "Error: dup", mystrerror); + + Dwfl_Module *mod + = throw_if_failed (dwfl_report_offline (dwfl, fname, fname, dwfl_fd), + "Couldn't add DWFL module", dwfl_errmsg); + dwfl_report_end (dwfl, NULL, NULL); + Dwarf_Addr bias; + throw_if_failed (dwfl_module_getelf (mod, &bias), + "Couldn't open ELF.", dwfl_errmsg); + return throw_if_failed (dwfl_module_getdwarf (mod, &bias), + "Couldn't obtain DWARF descriptor", dwfl_errmsg); +} + +elfutils::dwarf +files::open_dwarf (Dwarf *dw) + try + { + return dw; + } + catch (...) + { + throw std::runtime_error + ("Couldn't initialize high-level DWARF descriptor"); + } diff --git a/dwarflint/files.hh b/dwarflint/files.hh new file mode 100644 index 00000000..a7baa3b4 --- /dev/null +++ b/dwarflint/files.hh @@ -0,0 +1,39 @@ +/* Pedantic checking of DWARF files. + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifndef _DWARFLINT_FILES_H_ +#define _DWARFLINT_FILES_H_ + +#include "../libdwfl/libdwfl.h" +#include "../libdw/c++/dwarf" + +// The functions in this module do their own error handling, and throw +// std::runtime_error with descriptive error message on error. +namespace files +{ + int open (char const *fname); + + Dwfl *open_dwfl () + __attribute__ ((nonnull, malloc)); + + Dwarf *open_dwarf (Dwfl *dwfl, char const *fname, int fd) + __attribute__ ((nonnull, malloc)); + + elfutils::dwarf open_dwarf (Dwarf *dw); +} + +#endif /* _DWARFLINT_FILES_H_ */ diff --git a/dwarflint/highlevel_check.cc b/dwarflint/highlevel_check.cc new file mode 100644 index 00000000..071ad93f --- /dev/null +++ b/dwarflint/highlevel_check.cc @@ -0,0 +1,33 @@ +/* Initialization of high-level check context + 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 <http://www.gnu.org/licenses/>. */ + +#include "highlevel_check.hh" +#include "lowlevel_checks.hh" +#include "files.hh" + +open_highlevel_dwarf::open_highlevel_dwarf (checkstack &stack, dwarflint &lint) + : _m_dwfl ((lint.check<lowlevel_checks> (stack), + files::open_dwfl ())) + , c_dw (files::open_dwarf (_m_dwfl, lint.fname (), lint.fd ())) + , dw (files::open_dwarf (c_dw)) +{} + +open_highlevel_dwarf::~open_highlevel_dwarf () +{ + dwfl_end (_m_dwfl); +} diff --git a/dwarflint/highlevel_check.hh b/dwarflint/highlevel_check.hh new file mode 100644 index 00000000..46b4d386 --- /dev/null +++ b/dwarflint/highlevel_check.hh @@ -0,0 +1,79 @@ +/* Pedantic checking of DWARF files. + Copyright (C) 2009, 2010, 2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_CHECKS_HIGH_HH +#define DWARFLINT_CHECKS_HIGH_HH + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "checks.hh" +#include "check_debug_info.hh" + +#include "../libdw/c++/dwarf" +#include "../libdwfl/libdwfl.h" + +class open_highlevel_dwarf + : public check<open_highlevel_dwarf> +{ + Dwfl *const _m_dwfl; +public: + static checkdescriptor const *descriptor () { + static checkdescriptor cd + (checkdescriptor::create ("open_highlevel_dwarf") + .hidden ()); + return &cd; + } + + Dwarf *const c_dw; + elfutils::dwarf const dw; + open_highlevel_dwarf (checkstack &stack, dwarflint &lint); + ~open_highlevel_dwarf (); +}; + +struct highlevel_check_i +{ + open_highlevel_dwarf *_m_loader; + Dwarf *const c_dw; + elfutils::dwarf const &dw; + + highlevel_check_i (checkstack &stack, dwarflint &lint) + : _m_loader (lint.check (stack, _m_loader)) + , c_dw (_m_loader->c_dw) + , dw (_m_loader->dw) + {} +}; + +template<class T> +class highlevel_check + : public check<highlevel_check<T> > + , public highlevel_check_i +{ + open_highlevel_dwarf *_m_loader; +public: + static checkdescriptor const *descriptor () { + static checkdescriptor cd ("highlevel_check"); + return &cd; + } + + highlevel_check (checkstack &stack, dwarflint &lint) + : highlevel_check_i (stack, lint) + {} +}; + +#endif//DWARFLINT_CHECKS_HIGH_HH diff --git a/dwarflint/highlevel_check_i.hh b/dwarflint/highlevel_check_i.hh new file mode 100644 index 00000000..ddf38e66 --- /dev/null +++ b/dwarflint/highlevel_check_i.hh @@ -0,0 +1 @@ +class highlevel_check_i; diff --git a/dwarflint/locstats.cc b/dwarflint/locstats.cc new file mode 100644 index 00000000..7602e7fd --- /dev/null +++ b/dwarflint/locstats.cc @@ -0,0 +1,657 @@ +/* + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "highlevel_check.hh" +#include "all-dies-it.hh" +#include "option.hh" +#include "pri.hh" +#include "files.hh" + +#include <libintl.h> + +#include <sstream> +#include <bitset> + +using elfutils::dwarf; + +#define DIE_OPTSTRING \ + "}[,...]" + +global_opt<string_option> opt_ignore + ("Skip certain DIEs. class may be one of single_addr, artificial, inlined, \ +inlined_subroutine, no_coverage, mutable, or immutable.", + "class[,...]", "ignore"); + +global_opt<string_option> opt_dump + ("Dump certain DIEs. For classes, see option 'ignore'.", + "class[,...]", "dump"); + +global_opt<string_option> opt_tabulation_rule + ("Rule for sorting results into buckets. start is either integer 0..100, \ +or special value 0.0 indicating cases with no coverage whatsoever \ +(i.e. not those that happen to round to 0%).", + "start[:step][,...]", "tabulate"); + +// where.c needs to know how to format certain wheres. The module +// doesn't know that we don't use these :) +extern "C" +bool +show_refs () +{ + return false; +} + +#define DIE_TYPES \ + TYPE(single_addr) \ + TYPE(artificial) \ + TYPE(inlined) \ + TYPE(inlined_subroutine) \ + TYPE(no_coverage) \ + TYPE(mutable) \ + TYPE(immutable) + +struct tabrule +{ + int start; + int step; + tabrule (int a_start, int a_step) + : start (a_start), step (a_step) + {} + bool operator < (tabrule const &other) const { + return start < other.start; + } +}; + +// Sharp 0.0% coverage (i.e. not a single address byte is covered) +const int cov_00 = -1; + +struct tabrules_t + : public std::vector<tabrule> +{ + tabrules_t (std::string const &rule) + { + std::stringstream ss; + ss << rule; + + std::string item; + while (std::getline (ss, item, ',')) + { + if (item.empty ()) + continue; + int start; + int step; + char const *ptr = item.c_str (); + + if (item.length () >= 3 + && std::strncmp (ptr, "0.0", 3) == 0) + { + start = cov_00; + ptr += 3; + } + else + start = std::strtol (ptr, const_cast<char **> (&ptr), 10); + + if (*ptr == 0) + step = 0; + else + { + if (*ptr != ':') + { + step = 0; + goto garbage; + } + else + ptr++; + + step = std::strtol (ptr, const_cast<char **> (&ptr), 10); + if (*ptr != 0) + garbage: + std::cerr << "Ignoring garbage at the end of the rule item: '" + << ptr << '\'' << std::endl; + } + + push_back (tabrule (start, step)); + } + + push_back (tabrule (100, 0)); + std::sort (begin (), end ()); + } + + void next () + { + if (at (0).step == 0) + erase (begin ()); + else + { + if (at (0).start == cov_00) + at (0).start = 0; + at (0).start += at (0).step; + if (size () > 1) + { + if (at (0).start > at (1).start) + erase (begin ()); + while (size () > 1 + && at (0).start == at (1).start) + erase (begin ()); + } + } + } + + bool match (int value) const + { + return at (0).start == value; + } +}; + +#define TYPE(T) dt_##T, + enum die_type_e + { + DIE_TYPES + dt__count + }; +#undef TYPE + +class die_type_matcher + : public std::bitset<dt__count> +{ + class invalid {}; + std::pair<die_type_e, bool> + parse (std::string &desc) + { + bool val = true; + if (desc == "") + throw invalid (); + +#define TYPE(T) \ + if (desc == #T) \ + return std::make_pair (dt_##T, val); + DIE_TYPES +#undef TYPE + + throw invalid (); + } + +public: + die_type_matcher (std::string const &rule) + { + std::stringstream ss; + ss << rule; + + std::string item; + while (std::getline (ss, item, ',')) + try + { + std::pair<die_type_e, bool> const &ig = parse (item); + set (ig.first, ig.second); + } + catch (invalid &i) + { + std::cerr << "Invalid die type: " << item << std::endl; + } + } +}; + +class mutability_t +{ + bool _m_is_mutable; + bool _m_is_immutable; + +public: + mutability_t () + : _m_is_mutable (false) + , _m_is_immutable (false) + { + } + + void set (bool what) + { + if (what) + _m_is_mutable = true; + else + _m_is_immutable = true; + } + + void set_both () + { + set (true); + set (false); + } + + void locexpr (Dwarf_Op *expr, size_t len) + { + // We scan the expression looking for DW_OP_{bit_,}piece + // operators which mark ends of sub-expressions to us. + bool m = false; + for (size_t i = 0; i < len; ++i) + switch (expr[i].atom) + { + case DW_OP_implicit_value: + case DW_OP_stack_value: + m = true; + break; + + case DW_OP_bit_piece: + case DW_OP_piece: + set (m); + m = false; + break; + }; + set (m); + } + + bool is_mutable () const { return _m_is_mutable; } + bool is_immutable () const { return _m_is_immutable; } +}; + +struct error + : public std::runtime_error +{ + explicit error (std::string const &what_arg) + : std::runtime_error (what_arg) + {} +}; + +// Look through the stack of parental dies and return the non-empty +// ranges instance closest to the stack top (i.e. die_stack.end ()). +dwarf::ranges +find_ranges (std::vector<dwarf::debug_info_entry> const &die_stack) +{ + for (auto it = die_stack.rbegin (); it != die_stack.rend (); ++it) + if (!it->ranges ().empty ()) + return it->ranges (); + throw error ("no ranges for this DIE"); +} + +bool +is_inlined (dwarf::debug_info_entry const &die) +{ + dwarf::debug_info_entry::attributes_type::const_iterator it + = die.attributes ().find (DW_AT_inline); + if (it != die.attributes ().end ()) + { + char const *name = (*it).second.dwarf_constant ().name (); + return std::strcmp (name, "declared_inlined") == 0 + || std::strcmp (name, "inlined") == 0; + } + return false; +} + +void +process(Dwarf *c_dw, dwarf const &dw) +{ + // map percentage->occurrences. Percentage is cov_00..100, where + // 0..100 is rounded-down integer division. + std::map<int, unsigned long> tally; + unsigned long total = 0; + for (int i = 0; i <= 100; ++i) + tally[i] = 0; + + tabrules_t tabrules (opt_tabulation_rule.seen () + ? opt_tabulation_rule.value () : "10:10"); + die_type_matcher ignore (opt_ignore.seen () ? opt_ignore.value () : ""); + die_type_matcher dump (opt_dump.seen () ? opt_dump.value () : ""); + std::bitset<dt__count> interested = ignore | dump; + bool interested_mutability + = interested.test (dt_mutable) || interested.test (dt_immutable); + + for (all_dies_iterator<dwarf> it = all_dies_iterator<dwarf> (dw); + it != all_dies_iterator<dwarf> (); ++it) + { + std::bitset<dt__count> die_type; + dwarf::debug_info_entry const &die = *it; + + // We are interested in variables and formal parameters + bool is_formal_parameter = die.tag () == DW_TAG_formal_parameter; + if (!is_formal_parameter && die.tag () != DW_TAG_variable) + continue; + + dwarf::debug_info_entry::attributes_type const &attrs + = die.attributes (); + + // ... except those that are just declarations + if (attrs.find (DW_AT_declaration) != attrs.end ()) + continue; + + if (ignore.test (dt_artificial) + && attrs.find (DW_AT_artificial) != attrs.end ()) + continue; + + // Of formal parameters we ignore those that are children of + // subprograms that are themselves declarations. + std::vector<dwarf::debug_info_entry> const &die_stack = it.stack (); + dwarf::debug_info_entry const &parent = *(die_stack.rbegin () + 1); + if (is_formal_parameter) + if (parent.tag () == DW_TAG_subroutine_type + || (parent.attributes ().find (DW_AT_declaration) + != parent.attributes ().end ())) + continue; + + if (interested.test (dt_inlined) + || interested.test (dt_inlined_subroutine)) + { + bool inlined = false; + bool inlined_subroutine = false; + for (std::vector<dwarf::debug_info_entry>::const_reverse_iterator + stit = die_stack.rbegin (); stit != die_stack.rend (); ++stit) + { + if (interested.test (dt_inlined) + && stit->tag () == DW_TAG_subprogram + && is_inlined (*stit)) + { + inlined = true; + if (interested.test (dt_inlined_subroutine) + && inlined_subroutine) + break; + } + if (interested.test (dt_inlined_subroutine) + && stit->tag () == DW_TAG_inlined_subroutine) + { + inlined_subroutine = true; + if (interested.test (dt_inlined) + && inlined) + break; + } + } + + if (inlined) + { + if (ignore.test (dt_inlined)) + continue; + die_type.set (dt_inlined); + } + if (inlined_subroutine) + { + if (ignore.test (dt_inlined_subroutine)) + continue; + die_type.set (dt_inlined_subroutine, inlined_subroutine); + } + } + + // Unfortunately the location expression is not yet wrapped + // in c++, so we need to revert back to C code. + Dwarf_Die die_c_mem, + *die_c = dwarf_offdie (c_dw, die.offset (), &die_c_mem); + assert (die_c != NULL); + + Dwarf_Attribute locattr_mem, + *locattr = dwarf_attr_integrate (die_c, DW_AT_location, &locattr_mem); + + // Also ignore extern globals -- these have DW_AT_external and + // no DW_AT_location. + if (attrs.find (DW_AT_external) != attrs.end () && locattr == NULL) + continue; + + /* + Dwarf_Attribute name_attr_mem, + *name_attr = dwarf_attr_integrate (die_c, DW_AT_name, &name_attr_mem); + std::string name = name_attr != NULL + ? dwarf_formstring (name_attr) + : (dwarf_hasattr_integrate (die_c, DW_AT_artificial) + ? "<artificial>" : "???"); + + std::cerr << "die=" << std::hex << die.offset () + << " '" << name << '\''; + */ + + int coverage; + Dwarf_Op *expr; + size_t len; + mutability_t mut; + + // consts need no location + if (attrs.find (DW_AT_const_value) != attrs.end ()) + { + coverage = 100; + if (interested_mutability) + mut.set (true); + } + + // no location + else if (locattr == NULL) + { + coverage = cov_00; + if (interested_mutability) + mut.set_both (); + } + + // non-list location + else if (dwarf_getlocation (locattr, &expr, &len) == 0) + { + // Globals and statics have non-list location that is a + // singleton DW_OP_addr expression. + if (len == 1 && expr[0].atom == DW_OP_addr) + { + if (ignore.test (dt_single_addr)) + continue; + die_type.set (dt_single_addr); + } + if (interested_mutability) + mut.locexpr (expr, len); + coverage = (len == 0) ? cov_00 : 100; + } + + // location list + else + { + try + { + dwarf::ranges ranges (find_ranges (die_stack)); + size_t length = 0; + size_t covered = 0; + + // Arbitrarily assume that there will be no more than 10 + // expressions per address. + size_t nlocs = 10; + Dwarf_Op *exprs[nlocs]; + size_t exprlens[nlocs]; + + for (dwarf::ranges::const_iterator rit = ranges.begin (); + rit != ranges.end (); ++rit) + { + Dwarf_Addr low = (*rit).first; + Dwarf_Addr high = (*rit).second; + length += high - low; + //std::cerr << " " << low << ".." << high << std::endl; + + for (Dwarf_Addr addr = low; addr < high; ++addr) + { + int got = dwarf_getlocation_addr (locattr, addr, + exprs, exprlens, nlocs); + if (got < 0) + throw ::error (std::string ("dwarf_getlocation_addr: ") + + dwarf_errmsg (-1)); + + // At least one expression for the address must + // be of non-zero length for us to count that + // address as covered. + for (int i = 0; i < got; ++i) + { + if (interested_mutability) + mut.locexpr (exprs[i], exprlens[i]); + if (exprlens[i] > 0) + { + covered++; + break; + } + } + } + } + + if (length == 0) + throw ::error ("zero-length range"); + + if (covered == 0) + coverage = cov_00; + else + coverage = 100 * covered / length; + } + catch (::error const &e) + { + std::cerr << "error: " << die_locus (die) << ": " + << e.what () << '.' << std::endl; + continue; + } + } + + if (coverage == cov_00) + { + if (ignore.test (dt_no_coverage)) + continue; + die_type.set (dt_no_coverage); + } + else if (interested_mutability) + { + assert (mut.is_mutable () || mut.is_immutable ()); + if (mut.is_mutable ()) + { + if (ignore.test (dt_mutable)) + continue; + die_type.set (dt_mutable); + } + if (mut.is_immutable ()) + { + if (ignore.test (dt_immutable)) + continue; + die_type.set (dt_immutable); + } + } + + if ((dump & die_type).any ()) + { +#define TYPE(T) << (die_type.test (dt_##T) ? " "#T : "") + std::cerr << "dumping" DIE_TYPES << " DIE" << std::endl; +#undef TYPE + + std::string pad = ""; + for (auto sit = die_stack.begin (); sit != die_stack.end (); ++sit) + { + auto const &d = *sit; + std::cerr << pad << pri::ref (d) << " " + << elfutils::dwarf::tags::name (d.tag ()) << std::endl; + for (auto atit = d.attributes ().begin (); + atit != d.attributes ().end (); ++atit) + { + auto const &attr = *atit; + std::cerr << pad << " " << to_string (attr) << std::endl; + } + pad += " "; + } + + std::cerr << "empty coverage " << pri::ref (die) << " " + << to_string (die) << std::endl; + } + + tally[coverage]++; + total++; + //std::cerr << std::endl; + } + + unsigned long cumulative = 0; + unsigned long last = 0; + int last_pct = cov_00; + if (total == 0) + { + std::cout << "No coverage recorded." << std::endl; + return; + } + + std::cout << "cov%\tsamples\tcumul" << std::endl; + for (int i = cov_00; i <= 100; ++i) + { + cumulative += tally.find (i)->second; + if (tabrules.match (i)) + { + long int samples = cumulative - last; + + // The case 0.0..x should be printed simply as 0 + if (last_pct == cov_00 && i > cov_00) + last_pct = 0; + + if (last_pct == cov_00) + std::cout << "0.0"; + else + std::cout << std::dec << last_pct; + + if (last_pct != i) + std::cout << ".." << i; + std::cout << "\t" << samples + << '/' << (100*samples / total) << '%' + << "\t" << cumulative + << '/' << (100*cumulative / total) << '%' + << std::endl; + last = cumulative; + last_pct = i + 1; + + tabrules.next (); + } + } +} + +int +main(int argc, char *argv[]) +{ + /* Set locale. */ + setlocale (LC_ALL, ""); + + /* Initialize the message catalog. */ + textdomain (PACKAGE_TARNAME); + + /* Parse and process arguments. */ + argppp argp (global_opts ()); + int remaining; + argp.parse (argc, argv, 0, &remaining); + + if (remaining == argc) + { + fputs (gettext ("Missing file name.\n"), stderr); + argp.help (stderr, ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR, + program_invocation_short_name); + std::exit (1); + } + + bool only_one = remaining + 1 == argc; + do + { + try + { + char const *fname = argv[remaining]; + if (!only_one) + std::cout << std::endl << fname << ":" << std::endl; + + int fd = files::open (fname); + Dwfl *dwfl = files::open_dwfl (); + Dwarf *c_dw = files::open_dwarf (dwfl, fname, fd); + dwarf dw = files::open_dwarf (c_dw); + + process (c_dw, dw); + + close (fd); + dwfl_end (dwfl); + } + catch (std::runtime_error &e) + { + std::cerr << "error: " + << e.what () << '.' << std::endl; + continue; + } + } + while (++remaining < argc); +} diff --git a/dwarflint/locus.cc b/dwarflint/locus.cc new file mode 100644 index 00000000..105ef418 --- /dev/null +++ b/dwarflint/locus.cc @@ -0,0 +1,64 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2008,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 <http://www.gnu.org/licenses/>. */ + +#include "locus.hh" +#include "section_id.hh" +#include <sstream> +#include <iostream> + +std::ostream & +operator << (std::ostream &os, locus const &loc) +{ + os << loc.format (); + return os; +} + +char const * +locus_simple_fmt::offset_n () +{ + return "offset"; +} + +void +locus_simple_fmt::hex (std::ostream &ss, uint64_t off) +{ + ss << "0x" << std::hex << off; +} + +void +locus_simple_fmt::dec (std::ostream &ss, uint64_t off) +{ + ss << std::dec << off; +} + +std::string +simple_locus_aux::format_simple_locus (char const *(*N) (), + void (*F) (std::ostream &, uint64_t), + bool brief, section_id sec, uint64_t off) +{ + std::stringstream ss; + if (!brief) + ss << section_name[sec]; + if (off != (uint64_t)-1) + { + if (!brief) + ss << ": "; + ss << N() << " "; + F (ss, off); + } + return ss.str (); +} diff --git a/dwarflint/locus.hh b/dwarflint/locus.hh new file mode 100644 index 00000000..f594f449 --- /dev/null +++ b/dwarflint/locus.hh @@ -0,0 +1,104 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009,2010,2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_WHERE_HH +#define DWARFLINT_WHERE_HH + +#include "section_id.hh" + +#include <stdint.h> +#include <iosfwd> +#include <string> + +/// Instances of the locus subclasses are used as pointers into +/// debuginfo for documentation purposes (messages and errors). They +/// are usually tiny structures, and should be used as values, but we +/// need the abstract interface to be able to format them, and copy +/// them into ref_record. +class locus +{ +public: + virtual std::string format (bool brief = false) const = 0; + virtual ~locus () {} +}; + +std::ostream &operator << (std::ostream &os, locus const &loc); + +/// Helper class for simple_locus to reduce the template bloat. +class simple_locus_aux +{ +protected: + static std::string format_simple_locus (char const *(*N) (), + void (*F) (std::ostream &, uint64_t), + bool brief, section_id sec, + uint64_t off); +}; + +/// Template for quick construction of straightforward locus +/// subclasses (one address, one way of formatting). N should be a +/// function that returns the name of the argument. +/// locus_simple_fmt::hex, locus_simple_fmt::dec would be candidate +/// parameters for argument F. +template<char const *(*N) (), + void (*F) (std::ostream &, uint64_t)> +class simple_locus + : public locus + , private simple_locus_aux +{ + section_id _m_sec; + uint64_t _m_offset; + +public: + explicit simple_locus (section_id sec, uint64_t offset = -1) + : _m_sec (sec) + , _m_offset (offset) + {} + + std::string + format (bool brief = false) const + { + return format_simple_locus (N, F, brief, _m_sec, _m_offset); + } +}; + +/// Constructor of simple_locus that fixes the section_id argument. +template<section_id S, + char const *(*N) (), + void (*F) (std::ostream &, uint64_t)> +class fixed_locus + : public simple_locus<N, F> +{ +public: + explicit fixed_locus (uint64_t offset = -1) + : simple_locus<N, F> (S, offset) + {} +}; + +namespace locus_simple_fmt +{ + char const *offset_n (); + void hex (std::ostream &ss, uint64_t off); + void dec (std::ostream &ss, uint64_t off); +} + +/// Straightforward locus for cases where either offset is not +/// necessary at all, or if it is present, it's simply shown as +/// "offset: 0xf00". +typedef simple_locus<locus_simple_fmt::offset_n, + locus_simple_fmt::hex> section_locus; + +#endif//DWARFLINT_WHERE_HH diff --git a/dwarflint/lowlevel_checks.cc b/dwarflint/lowlevel_checks.cc new file mode 100644 index 00000000..13b92d93 --- /dev/null +++ b/dwarflint/lowlevel_checks.cc @@ -0,0 +1,87 @@ +/* Scheduler for low_level checks + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "lowlevel_checks.hh" +#include "sections.hh" +#include "check_debug_info.hh" +#include "check_debug_abbrev.hh" +#include "check_debug_aranges.hh" +#include "check_debug_pub.hh" +#include "check_debug_loc_range.hh" +#include "check_debug_line.hh" + +checkdescriptor const * +lowlevel_checks::descriptor () +{ + static checkdescriptor cd + (checkdescriptor::create ("lowlevel_checks") + .hidden ()); + return &cd; +} + +static reg<lowlevel_checks> reg_lowlevel_checks; + +namespace +{ + template<class T> struct do_check { + static bool check (checkstack &stack, dwarflint &lint) { + return lint.toplev_check<T> (stack); + } + }; + + // There is no separate check_debug_str pass. Make a stub so that + // we can do it all in one macro-expanded sweep below. +#define STUBBED_CHECK(NAME) \ + struct check_debug_##NAME {}; \ + template<> struct do_check<check_debug_##NAME> { \ + static bool check (__attribute__ ((unused)) checkstack &stack, \ + __attribute__ ((unused)) dwarflint &lint) \ + { \ + return true; \ + } \ + } + STUBBED_CHECK(str); + STUBBED_CHECK(mac); +#undef STUBBED_CHECK +} + +lowlevel_checks::lowlevel_checks (checkstack &stack, dwarflint &lint) +{ + // Then check all the debug sections that are there. For each + // existing section request that the check passes. Re-requesting + // already-passed checks is OK, the scheduler caches it. + bool passed = true; + +#define SEC(NAME) \ + section<sec_##NAME> *NAME = \ + lint.toplev_check<section<sec_##NAME> > (stack); \ + if (NAME != NULL) \ + if (!do_check<check_debug_##NAME>::check (stack, lint)) \ + passed = false; + + DEBUGINFO_SECTIONS; +#undef SEC + + lint.check<check_debug_info_refs> (stack); + + if (!passed) + throw check_base::failed (); +} diff --git a/dwarflint/lowlevel_checks.hh b/dwarflint/lowlevel_checks.hh new file mode 100644 index 00000000..518bb850 --- /dev/null +++ b/dwarflint/lowlevel_checks.hh @@ -0,0 +1,32 @@ +/* Scheduler for low_level checks + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_LOWLEVEL_CHECKS_HH +#define DWARFLINT_LOWLEVEL_CHECKS_HH + +#include "checks.hh" + +class lowlevel_checks + : public check<lowlevel_checks> +{ +public: + static checkdescriptor const *descriptor (); + lowlevel_checks (checkstack &stack, dwarflint &lint); +}; + +#endif//DWARFLINT_LOWLEVEL_CHECKS_HH diff --git a/dwarflint/main.cc b/dwarflint/main.cc new file mode 100644 index 00000000..dc4abda8 --- /dev/null +++ b/dwarflint/main.cc @@ -0,0 +1,224 @@ +/* Main entry point for dwarflint, a pedantic checker for DWARF files. + Copyright (C) 2008,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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <libintl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <iostream> +#include <sstream> + +#include "dwarflint.hh" +#include "readctx.hh" +#include "checks.hh" +#include "option.hh" +#include "messages.hh" + +/* Messages that are accepted (and made into warning). */ +struct message_criteria warning_criteria; + +/* Accepted (warning) messages, that are turned into errors. */ +struct message_criteria error_criteria; + +struct check_option_t + : public global_opt<option_common> +{ + struct initial_checkrules + : public checkrules + { + initial_checkrules () + { + push_back (checkrule_internal ("@all", checkrule::request)); + push_back (checkrule_internal ("@nodefault", checkrule::forbid)); + } + } rules; + + check_option_t () + : global_opt<option_common> ("Only run selected checks.", + "[+-][@]name,...", "check", 0) + {} + + error_t parse_opt (char *arg, __attribute__ ((unused)) argp_state *state) + { + static bool first = true; + std::stringstream ss (arg); + std::string item; + + while (std::getline (ss, item, ',')) + { + if (item.empty ()) + continue; + + enum + { + forbid, + request, + replace + } act; + + // If the first rule has no operator, we assume the user + // wants to replace the implicit set of checks. + if (first) + { + act = replace; + first = false; + } + else + // Otherwise the rules are implicitly requesting, even + // without the '+' operator. + act = request; + + bool minus = item[0] == '-'; + bool plus = item[0] == '+'; + if (plus || minus) + item = item.substr (1); + if (plus) + act = request; + if (minus) + act = forbid; + + if (act == replace) + { + rules.clear (); + act = request; + } + + checkrule::action_t action + = act == request ? checkrule::request : checkrule::forbid; + rules.push_back (checkrule (item, action)); + } + return 0; + } +} check_option; + +global_opt<void_option> + be_quiet ("Do not print anything if successful", + "quiet", 'q'); + +global_opt<string_option> + opt_list_checks ("List all the available checks.", + "full", "list-checks", 0, + OPTION_ARG_OPTIONAL); + +// xxx The following three should go away when we introduce the +// message filtering. Or should be preserved, but in a way that makes +// more sense, right now they are simply a misnomer. +global_opt<void_option> + ignore_bloat ("Ignore messages related to bloat.", "ignore-bloat"); +global_opt<void_option> + be_strict ("Be somewhat stricter.", "strict"); +global_opt<void_option> + be_tolerant ("Be somewhat more tolerant.", "tolerant"); + +int +main (int argc, char *argv[]) +{ + /* Set locale. */ + setlocale (LC_ALL, ""); + + /* Initialize the message catalog. */ + textdomain (PACKAGE_TARNAME); + + /* Parse and process arguments. */ + argppp argp (global_opts (), + dwarflint::main_registrar ()->get_descriptors ()); + + int remaining; + argp.parse (argc, argv, 0, &remaining); + + if (opt_list_checks.seen ()) + { + dwarflint::list_checks (); + std::exit (0); + } + else if (remaining == argc) + { + fputs (gettext ("Missing file name.\n"), stderr); + argp.help (stderr, ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR, + program_invocation_short_name); + std::exit (1); + } + + /* Initialize warning & error criteria. */ + warning_criteria |= message_term (mc_none, mc_none); + + error_criteria |= message_term (mc_impact_4, mc_none); + error_criteria |= message_term (mc_error, mc_none); + + /* Configure warning & error criteria according to configuration. */ + if (ignore_bloat) + warning_criteria &= message_term (mc_none, mc_acc_bloat); + + if (!be_strict) + { + warning_criteria &= message_term (mc_none, mc_strings); + warning_criteria.and_not (mc_line | mc_acc_bloat); + warning_criteria &= message_term (mc_none, mc_pubtypes); + } + + if (be_tolerant) + { + warning_criteria &= message_term (mc_none, mc_loc); + warning_criteria &= message_term (mc_none, mc_ranges); + } + + if (false) // for debugging + { + std::cout << "warning criteria: " << warning_criteria << std::endl; + std::cout << "error criteria: " << error_criteria << std::endl; + } + + /* Before we start tell the ELF library which version we are using. */ + elf_version (EV_CURRENT); + + /* Now process all the files given at the command line. */ + bool only_one = remaining + 1 == argc; + bool one_passed = false; + do + { + try + { + char const *fname = argv[remaining]; + if (!only_one) + std::cout << std::endl << fname << ":" << std::endl; + wr_reset_counters (); + dwarflint lint (fname, check_option.rules); + one_passed = true; + + if (error_count == 0 && !be_quiet) + puts (gettext ("No errors")); + } + catch (std::runtime_error &e) + { + wr_error () << e.what () << std::endl; + continue; + } + } + while (++remaining < argc); + + if (one_passed) + for (checkrules::const_iterator it = check_option.rules.begin (); + it != check_option.rules.end (); ++it) + if (!it->used ()) + std::cerr << "warning: the rule `" << it->name () + << "' never matched." << std::endl; + + return error_count != 0; +} diff --git a/dwarflint/main.hh b/dwarflint/main.hh new file mode 100644 index 00000000..96368f52 --- /dev/null +++ b/dwarflint/main.hh @@ -0,0 +1,25 @@ +/* Pedantic checker for DWARF files + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_MAIN_HH +#define DWARFLINT_MAIN_HH + +#include "option.hh" +extern string_option opt_list_checks; + +#endif//DWARFLINT_MAIN_HH diff --git a/dwarflint/messages.cc b/dwarflint/messages.cc new file mode 100644 index 00000000..6b6cade8 --- /dev/null +++ b/dwarflint/messages.cc @@ -0,0 +1,491 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009-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 <http://www.gnu.org/licenses/>. */ + +#include "messages.hh" +#include "coverage.hh" +#include "option.hh" + +#include <vector> +#include <sstream> +#include <cassert> +#include <cstdarg> +#include <libintl.h> + +unsigned error_count = 0; +bool message_context::_m_last_emitted; + +bool +message_accept (struct message_criteria const *cri, + unsigned long cat) +{ + for (size_t i = 0; i < cri->size (); ++i) + { + message_term const &t = cri->at (i); + if ((t.positive & cat) == t.positive + && (t.negative & cat) == 0) + return true; + } + + return false; +} + +namespace +{ + struct cat_to_str + : public std::vector<std::string> + { + cat_to_str () + { + int count = 0; +#define MC(CAT, ID) if (ID > count) count = ID; + MESSAGE_CATEGORIES +#undef MC + + resize (count + 1); +#define MC(CAT, ID) (*this)[ID] = #CAT; + MESSAGE_CATEGORIES +#undef MC + } + } cat_names; + size_t cat_max = cat_names.size (); +} + + +message_category +operator | (message_category a, message_category b) +{ + return static_cast<message_category> ((unsigned long)a | b); +} + +message_category & +operator |= (message_category &a, message_category b) +{ + a = a | b; + return a; +} + +std::string +message_term::str () const +{ + std::ostringstream os; + os << '('; + + bool got = false; + for (size_t i = 0; i <= cat_max; ++i) + { + size_t mask = 1u << i; + if ((positive & mask) != 0 + || (negative & mask) != 0) + { + if (got) + os << " & "; + if ((negative & (1u << i)) != 0) + os << '~'; + os << cat_names[i]; + got = true; + } + } + + if (!got) + os << '1'; + + os << ')'; + return os.str (); +} + +std::string +message_criteria::str () const +{ + std::ostringstream os; + + for (size_t i = 0; i < size (); ++i) + { + message_term const &t = at (i); + if (i > 0) + os << " | "; + os << t.str (); + } + + return os.str (); +} + +void +message_criteria::operator &= (message_term const &term) +{ + assert ((term.positive & term.negative) == 0); + for (size_t i = 0; i < size (); ) + { + message_term &t = at (i); + t.positive = t.positive | term.positive; + t.negative = t.negative | term.negative; + if ((t.positive & t.negative) != 0) + /* A ^ ~A -> drop the term. */ + erase (begin () + i); + else + ++i; + } +} + +void +message_criteria::operator |= (message_term const &term) +{ + assert ((term.positive & term.negative) == 0); + push_back (term); +} + +// xxx this one is inaccessible from the outside. Make it like &=, |= +// above +/* NEG(a&b&~c) -> (~a + ~b + c) */ +message_criteria +operator ! (message_term const &term) +{ + assert ((term.positive & term.negative) == 0); + + message_criteria ret; + for (size_t i = 0; i < cat_max; ++i) + { + unsigned mask = 1u << i; + if ((term.positive & mask) != 0) + ret |= message_term ((message_category)(1u << i), mc_none); + else if ((term.negative & mask) != 0) + ret |= message_term (mc_none, (message_category)(1u << i)); + } + + return ret; +} + +std::ostream & +operator<< (std::ostream &o, message_category cat) +{ + o << '('; + + bool got = false; + for (size_t i = 0; i <= cat_max; ++i) + { + size_t mask = 1u << i; + if ((cat & mask) != 0) + { + if (got) + o << ","; + o << cat_names[i]; + got = true; + } + } + + if (!got) + o << "none"; + + return o << ')'; +} + +std::ostream & +operator<< (std::ostream &o, message_term const &term) +{ + return o << term.str (); +} + +std::ostream & +operator<< (std::ostream &o, __attribute__ ((unused)) message_criteria const &criteria) +{ + return o << criteria.str (); +} + +/* MUL((a&b + c&d), (e&f + g&h)) -> (a&b&e&f + a&b&g&h + c&d&e&f + c&d&g&h) */ +void +message_criteria::operator *= (message_criteria const &rhs) +{ + message_criteria ret; + for (size_t i = 0; i < size (); ++i) + for (size_t j = 0; j < rhs.size (); ++j) + { + message_term t1 = at (i); + message_term const &t2 = rhs.at (j); + t1.positive |= t2.positive; + t1.negative |= t2.negative; + if (t1.positive & t1.negative) + /* A ^ ~A -> drop the term. */ + continue; + ret |= t1; + } + + *this = ret; +} + +// xxx this one is inaccessible from the outside. Bind it properly +/* Reject message if TERM passes. */ +void +message_criteria::and_not (message_term const &term) +{ + // xxxxx really?? "!"?? + message_criteria tmp = !message_term (term.negative, term.positive); + *this *= tmp; +} + +static void +wr_verror (locus const &loc, const char *format, va_list ap) +{ + printf ("error: %s", loc.format ().c_str ()); + vprintf (format, ap); + ++error_count; +} + +static void +wr_vwarning (locus const &loc, const char *format, va_list ap) +{ + printf ("%s", loc.format ().c_str ()); + vprintf (format, ap); + ++error_count; +} + +void +wr_error (locus const *loc, const char *format, ...) +{ + va_list ap; + va_start (ap, format); + wr_verror (*loc, format, ap); + va_end (ap); +} + +void +wr_message (unsigned long category, locus const *loc, + const char *format, ...) +{ + va_list ap; + va_start (ap, format); + // Clumsy duplicate filtering. Use format as key. + bool whether = false; + message_category cat = (message_category) category; + wr_message (cat).id (format, whether); + if (whether && message_accept (&warning_criteria, category)) + { + if (message_accept (&error_criteria, category)) + wr_verror (*loc, format, ap); + else + wr_vwarning (*loc, format, ap); + } + va_end (ap); +} + +namespace +{ + class nostream: public std::ostream {}; + static nostream nostream; + + std::ostream & + get_stream () + { + return std::cout; + } +} + +global_opt<unsigned_option> + dup_threshold_opt ("Threshold for duplicate messages." + " Defaults to 16." + " Use zero for no limit.", + "count", "dups"); + +namespace +{ + unsigned + dup_threshold () + { + static unsigned t = dup_threshold_opt.value (16); + if (t == 0) + t = -1; + return t; + } +} + +int +message_count_filter::should_emit (void const *key) +{ + unsigned count = ++_m_counters[key]; + if (count > dup_threshold ()) + return 0; + else if (count == dup_threshold ()) + return -1; + else + return 1; +} + +message_context::message_context (message_count_filter *filter, + locus const *loc, char const *prefix) + : _m_filter (filter) + , _m_loc (loc) + , _m_prefix (prefix) +{} + +std::ostream & +message_context::when (bool whether) const +{ + _m_last_emitted = false; + if (whether) + { + ++error_count; + _m_last_emitted = true; + + std::ostream &ret = get_stream (); + ret << _m_prefix; + if (_m_loc != NULL) + ret << _m_loc->format () << ": "; + return ret; + } + else + return nostream; +} + +std::ostream & +message_context::when_prev () const +{ + return when (wr_prev_emitted ()); +} + +std::ostream & +message_context::id (void const *key, bool &whether) +{ + if (_m_filter == NULL) + return nostream; + else if (int status = _m_filter->should_emit (key)) + { + if (status == -1) + get_stream () << "(threshold [--dups=" << dup_threshold () + << "] reached for the following message)" + << std::endl; + whether = true; + return when (true); + } + else + return nostream; +} + +std::ostream & +message_context::id (void const *key) +{ + bool whether; + return id (key, whether); +} + +std::ostream & +message_context::operator << (char const *message) +{ + return id (message) << message; +} + +std::ostream & +message_context::operator << (std::string const &message) +{ + return *this << message.c_str (); +} + +std::ostream & +wr_error () +{ + ++error_count; + return get_stream () << gettext ("error: "); +} + +std::ostream & +wr_error (locus const &loc) +{ + std::string fmt = loc.format (); + return wr_error () << fmt << ": "; +} + +message_context +message_context::filter_message (locus const *loc, message_category category) +{ + if (!message_accept (&warning_criteria, category)) + return message_context (NULL, NULL, NULL); + else if (message_accept (&error_criteria, category)) + return message_context (message_count_filter::inst (), + loc, "error: "); + else + return message_context (message_count_filter::inst (), + loc, "warning: "); +} + +message_context +wr_message (message_category category) +{ + return message_context::filter_message (NULL, category); +} + +message_context +wr_message (locus const &loc, message_category category) +{ + return message_context::filter_message (&loc, category); +} + +void +wr_format_padding_message (message_category category, + locus const &loc, + uint64_t start, uint64_t end, char const *kind) +{ + char msg[128]; + wr_message (loc, category) + << range_fmt (msg, sizeof msg, start, end) + << ": " << kind << "." << std::endl; +} + +void +wr_format_leb128_message (locus const &loc, + const char *what, + const char *purpose, + const unsigned char *begin, const unsigned char *end) +{ + message_category category = mc_leb128 | mc_acc_bloat | mc_impact_3; + char buf[(end - begin) * 3 + 1]; // 2 hexa digits+" " per byte, and term. 0 + char *ptr = buf; + for (; begin < end; ++begin) + ptr += sprintf (ptr, " %02x", *begin); + wr_message (loc, category) + << what << ": value " << purpose << " encoded as `" + << (buf + 1) << "'." << std::endl; +} + +void +wr_message_padding_0 (message_category category, + locus const &loc, + uint64_t start, uint64_t end) +{ + wr_format_padding_message (category | mc_acc_bloat | mc_impact_1, + loc, start, end, + "unnecessary padding with zero bytes"); +} + +void +wr_message_padding_n0 (message_category category, + locus const &loc, + uint64_t start, uint64_t end) +{ + wr_format_padding_message (category | mc_acc_bloat | mc_impact_1, + loc, start, end, + "unreferenced non-zero bytes"); +} + +void +wr_reset_counters () +{ + error_count = 0; + message_count_filter::inst ()->clear (); +} + +bool +wr_prev_emitted () +{ + return message_context::_m_last_emitted; +} diff --git a/dwarflint/messages.hh b/dwarflint/messages.hh new file mode 100644 index 00000000..7a5828a1 --- /dev/null +++ b/dwarflint/messages.hh @@ -0,0 +1,233 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009,2010,2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_MESSAGES_HH +#define DWARFLINT_MESSAGES_HH + +#include "locus.hh" +#include "libdw.h" +#include <string> +#include <iosfwd> +#include <vector> +#include <sstream> +#include <map> + +#define MESSAGE_CATEGORIES \ + /* Severity: */ \ + MC (impact_1, 0) /* no impact on the consumer */ \ + MC (impact_2, 1) /* still no impact, but suspicious or worth mentioning */ \ + MC (impact_3, 2) /* some impact */ \ + MC (impact_4, 3) /* high impact */ \ + \ + /* Accuracy: */ \ + MC (acc_bloat, 4) /* unnecessary constructs (e.g. unreferenced strings) */ \ + MC (acc_suboptimal, 5) /* suboptimal construct (e.g. lack of siblings) */ \ + \ + /* Various: */ \ + MC (error, 6) /* turn the message into an error */ \ + \ + /* Area: */ \ + MC (leb128, 7) /* ULEB/SLEB storage */ \ + MC (abbrevs, 8) /* abbreviations and abbreviation tables */ \ + MC (die_rel, 9) /* DIE relationship */ \ + MC (die_other, 10) /* other messages related to DIEs */ \ + MC (info, 11) /* messages related to .debug_info, but not particular DIEs */ \ + MC (strings, 12) /* string table */ \ + MC (aranges, 13) /* address ranges table */ \ + MC (elf, 14) /* ELF structure, e.g. missing optional sections */ \ + MC (pubtables, 15) /* table of public names/types */ \ + MC (pubtypes, 16) /* .debug_pubtypes presence */ \ + MC (loc, 17) /* messages related to .debug_loc */ \ + MC (ranges, 18) /* messages related to .debug_ranges */ \ + MC (line, 19) /* messages related to .debug_line */ \ + MC (reloc, 20) /* messages related to relocation handling */ \ + MC (header, 21) /* messages related to header portions in general */ \ + MC (mac, 22) /* messages related to .debug_mac */ \ + MC (other, 31) /* messages unrelated to any of the above */ + +enum message_category + { + mc_none = 0, + +#define MC(CAT, ID) \ + mc_##CAT = 1u << ID, + MESSAGE_CATEGORIES +#undef MC + }; + +message_category operator | (message_category a, message_category b); +message_category &operator |= (message_category &a, message_category b); +std::ostream &operator<< (std::ostream &o, message_category cat); + +struct message_term +{ + /* Given a term like A && !B && C && !D, we decompose it thus: */ + message_category positive; /* non-zero bits for plain predicates */ + message_category negative; /* non-zero bits for negated predicates */ + + message_term (message_category pos, message_category neg = mc_none) + : positive (pos), negative (neg) + {} + + std::string str () const; +}; + +std::ostream &operator<< (std::ostream &o, message_term const &term); + +struct message_criteria + : protected std::vector<message_term> +{ + using std::vector<message_term>::at; + using std::vector<message_term>::size; + + void operator |= (message_term const &term); + void operator &= (message_term const &term); + void operator *= (message_criteria const &term); + void and_not (message_term const &term); + + std::string str () const; +}; + +std::ostream &operator<< (std::ostream &o, message_criteria const &criteria); + +message_criteria operator ! (message_term const &); + +extern void wr_error (locus const *wh, const char *format, ...) + __attribute__ ((format (printf, 2, 3))); + +extern void wr_message (unsigned long category, locus const *loc, + const char *format, ...) + __attribute__ ((format (printf, 3, 4))); + +extern void wr_format_padding_message (message_category category, + locus const &loc, + uint64_t start, uint64_t end, + char const *kind); + +extern void wr_format_leb128_message (locus const &loc, + const char *what, + const char *purpose, + const unsigned char *begin, + const unsigned char *end); + +extern void wr_message_padding_0 (message_category category, + locus const &loc, + uint64_t start, uint64_t end); + +extern void wr_message_padding_n0 (message_category category, + locus const &loc, + uint64_t start, uint64_t end); + +extern bool message_accept (struct message_criteria const *cri, + unsigned long cat); + + +extern unsigned error_count; + +/* Messages that are accepted (and made into warning). */ +extern struct message_criteria warning_criteria; + +/* Accepted (warning) messages, that are turned into errors. */ +extern struct message_criteria error_criteria; + +class message_count_filter +{ + struct counter + { + unsigned value; + counter () : value (0) {} + unsigned operator++ () { return ++value; } + }; + + typedef std::map<void const *, counter> counters_t; + + // NULL for filtered-out message, otherwise array of <key, count> + // pairs sorted by key. + counters_t _m_counters; + friend void wr_reset_counters (); + + void + clear () + { + counters_t empty; + _m_counters.swap (empty); + } + +public: + + int should_emit (void const *key); + static message_count_filter * + inst () + { + static message_count_filter inst; + return &inst; + } +}; + +class message_context +{ + static bool _m_last_emitted; + + message_count_filter *_m_filter; + locus const *_m_loc; + char const *_m_prefix; + + friend message_context wr_message (locus const &loc, message_category cat); + friend message_context wr_message (message_category cat); + friend bool wr_prev_emitted (); + + message_context (message_count_filter *filter, + locus const *loc, char const *prefix); + +public: + static message_context filter_message (locus const *loc, + message_category category); + + std::ostream &operator << (char const *message); + std::ostream &operator << (std::string const &message); + + template<class T> + std::ostream & + operator << (T const &t) + { + std::stringstream ss; + ss << t; + return (*this) << ss.str (); + } + + // Use KEY for count filtering. + std::ostream &id (void const *key); + + // Use KEY for count filtering. WHETHER is true if the message will + // be emitted. It doesn't touch that value otherwise, so WHETHER + // must be pre-initialized to false. + std::ostream &id (void const *key, bool &whether); + + // Return either the full stream, or a sink, depending on WHETHER. + std::ostream &when (bool whether) const; + + std::ostream &when_prev () const; +}; + +std::ostream &wr_error (locus const &loc); +std::ostream &wr_error (); +message_context wr_message (locus const &loc, message_category cat); +message_context wr_message (message_category cat); +void wr_reset_counters (); +bool wr_prev_emitted (); + +#endif//DWARFLINT_MESSAGES_HH diff --git a/dwarflint/misc.cc b/dwarflint/misc.cc new file mode 100644 index 00000000..114d0d96 --- /dev/null +++ b/dwarflint/misc.cc @@ -0,0 +1,60 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2008, 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "misc.hh" +#include "messages.hh" +#include <stdarg.h> + +bool +address_aligned (uint64_t addr, uint64_t align) +{ + return align < 2 || (addr % align == 0); +} + +bool +necessary_alignment (uint64_t start, uint64_t length, uint64_t align) +{ + return address_aligned (start + length, align) && length < align; +} + +bool +supported_version (unsigned version, + size_t num_supported, locus const &loc, ...) +{ + bool retval = false; + va_list ap; + va_start (ap, loc); + for (size_t i = 0; i < num_supported; ++i) + { + unsigned v = va_arg (ap, unsigned); + if (version == v) + { + retval = true; + break; + } + } + va_end (ap); + + if (!retval) + wr_error (loc) << "unsupported version " << version << ".\n"; + + return retval; +} diff --git a/dwarflint/misc.hh b/dwarflint/misc.hh new file mode 100644 index 00000000..f779ed61 --- /dev/null +++ b/dwarflint/misc.hh @@ -0,0 +1,54 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009,2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_MISC_HH +#define DWARFLINT_MISC_HH + +#include <cstring> +#include "locus.hh" + +extern "C" +{ +#include "../lib/system.h" +} + +#define REALLOC(A, BUF) \ + do { \ + typeof ((A)) _a = (A); \ + if (_a->size == _a->alloc) \ + { \ + if (_a->alloc == 0) \ + _a->alloc = 8; \ + else \ + _a->alloc *= 2; \ + _a->BUF = (typeof (_a->BUF)) \ + xrealloc (_a->BUF, \ + sizeof (*_a->BUF) * _a->alloc); \ + } \ + } while (0) + +bool address_aligned (uint64_t addr, uint64_t align); +bool necessary_alignment (uint64_t start, uint64_t length, + uint64_t align); + +bool supported_version (unsigned version, + size_t num_supported, locus const &loc, ...); + +#define UNREACHABLE assert (!"unreachable") + + +#endif//DWARFLINT_MISC_HH diff --git a/dwarflint/option.cc b/dwarflint/option.cc new file mode 100644 index 00000000..7248258c --- /dev/null +++ b/dwarflint/option.cc @@ -0,0 +1,223 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009,2010,2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "option.hh" +#include "dwarflint.hh" +#include "checkdescriptor.hh" +#include "check_registrar.hh" +#include <cassert> +#include <cstring> +#include <iostream> + +argppp *argppp::instance = NULL; + +option_i * +options::find_opt (int key) const +{ + const_iterator it = find (key); + if (it == end ()) + return NULL; + return it->second; +} + +option_i const * +options::getopt (int key) const +{ + return find_opt (key); +} + +struct last_option + : public argp_option +{ + last_option () + { + std::memset (this, 0, sizeof (*this)); + } +}; + +void +options::add (option_i *opt) +{ + int key = opt->key (); + assert (getopt (key) == NULL); + (*this)[key] = opt; +} + +/* Bug report address. */ +const char *argp_program_bug_address = PACKAGE_BUGREPORT; + +argp +options::build_argp (bool toplev) const +{ + _m_opts.clear (); + for (const_iterator it = begin (); it != end (); ++it) + _m_opts.push_back (it->second->build_option ()); + _m_opts.push_back (last_option ()); + argp a = { + &_m_opts.front (), + NULL, // needs to be initialized later, in argppp + !toplev ? NULL : "FILE...", + !toplev ? NULL : "\ +Pedantic checking of DWARF stored in ELF files.", + NULL, NULL, NULL + }; + return a; +} + +argppp::argppp (options const &global) + : _m_inited (false) +{ + argp main = global.build_argp (true); + main.parser = &parse_opt; + _m_argp = main; + + // Only one instance is allowed per program. + assert (instance == NULL); + instance = this; +} + +argppp::argppp (options const &global, + std::vector<checkdescriptor const *> checkdescriptors) + : _m_inited (false) +{ + argp main = global.build_argp (true); + + typedef main_check_registrar::checkdescriptors_t checkdescriptors_t; + for (checkdescriptors_t::const_iterator it = checkdescriptors.begin (); + it != checkdescriptors.end (); ++it) + if (!(*it)->opts ().empty ()) + { + _m_children_argps.push_back ((*it)->opts ().build_argp ()); + _m_children_argps.back ().parser = &parse_opt; + _m_children_headers.push_back (std::string ("Options for ") + + (*it)->name () + + ":"); + _m_children_inputs.push_back (&(*it)->opts ()); + } + + unsigned pos = 0; + for (checkdescriptors_t::const_iterator it = checkdescriptors.begin (); + it != checkdescriptors.end (); ++it) + if (!(*it)->opts ().empty ()) + { + argp_child child = {&_m_children_argps[pos], 0, + _m_children_headers[pos].c_str (), 0}; + _m_children.push_back (child); + pos++; + } + assert (_m_children_argps.size () == _m_children.size ()); + + if (!_m_children.empty ()) + { + _m_children.push_back ((argp_child){NULL, 0, NULL, 0}); + main.children = &_m_children.front (); + } + + main.parser = &parse_opt; + _m_argp = main; + + // Only one instance is allowed per program. + assert (instance == NULL); + instance = this; +} + +error_t +argppp::parse_opt (int key, char *arg, argp_state *state) +{ + assert (instance != NULL); + if (key == ARGP_KEY_INIT && !instance->_m_inited) + { + instance->_m_inited = true; + unsigned i = 0; + for (std::vector<options const *>::const_iterator it + = instance->_m_children_inputs.begin (); + it != instance->_m_children_inputs.end (); ++it) + state->child_inputs[i++] = const_cast<options *> (*it); + return 0; + } + else + { + assert (state->input != NULL); + options const *opts = static_cast<options const *> (state->input); + option_i *o = opts->find_opt (key); + if (o == NULL) + return ARGP_ERR_UNKNOWN; + return o->parse_opt (arg, state); + } +} + +void +argppp::parse (int argc, char **argv, unsigned flags, int *remaining) +{ + assert (!_m_inited); + argp_parse (&_m_argp, argc, argv, flags, remaining, &global_opts ()); +} + +void +argppp::help (FILE *stream, unsigned flags, char *name) +{ + argp_help (&_m_argp, stream, flags, name); +} + +int option_i::_m_last_opt = 300; + +int +option_i::get_short_option (char opt_short) +{ + if (opt_short) + return opt_short; + return _m_last_opt++; +} + +namespace +{ + argp_option argp_option_ctor (char const *name, int key, + char const *arg, int flags, + char const *doc, int group) + { + assert (name != NULL); + assert (doc != NULL); + argp_option opt = { + name, key, arg, flags, doc, group + }; + return opt; + } +} + +option_common::option_common (char const *description, + char const *arg_description, + char const *opt_long, char opt_short, + int flags) + : _m_opt (argp_option_ctor (opt_long, get_short_option (opt_short), + arg_description, flags, + description, 0)) + , _m_seen (false) +{} + +// Trick to make sure the static options are always initialized +// before access (it is used from various global initializers. + +options & +global_opts () +{ + static options inst; + return inst; +} diff --git a/dwarflint/option.hh b/dwarflint/option.hh new file mode 100644 index 00000000..2d3e427c --- /dev/null +++ b/dwarflint/option.hh @@ -0,0 +1,234 @@ +/* Pedantic checker for DWARF files + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_OPTION_HH +#define DWARFLINT_OPTION_HH + +#include <string> +#include <argp.h> +#include <map> +#include <vector> +#include <cassert> +#include <iostream> + +#include "option_i.hh" +#include "checkdescriptor_i.hh" + +class options + : private std::map<int, option_i *> +{ + friend class argppp; + mutable std::vector<argp_option> _m_opts; + + option_i *find_opt (int key) const; + +public: + option_i const *getopt (int key) const; + argp build_argp (bool toplev = false) const; + void add (option_i *opt); + using std::map<int, option_i *>::empty; + using std::map<int, option_i *>::begin; + using std::map<int, option_i *>::end; + using std::map<int, option_i *>::const_iterator; +}; + +// Wrapper of argp parsing. While in general argp does a decent job, +// it's not possible to pass user arguments to the parser function +// from the argp structure itself, and therefore share the same parser +// for all the children. Since that's what we need to do, we need to +// pass the "input" argument to argp_parse, and carefully setup the +// child_inputs arguments. That means coordinating the whole parsing +// process from one place. As a result this is hardly a nice, +// reusable piece of code. +class argppp +{ + std::vector<argp> _m_children_argps; + std::vector<std::string> _m_children_headers; + std::vector<argp_child> _m_children; + std::vector<options const *> _m_children_inputs; + argp _m_argp; + bool _m_inited; + + static error_t parse_opt (int key, char *arg, argp_state *state); + static argppp *instance; + +public: + argppp (options const &global); + argppp (options const &global, + std::vector<checkdescriptor const *> checkdescriptors); + + void parse (int argc, char **argv, unsigned flags, int *remaining); + void help (FILE *stream, unsigned flags, char *name); +}; + +class option_i // we cannot call it simply "option", this conflicts + // with another global declaration +{ + // last allocated option unique identifier + static int _m_last_opt; + +protected: + // Answer either opt_short argument if it's non-0. Otherwise create + // new unique identifier. + static int get_short_option (char opt_short); + +public: + virtual ~option_i () {} + + virtual bool seen () const = 0; + virtual argp_option const &build_option () const = 0; + virtual error_t parse_opt (char *arg, argp_state *state) = 0; + virtual int key () const = 0; +}; + +class option_common + : public option_i +{ + argp_option _m_opt; + +protected: + bool _m_seen; + + option_common (char const *description, + char const *arg_description, + char const *opt_long, char opt_short, + int flags = 0); + +public: + bool + seen () const + { + return _m_seen; + } + + argp_option const & + build_option () const + { + return _m_opt; + } + + int + key () const + { + return _m_opt.key; + } +}; + +template<class arg_type> +class value_converter; + +template<class arg_type> +class xoption + : public option_common +{ + arg_type _m_arg; + +public: + xoption (char const *description, + char const *arg_description, + char const *opt_long, char opt_short = 0, + int flags = 0) + : option_common (description, arg_description, opt_long, opt_short, flags) + { + } + + arg_type const &value () const + { + return _m_arg; + } + + arg_type const &value (arg_type arg) + { + return seen () ? _m_arg : arg; + } + + error_t parse_opt (char *arg, __attribute__ ((unused)) argp_state *state) + { + _m_seen = true; + _m_arg = value_converter<arg_type>::convert (arg); + return 0; + } +}; + +template<> +class xoption<void> + : public option_common +{ +public: + xoption (char const *description, + char const *opt_long, char opt_short = 0, int flags = 0) + : option_common (description, NULL, opt_long, opt_short, flags) + { + } + + error_t parse_opt (char *arg, __attribute__ ((unused)) argp_state *state) + { + assert (arg == NULL); + _m_seen = true; + return 0; + } + + // This shouldn't be promoted to option_common, as + // e.g. xoption<bool> will naturally have a different + // implementation. + operator bool () { return seen (); } +}; + +template<> +struct value_converter<std::string> +{ + static std::string convert (char const *arg) + { + if (arg == NULL) + return ""; + else + return arg; + } +}; + +template<> +struct value_converter<unsigned> +{ + static unsigned convert (char const *arg) + { + unsigned u; + if (std::sscanf (arg, "%u", &u) == 1) + return u; + else + return -1; + } +}; + +typedef xoption<void> void_option; +typedef xoption<std::string> string_option; +typedef xoption<unsigned> unsigned_option; + +options & global_opts (); + +template<class OPT> +struct global_opt + : public OPT +{ + template<typename... Args> + global_opt (Args const&... args) + : OPT (args...) + { + global_opts ().add (this); + } +}; + +#endif//DWARFLINT_OPTION_HH diff --git a/dwarflint/option_i.hh b/dwarflint/option_i.hh new file mode 100644 index 00000000..473d62a8 --- /dev/null +++ b/dwarflint/option_i.hh @@ -0,0 +1 @@ +class option_i; diff --git a/dwarflint/pri.cc b/dwarflint/pri.cc new file mode 100644 index 00000000..fc015428 --- /dev/null +++ b/dwarflint/pri.cc @@ -0,0 +1,65 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2008,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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <sstream> + +#include "../src/dwarfstrings.h" +#include "../libdw/c++/dwarf" + +#include "pri.hh" + +std::ostream & +pri::operator << (std::ostream &os, pri::pribase const &obj) +{ + return os << obj.m_s; +} + +std::ostream & +pri::operator << (std::ostream &os, pri::ref const &obj) +{ + std::stringstream ss; + ss << std::hex << "DIE " << obj.off; + return os << ss.str (); +} + +std::ostream & +pri::operator << (std::ostream &os, pri::hex const &obj) +{ + std::stringstream ss; + if (obj.pre) + ss << obj.pre << " "; + ss << std::hex << "0x" << obj.value; + return os << ss.str (); +} + +std::ostream & +pri::operator << (std::ostream &os, pri::range const &obj) +{ + return os << "[" << pri::addr (obj.start) + << ", " << pri::addr (obj.end) << ")"; +} + +std::string +pri::attr_name (int name) +{ + assert (name != -1); + return elfutils::dwarf::attributes::name (name); +} diff --git a/dwarflint/pri.hh b/dwarflint/pri.hh new file mode 100644 index 00000000..a1bb8ef6 --- /dev/null +++ b/dwarflint/pri.hh @@ -0,0 +1,113 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009,2010,2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_PRI_H +#define DWARFLINT_PRI_H + +#include "../libdw/libdw.h" +#include <string> + +#define PRI_NOT_ENOUGH ": not enough data for %s.\n" + +namespace pri +{ + class pribase + { + std::string m_s; + + protected: + pribase (std::string const &a, + std::string const &b = "", + std::string const &c = "") + : m_s (a + b + c) + {} + friend std::ostream &operator << (std::ostream &os, pribase const &obj); + + public: + operator std::string const &() const { return m_s; } + }; + std::ostream &operator << (std::ostream &os, pribase const &obj); + + struct not_enough + : public pribase + { + not_enough (std::string const &what) + : pribase ("not enough data for ", what) + {} + }; + + struct lacks_relocation + : public pribase + { + lacks_relocation (std::string const &what) + : pribase (what, " seems to lack a relocation") + {} + }; + + class ref + { + Dwarf_Off off; + public: + template <class T> + ref (T const &die) + : off (die.offset ()) + {} + friend std::ostream &operator << (std::ostream &os, ref const &obj); + }; + std::ostream &operator << (std::ostream &os, ref const &obj); + + class hex + { + Dwarf_Off value; + char const *const pre; + public: + hex (Dwarf_Off a_value, char const *a_pre = NULL) + : value (a_value) + , pre (a_pre) + {} + friend std::ostream &operator << (std::ostream &os, hex const &obj); + }; + std::ostream &operator << (std::ostream &os, hex const &obj); + + struct addr: public hex { + addr (Dwarf_Off off) : hex (off) {} + }; + + struct DIE: public hex { + DIE (Dwarf_Off off) : hex (off, "DIE ") {} + }; + + struct CU: public hex { + CU (Dwarf_Off off) : hex (off, "CU ") {} + }; + + class range + { + Dwarf_Off start; + Dwarf_Off end; + public: + range (Dwarf_Off a_start, Dwarf_Off a_end) + : start (a_start), end (a_end) + {} + friend std::ostream &operator << (std::ostream &os, range const &obj); + }; + std::ostream &operator << (std::ostream &os, range const &obj); + + std::string attr_name (int name); +} + +#endif//DWARFLINT_PRI_H diff --git a/dwarflint/readctx.cc b/dwarflint/readctx.cc new file mode 100644 index 00000000..c38eb398 --- /dev/null +++ b/dwarflint/readctx.cc @@ -0,0 +1,342 @@ +/* Pedantic checking of DWARF files. + Copyright (C) 2009, 2010 Red Hat, Inc. + This file is part of elfutils. + Written by Petr Machata <pmachata@redhat.com>, 2009. + + 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 <http://www.gnu.org/licenses/>. */ + +#include "readctx.hh" +#include "../libdw/dwarf.h" + +#include <stdlib.h> +#include <assert.h> +#include <byteswap.h> +#include <inttypes.h> + +/* read_Xubyte_* is basically cut'n'paste from memory-access.h. */ +union unaligned + { + void *p; + uint16_t u2; + uint32_t u4; + uint64_t u8; + int16_t s2; + int32_t s4; + int64_t s8; + } __attribute__ ((packed)); + +static uint16_t +read_2ubyte_unaligned (const void *p, bool other_byte_order) +{ + const union unaligned *up = (const union unaligned *)p; + if (other_byte_order) + return bswap_16 (up->u2); + return up->u2; +} + +/* Prefix with dwarflint_ for export, so that it doesn't get confused + with functions and macros in memory-access.h. */ +uint32_t +dwarflint_read_4ubyte_unaligned (const void *p, bool other_byte_order) +{ + const union unaligned *up = (const union unaligned *)p; + if (other_byte_order) + return bswap_32 (up->u4); + return up->u4; +} + +uint64_t +dwarflint_read_8ubyte_unaligned (const void *p, bool other_byte_order) +{ + const union unaligned *up = (const union unaligned *)p; + if (other_byte_order) + return bswap_64 (up->u8); + return up->u8; +} + + +#define read_2ubyte_unaligned_inc(Addr, OtherByteOrder) \ + ({ uint16_t t_ = read_2ubyte_unaligned (Addr, OtherByteOrder); \ + Addr = (__typeof (Addr)) (((uintptr_t) (Addr)) + 2); \ + t_; }) + +#define read_4ubyte_unaligned_inc(Addr, OtherByteOrder) \ + ({ uint32_t t_ = dwarflint_read_4ubyte_unaligned (Addr, OtherByteOrder); \ + Addr = (__typeof (Addr)) (((uintptr_t) (Addr)) + 4); \ + t_; }) + +#define read_8ubyte_unaligned_inc(Addr, OtherByteOrder) \ + ({ uint64_t t_ = dwarflint_read_8ubyte_unaligned (Addr, OtherByteOrder); \ + Addr = (__typeof (Addr)) (((uintptr_t) (Addr)) + 8); \ + t_; }) + + + +void +read_ctx_init (struct read_ctx *ctx, Elf_Data *data, bool other_byte_order) +{ + if (data == NULL) + abort (); + + ctx->data = data; + ctx->begin = (const unsigned char *)data->d_buf; + ctx->end = (const unsigned char *)data->d_buf + data->d_size; + ctx->ptr = (const unsigned char *)data->d_buf; + ctx->other_byte_order = other_byte_order; +} + +bool +read_ctx_init_sub (struct read_ctx *ctx, struct read_ctx *parent, + const unsigned char *begin, const unsigned char *end) +{ + if (parent == NULL) + abort (); + + if (begin < parent->begin + || end > parent->end) + return false; + + ctx->data = parent->data; + ctx->begin = begin; + ctx->end = end; + ctx->ptr = begin; + ctx->other_byte_order = parent->other_byte_order; + return true; +} + +uint64_t +read_ctx_get_offset (struct read_ctx *ctx) +{ + assert (ctx->ptr >= ctx->begin); + return (uint64_t)(ctx->ptr - ctx->begin); +} + +bool +read_ctx_need_data (struct read_ctx *ctx, size_t length) +{ + const unsigned char *ptr = ctx->ptr + length; + return ptr <= ctx->end && (length == 0 || ptr > ctx->ptr); +} + +bool +read_ctx_read_ubyte (struct read_ctx *ctx, unsigned char *ret) +{ + if (!read_ctx_need_data (ctx, 1)) + return false; + if (ret != NULL) + *ret = *ctx->ptr; + ctx->ptr++; + return true; +} + +int +read_ctx_read_uleb128 (struct read_ctx *ctx, uint64_t *ret) +{ + uint64_t result = 0; + int shift = 0; + int size = 8 * sizeof (result); + bool zero_tail = false; + + while (1) + { + uint8_t byte; + if (!read_ctx_read_ubyte (ctx, &byte)) + return -1; + + uint8_t payload = byte & 0x7f; + zero_tail = payload == 0 && shift > 0; + result |= (uint64_t)payload << shift; + shift += 7; + if (shift > size && byte != 0x1) + return -1; + if ((byte & 0x80) == 0) + break; + } + + if (ret != NULL) + *ret = result; + return zero_tail ? 1 : 0; +} + +int +read_ctx_read_sleb128 (struct read_ctx *ctx, int64_t *ret) +{ + int64_t result = 0; + int shift = 0; + int size = 8 * sizeof (result); + bool zero_tail = false; + bool sign = false; + + while (1) + { + uint8_t byte; + if (!read_ctx_read_ubyte (ctx, &byte)) + return -1; + + uint8_t payload = byte & 0x7f; + zero_tail = shift > 0 && ((payload == 0x7f && sign) + || (payload == 0 && !sign)); + sign = (byte & 0x40) != 0; /* Set sign for rest of loop & next round. */ + result |= (int64_t)payload << shift; + shift += 7; + if ((byte & 0x80) == 0) + { + if (shift < size && sign) + result |= -((int64_t)1 << shift); + break; + } + if (shift > size) + return -1; + } + + if (ret != NULL) + *ret = result; + return zero_tail ? 1 : 0; +} + +bool +read_ctx_read_2ubyte (struct read_ctx *ctx, uint16_t *ret) +{ + if (!read_ctx_need_data (ctx, 2)) + return false; + uint16_t val = read_2ubyte_unaligned_inc (ctx->ptr, ctx->other_byte_order); + if (ret != NULL) + *ret = val; + return true; +} + +bool +read_ctx_read_4ubyte (struct read_ctx *ctx, uint32_t *ret) +{ + if (!read_ctx_need_data (ctx, 4)) + return false; + uint32_t val = read_4ubyte_unaligned_inc (ctx->ptr, ctx->other_byte_order); + if (ret != NULL) + *ret = val; + return true; +} + +bool +read_ctx_read_8ubyte (struct read_ctx *ctx, uint64_t *ret) +{ + if (!read_ctx_need_data (ctx, 8)) + return false; + uint64_t val = read_8ubyte_unaligned_inc (ctx->ptr, ctx->other_byte_order); + if (ret != NULL) + *ret = val; + return true; +} + +bool +read_ctx_read_offset (struct read_ctx *ctx, bool dwarf64, uint64_t *ret) +{ + if (dwarf64) + return read_ctx_read_8ubyte (ctx, ret); + + uint32_t v; + if (!read_ctx_read_4ubyte (ctx, &v)) + return false; + + if (ret != NULL) + *ret = (uint64_t)v; + return true; +} + +bool +read_ctx_read_var (struct read_ctx *ctx, int width, uint64_t *ret) +{ + switch (width) + { + case 4: + case 8: + return read_ctx_read_offset (ctx, width == 8, ret); + case 2: + { + uint16_t val; + if (!read_ctx_read_2ubyte (ctx, &val)) + return false; + *ret = val; + return true; + } + case 1: + { + uint8_t val; + if (!read_ctx_read_ubyte (ctx, &val)) + return false; + *ret = val; + return true; + } + default: + return false; + }; +} + +const char * +read_ctx_read_str (struct read_ctx *ctx) +{ + const char *ret = (const char *)ctx->ptr; + uint8_t byte; + do + if (!read_ctx_read_ubyte (ctx, &byte)) + return NULL; + while (byte != 0); + return ret; +} + +bool +read_ctx_skip (struct read_ctx *ctx, uint64_t len) +{ + if (!read_ctx_need_data (ctx, len)) + return false; + ctx->ptr += len; + return true; +} + +bool +read_ctx_eof (struct read_ctx *ctx) +{ + return !read_ctx_need_data (ctx, 1); +} + +static void +update_off (struct read_ctx *ctx, + uint64_t *ret_off) +{ + if (ret_off != NULL) + *ret_off = (uint64_t)(ctx->ptr - ctx->begin); +} + +bool +read_check_zero_padding (struct read_ctx *ctx, + uint64_t *ret_off_start, + uint64_t *ret_off_end) +{ + assert (ctx->ptr != ctx->end); + update_off (ctx, ret_off_start); + bool ret = true; + + const unsigned char *save_ptr = ctx->ptr; + while (!read_ctx_eof (ctx)) + if (*ctx->ptr++ != 0) + { + ctx->ptr = save_ptr; + goto done; + } + + done: + update_off (ctx, ret_off_end); + return ret; +} diff --git a/dwarflint/readctx.hh b/dwarflint/readctx.hh new file mode 100644 index 00000000..95033ca4 --- /dev/null +++ b/dwarflint/readctx.hh @@ -0,0 +1,73 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009, 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef dwarflint_readctx_h +#define dwarflint_readctx_h + +#include <stdbool.h> +#include "../libelf/libelf.h" + +/* Functions and data structures related to bounds-checked + reading. */ + +struct read_ctx +{ + Elf_Data *data; + const unsigned char *ptr; + const unsigned char *begin; + const unsigned char *end; + bool other_byte_order; +}; + +uint32_t dwarflint_read_4ubyte_unaligned (const void *p, + bool other_byte_order); +uint64_t dwarflint_read_8ubyte_unaligned (const void *p, + bool other_byte_order); + + +void read_ctx_init (struct read_ctx *ctx, + Elf_Data *data, + bool other_byte_order); +bool read_ctx_init_sub (struct read_ctx *ctx, + struct read_ctx *parent, + const unsigned char *begin, + const unsigned char *end); +uint64_t read_ctx_get_offset (struct read_ctx *ctx); +bool read_ctx_need_data (struct read_ctx *ctx, size_t length); +bool read_ctx_read_ubyte (struct read_ctx *ctx, unsigned char *ret); +int read_ctx_read_uleb128 (struct read_ctx *ctx, uint64_t *ret); +int read_ctx_read_sleb128 (struct read_ctx *ctx, int64_t *ret); +bool read_ctx_read_2ubyte (struct read_ctx *ctx, uint16_t *ret); +bool read_ctx_read_4ubyte (struct read_ctx *ctx, uint32_t *ret); +bool read_ctx_read_8ubyte (struct read_ctx *ctx, uint64_t *ret); +bool read_ctx_read_offset (struct read_ctx *ctx, bool dwarf64, + uint64_t *ret); +bool read_ctx_read_var (struct read_ctx *ctx, int width, uint64_t *ret); +const char *read_ctx_read_str (struct read_ctx *ctx); +bool read_ctx_skip (struct read_ctx *ctx, uint64_t len); +bool read_ctx_eof (struct read_ctx *ctx); + +/* See if what remains in the read context is just a zero padding. If + yes, return true. If it isn't, revert the read pointer back as if + nothing had happened and return false. Furthermore, in any case, + if any of the ret pointers is non-NULL, it is filled, respectively, + with start and end offset of the zero padding run. */ +bool read_check_zero_padding (struct read_ctx *ctx, + uint64_t *ret_off_start, + uint64_t *ret_off_end); + +#endif diff --git a/dwarflint/reloc.cc b/dwarflint/reloc.cc new file mode 100644 index 00000000..ec8232dd --- /dev/null +++ b/dwarflint/reloc.cc @@ -0,0 +1,510 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009,2010,2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +// xxx drop as soon as not necessary +#define __STDC_FORMAT_MACROS + +#include "reloc.hh" +#include "elf_file.hh" +#include "messages.hh" +#include "misc.hh" +#include "readctx.hh" +#include "pri.hh" + +#include <sstream> +#include <libebl.h> +#include <cassert> +#include <cinttypes> + +namespace +{ + class reloc_locus + : public locus + { + locus const &_m_ref; + size_t _m_index; + uint64_t _m_offset; + int _m_type; + + public: + reloc_locus (int type, locus const &ref, uint64_t offset) + : _m_ref (ref) + , _m_index (-1) + , _m_offset (offset) + , _m_type (type) + { + } + + reloc_locus (int type, locus const &ref, unsigned index) + : _m_ref (ref) + , _m_index (index) + , _m_offset (-1) + , _m_type (type) + { + } + + void + set_offset (uint64_t offset) + { + _m_offset = offset; + } + + virtual std::string + format (bool) const + { + std::stringstream ss; + ss << (_m_type == SHT_REL ? ".rel" : ".rela") << " "; + if (_m_offset != (uint64_t)-1) + ss << pri::hex (_m_offset); + else + { + assert (_m_index != (size_t)-1); + ss << "#" << _m_index; + } + + // Do non-brief formatting of referee + ss << " of " << _m_ref.format (); + return ss.str (); + } + }; +} + +relocation * +relocation_next (relocation_data *reloc, uint64_t offset, + locus const &loc, enum skip_type st) +{ + if (reloc == NULL || reloc->rel == NULL) + return NULL; + + while (reloc->index < reloc->size) + { + struct relocation *rel = reloc->rel + reloc->index; + + /* This relocation entry is ahead of us. */ + if (rel->offset > offset) + return NULL; + + reloc->index++; + + if (rel->invalid) + continue; + + if (rel->offset < offset) + { + if (st != skip_ok) + { + reloc_locus reloc_where (reloc->type, loc, rel->offset); + wr_error (reloc_where) + << (st == skip_unref + ? "relocation targets unreferenced portion of the section." + : (assert (st == skip_mismatched), + "relocation relocates unknown datum.")) + << std::endl; + } + continue; + } + + return rel; + } + + return NULL; +} + +/* Skip all relocation up to offset, and leave cursor pointing at that + relocation, so that next time relocation_next is called, relocation + matching that offset is immediately yielded. */ +void +relocation_skip (struct relocation_data *reloc, uint64_t offset, + locus const &loc, enum skip_type st) +{ + if (reloc != NULL && reloc->rel != NULL) + relocation_next (reloc, offset - 1, loc, st); +} + +void +relocation_reset (struct relocation_data *reloc) +{ + if (reloc != NULL) + reloc->index = 0; +} + +/* Skip all the remaining relocations. */ +void +relocation_skip_rest (relocation_data *reloc, + locus const &loc) +{ + if (reloc->rel != NULL) + relocation_next (reloc, (uint64_t)-1, loc, skip_mismatched); +} + +static void +do_one_relocation (elf_file const *file, + relocation_data *reloc, + relocation *rel, + unsigned rel_width, + uint64_t *value, + reloc_locus const &reloc_where, + rel_target reltgt, + GElf_Sym *symbol, + GElf_Sym **symptr) +{ +#define require(T, STREAMOPS) \ + do { \ + if (!(T)) \ + { \ + wr_error (reloc_where) << STREAMOPS \ + << std::endl; \ + return; \ + } \ + } while (0) + +#define require_valid_section_index \ + require (section_index_valid, \ + "invalid associated section #" << section_index << '.') + + symbol = gelf_getsym (reloc->symdata, rel->symndx, symbol); + if (symptr != NULL) + *symptr = symbol; + if (symbol == NULL) + { + wr_error (&reloc_where, + ": couldn't obtain symbol #%d: %s.\n", + rel->symndx, elf_errmsg (-1)); + return; + } + + GElf_Section section_index = symbol->st_shndx; + /* XXX We should handle SHN_XINDEX here. Or, instead, maybe it + would be possible to use dwfl, which already does XINDEX + translation. */ + + if (section_index == 0) + { + wr_error (reloc_where) + << "relocation refers to an undefined symbol." + << std::endl; + return; + } + + // Valid in the sense that it can be used as an index to file->sec + bool section_index_valid = section_index < file->size; + + /* For ET_REL files, we do section layout manually. But we + don't update symbol table doing that. So instead of looking + at symbol value, look at section address. */ + GElf_Addr sym_value = symbol->st_value; + if (file->ehdr.e_type == ET_REL + && ELF64_ST_TYPE (symbol->st_info) == STT_SECTION) + { + if (sym_value != 0) + wr_message (reloc_where, mc_reloc | mc_impact_1) + << "relocation formed using STT_SECTION symbol with non-zero value." + << std::endl; + + require_valid_section_index; + sym_value = file->sec[section_index].shdr.sh_addr; + } + + /* It's target value, not section offset. */ + if (reltgt == rel_target::rel_value + || reltgt == rel_target::rel_address + || reltgt == rel_target::rel_exec) + { + /* If a target value is what's expected, then complain if + it's not either SHN_ABS, an SHF_ALLOC section, or + SHN_UNDEF. For data forms of address_size, an SHN_UNDEF + reloc is acceptable, otherwise reject it. */ + if (!(section_index == SHN_ABS + || (reltgt == rel_target::rel_address + && (section_index == SHN_UNDEF + || section_index == SHN_COMMON)))) + { + if (reltgt != rel_target::rel_address && section_index == SHN_UNDEF) + wr_error (&reloc_where, + ": relocation of an address is formed using SHN_UNDEF symbol" + " (symtab index %d).\n", rel->symndx); + else + { + require_valid_section_index; + GElf_Shdr *shdr = &file->sec[section_index].shdr; + if ((shdr->sh_flags & SHF_ALLOC) != SHF_ALLOC) + wr_message (mc_reloc | mc_impact_3, &reloc_where, + ": associated section %s isn't SHF_ALLOC.\n", + file->sec[section_index].name); + if (reltgt == rel_target::rel_exec + && (shdr->sh_flags & SHF_EXECINSTR) != SHF_EXECINSTR) + /* This may still be kosher, but it's suspicious. */ + wr_message (mc_reloc | mc_impact_2, &reloc_where, + ": relocation against %s is suspicious, expected executable section.\n", + file->sec[section_index].name); + } + } + } + else + { + require_valid_section_index; + section_id secid = file->sec[section_index].id; + if (reltgt != secid) + // If symtab[symndx].st_shndx does not match the expected + // debug section's index, complain. + wr_error (reloc_where) + << "relocation references section " + << (file->sec[section_index].name ?: "<invalid>") << ", but " + << section_locus (secid) << " was expected." << std::endl; + } + + /* Only do the actual relocation if we have ET_REL files. For + non-ET_REL files, only do the above checking. */ + if (file->ehdr.e_type == ET_REL) + { + *value = rel->addend + sym_value; + if (rel_width == 4) + *value = *value & (uint64_t)(uint32_t)-1; + } + +#undef require_valid_section_index +#undef require +} + +/* SYMPTR may be NULL, otherwise (**SYMPTR) has to yield valid memory + location. When the function returns, (*SYMPTR) is either NULL, in + which case we failed or didn't get around to obtain the symbol from + symbol table, or non-NULL, in which case the symbol was initialized. */ +void +relocate_one (struct elf_file const *file, + struct relocation_data *reloc, + struct relocation *rel, + unsigned width, uint64_t *value, + locus const &loc, + rel_target reltgt, + GElf_Sym **symptr) +{ + if (rel->invalid) + return; + + reloc_locus reloc_where (reloc->type, loc, rel->offset); + + GElf_Sym symbol_mem, *symbol; + if (symptr != NULL) + { + symbol = *symptr; + *symptr = NULL; + } + else + symbol = &symbol_mem; + + if (reltgt == sec_invalid) + { + wr_message (reloc_where, mc_impact_3 | mc_reloc) + << "relocates a datum that shouldn't be relocated.\n"; + return; + } + + Elf_Type type = ebl_reloc_simple_type (file->ebl, rel->type); + + unsigned rel_width; + switch (type) + { + case ELF_T_BYTE: + rel_width = 1; + break; + + case ELF_T_HALF: + rel_width = 2; + break; + + case ELF_T_WORD: + case ELF_T_SWORD: + rel_width = 4; + break; + + case ELF_T_XWORD: + case ELF_T_SXWORD: + rel_width = 8; + break; + + default: + /* This has already been diagnosed during the isolated + validation of relocation section. */ + return; + }; + + if (rel_width != width) + wr_error (reloc_where) + << rel_width << "-byte relocation relocates " + << width << "-byte datum.\n"; + + // Tolerate if we failed to obtain the symbol table. + if (reloc->symdata != NULL) + do_one_relocation (file, reloc, rel, rel_width, value, + reloc_where, reltgt, symbol, symptr); +} + +static GElf_Rela * +get_rel_or_rela (Elf_Data *data, int ndx, + GElf_Rela *dst, size_t type) +{ + if (type == SHT_RELA) + return gelf_getrela (data, ndx, dst); + else + { + assert (type == SHT_REL); + GElf_Rel rel_mem; + if (gelf_getrel (data, ndx, &rel_mem) == NULL) + return NULL; + dst->r_offset = rel_mem.r_offset; + dst->r_info = rel_mem.r_info; + dst->r_addend = 0; + return dst; + } +} + +/* Sort the reloc section so that the applicable addresses of + relocation entries are monotonously increasing. */ +static int +compare_rel (const void *a, const void *b) +{ + return ((struct relocation *)a)->offset + - ((struct relocation *)b)->offset; +} + +bool +read_rel (struct elf_file *file, + struct sec *sec, + Elf_Data *reldata, + bool elf_64) +{ + assert (sec->rel.type == SHT_REL + || sec->rel.type == SHT_RELA); + bool is_rela = sec->rel.type == SHT_RELA; + + struct read_ctx ctx; + read_ctx_init (&ctx, sec->data, file->other_byte_order); + + size_t entrysize + = elf_64 + ? (is_rela ? sizeof (Elf64_Rela) : sizeof (Elf64_Rel)) + : (is_rela ? sizeof (Elf32_Rela) : sizeof (Elf32_Rel)); + size_t count = reldata->d_size / entrysize; + + section_locus parent (sec->id); + + for (unsigned i = 0; i < count; ++i) + { + reloc_locus where (sec->rel.type, parent, i); + + REALLOC (&sec->rel, rel); + struct relocation *cur = sec->rel.rel + sec->rel.size++; + new (cur) relocation (); + + GElf_Rela rela_mem, *rela + = get_rel_or_rela (reldata, i, &rela_mem, sec->rel.type); + if (rela == NULL) + { + wr_error (&where, ": couldn't read relocation.\n"); + skip: + cur->invalid = true; + continue; + } + + int cur_type = GELF_R_TYPE (rela->r_info); + if (cur_type == 0) /* No relocation. */ + { + wr_message (mc_impact_3 | mc_reloc | mc_acc_bloat, &where, + ": NONE relocation is superfluous.\n"); + goto skip; + } + + cur->offset = rela->r_offset; + cur->symndx = GELF_R_SYM (rela->r_info); + cur->type = cur_type; + + where.set_offset (cur->offset); + + Elf_Type type = ebl_reloc_simple_type (file->ebl, cur->type); + int width; + + switch (type) + { + case ELF_T_WORD: + case ELF_T_SWORD: + width = 4; + break; + + case ELF_T_XWORD: + case ELF_T_SXWORD: + width = 8; + break; + + case ELF_T_BYTE: + case ELF_T_HALF: + /* Technically legal, but never used. Better have dwarflint + flag them as erroneous, because it's more likely these + are a result of a bug than actually being used. */ + { + char buf[64]; + wr_error (&where, ": 8 or 16-bit relocation type %s.\n", + ebl_reloc_type_name (file->ebl, cur->type, + buf, sizeof (buf))); + goto skip; + } + + default: + { + char buf[64]; + wr_error (&where, ": invalid relocation %d (%s).\n", + cur->type, + ebl_reloc_type_name (file->ebl, cur->type, + buf, sizeof (buf))); + goto skip; + } + }; + + if (cur->offset + width >= sec->data->d_size) + { + wr_error (&where, + ": relocation doesn't fall into relocated section.\n"); + goto skip; + } + + uint64_t value; + if (width == 4) + value = dwarflint_read_4ubyte_unaligned + ((char *)sec->data->d_buf + cur->offset, file->other_byte_order); + else + { + assert (width == 8); + value = dwarflint_read_8ubyte_unaligned + ((char *)sec->data->d_buf + cur->offset, file->other_byte_order); + } + + if (is_rela) + { + if (value != 0) + wr_message (mc_impact_2 | mc_reloc, &where, + ": SHR_RELA relocates a place with non-zero value (addend=%#" + PRIx64", value=%#"PRIx64").\n", rela->r_addend, value); + cur->addend = rela->r_addend; + } + else + cur->addend = value; + } + + qsort (sec->rel.rel, sec->rel.size, + sizeof (*sec->rel.rel), &compare_rel); + return true; +} diff --git a/dwarflint/reloc.hh b/dwarflint/reloc.hh new file mode 100644 index 00000000..eda9fd97 --- /dev/null +++ b/dwarflint/reloc.hh @@ -0,0 +1,151 @@ +/* Pedantic checking of DWARF files. + Copyright (C) 2008, 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_RELOC_H +#define DWARFLINT_RELOC_H + +#include "locus.hh" +#include "elf_file_i.hh" +#include <libelf.h> +#include <gelf.h> + +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. */ + + relocation () + : offset (0) + , addend (0) + , symndx (0) + , type (0) + , invalid (false) + {} +}; + +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. */ + + relocation_data () + : symdata (NULL) + , type (SHT_NULL) + , rel (NULL) + , size (0) + , alloc (0) + , index (0) + {} +}; + +enum skip_type + { + skip_unref = 0, + skip_mismatched = 1, + skip_ok, + }; + +struct rel_target +{ + enum target + { + rel_value, /* For relocations, this denotes that the + relocation is applied to target value, not a + section offset. */ + rel_address, /* Same as above, but for addresses. */ + rel_exec, /* Some as above, but we expect EXEC bit. */ + }; + +private: + bool _m_is_section; + union + { + section_id _m_section; + target _m_target; + }; + +public: + rel_target (section_id sec) + : _m_is_section (true) + , _m_section (sec) + {} + + rel_target (target t) + : _m_is_section (false) + , _m_target (t) + {} + + bool + operator== (section_id sec) + { + return _m_is_section && _m_section == sec; + } + + bool + operator== (target tgt) + { + return !_m_is_section && _m_target == tgt; + } + + template<class T> + bool + operator!= (T t) + { + return !(*this == t); + } +}; + +bool read_rel (struct elf_file *file, struct sec *sec, + Elf_Data *reldata, bool elf_64); + +relocation *relocation_next (struct relocation_data *reloc, + uint64_t offset, + locus const &loc, + enum skip_type st); + +void relocation_reset (struct relocation_data *reloc); + +void relocation_skip (struct relocation_data *reloc, uint64_t offset, + locus const &loc, enum skip_type st); + +void relocation_skip_rest (struct relocation_data *reloc, + locus const &loc); + +void relocate_one (struct elf_file const *file, + struct relocation_data *reloc, + struct relocation *rel, + unsigned width, uint64_t *value, + locus const &loc, + rel_target reltgt, + GElf_Sym **symptr); + +#define PRI_LACK_RELOCATION ": %s seems to lack a relocation.\n" + +#endif//DWARFLINT_RELOC_H diff --git a/dwarflint/section_id.cc b/dwarflint/section_id.cc new file mode 100644 index 00000000..a5c857cd --- /dev/null +++ b/dwarflint/section_id.cc @@ -0,0 +1,29 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#include "section_id.hh" + +#include <stdlib.h> + +char const *section_name[] = { + "<invalid>", +#define SEC(n) ".debug_"#n, + DEBUGINFO_SECTIONS + NULL +#undef SEC +}; diff --git a/dwarflint/section_id.hh b/dwarflint/section_id.hh new file mode 100644 index 00000000..2b38f1eb --- /dev/null +++ b/dwarflint/section_id.hh @@ -0,0 +1,49 @@ +/* Pedantic checking of DWARF files + Copyright (C) 2009, 2010, 2011 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_SECTION_ID_HH +#define DWARFLINT_SECTION_ID_HH + +#define DEBUGINFO_SECTIONS \ + SEC (info) \ + SEC (abbrev) \ + SEC (aranges) \ + SEC (pubnames) \ + SEC (pubtypes) \ + SEC (str) \ + SEC (line) \ + SEC (loc) \ + SEC (mac) \ + SEC (ranges) + +enum section_id + { + sec_invalid = 0, + + /* Debuginfo sections: */ +#define SEC(n) sec_##n, + DEBUGINFO_SECTIONS +#undef SEC + + count_debuginfo_sections + }; + +// section_name[0] is for sec_invalid. The last index is for +// count_debuginfo_sections and is NULL. +extern char const *section_name[]; + +#endif//DWARFLINT_SECTION_ID_HH diff --git a/dwarflint/sections.cc b/dwarflint/sections.cc new file mode 100644 index 00000000..f17ce531 --- /dev/null +++ b/dwarflint/sections.cc @@ -0,0 +1,444 @@ +/* Low-level section handling. + 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <cstdlib> +#include <cstring> +#include "../libelf/gelf.h" + +#include "sections.hh" +#include "messages.hh" +#include "pri.hh" +#include "misc.hh" + +checkdescriptor const * +load_sections::descriptor () +{ + static checkdescriptor cd + (checkdescriptor::create ("load_sections") + .hidden ()); + return &cd; +} + +checkdescriptor const * +section_base::descriptor () +{ + static checkdescriptor cd; + return &cd; +} + +namespace +{ + int + layout_rel_file (Elf *elf) + { + GElf_Ehdr ehdr; + if (gelf_getehdr (elf, &ehdr) == NULL) + return 1; + + if (ehdr.e_type != ET_REL) + return 0; + + /* Taken from libdwfl. */ + GElf_Addr base = 0; + GElf_Addr start = 0x1000, end = 0x1000, bias = 0; + + bool first = true; + Elf_Scn *scn = NULL; + while ((scn = elf_nextscn (elf, scn)) != NULL) + { + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); + if (unlikely (shdr == NULL)) + return 1; + + if (shdr->sh_flags & SHF_ALLOC) + { + const GElf_Xword align = shdr->sh_addralign ?: 1; + const GElf_Addr next = (end + align - 1) & -align; + if (shdr->sh_addr == 0 + /* Once we've started doing layout we have to do it all, + unless we just layed out the first section at 0 when + it already was at 0. */ + || (bias == 0 && end > start && end != next)) + { + shdr->sh_addr = next; + if (end == base) + /* This is the first section assigned a location. + Use its aligned address as the module's base. */ + start = base = shdr->sh_addr; + else if (unlikely (base & (align - 1))) + { + /* If BASE has less than the maximum alignment of + any section, we eat more than the optimal amount + of padding and so make the module's apparent + size come out larger than it would when placed + at zero. So reset the layout with a better base. */ + + start = end = base = (base + align - 1) & -align; + Elf_Scn *prev_scn = NULL; + do + { + prev_scn = elf_nextscn (elf, prev_scn); + GElf_Shdr prev_shdr_mem; + GElf_Shdr *prev_shdr = gelf_getshdr (prev_scn, + &prev_shdr_mem); + if (unlikely (prev_shdr == NULL)) + return 1; + if (prev_shdr->sh_flags & SHF_ALLOC) + { + const GElf_Xword prev_align + = prev_shdr->sh_addralign ?: 1; + + prev_shdr->sh_addr + = (end + prev_align - 1) & -prev_align; + end = prev_shdr->sh_addr + prev_shdr->sh_size; + + if (unlikely (! gelf_update_shdr (prev_scn, + prev_shdr))) + return 1; + } + } + while (prev_scn != scn); + continue; + } + + end = shdr->sh_addr + shdr->sh_size; + if (likely (shdr->sh_addr != 0) + && unlikely (! gelf_update_shdr (scn, shdr))) + return 1; + } + else + { + /* The address is already assigned. Just track it. */ + if (first || end < shdr->sh_addr + shdr->sh_size) + end = shdr->sh_addr + shdr->sh_size; + if (first || bias > shdr->sh_addr) + /* This is the lowest address in the module. */ + bias = shdr->sh_addr; + + if ((shdr->sh_addr - bias + base) & (align - 1)) + /* This section winds up misaligned using BASE. + Adjust BASE upwards to make it congruent to + the lowest section address in the file modulo ALIGN. */ + base = (((base + align - 1) & -align) + + (bias & (align - 1))); + } + + first = false; + } + } + return 0; + } + + Elf * + open_elf (int fd) + { + Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL); + if (unlikely (elf == NULL)) + { + wr_error () + << "Error opening file: " << elf_errmsg (-1) << std::endl; + throw check_base::failed (); + } + + if (layout_rel_file (elf)) + { + wr_error () + << "Couldn't layout ET_REL file." << std::endl; + throw check_base::failed (); + } + + return elf; + } + + struct secentry + { + Elf_Data *reldata; /* Relocation data if any found. */ + size_t reltype; /* SHT_REL or SHT_RELA. We need this + temporary store to be able to resolve + relocation section appearing before + relocated section. */ + size_t secndx; /* Index into file->sec or 0 if not yet loaded. */ + section_id id; /* Section type. */ + + explicit secentry (section_id a_id = sec_invalid) + : reldata (NULL) + , reltype (0) + , secndx (0) + , id (a_id) + {} + }; + + struct secinfo_map + : public std::map <std::string, secentry> + { + secinfo_map () + { + // 0 is invalid section + for (unsigned i = 1; i < count_debuginfo_sections; ++i) + (*this)[section_name[i]] = secentry (static_cast<section_id> (i)); + } + + secentry *get (const char *name) + { + iterator it = find (std::string (name)); + if (it == end ()) + return NULL; + else + return &it->second; + } + }; + + bool + elf_file_init (struct elf_file *file, int fd) + { + Elf *elf = open_elf (fd); + assert (elf != NULL); + memset (file, 0, sizeof (*file)); + + file->elf = elf; + file->ebl = ebl_openbackend (elf); + + if (file->ebl == NULL + || gelf_getehdr (elf, &file->ehdr) == NULL) + return false; + + file->addr_64 = file->ehdr.e_ident[EI_CLASS] == ELFCLASS64; + + /* Taken from dwarf_begin_elf.c. */ + if ((BYTE_ORDER == LITTLE_ENDIAN + && file->ehdr.e_ident[EI_DATA] == ELFDATA2MSB) + || (BYTE_ORDER == BIG_ENDIAN + && file->ehdr.e_ident[EI_DATA] == ELFDATA2LSB)) + file->other_byte_order = true; + + Elf_Scn *reloc_symtab = NULL; + secinfo_map secinfo; + + /* Now find all necessary debuginfo sections and associated + relocation sections. */ + + /* Section 0 is special, skip it. */ + REALLOC (file, sec); + new (file->sec + file->size++) sec (); + + if (false) + { + invalid_elf: + wr_error () << "Broken ELF: " << elf_errmsg (-1) << "." + << std::endl; + goto close_and_out; + } + + /* Check that the ELF file is sound. */ + for (Elf_Scn *scn = NULL; (scn = elf_nextscn (elf, scn)); ) + if (elf_rawdata (scn, NULL) == NULL) + goto invalid_elf; + + for (Elf_Scn *scn = NULL; (scn = elf_nextscn (elf, scn)); ) + { + REALLOC (file, sec); + size_t curndx = file->size++; + struct sec *cursec = file->sec + curndx; + new (cursec) sec (); + + GElf_Shdr *shdr = gelf_getshdr (scn, &cursec->shdr); + if (shdr == NULL) + goto invalid_elf; + + const char *scnname = elf_strptr (elf, file->ehdr.e_shstrndx, + shdr->sh_name); + // Validate the section name + if (scnname == NULL) + goto invalid_elf; + + if (!address_aligned (shdr->sh_addr, shdr->sh_addralign)) + wr_error () + << "Base address of section " << scnname << ", " + << pri::addr (shdr->sh_addr) << ", should have an alignment of " + << shdr->sh_addralign << std::endl; + + secentry *entry = secinfo.get (scnname); + cursec->scn = scn; + if (entry != NULL) + cursec->id = entry->id; + cursec->name = scnname; + + /* Dwarf section. */ + if (entry != NULL) + { + if (unlikely (entry->secndx != 0)) + wr_error () + << "Multiple occurrences of section " << scnname << std::endl; + else + { + /* Haven't seen a section of that name yet. */ + cursec->data = elf_getdata (scn, NULL); + if (cursec->data == NULL || cursec->data->d_buf == NULL) + /* Don't print out a warning, we'll get to that in + process_file. */ + cursec->data = NULL; + entry->secndx = curndx; + } + } + /* Relocation section. */ + else if (shdr->sh_type == SHT_RELA || shdr->sh_type == SHT_REL) + { + /* Get data of section that this REL(A) section relocates. */ + Elf_Scn *relocated_scn = elf_getscn (elf, shdr->sh_info); + Elf_Scn *symtab_scn = elf_getscn (elf, shdr->sh_link); + if (relocated_scn == NULL || symtab_scn == NULL) + goto invalid_elf; + + GElf_Shdr relocated_shdr_mem; + GElf_Shdr *relocated_shdr = gelf_getshdr (relocated_scn, + &relocated_shdr_mem); + if (relocated_shdr == NULL) + goto invalid_elf; + + const char *relocated_scnname + = elf_strptr (elf, file->ehdr.e_shstrndx, + relocated_shdr->sh_name); + if (unlikely (relocated_scnname == NULL)) + goto invalid_elf; + + secentry *relocated = secinfo.get (relocated_scnname); + if (relocated != NULL) + { + if (relocated->reldata != NULL) + wr_error () + << "Several relocation sections for debug section " + << relocated_scnname << ". Ignoring " << scnname + << "." << std::endl; + else + { + relocated->reldata = elf_getdata (scn, NULL); + if (unlikely (relocated->reldata == NULL + || relocated->reldata->d_buf == NULL)) + { + wr_error () + << "Data-less relocation section " << scnname + << "." << std::endl; + relocated->reldata = NULL; + } + else + relocated->reltype = shdr->sh_type; + } + + if (reloc_symtab == NULL) + reloc_symtab = symtab_scn; + else if (reloc_symtab != symtab_scn) + wr_error () + << "Relocation sections use multiple symbol tables." + << std::endl; + } + } + } + + for (secinfo_map::iterator it = secinfo.begin (); it != secinfo.end (); ++it) + if (it->second.secndx != 0) + file->debugsec[it->second.id] = file->sec + it->second.secndx; + + if (true) + { + Elf_Data *reloc_symdata = NULL; + if (reloc_symtab != NULL) + { + reloc_symdata = elf_getdata (reloc_symtab, NULL); + if (reloc_symdata == NULL) + /* Not a show stopper, we can check a lot of stuff even + without a symbol table. */ + wr_error () << "Couldn't obtain symtab data." << std::endl; + } + + /* Check relocation sections that we've got. */ + for (secinfo_map::iterator it = secinfo.begin (); it != secinfo.end (); ++it) + { + secentry *cur = &it->second; + if (cur->secndx != 0 && cur->reldata != NULL) + { + struct sec *sec = file->sec + cur->secndx; + sec->rel.type = cur->reltype; + if (sec->data == NULL) + wr_error (section_locus (sec->id)) + << "this data-less section has a relocation section." + << std::endl; + else if (read_rel (file, sec, cur->reldata, file->addr_64)) + sec->rel.symdata = reloc_symdata; + } + } + + if (secentry *str = secinfo.get (".debug_str")) + if (str->reldata != NULL) + wr_message (section_locus (sec_str), mc_impact_2 | mc_elf) + << "there's a relocation section associated with this section." + << std::endl; + } + + return true; + + close_and_out: + if (elf != NULL) + { + elf_errno (); // clear errno + elf_end (elf); + int err = elf_errno (); + if (err != 0) + wr_error () + << "error while closing Elf descriptor: " + << elf_errmsg (err) << std::endl; + } + return false; + } +} + +load_sections::load_sections (checkstack &stack __attribute__ ((unused)), + dwarflint &lint) +{ + if (!elf_file_init (&file, lint.fd ())) + throw check_base::failed (); +} + +load_sections::~load_sections () +{ + if (file.ebl != NULL) + ebl_closebackend (file.ebl); + free (file.sec); + elf_end (file.elf); +} + +sec & +section_base::get_sec_or_throw (section_id secid) +{ + if (sec *s = sections->file.debugsec[secid]) + if (s->data != NULL) + return *s; + + throw check_base::failed (); +} + +section_base::section_base (checkstack &stack, + dwarflint &lint, section_id secid) + : sections (lint.check (stack, sections)) + , sect (get_sec_or_throw (secid)) + , file (sections->file) +{ +} diff --git a/dwarflint/sections.hh b/dwarflint/sections.hh new file mode 100644 index 00000000..9e99f2f7 --- /dev/null +++ b/dwarflint/sections.hh @@ -0,0 +1,72 @@ +/* Low-level section handling. + 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_SECTIONS_HH +#define DWARFLINT_SECTIONS_HH + +#include "checks.hh" +#include "elf_file.hh" + +class load_sections + : public check<load_sections> +{ +public: + static checkdescriptor const *descriptor (); + + elf_file file; + load_sections (checkstack &stack, dwarflint &lint); + ~load_sections (); +}; + +class section_base +{ + load_sections *sections; + sec &get_sec_or_throw (section_id secid); + +public: + static checkdescriptor const *descriptor (); + + sec § + elf_file &file; + section_base (checkstack &stack, + dwarflint &lint, section_id secid); + + relocation_data *reldata () const + { + return sect.rel.size > 0 ? §.rel : NULL; + } +}; + +template<section_id sec_id> +class section + : public section_base + , public check<section<sec_id> > +{ +public: + static checkdescriptor const *descriptor () { + static checkdescriptor cd + (checkdescriptor::create (section_name[sec_id]) + .hidden ()); + return &cd; + } + + explicit section (checkstack &stack, dwarflint &lint) + : section_base (stack, lint, sec_id) + {} +}; + +#endif//DWARFLINT_SECTIONS_HH diff --git a/dwarflint/sections_i.hh b/dwarflint/sections_i.hh new file mode 100644 index 00000000..ef5ec213 --- /dev/null +++ b/dwarflint/sections_i.hh @@ -0,0 +1,2 @@ +#include "section_id.hh" +template<enum section_id> class section; diff --git a/dwarflint/tests/DW_AT-later-version.bz2 b/dwarflint/tests/DW_AT-later-version.bz2 Binary files differnew file mode 100644 index 00000000..2a5690b3 --- /dev/null +++ b/dwarflint/tests/DW_AT-later-version.bz2 diff --git a/dwarflint/tests/DW_AT_high_pc-below.bz2 b/dwarflint/tests/DW_AT_high_pc-below.bz2 Binary files differnew file mode 100644 index 00000000..0221d4e1 --- /dev/null +++ b/dwarflint/tests/DW_AT_high_pc-below.bz2 diff --git a/dwarflint/tests/DW_AT_high_pc-relative.bz2 b/dwarflint/tests/DW_AT_high_pc-relative.bz2 Binary files differnew file mode 100644 index 00000000..d5196652 --- /dev/null +++ b/dwarflint/tests/DW_AT_high_pc-relative.bz2 diff --git a/dwarflint/tests/aranges_terminate_early.bz2 b/dwarflint/tests/aranges_terminate_early.bz2 Binary files differnew file mode 100755 index 00000000..d61925c0 --- /dev/null +++ b/dwarflint/tests/aranges_terminate_early.bz2 diff --git a/dwarflint/tests/check_debug_info_refs-1.bz2 b/dwarflint/tests/check_debug_info_refs-1.bz2 Binary files differnew file mode 100755 index 00000000..6fe30488 --- /dev/null +++ b/dwarflint/tests/check_debug_info_refs-1.bz2 diff --git a/dwarflint/tests/check_debug_info_refs-2.bz2 b/dwarflint/tests/check_debug_info_refs-2.bz2 Binary files differnew file mode 100644 index 00000000..04749292 --- /dev/null +++ b/dwarflint/tests/check_debug_info_refs-2.bz2 diff --git a/dwarflint/tests/check_range_out_of_scope-1.bz2 b/dwarflint/tests/check_range_out_of_scope-1.bz2 Binary files differnew file mode 100755 index 00000000..1ef81cb8 --- /dev/null +++ b/dwarflint/tests/check_range_out_of_scope-1.bz2 diff --git a/dwarflint/tests/check_self_referential_die.bz2 b/dwarflint/tests/check_self_referential_die.bz2 Binary files differnew file mode 100644 index 00000000..06a45305 --- /dev/null +++ b/dwarflint/tests/check_self_referential_die.bz2 diff --git a/dwarflint/tests/crc7.ko.debug.bz2 b/dwarflint/tests/crc7.ko.debug.bz2 Binary files differnew file mode 100644 index 00000000..be66966e --- /dev/null +++ b/dwarflint/tests/crc7.ko.debug.bz2 diff --git a/dwarflint/tests/debug_abbrev-duplicate-attribute.bz2 b/dwarflint/tests/debug_abbrev-duplicate-attribute.bz2 Binary files differnew file mode 100644 index 00000000..119c3996 --- /dev/null +++ b/dwarflint/tests/debug_abbrev-duplicate-attribute.bz2 diff --git a/dwarflint/tests/empty-1.bz2 b/dwarflint/tests/empty-1.bz2 Binary files differnew file mode 100644 index 00000000..25a7ada0 --- /dev/null +++ b/dwarflint/tests/empty-1.bz2 diff --git a/dwarflint/tests/garbage-1.bz2 b/dwarflint/tests/garbage-1.bz2 Binary files differnew file mode 100644 index 00000000..de1b26d2 --- /dev/null +++ b/dwarflint/tests/garbage-1.bz2 diff --git a/dwarflint/tests/garbage-10.bz2 b/dwarflint/tests/garbage-10.bz2 Binary files differnew file mode 100644 index 00000000..2afab22f --- /dev/null +++ b/dwarflint/tests/garbage-10.bz2 diff --git a/dwarflint/tests/garbage-11.bz2 b/dwarflint/tests/garbage-11.bz2 Binary files differnew file mode 100644 index 00000000..450e72cc --- /dev/null +++ b/dwarflint/tests/garbage-11.bz2 diff --git a/dwarflint/tests/garbage-12.bz2 b/dwarflint/tests/garbage-12.bz2 Binary files differnew file mode 100644 index 00000000..a38e928e --- /dev/null +++ b/dwarflint/tests/garbage-12.bz2 diff --git a/dwarflint/tests/garbage-2.bz2 b/dwarflint/tests/garbage-2.bz2 Binary files differnew file mode 100644 index 00000000..3cd56192 --- /dev/null +++ b/dwarflint/tests/garbage-2.bz2 diff --git a/dwarflint/tests/garbage-3.bz2 b/dwarflint/tests/garbage-3.bz2 Binary files differnew file mode 100644 index 00000000..ad66a414 --- /dev/null +++ b/dwarflint/tests/garbage-3.bz2 diff --git a/dwarflint/tests/garbage-4.bz2 b/dwarflint/tests/garbage-4.bz2 Binary files differnew file mode 100644 index 00000000..20e75d88 --- /dev/null +++ b/dwarflint/tests/garbage-4.bz2 diff --git a/dwarflint/tests/garbage-5.bz2 b/dwarflint/tests/garbage-5.bz2 Binary files differnew file mode 100644 index 00000000..9b0401eb --- /dev/null +++ b/dwarflint/tests/garbage-5.bz2 diff --git a/dwarflint/tests/garbage-6.bz2 b/dwarflint/tests/garbage-6.bz2 Binary files differnew file mode 100644 index 00000000..6cb8a44b --- /dev/null +++ b/dwarflint/tests/garbage-6.bz2 diff --git a/dwarflint/tests/garbage-7.bz2 b/dwarflint/tests/garbage-7.bz2 Binary files differnew file mode 100644 index 00000000..41963b62 --- /dev/null +++ b/dwarflint/tests/garbage-7.bz2 diff --git a/dwarflint/tests/garbage-8.bz2 b/dwarflint/tests/garbage-8.bz2 Binary files differnew file mode 100644 index 00000000..b9889832 --- /dev/null +++ b/dwarflint/tests/garbage-8.bz2 diff --git a/dwarflint/tests/garbage-9.bz2 b/dwarflint/tests/garbage-9.bz2 Binary files differnew file mode 100644 index 00000000..c2e7a8bf --- /dev/null +++ b/dwarflint/tests/garbage-9.bz2 diff --git a/dwarflint/tests/hello.bad-1.bz2 b/dwarflint/tests/hello.bad-1.bz2 Binary files differnew file mode 100644 index 00000000..52531fff --- /dev/null +++ b/dwarflint/tests/hello.bad-1.bz2 diff --git a/dwarflint/tests/hello.bad-1.s b/dwarflint/tests/hello.bad-1.s new file mode 100644 index 00000000..8679ec10 --- /dev/null +++ b/dwarflint/tests/hello.bad-1.s @@ -0,0 +1,246 @@ + .file "hello.c" +# GNU C (GCC) version 4.5.1 20100924 (Red Hat 4.5.1-4) (x86_64-redhat-linux) +# compiled by GNU C version 4.5.1 20100924 (Red Hat 4.5.1-4), GMP version 4.3.1, MPFR version 2.4.2, MPC version 0.8.1 +# GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 +# options passed: hello.c -mtune=generic -march=x86-64 -g -fverbose-asm +# options enabled: -falign-loops -fargument-alias +# -fasynchronous-unwind-tables -fauto-inc-dec -fbranch-count-reg -fcommon +# -fdelete-null-pointer-checks -fdwarf2-cfi-asm -fearly-inlining +# -feliminate-unused-debug-types -ffunction-cse -fgcse-lm -fident +# -finline-functions-called-once -fira-share-save-slots +# -fira-share-spill-slots -fivopts -fkeep-static-consts +# -fleading-underscore -fmath-errno -fmerge-debug-strings +# -fmove-loop-invariants -fpeephole -freg-struct-return +# -fsched-critical-path-heuristic -fsched-dep-count-heuristic +# -fsched-group-heuristic -fsched-interblock -fsched-last-insn-heuristic +# -fsched-rank-heuristic -fsched-spec -fsched-spec-insn-heuristic +# -fsched-stalled-insns-dep -fshow-column -fsigned-zeros +# -fsplit-ivs-in-unroller -ftrapping-math -ftree-cselim -ftree-forwprop +# -ftree-loop-im -ftree-loop-ivcanon -ftree-loop-optimize +# -ftree-parallelize-loops= -ftree-phiprop -ftree-pta -ftree-reassoc +# -ftree-scev-cprop -ftree-slp-vectorize -ftree-vect-loop-version +# -funit-at-a-time -funwind-tables -fvect-cost-model -fverbose-asm +# -fzero-initialized-in-bss -m128bit-long-double -m64 -m80387 +# -maccumulate-outgoing-args -malign-stringops -mfancy-math-387 +# -mfp-ret-in-387 -mfused-madd -mglibc -mieee-fp -mmmx -mno-sse4 +# -mpush-args -mred-zone -msse -msse2 -mtls-direct-seg-refs + + .section .debug_abbrev,"",@progbits +.Ldebug_abbrev0: + .section .debug_info,"",@progbits +.Ldebug_info0: + .section .debug_line,"",@progbits +.Ldebug_line0: + .text +.Ltext0: +# Compiler executable checksum: ea394b69293dd698607206e8e43d607e + +.globl main + .type main, @function +main: +.LFB0: + .file 1 "hello.c" + # hello.c:3 + .loc 1 3 0 + .cfi_startproc + # basic block 2 + pushq %rbp # # 15 *pushdi2_rex64/1 [length = 1] + .cfi_def_cfa_offset 16 + .cfi_offset 6, -16 + movq %rsp, %rbp #, # 16 *movdi_1_rex64/2 [length = 3] + .cfi_def_cfa_register 6 + movl %edi, -4(%rbp) # argc, argc # 2 *movsi_1/2 [length = 3] + movq %rsi, -16(%rbp) # argv, argv # 3 *movdi_1_rex64/4 [length = 4] + # hello.c:4 + .loc 1 4 0 + leave # 21 leave_rex64 [length = 1] + .cfi_def_cfa 7, 8 + ret # 22 return_internal [length = 1] + .cfi_endproc +.LFE0: + .size main, .-main +.Letext0: + .section .debug_info + .long 0x84 # Length of Compilation Unit Info + .value 0x3 # DWARF version number + .long .Ldebug_abbrev0 # Offset Into Abbrev. Section + .byte 0x8 # Pointer Size (in bytes) + .uleb128 0x1 # (DIE (0xb) DW_TAG_compile_unit) + .long .LASF3 # DW_AT_producer: "GNU C 4.5.1 20100924 (Red Hat 4.5.1-4)" + .byte 0x1 # DW_AT_language + .long .LASF4 # DW_AT_name: "hello.c" + .long .LASF5 # DW_AT_comp_dir: "/home/mark/src/tests" + .quad .Ltext0 # DW_AT_low_pc + .quad .Letext0 # DW_AT_high_pc + .long .Ldebug_line0 # DW_AT_stmt_list + .uleb128 0x2 # (DIE (0x2d) DW_TAG_subprogram) + .byte 0x1 # DW_AT_external + .long .LASF6 # DW_AT_name: "main" + .byte 0x1 # DW_AT_decl_file (hello.c) + .byte 0x2 # DW_AT_decl_line + .byte 0x1 # DW_AT_prototyped + .long 0x6d # DW_AT_type + .quad .LFB0 # DW_AT_low_pc + .quad .LFE0 # DW_AT_high_pc + .byte 0x1 # DW_AT_frame_base + .byte 0x9c # DW_OP_call_frame_cfa + .long 0x6d # DW_AT_sibling + .uleb128 0x3 # (DIE (0x50) DW_TAG_formal_parameter) + .long .LASF0 # DW_AT_name: "argc" + .byte 0x1 # DW_AT_decl_file (hello.c) + .byte 0x2 # DW_AT_decl_line + .long 0x6d # DW_AT_type + .byte 0x2 # DW_AT_location + .byte 0x91 # DW_OP_fbreg + .sleb128 -20 + .uleb128 0x3 # (DIE (0x5e) DW_TAG_formal_parameter) + .long .LASF1 # DW_AT_name: "argv" + .byte 0x1 # DW_AT_decl_file (hello.c) + .byte 0x2 # DW_AT_decl_line + .long 0x74 # DW_AT_type + .byte 0x2 # DW_AT_location + .byte 0x91 # DW_OP_fbreg + .sleb128 -32 + .byte 0x0 # end of children of DIE 0x2d + .uleb128 0x4 # (DIE (0x6d) DW_TAG_base_type) + .byte 0x4 # DW_AT_byte_size + .byte 0x5 # DW_AT_encoding + .ascii "int\0" # DW_AT_name + .uleb128 0x5 # (DIE (0x74) DW_TAG_pointer_type) + .byte 0x8 # DW_AT_byte_size + .long 0x7a # DW_AT_type + .uleb128 0x5 # (DIE (0x7a) DW_TAG_pointer_type) + .byte 0x8 # DW_AT_byte_size + .long 0x80 # DW_AT_type + .uleb128 0x6 # (DIE (0x80) DW_TAG_base_type) + .byte 0x1 # DW_AT_byte_size + .byte 0x6 # DW_AT_encoding + .long .LASF2 # DW_AT_name: "char" + .byte 0x0 # end of children of DIE 0xb + .section .debug_abbrev + .uleb128 0x1 # (abbrev code) + .uleb128 0x11 # (TAG: DW_TAG_compile_unit) + .byte 0x1 # DW_children_yes + .uleb128 0x25 # (DW_AT_producer) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x13 # (DW_AT_language) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3 # (DW_AT_name) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x1b # (DW_AT_comp_dir) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x11 # (DW_AT_low_pc) + .uleb128 0x1 # (DW_FORM_addr) + .uleb128 0x12 # (DW_AT_high_pc) + .uleb128 0x1 # (DW_FORM_addr) + .uleb128 0x10 # (DW_AT_stmt_list) + .uleb128 0x6 # (DW_FORM_data4) + .byte 0x0 + .byte 0x0 + .uleb128 0x2 # (abbrev code) + .uleb128 0x2e # (TAG: DW_TAG_subprogram) + .byte 0x1 # DW_children_yes + .uleb128 0x3f # (DW_AT_external) + .uleb128 0xc # (DW_FORM_flag) + .uleb128 0x3 # (DW_AT_name) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x3a # (DW_AT_decl_file) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3b # (DW_AT_decl_line) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x27 # (DW_AT_prototyped) + .uleb128 0xc # (DW_FORM_flag) + .uleb128 0x49 # (DW_AT_type) + .uleb128 0x13 # (DW_FORM_ref4) + .uleb128 0x11 # (DW_AT_low_pc) + .uleb128 0x1 # (DW_FORM_addr) + .uleb128 0x12 # (DW_AT_high_pc) + .uleb128 0x1 # (DW_FORM_addr) + .uleb128 0x40 # (DW_AT_frame_base) + .uleb128 0xa # (DW_FORM_block1) + .uleb128 0x1 # (DW_AT_sibling) + .uleb128 0x13 # (DW_FORM_ref4) + .byte 0x0 + .byte 0x0 + .uleb128 0x3 # (abbrev code) + .uleb128 0x5 # (TAG: DW_TAG_formal_parameter) + .byte 0x0 # DW_children_no + .uleb128 0x3 # (DW_AT_name) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x3a # (DW_AT_decl_file) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3b # (DW_AT_decl_line) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x49 # (DW_AT_type) + .uleb128 0x13 # (DW_FORM_ref4) + .uleb128 0x2 # (DW_AT_location) + .uleb128 0xa # (DW_FORM_block1) + .byte 0x0 + .byte 0x0 + .uleb128 0x4 # (abbrev code) + .uleb128 0x24 # (TAG: DW_TAG_base_type) + .byte 0x0 # DW_children_no + .uleb128 0xb # (DW_AT_byte_size) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3e # (DW_AT_encoding) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3 # (DW_AT_name) + .uleb128 0x8 # (DW_FORM_string) + .byte 0x0 + .byte 0x0 + .uleb128 0x5 # (abbrev code) + .uleb128 0xf # (TAG: DW_TAG_pointer_type) + .byte 0x0 # DW_children_no + .uleb128 0xb # (DW_AT_byte_size) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x49 # (DW_AT_type) + .uleb128 0x13 # (DW_FORM_ref4) + .byte 0x0 + .byte 0x0 + .uleb128 0x6 # (abbrev code) + .uleb128 0x24 # (TAG: DW_TAG_base_type) + .byte 0x0 # DW_children_no + .uleb128 0xb # (DW_AT_byte_size) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3e # (DW_AT_encoding) + .uleb128 0xb # (DW_FORM_data1) + .byte 0x0 + .byte 0x0 + .byte 0x0 + .section .debug_pubnames,"",@progbits + .long 0x17 # Length of Public Names Info + .value 0x2 # DWARF Version + .long .Ldebug_info0 # Offset of Compilation Unit Info + .long 0x88 # Compilation Unit Length + .long 0x2d # DIE offset + .ascii "main\0" # external name + .long 0x0 + .section .debug_aranges,"",@progbits + .long 0x2c # Length of Address Ranges Info + .value 0x2 # DWARF Version + .long .Ldebug_info0 # Offset of Compilation Unit Info + .byte 0x8 # Size of Address + .byte 0x0 # Size of Segment Descriptor + .value 0x0 # Pad to 16 byte boundary + .value 0x0 + .quad .Ltext0 # Address + .quad .Letext0-.Ltext0 # Length + .quad 0x0 + .quad 0x0 + .section .debug_str,"MS",@progbits,1 +.LASF1: + .string "argv" +.LASF4: + .string "hello.c" +.LASF5: + .string "/home/mark/src/tests" +.LASF6: + .string "main" +.LASF0: + .string "argc" +.LASF3: + .string "GNU C 4.5.1 20100924 (Red Hat 4.5.1-4)" +.LASF2: + .string "char" + .ident "GCC: (GNU) 4.5.1 20100924 (Red Hat 4.5.1-4)" + .section .note.GNU-stack,"",@progbits diff --git a/dwarflint/tests/hello.bad-2.bz2 b/dwarflint/tests/hello.bad-2.bz2 Binary files differnew file mode 100644 index 00000000..8ccbe37b --- /dev/null +++ b/dwarflint/tests/hello.bad-2.bz2 diff --git a/dwarflint/tests/hello.bad-2.s b/dwarflint/tests/hello.bad-2.s new file mode 100644 index 00000000..5fcd2d50 --- /dev/null +++ b/dwarflint/tests/hello.bad-2.s @@ -0,0 +1,246 @@ + .file "hello.c" +# GNU C (GCC) version 4.5.1 20100924 (Red Hat 4.5.1-4) (x86_64-redhat-linux) +# compiled by GNU C version 4.5.1 20100924 (Red Hat 4.5.1-4), GMP version 4.3.1, MPFR version 2.4.2, MPC version 0.8.1 +# GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 +# options passed: hello.c -mtune=generic -march=x86-64 -g -fverbose-asm +# options enabled: -falign-loops -fargument-alias +# -fasynchronous-unwind-tables -fauto-inc-dec -fbranch-count-reg -fcommon +# -fdelete-null-pointer-checks -fdwarf2-cfi-asm -fearly-inlining +# -feliminate-unused-debug-types -ffunction-cse -fgcse-lm -fident +# -finline-functions-called-once -fira-share-save-slots +# -fira-share-spill-slots -fivopts -fkeep-static-consts +# -fleading-underscore -fmath-errno -fmerge-debug-strings +# -fmove-loop-invariants -fpeephole -freg-struct-return +# -fsched-critical-path-heuristic -fsched-dep-count-heuristic +# -fsched-group-heuristic -fsched-interblock -fsched-last-insn-heuristic +# -fsched-rank-heuristic -fsched-spec -fsched-spec-insn-heuristic +# -fsched-stalled-insns-dep -fshow-column -fsigned-zeros +# -fsplit-ivs-in-unroller -ftrapping-math -ftree-cselim -ftree-forwprop +# -ftree-loop-im -ftree-loop-ivcanon -ftree-loop-optimize +# -ftree-parallelize-loops= -ftree-phiprop -ftree-pta -ftree-reassoc +# -ftree-scev-cprop -ftree-slp-vectorize -ftree-vect-loop-version +# -funit-at-a-time -funwind-tables -fvect-cost-model -fverbose-asm +# -fzero-initialized-in-bss -m128bit-long-double -m64 -m80387 +# -maccumulate-outgoing-args -malign-stringops -mfancy-math-387 +# -mfp-ret-in-387 -mfused-madd -mglibc -mieee-fp -mmmx -mno-sse4 +# -mpush-args -mred-zone -msse -msse2 -mtls-direct-seg-refs + + .section .debug_abbrev,"",@progbits +.Ldebug_abbrev0: + .section .debug_info,"",@progbits +.Ldebug_info0: + .section .debug_line,"",@progbits +.Ldebug_line0: + .text +.Ltext0: +# Compiler executable checksum: ea394b69293dd698607206e8e43d607e + +.globl main + .type main, @function +main: +.LFB0: + .file 1 "hello.c" + # hello.c:3 + .loc 1 3 0 + .cfi_startproc + # basic block 2 + pushq %rbp # # 15 *pushdi2_rex64/1 [length = 1] + .cfi_def_cfa_offset 16 + .cfi_offset 6, -16 + movq %rsp, %rbp #, # 16 *movdi_1_rex64/2 [length = 3] + .cfi_def_cfa_register 6 + movl %edi, -4(%rbp) # argc, argc # 2 *movsi_1/2 [length = 3] + movq %rsi, -16(%rbp) # argv, argv # 3 *movdi_1_rex64/4 [length = 4] + # hello.c:4 + .loc 1 4 0 + leave # 21 leave_rex64 [length = 1] + .cfi_def_cfa 7, 8 + ret # 22 return_internal [length = 1] + .cfi_endproc +.LFE0: + .size main, .-main +.Letext0: + .section .debug_info + .long 0x84 # Length of Compilation Unit Info + .value 0x3 # DWARF version number + .long .Ldebug_abbrev0 # Offset Into Abbrev. Section + .byte 0x8 # Pointer Size (in bytes) + .uleb128 0x1 # (DIE (0xb) DW_TAG_compile_unit) + .long .LASF3 # DW_AT_producer: "GNU C 4.5.1 20100924 (Red Hat 4.5.1-4)" + .byte 0x1 # DW_AT_language + .long .LASF4 # DW_AT_name: "hello.c" + .long .LASF5 # DW_AT_comp_dir: "/home/mark/src/tests" + .quad .Ltext0 # DW_AT_low_pc + .quad .Letext0 # DW_AT_high_pc + .long .Ldebug_line0 # DW_AT_stmt_list + .uleb128 0x2 # (DIE (0x2d) DW_TAG_subprogram) + .byte 0x1 # DW_AT_external + .long .LASF6 # DW_AT_name: "main" + .byte 0x1 # DW_AT_decl_file (hello.c) + .byte 0x2 # DW_AT_decl_line + .byte 0x1 # DW_AT_prototyped + .long 0x6d # DW_AT_type + .quad .LFB0 # DW_AT_low_pc + .quad .LFE0 # DW_AT_high_pc + .byte 0x1 # DW_AT_frame_base + .byte 0x9c # DW_OP_call_frame_cfa + .long 0x6d # DW_AT_sibling + .uleb128 0x3 # (DIE (0x50) DW_TAG_formal_parameter) + .long .LASF0 # DW_AT_name: "argc" + .byte 0x1 # DW_AT_decl_file (hello.c) + .byte 0x2 # DW_AT_decl_line + .long 0x6d # DW_AT_type + .byte 0x2 # DW_AT_location + .byte 0x91 # DW_OP_fbreg + .sleb128 -20 + .uleb128 0x3 # (DIE (0x5e) DW_TAG_formal_parameter) + .long .LASF1 # DW_AT_name: "argv" + .byte 0x1 # DW_AT_decl_file (hello.c) + .byte 0x2 # DW_AT_decl_line + .long 0x74 # DW_AT_type + .byte 0x2 # DW_AT_location + .byte 0x91 # DW_OP_fbreg + .sleb128 -32 + .byte 0x0 # end of children of DIE 0x2d + .uleb128 0x4 # (DIE (0x6d) DW_TAG_base_type) + .byte 0x4 # DW_AT_byte_size + .byte 0x5 # DW_AT_encoding + .ascii "int\0" # DW_AT_name + .uleb128 0x5 # (DIE (0x74) DW_TAG_pointer_type) + .byte 0x8 # DW_AT_byte_size + .long 0x7a # DW_AT_type + .uleb128 0x5 # (DIE (0x7a) DW_TAG_pointer_type) + .byte 0x8 # DW_AT_byte_size + .long 0x80 # DW_AT_type + .uleb128 0x6 # (DIE (0x80) DW_TAG_base_type) + .byte 0x1 # DW_AT_byte_size + .byte 0x6 # DW_AT_encoding + .long .LASF2 # DW_AT_name: "char" + .byte 0x0 # end of children of DIE 0xb + .section .debug_abbrev + .uleb128 0x1 # (abbrev code) + .uleb128 0x11 # (TAG: DW_TAG_compile_unit) + .byte 0x1 # DW_children_yes + .uleb128 0x25 # (DW_AT_producer) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x13 # (DW_AT_language) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3 # (DW_AT_name) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x1b # (DW_AT_comp_dir) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x11 # (DW_AT_low_pc) + .uleb128 0x1 # (DW_FORM_addr) + .uleb128 0x12 # (DW_AT_high_pc) + .uleb128 0x1 # (DW_FORM_addr) + .byte 0x0 + .byte 0x0 + .uleb128 0x2 # (abbrev code) + .uleb128 0x2e # (TAG: DW_TAG_subprogram) + .byte 0x1 # DW_children_yes + .uleb128 0x3f # (DW_AT_external) + .uleb128 0xc # (DW_FORM_flag) + .uleb128 0x3 # (DW_AT_name) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x3a # (DW_AT_decl_file) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3b # (DW_AT_decl_line) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x27 # (DW_AT_prototyped) + .uleb128 0xc # (DW_FORM_flag) + .uleb128 0x49 # (DW_AT_type) + .uleb128 0x13 # (DW_FORM_ref4) + .uleb128 0x11 # (DW_AT_low_pc) + .uleb128 0x1 # (DW_FORM_addr) + .uleb128 0x12 # (DW_AT_high_pc) + .uleb128 0x1 # (DW_FORM_addr) + .uleb128 0x40 # (DW_AT_frame_base) + .uleb128 0xa # (DW_FORM_block1) + .uleb128 0x1 # (DW_AT_sibling) + .uleb128 0x13 # (DW_FORM_ref4) + .byte 0x0 + .byte 0x0 + .uleb128 0x3 # (abbrev code) + .uleb128 0x5 # (TAG: DW_TAG_formal_parameter) + .byte 0x0 # DW_children_no + .uleb128 0x3 # (DW_AT_name) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x3a # (DW_AT_decl_file) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3b # (DW_AT_decl_line) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x49 # (DW_AT_type) + .uleb128 0x13 # (DW_FORM_ref4) + .uleb128 0x2 # (DW_AT_location) + .uleb128 0xa # (DW_FORM_block1) + .byte 0x0 + .byte 0x0 + .uleb128 0x4 # (abbrev code) + .uleb128 0x24 # (TAG: DW_TAG_base_type) + .byte 0x0 # DW_children_no + .uleb128 0xb # (DW_AT_byte_size) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3e # (DW_AT_encoding) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3 # (DW_AT_name) + .uleb128 0x8 # (DW_FORM_string) + .byte 0x0 + .byte 0x0 + .uleb128 0x5 # (abbrev code) + .uleb128 0xf # (TAG: DW_TAG_pointer_type) + .byte 0x0 # DW_children_no + .uleb128 0xb # (DW_AT_byte_size) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x49 # (DW_AT_type) + .uleb128 0x13 # (DW_FORM_ref4) + .byte 0x0 + .byte 0x0 + .uleb128 0x6 # (abbrev code) + .uleb128 0x24 # (TAG: DW_TAG_base_type) + .byte 0x0 # DW_children_no + .uleb128 0xb # (DW_AT_byte_size) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3e # (DW_AT_encoding) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3 # (DW_AT_name) + .uleb128 0xe # (DW_FORM_strp) + .byte 0x0 + .byte 0x0 + .byte 0x0 + .section .debug_pubnames,"",@progbits + .long 0x17 # Length of Public Names Info + .value 0x2 # DWARF Version + .long .Ldebug_info0 # Offset of Compilation Unit Info + .long 0x88 # Compilation Unit Length + .long 0x2d # DIE offset + .ascii "main\0" # external name + .long 0x0 + .section .debug_aranges,"",@progbits + .long 0x2c # Length of Address Ranges Info + .value 0x2 # DWARF Version + .long .Ldebug_info0 # Offset of Compilation Unit Info + .byte 0x8 # Size of Address + .byte 0x0 # Size of Segment Descriptor + .value 0x0 # Pad to 16 byte boundary + .value 0x0 + .quad .Ltext0 # Address + .quad .Letext0-.Ltext0 # Length + .quad 0x0 + .quad 0x0 + .section .debug_str,"MS",@progbits,1 +.LASF1: + .string "argv" +.LASF4: + .string "hello.c" +.LASF5: + .string "/home/mark/src/tests" +.LASF6: + .string "main" +.LASF0: + .string "argc" +.LASF3: + .string "GNU C 4.5.1 20100924 (Red Hat 4.5.1-4)" +.LASF2: + .string "char" + .ident "GCC: (GNU) 4.5.1 20100924 (Red Hat 4.5.1-4)" + .section .note.GNU-stack,"",@progbits diff --git a/dwarflint/tests/hello.bad-3.bz2 b/dwarflint/tests/hello.bad-3.bz2 Binary files differnew file mode 100644 index 00000000..35b73e76 --- /dev/null +++ b/dwarflint/tests/hello.bad-3.bz2 diff --git a/dwarflint/tests/hello.bad-3.s b/dwarflint/tests/hello.bad-3.s new file mode 100644 index 00000000..5f0041b7 --- /dev/null +++ b/dwarflint/tests/hello.bad-3.s @@ -0,0 +1,313 @@ + .file "hello-nested.c" +# GNU C (GCC) version 4.5.1 20100924 (Red Hat 4.5.1-4) (x86_64-redhat-linux) +# compiled by GNU C version 4.5.1 20100924 (Red Hat 4.5.1-4), GMP version 4.3.1, MPFR version 2.4.2, MPC version 0.8.1 +# GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 +# options passed: hello-nested.c -mtune=generic -march=x86-64 -g +# -fverbose-asm +# options enabled: -falign-loops -fargument-alias +# -fasynchronous-unwind-tables -fauto-inc-dec -fbranch-count-reg -fcommon +# -fdelete-null-pointer-checks -fdwarf2-cfi-asm -fearly-inlining +# -feliminate-unused-debug-types -ffunction-cse -fgcse-lm -fident +# -finline-functions-called-once -fira-share-save-slots +# -fira-share-spill-slots -fivopts -fkeep-static-consts +# -fleading-underscore -fmath-errno -fmerge-debug-strings +# -fmove-loop-invariants -fpeephole -freg-struct-return +# -fsched-critical-path-heuristic -fsched-dep-count-heuristic +# -fsched-group-heuristic -fsched-interblock -fsched-last-insn-heuristic +# -fsched-rank-heuristic -fsched-spec -fsched-spec-insn-heuristic +# -fsched-stalled-insns-dep -fshow-column -fsigned-zeros +# -fsplit-ivs-in-unroller -ftrapping-math -ftree-cselim -ftree-forwprop +# -ftree-loop-im -ftree-loop-ivcanon -ftree-loop-optimize +# -ftree-parallelize-loops= -ftree-phiprop -ftree-pta -ftree-reassoc +# -ftree-scev-cprop -ftree-slp-vectorize -ftree-vect-loop-version +# -funit-at-a-time -funwind-tables -fvect-cost-model -fverbose-asm +# -fzero-initialized-in-bss -m128bit-long-double -m64 -m80387 +# -maccumulate-outgoing-args -malign-stringops -mfancy-math-387 +# -mfp-ret-in-387 -mfused-madd -mglibc -mieee-fp -mmmx -mno-sse4 +# -mpush-args -mred-zone -msse -msse2 -mtls-direct-seg-refs + + .section .debug_abbrev,"",@progbits +.Ldebug_abbrev0: + .section .debug_info,"",@progbits +.Ldebug_info0: + .section .debug_line,"",@progbits +.Ldebug_line0: + .text +.Ltext0: +# Compiler executable checksum: ea394b69293dd698607206e8e43d607e + +.globl main + .type main, @function +main: +.LFB0: + .file 1 "hello-nested.c" + # hello-nested.c:3 + .loc 1 3 0 + .cfi_startproc + # basic block 2 + pushq %rbp # # 15 *pushdi2_rex64/1 [length = 1] + .cfi_def_cfa_offset 16 + .cfi_offset 6, -16 + movq %rsp, %rbp #, # 16 *movdi_1_rex64/2 [length = 3] + .cfi_def_cfa_register 6 + movl %edi, -4(%rbp) # argc, argc # 2 *movsi_1/2 [length = 3] + movq %rsi, -16(%rbp) # argv, argv # 3 *movdi_1_rex64/4 [length = 4] + # hello-nested.c:6 + .loc 1 6 0 + leave # 21 leave_rex64 [length = 1] + .cfi_def_cfa 7, 8 + ret # 22 return_internal [length = 1] + .cfi_endproc +.LFE0: + .size main, .-main +.Letext0: + .section .debug_info + .long 0xae # Length of Compilation Unit Info + .value 0x3 # DWARF version number + .long .Ldebug_abbrev0 # Offset Into Abbrev. Section + .byte 0x8 # Pointer Size (in bytes) + .uleb128 0x1 # (DIE (0xb) DW_TAG_compile_unit) + .long .LASF3 # DW_AT_producer: "GNU C 4.5.1 20100924 (Red Hat 4.5.1-4)" + .byte 0x1 # DW_AT_language + .long .LASF4 # DW_AT_name: "hello-nested.c" + .long .LASF5 # DW_AT_comp_dir: "/home/mark/src/tests" + .quad .Ltext0 # DW_AT_low_pc + .quad .Letext0 # DW_AT_high_pc + .long .Ldebug_line0 # DW_AT_stmt_list + .uleb128 0x2 # (DIE (0x2d) DW_TAG_subprogram) + .byte 0x1 # DW_AT_external + .long .LASF6 # DW_AT_name: "main" + .byte 0x1 # DW_AT_decl_file (hello-nested.c) + .byte 0x2 # DW_AT_decl_line + .byte 0x1 # DW_AT_prototyped + .long 0x91 # DW_AT_type + .quad .LFB0 # DW_AT_low_pc + .quad .LFE0 # DW_AT_high_pc + .byte 0x1 # DW_AT_frame_base + .byte 0x9c # DW_OP_call_frame_cfa + .long 0x91 # DW_AT_sibling + .uleb128 0x3 # (DIE (0x50) DW_TAG_formal_parameter) + .long .LASF0 # DW_AT_name: "argc" + .byte 0x1 # DW_AT_decl_file (hello-nested.c) + .byte 0x2 # DW_AT_decl_line + .long 0x91 # DW_AT_type + .byte 0x2 # DW_AT_location + .byte 0x91 # DW_OP_fbreg + .sleb128 -20 + .uleb128 0x3 # (DIE (0x5e) DW_TAG_formal_parameter) + .long .LASF1 # DW_AT_name: "argv" + .byte 0x1 # DW_AT_decl_file (hello-nested.c) + .byte 0x2 # DW_AT_decl_line + .long 0x98 # DW_AT_type + .byte 0x2 # DW_AT_location + .byte 0x91 # DW_OP_fbreg + .sleb128 -32 + .uleb128 0x4 # (DIE (0x6c) DW_TAG_structure_type) + .ascii "foo\0" # DW_AT_name + .byte 0x4 # DW_AT_byte_size + .byte 0x1 # DW_AT_decl_file (hello-nested.c) + .byte 0x4 # DW_AT_decl_line + .long 0x85 # DW_AT_sibling + .uleb128 0x5 # (DIE (0x78) DW_TAG_member) + .ascii "bar\0" # DW_AT_name + .byte 0x1 # DW_AT_decl_file (hello-nested.c) + .byte 0x4 # DW_AT_decl_line + .long 0x91 # DW_AT_type + .sleb128 0 # DW_AT_data_member_location + .byte 0x0 # end of children of DIE 0x6c + .uleb128 0x6 # (DIE (0x85) DW_TAG_variable) + .ascii "baz\0" # DW_AT_name + .byte 0x1 # DW_AT_decl_file (hello-nested.c) + .byte 0x5 # DW_AT_decl_line + .long 0xab # DW_AT_type + .byte 0x0 # end of children of DIE 0x2d + .uleb128 0x7 # (DIE (0x91) DW_TAG_base_type) + .byte 0x4 # DW_AT_byte_size + .byte 0x5 # DW_AT_encoding + .ascii "int\0" # DW_AT_name + .uleb128 0x8 # (DIE (0x98) DW_TAG_pointer_type) + .byte 0x8 # DW_AT_byte_size + .long 0x9e # DW_AT_type + .uleb128 0x8 # (DIE (0x9e) DW_TAG_pointer_type) + .byte 0x8 # DW_AT_byte_size + .long 0xa4 # DW_AT_type + .uleb128 0x9 # (DIE (0xa4) DW_TAG_base_type) + .byte 0x1 # DW_AT_byte_size + .byte 0x6 # DW_AT_encoding + .long .LASF2 # DW_AT_name: "char" + .uleb128 0x8 # (DIE (0xab) DW_TAG_pointer_type) + .byte 0x8 # DW_AT_byte_size + .long 0x6c # DW_AT_type + .byte 0x0 # end of children of DIE 0xb + .section .debug_abbrev + .uleb128 0x1 # (abbrev code) + .uleb128 0x11 # (TAG: DW_TAG_compile_unit) + .byte 0x1 # DW_children_yes + .uleb128 0x25 # (DW_AT_producer) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x13 # (DW_AT_language) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3 # (DW_AT_name) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x1b # (DW_AT_comp_dir) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x11 # (DW_AT_low_pc) + .uleb128 0x1 # (DW_FORM_addr) + .uleb128 0x12 # (DW_AT_high_pc) + .uleb128 0x1 # (DW_FORM_addr) + .uleb128 0x10 # (DW_AT_stmt_list) + .uleb128 0x6 # (DW_FORM_data4) + .byte 0x0 + .byte 0x0 + .uleb128 0x2 # (abbrev code) + .uleb128 0x2e # (TAG: DW_TAG_subprogram) + .byte 0x1 # DW_children_no + .uleb128 0x3f # (DW_AT_external) + .uleb128 0xc # (DW_FORM_flag) + .uleb128 0x3 # (DW_AT_name) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x3a # (DW_AT_decl_file) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3b # (DW_AT_decl_line) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x27 # (DW_AT_prototyped) + .uleb128 0xc # (DW_FORM_flag) + .uleb128 0x49 # (DW_AT_type) + .uleb128 0x13 # (DW_FORM_ref4) + .uleb128 0x11 # (DW_AT_low_pc) + .uleb128 0x1 # (DW_FORM_addr) + .uleb128 0x12 # (DW_AT_high_pc) + .uleb128 0x1 # (DW_FORM_addr) + .uleb128 0x40 # (DW_AT_frame_base) + .uleb128 0xa # (DW_FORM_block1) + .uleb128 0x54 # (DW_AT_extension) + .uleb128 0x13 # (DW_FORM_ref4) + .byte 0x0 + .byte 0x0 + .uleb128 0x3 # (abbrev code) + .uleb128 0x5 # (TAG: DW_TAG_formal_parameter) + .byte 0x0 # DW_children_no + .uleb128 0x3 # (DW_AT_name) + .uleb128 0xe # (DW_FORM_strp) + .uleb128 0x3a # (DW_AT_decl_file) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3b # (DW_AT_decl_line) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x49 # (DW_AT_type) + .uleb128 0x13 # (DW_FORM_ref4) + .uleb128 0x2 # (DW_AT_location) + .uleb128 0xa # (DW_FORM_block1) + .byte 0x0 + .byte 0x0 + .uleb128 0x4 # (abbrev code) + .uleb128 0x13 # (TAG: DW_TAG_structure_type) + .byte 0x0 # DW_children_no + .uleb128 0x3 # (DW_AT_name) + .uleb128 0x8 # (DW_FORM_string) + .uleb128 0xb # (DW_AT_byte_size) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3a # (DW_AT_decl_file) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3b # (DW_AT_decl_line) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x54 # (DW_AT_extension) + .uleb128 0x13 # (DW_FORM_ref4) + .byte 0x0 + .byte 0x0 + .uleb128 0x5 # (abbrev code) + .uleb128 0xd # (TAG: DW_TAG_member) + .byte 0x0 # DW_children_no + .uleb128 0x3 # (DW_AT_name) + .uleb128 0x8 # (DW_FORM_string) + .uleb128 0x3a # (DW_AT_decl_file) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3b # (DW_AT_decl_line) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x49 # (DW_AT_type) + .uleb128 0x13 # (DW_FORM_ref4) + .uleb128 0x38 # (DW_AT_data_member_location) + .uleb128 0xd # (DW_FORM_sdata) + .byte 0x0 + .byte 0x0 + .uleb128 0x6 # (abbrev code) + .uleb128 0x34 # (TAG: DW_TAG_variable) + .byte 0x0 # DW_children_no + .uleb128 0x3 # (DW_AT_name) + .uleb128 0x8 # (DW_FORM_string) + .uleb128 0x3a # (DW_AT_decl_file) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3b # (DW_AT_decl_line) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x49 # (DW_AT_type) + .uleb128 0x13 # (DW_FORM_ref4) + .byte 0x0 + .byte 0x0 + .uleb128 0x7 # (abbrev code) + .uleb128 0x24 # (TAG: DW_TAG_base_type) + .byte 0x0 # DW_children_no + .uleb128 0xb # (DW_AT_byte_size) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3e # (DW_AT_encoding) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3 # (DW_AT_name) + .uleb128 0x8 # (DW_FORM_string) + .byte 0x0 + .byte 0x0 + .uleb128 0x8 # (abbrev code) + .uleb128 0xf # (TAG: DW_TAG_pointer_type) + .byte 0x0 # DW_children_no + .uleb128 0xb # (DW_AT_byte_size) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x49 # (DW_AT_type) + .uleb128 0x13 # (DW_FORM_ref4) + .byte 0x0 + .byte 0x0 + .uleb128 0x9 # (abbrev code) + .uleb128 0x24 # (TAG: DW_TAG_base_type) + .byte 0x0 # DW_children_no + .uleb128 0xb # (DW_AT_byte_size) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3e # (DW_AT_encoding) + .uleb128 0xb # (DW_FORM_data1) + .uleb128 0x3 # (DW_AT_name) + .uleb128 0xe # (DW_FORM_strp) + .byte 0x0 + .byte 0x0 + .byte 0x0 + .section .debug_pubnames,"",@progbits + .long 0x17 # Length of Public Names Info + .value 0x2 # DWARF Version + .long .Ldebug_info0 # Offset of Compilation Unit Info + .long 0xb2 # Compilation Unit Length + .long 0x2d # DIE offset + .ascii "main\0" # external name + .long 0x0 + .section .debug_aranges,"",@progbits + .long 0x2c # Length of Address Ranges Info + .value 0x2 # DWARF Version + .long .Ldebug_info0 # Offset of Compilation Unit Info + .byte 0x8 # Size of Address + .byte 0x0 # Size of Segment Descriptor + .value 0x0 # Pad to 16 byte boundary + .value 0x0 + .quad .Ltext0 # Address + .quad .Letext0-.Ltext0 # Length + .quad 0x0 + .quad 0x0 + .section .debug_str,"MS",@progbits,1 +.LASF5: + .string "/home/mark/src/tests" +.LASF3: + .string "GNU C 4.5.1 20100924 (Red Hat 4.5.1-4)" +.LASF0: + .string "argc" +.LASF2: + .string "char" +.LASF6: + .string "main" +.LASF4: + .string "hello-nested.c" +.LASF1: + .string "argv" + .ident "GCC: (GNU) 4.5.1 20100924 (Red Hat 4.5.1-4)" + .section .note.GNU-stack,"",@progbits diff --git a/dwarflint/tests/libdl-2.12.so.debug.bz2 b/dwarflint/tests/libdl-2.12.so.debug.bz2 Binary files differnew file mode 100644 index 00000000..356520ea --- /dev/null +++ b/dwarflint/tests/libdl-2.12.so.debug.bz2 diff --git a/dwarflint/tests/location-leaks.bz2 b/dwarflint/tests/location-leaks.bz2 Binary files differnew file mode 100644 index 00000000..ccf6532e --- /dev/null +++ b/dwarflint/tests/location-leaks.bz2 diff --git a/dwarflint/tests/nodebug.bz2 b/dwarflint/tests/nodebug.bz2 Binary files differnew file mode 100755 index 00000000..22320a45 --- /dev/null +++ b/dwarflint/tests/nodebug.bz2 diff --git a/dwarflint/tests/null.o.bz2 b/dwarflint/tests/null.o.bz2 Binary files differnew file mode 100644 index 00000000..f272d5a7 --- /dev/null +++ b/dwarflint/tests/null.o.bz2 diff --git a/dwarflint/tests/run-DW_AT-later-version.sh b/dwarflint/tests/run-DW_AT-later-version.sh new file mode 100755 index 00000000..d662502b --- /dev/null +++ b/dwarflint/tests/run-DW_AT-later-version.sh @@ -0,0 +1,28 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +testfiles DW_AT-later-version + +testrun_compare ./dwarflint --nognu DW_AT-later-version <<EOF +warning: .debug_abbrev: abbr. 0x11, attr. endianity: attribute from later DWARF version. +warning: .debug_info: DIE 0xb: DW_AT_low_pc value not below DW_AT_high_pc. +warning: .debug_info: DIE 0x29: variable has decl_file, but NOT decl_line +EOF diff --git a/dwarflint/tests/run-DW_AT_high_pc-below.sh b/dwarflint/tests/run-DW_AT_high_pc-below.sh new file mode 100755 index 00000000..4b4d6eac --- /dev/null +++ b/dwarflint/tests/run-DW_AT_high_pc-below.sh @@ -0,0 +1,28 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +# Hand-crafted file that has 0,0 pair in aranges presented before the +# actual end of the table. +testfiles DW_AT_high_pc-below + +testrun_compare ./dwarflint --check=@low DW_AT_high_pc-below <<EOF +warning: .debug_info: DIE 0xb: DW_AT_low_pc value not below DW_AT_high_pc. +EOF diff --git a/dwarflint/tests/run-DW_AT_high_pc-relative.sh b/dwarflint/tests/run-DW_AT_high_pc-relative.sh new file mode 100755 index 00000000..8757ba0e --- /dev/null +++ b/dwarflint/tests/run-DW_AT_high_pc-relative.sh @@ -0,0 +1,28 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +# Hand-crafted file that has 0,0 pair in aranges presented before the +# actual end of the table. +testfiles DW_AT_high_pc-relative + +testrun_compare ./dwarflint --check=@low DW_AT_high_pc-relative <<EOF +No errors +EOF diff --git a/dwarflint/tests/run-aranges_terminate_early.sh b/dwarflint/tests/run-aranges_terminate_early.sh new file mode 100755 index 00000000..43b2ec2c --- /dev/null +++ b/dwarflint/tests/run-aranges_terminate_early.sh @@ -0,0 +1,34 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +# Hand-crafted file that has 0,0 pair in aranges presented before the +# actual end of the table. +testfiles aranges_terminate_early + +testrun_compare ./dwarflint --strict aranges_terminate_early <<EOF +warning: .debug_aranges: [0x20, 0x30): unnecessary padding with zero bytes. +warning: .debug_aranges: addresses [0x400474, 0x400481) are covered with CU DIEs, but not with aranges. +EOF + +testrun_compare ./dwarflint --check=check_debug_aranges --strict aranges_terminate_early <<EOF +warning: .debug_aranges: [0x20, 0x30): unnecessary padding with zero bytes. +warning: .debug_aranges: addresses [0x400474, 0x400481) are covered with CU DIEs, but not with aranges. +EOF diff --git a/dwarflint/tests/run-bad.sh b/dwarflint/tests/run-bad.sh new file mode 100755 index 00000000..ebe3873a --- /dev/null +++ b/dwarflint/tests/run-bad.sh @@ -0,0 +1,126 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +testfiles hello.bad-1 hello.bad-3 empty-1 \ + garbage-1 garbage-2 garbage-3 garbage-4 \ + garbage-5 garbage-6 garbage-7 garbage-8 \ + garbage-9 garbage-10 garbage-11 garbage-12 + +testrun_compare ./dwarflint hello.bad-1 <<EOF +error: .debug_info: DIE 0x83: abbrev section at 0x0 doesn't contain code 83. +EOF + +testrun_compare ./dwarflint --check=@low hello.bad-3 <<EOF +error: .debug_info: DIE 0x2d: This DIE had children, but no DW_AT_sibling attribute. +error: .debug_info: DIE 0xb: This DIE had children, but no DW_AT_sibling attribute. +error: .debug_info: DIE 0x91: toplevel DIE chain contains more than one DIE. +error: .debug_info: DIE 0x98: toplevel DIE chain contains more than one DIE. +error: .debug_info: DIE 0x9e: toplevel DIE chain contains more than one DIE. +error: .debug_info: DIE 0xa4: toplevel DIE chain contains more than one DIE. +error: .debug_info: DIE 0xab: toplevel DIE chain contains more than one DIE. +EOF + +testrun_compare ./dwarflint empty-1 <<EOF +warning: .debug_info: DIE 0xb: DW_AT_low_pc value not below DW_AT_high_pc. +warning: .debug_line: table 0: no CU uses this line table. +error: .debug_info: DIE 0x29, attr. decl_file: references .debug_line table, but CU DIE lacks DW_AT_stmt_list. +EOF + +testrun_compare ./dwarflint garbage-1 <<EOF +error: Broken ELF: offset out of range. +error: .debug_abbrev: data not found. +error: .debug_info: data not found. +EOF + +testrun_compare ./dwarflint garbage-2 <<EOF +error: .debug_info: CU 0: toplevel DIE must be either compile_unit or partial_unit. +error: .debug_info: DIE 0xab: DIE chain not terminated with null entry. +EOF + +testrun_compare ./dwarflint --check=@low garbage-3 <<EOF +error: .debug_abbrev: abbr. attribute 0xc: invalid attribute code 0. +EOF + +testrun_compare ./dwarflint garbage-4 <<EOF +error: .debug_info: DIE 0x6c: this DIE claims that its sibling is 0x80000085 but it's actually 0x85. +error: .debug_info: DIE 0xab: DIE chain not terminated with null entry. +EOF + +testrun_compare ./dwarflint garbage-5 <<EOF +error: .debug_info: DIE 0xab: DIE chain not terminated with null entry. +error: .debug_line: offset 0x3e: not enough data to read an opcode of length 5. +error: .debug_info: DIE 0xb, attr. stmt_list: unresolved reference to .debug_line table 0x0. +EOF + +testrun_compare ./dwarflint garbage-6 <<EOF +error: .debug_info: CU 0: invalid address size: 9 (only 4 or 8 allowed). +error: .debug_info: couldn't load CU headers for processing .debug_abbrev; assuming latest DWARF flavor. +error: .debug_abbrev: abbr. 0x0, attr. stmt_list: attribute with invalid form DW_FORM_data4. +error: .debug_abbrev: abbr. 0x13, attr. frame_base: attribute with invalid form DW_FORM_block1. +error: .debug_abbrev: abbr. 0x2c, attr. location: attribute with invalid form DW_FORM_block1. +EOF + +testrun_compare ./dwarflint garbage-7 <<EOF +warning: .debug_abbrev: abbr. attribute 0x7e: invalid or unknown name 0x703. +error: .debug_abbrev: abbr. 0x7a, attr. 0x703: invalid form 0x0. +error: .debug_abbrev: missing zero to mark end-of-table. +EOF + +testrun_compare ./dwarflint garbage-8 <<EOF +error: .debug_info: DIE 0x6c, attr. sibling: has a value of 0. +error: .debug_info: DIE 0x6c: This DIE had children, but no DW_AT_sibling attribute. +error: .debug_info: DIE 0xab: DIE chain not terminated with null entry. +EOF + +testrun_compare ./dwarflint garbage-9 <<EOF +error: .debug_info: DIE 0x84, attr. type: invalid reference outside the CU: 0xef00ab. +error: .debug_info: DIE 0x6c: is the last sibling in chain, but has a DW_AT_sibling attribute. +error: .debug_info: DIE 0xab: DIE chain not terminated with null entry. +EOF + +testrun_compare ./dwarflint garbage-10 <<EOF +warning: .rela 0xc of .debug_info: DIE 0xb, attr. producer: relocation formed using STT_SECTION symbol with non-zero value. +error: .rela 0x11 of .debug_info: DIE 0xb, attr. comp_dir: couldn't obtain symbol #7208969: invalid section index. +warning: .debug_info: DIE 0xb: DW_AT_low_pc value not below DW_AT_high_pc. +EOF + +testrun_compare ./dwarflint garbage-11 <<EOF +error: .rela 0x600 of .debug_info: invalid relocation 2560 (<INVALID RELOC>). +error: .rela 0xc00 of .debug_info: invalid relocation 2560 (<INVALID RELOC>). +error: .rela 0x1100 of .debug_info: invalid relocation 2560 (<INVALID RELOC>). +error: .rela 0x1500 of .debug_info: invalid relocation 256 (<INVALID RELOC>). +error: .rela 0x1d00 of .debug_info: invalid relocation 256 (<INVALID RELOC>). +error: .rela 0x2500 of .debug_info: invalid relocation 2560 (<INVALID RELOC>). +error: .rela 0x3600 of .debug_info: invalid relocation 256 (<INVALID RELOC>). +warning: .debug_info: CU 0: abbrev table offset seems to lack a relocation +warning: .debug_info: DIE 0xb, attr. producer: strp seems to lack a relocation +warning: .debug_info: DIE 0xb, attr. comp_dir: strp seems to lack a relocation +warning: .debug_info: DIE 0xb, attr. stmt_list: data4 seems to lack a relocation +warning: .debug_info: DIE 0xb: DW_AT_low_pc value not below DW_AT_high_pc. +error: .debug_line: table 0: header claims that it has a size of 542, but in fact it has a size of 30. +error: .debug_info: DIE 0xb, attr. stmt_list: unresolved reference to .debug_line table 0x0. +EOF + +testrun_compare ./dwarflint garbage-12 <<EOF +error: Broken ELF: invalid section header. +error: .debug_abbrev: data not found. +error: .debug_info: data not found. +EOF diff --git a/dwarflint/tests/run-check_debug_info_refs.sh b/dwarflint/tests/run-check_debug_info_refs.sh new file mode 100755 index 00000000..4e790b88 --- /dev/null +++ b/dwarflint/tests/run-check_debug_info_refs.sh @@ -0,0 +1,37 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +testfiles check_debug_info_refs-{1,2} + +testrun_compare ./dwarflint --check=check_debug_info_refs check_debug_info_refs-1 <<EOF +error: .debug_aranges: table 48 (CU DIE 95): there has already been arange section for this CU. +warning: .debug_info: CU 0: no aranges table is associated with this CU. +EOF + +testrun_compare ./dwarflint --check=check_debug_info_refs check_debug_info_refs-1 <<EOF +error: .debug_aranges: table 48 (CU DIE 95): there has already been arange section for this CU. +warning: .debug_info: CU 0: no aranges table is associated with this CU. +EOF + +testrun_compare ./dwarflint --check=check_debug_info_refs check_debug_info_refs-2 <<EOF +warning: .debug_info: DIE 0xb: DW_AT_low_pc value not below DW_AT_high_pc. +warning: .debug_info: CU 0: no aranges table is associated with this CU. +EOF diff --git a/dwarflint/tests/run-check_duplicate_DW_tag_variable.sh b/dwarflint/tests/run-check_duplicate_DW_tag_variable.sh new file mode 100755 index 00000000..c5622872 --- /dev/null +++ b/dwarflint/tests/run-check_duplicate_DW_tag_variable.sh @@ -0,0 +1,166 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +testfiles crc7.ko.debug + +testrun_compare ./dwarflint --dups=0 --check check_duplicate_DW_tag_variable crc7.ko.debug <<EOF +warning: .debug_info: DIE 0x40f1: DW_AT_low_pc value not below DW_AT_high_pc. +warning: .debug_info: CU 16614: no aranges table is associated with this CU. +warning: .debug_info: DIE 0x3d21: Redeclaration of variable 'console_printk', originally seen at DIE 37f3. +warning: .debug_info: DIE 0x3d2e: Redeclaration of variable 'hex_asc', originally seen at DIE 380b. +warning: .debug_info: DIE 0x3d41: Redeclaration of variable '__per_cpu_offset', originally seen at DIE 382e. +warning: .debug_info: DIE 0x3d4e: Redeclaration of variable 'per_cpu__current_task', originally seen at DIE 383b. +warning: .debug_info: DIE 0x3d5b: Redeclaration of variable 'pv_info', originally seen at DIE 3848. +warning: .debug_info: DIE 0x3d69: Redeclaration of variable 'pv_time_ops', originally seen at DIE 3856. +warning: .debug_info: DIE 0x3d77: Redeclaration of variable 'pv_cpu_ops', originally seen at DIE 3864. +warning: .debug_info: DIE 0x3d85: Redeclaration of variable 'pv_irq_ops', originally seen at DIE 3872. +warning: .debug_info: DIE 0x3d93: Redeclaration of variable 'pv_apic_ops', originally seen at DIE 3880. +warning: .debug_info: DIE 0x3da1: Redeclaration of variable 'pv_mmu_ops', originally seen at DIE 388e. +warning: .debug_info: DIE 0x3daf: Redeclaration of variable 'nr_cpu_ids', originally seen at DIE 389c. +warning: .debug_info: DIE 0x3dbc: Redeclaration of variable 'cpu_online_mask', originally seen at DIE 38a9. +warning: .debug_info: DIE 0x3dc9: Redeclaration of variable 'cpu_present_mask', originally seen at DIE 38bb. +warning: .debug_info: DIE 0x3dd6: Redeclaration of variable 'cpu_bit_bitmap', originally seen at DIE 38de. +warning: .debug_info: DIE 0x3de9: Redeclaration of variable 'cpu_callout_mask', originally seen at DIE 38fc. +warning: .debug_info: DIE 0x3df6: Redeclaration of variable 'boot_cpu_data', originally seen at DIE 3909. +warning: .debug_info: DIE 0x3e03: Redeclaration of variable 'per_cpu__cpu_info', originally seen at DIE 3916. +warning: .debug_info: DIE 0x3e10: Redeclaration of variable 'per_cpu__irq_stack_union', originally seen at DIE 3923. +warning: .debug_info: DIE 0x3e1e: Redeclaration of variable 'mmu_cr4_features', originally seen at DIE 3931. +warning: .debug_info: DIE 0x3e2c: Redeclaration of variable 'per_cpu__kernel_stack', originally seen at DIE 393f. +warning: .debug_info: DIE 0x3e39: Redeclaration of variable 'node_states', originally seen at DIE 395c. +warning: .debug_info: DIE 0x3e47: Redeclaration of variable 'nr_online_nodes', originally seen at DIE 396a. +warning: .debug_info: DIE 0x3e55: Redeclaration of variable 'page_group_by_mobility_disabled', originally seen at DIE 3978. +warning: .debug_info: DIE 0x3e6d: Redeclaration of variable 'node_data', originally seen at DIE 3996. +warning: .debug_info: DIE 0x3e7a: Redeclaration of variable 'ioport_resource', originally seen at DIE 39a3. +warning: .debug_info: DIE 0x3e87: Redeclaration of variable 'x86_init', originally seen at DIE 39b0. +warning: .debug_info: DIE 0x3e94: Redeclaration of variable 'smp_found_config', originally seen at DIE 39bd. +warning: .debug_info: DIE 0x3ea1: Redeclaration of variable 'phys_cpu_present_map', originally seen at DIE 39ca. +warning: .debug_info: DIE 0x3eae: Redeclaration of variable 'time_status', originally seen at DIE 39d7. +warning: .debug_info: DIE 0x3ebb: Redeclaration of variable 'jiffies', originally seen at DIE 39e4. +warning: .debug_info: DIE 0x3ec8: Redeclaration of variable 'timer_stats_active', originally seen at DIE 39f6. +warning: .debug_info: DIE 0x3ed5: Redeclaration of variable 'acpi_noirq', originally seen at DIE 3a03. +warning: .debug_info: DIE 0x3ee2: Redeclaration of variable 'acpi_disabled', originally seen at DIE 3a10. +warning: .debug_info: DIE 0x3eef: Redeclaration of variable 'acpi_ht', originally seen at DIE 3a1d. +warning: .debug_info: DIE 0x3efc: Redeclaration of variable 'acpi_pci_disabled', originally seen at DIE 3a2a. +warning: .debug_info: DIE 0x3f09: Redeclaration of variable 'apic_verbosity', originally seen at DIE 3a37. +warning: .debug_info: DIE 0x3f16: Redeclaration of variable 'disable_apic', originally seen at DIE 3a44. +warning: .debug_info: DIE 0x3f23: Redeclaration of variable 'x2apic_phys', originally seen at DIE 3a51. +warning: .debug_info: DIE 0x3f30: Redeclaration of variable 'apic', originally seen at DIE 3a5e. +warning: .debug_info: DIE 0x3f3e: Redeclaration of variable 'per_cpu__x86_bios_cpu_apicid', originally seen at DIE 3a72. +warning: .debug_info: DIE 0x3f4b: Redeclaration of variable 'per_cpu__cpu_sibling_map', originally seen at DIE 3a80. +warning: .debug_info: DIE 0x3f58: Redeclaration of variable 'per_cpu__cpu_core_map', originally seen at DIE 3a8d. +warning: .debug_info: DIE 0x3f65: Redeclaration of variable 'per_cpu__cpu_number', originally seen at DIE 3a9a. +warning: .debug_info: DIE 0x3f72: Redeclaration of variable 'smp_ops', originally seen at DIE 3aa7. +warning: .debug_info: DIE 0x3f7f: Redeclaration of variable 'memnode', originally seen at DIE 3ab4. +warning: .debug_info: DIE 0x3f8c: Redeclaration of variable 'mem_section', originally seen at DIE 3ad8. +warning: .debug_info: DIE 0x3f9a: Redeclaration of variable 'per_cpu__x86_cpu_to_node_map', originally seen at DIE 3ae6. +warning: .debug_info: DIE 0x3fa7: Redeclaration of variable 'x86_cpu_to_node_map_early_ptr', originally seen at DIE 3af3. +warning: .debug_info: DIE 0x3fb4: Redeclaration of variable 'per_cpu__node_number', originally seen at DIE 3b00. +warning: .debug_info: DIE 0x3fc1: Redeclaration of variable 'node_to_cpumask_map', originally seen at DIE 3b24. +warning: .debug_info: DIE 0x3fce: Redeclaration of variable 'gfp_allowed_mask', originally seen at DIE 3b31. +warning: .debug_info: DIE 0x3fdc: Redeclaration of variable '__tracepoint_kmalloc', originally seen at DIE 3b3f. +warning: .debug_info: DIE 0x3fe9: Redeclaration of variable '__tracepoint_kmem_cache_alloc', originally seen at DIE 3b4c. +warning: .debug_info: DIE 0x3ff6: Redeclaration of variable '__tracepoint_kmalloc_node', originally seen at DIE 3b59. +warning: .debug_info: DIE 0x4003: Redeclaration of variable '__tracepoint_kmem_cache_alloc_node', originally seen at DIE 3b66. +warning: .debug_info: DIE 0x4010: Redeclaration of variable '__tracepoint_kfree', originally seen at DIE 3b73. +warning: .debug_info: DIE 0x401d: Redeclaration of variable '__tracepoint_kmem_cache_free', originally seen at DIE 3b80. +warning: .debug_info: DIE 0x402a: Redeclaration of variable '__tracepoint_mm_page_free_direct', originally seen at DIE 3b8d. +warning: .debug_info: DIE 0x4037: Redeclaration of variable '__tracepoint_mm_pagevec_free', originally seen at DIE 3b9a. +warning: .debug_info: DIE 0x4044: Redeclaration of variable '__tracepoint_mm_page_alloc', originally seen at DIE 3ba7. +warning: .debug_info: DIE 0x4052: Redeclaration of variable '__tracepoint_mm_page_alloc_zone_locked', originally seen at DIE 3bb5. +warning: .debug_info: DIE 0x4060: Redeclaration of variable '__tracepoint_mm_page_pcpu_drain', originally seen at DIE 3bc3. +warning: .debug_info: DIE 0x406e: Redeclaration of variable '__tracepoint_mm_page_alloc_extfrag', originally seen at DIE 3bd1. +warning: .debug_info: DIE 0x407c: Redeclaration of variable 'kmalloc_caches', originally seen at DIE 3bef. +warning: .debug_info: DIE 0x4089: Redeclaration of variable '__tracepoint_module_load', originally seen at DIE 3bfc. +warning: .debug_info: DIE 0x4096: Redeclaration of variable '__tracepoint_module_free', originally seen at DIE 3c09. +warning: .debug_info: DIE 0x40a3: Redeclaration of variable '__tracepoint_module_get', originally seen at DIE 3c16. +warning: .debug_info: DIE 0x40b0: Redeclaration of variable '__tracepoint_module_put', originally seen at DIE 3c23. +warning: .debug_info: DIE 0x40bd: Redeclaration of variable '__tracepoint_module_request', originally seen at DIE 3c30. +warning: .debug_info: DIE 0x40ca: Found definition of variable 'crc7_syndrome_table' whose declaration was seen at DIE 3c4d. +warning: .debug_info: DIE 0x7cf9: Redeclaration of variable 'console_printk', originally seen at DIE 7831. +warning: .debug_info: DIE 0x7d06: Redeclaration of variable 'hex_asc', originally seen at DIE 7849. +warning: .debug_info: DIE 0x7d19: Redeclaration of variable '__per_cpu_offset', originally seen at DIE 786c. +warning: .debug_info: DIE 0x7d26: Redeclaration of variable 'per_cpu__current_task', originally seen at DIE 7879. +warning: .debug_info: DIE 0x7d33: Redeclaration of variable 'pv_info', originally seen at DIE 7886. +warning: .debug_info: DIE 0x7d41: Redeclaration of variable 'pv_time_ops', originally seen at DIE 7894. +warning: .debug_info: DIE 0x7d4f: Redeclaration of variable 'pv_cpu_ops', originally seen at DIE 78a2. +warning: .debug_info: DIE 0x7d5d: Redeclaration of variable 'pv_irq_ops', originally seen at DIE 78b0. +warning: .debug_info: DIE 0x7d6b: Redeclaration of variable 'pv_apic_ops', originally seen at DIE 78be. +warning: .debug_info: DIE 0x7d79: Redeclaration of variable 'pv_mmu_ops', originally seen at DIE 78cc. +warning: .debug_info: DIE 0x7d87: Redeclaration of variable 'nr_cpu_ids', originally seen at DIE 78da. +warning: .debug_info: DIE 0x7d94: Redeclaration of variable 'cpu_online_mask', originally seen at DIE 78e7. +warning: .debug_info: DIE 0x7da1: Redeclaration of variable 'cpu_present_mask', originally seen at DIE 78f9. +warning: .debug_info: DIE 0x7dae: Redeclaration of variable 'cpu_bit_bitmap', originally seen at DIE 791c. +warning: .debug_info: DIE 0x7dc1: Redeclaration of variable 'cpu_callout_mask', originally seen at DIE 793a. +warning: .debug_info: DIE 0x7dce: Redeclaration of variable 'boot_cpu_data', originally seen at DIE 7947. +warning: .debug_info: DIE 0x7ddb: Redeclaration of variable 'per_cpu__cpu_info', originally seen at DIE 7954. +warning: .debug_info: DIE 0x7de8: Redeclaration of variable 'per_cpu__irq_stack_union', originally seen at DIE 7961. +warning: .debug_info: DIE 0x7df6: Redeclaration of variable 'mmu_cr4_features', originally seen at DIE 796f. +warning: .debug_info: DIE 0x7e04: Redeclaration of variable 'per_cpu__kernel_stack', originally seen at DIE 797d. +warning: .debug_info: DIE 0x7e11: Redeclaration of variable 'node_states', originally seen at DIE 799a. +warning: .debug_info: DIE 0x7e1f: Redeclaration of variable 'nr_online_nodes', originally seen at DIE 79a8. +warning: .debug_info: DIE 0x7e2d: Redeclaration of variable 'page_group_by_mobility_disabled', originally seen at DIE 79b6. +warning: .debug_info: DIE 0x7e45: Redeclaration of variable 'node_data', originally seen at DIE 79d4. +warning: .debug_info: DIE 0x7e52: Redeclaration of variable 'ioport_resource', originally seen at DIE 79e1. +warning: .debug_info: DIE 0x7e5f: Redeclaration of variable 'x86_init', originally seen at DIE 79ee. +warning: .debug_info: DIE 0x7e6c: Redeclaration of variable 'smp_found_config', originally seen at DIE 79fb. +warning: .debug_info: DIE 0x7e79: Redeclaration of variable 'phys_cpu_present_map', originally seen at DIE 7a08. +warning: .debug_info: DIE 0x7e86: Redeclaration of variable 'time_status', originally seen at DIE 7a15. +warning: .debug_info: DIE 0x7e93: Redeclaration of variable 'jiffies', originally seen at DIE 7a22. +warning: .debug_info: DIE 0x7ea0: Redeclaration of variable 'timer_stats_active', originally seen at DIE 7a34. +warning: .debug_info: DIE 0x7ead: Redeclaration of variable 'acpi_noirq', originally seen at DIE 7a41. +warning: .debug_info: DIE 0x7eba: Redeclaration of variable 'acpi_disabled', originally seen at DIE 7a4e. +warning: .debug_info: DIE 0x7ec7: Redeclaration of variable 'acpi_ht', originally seen at DIE 7a5b. +warning: .debug_info: DIE 0x7ed4: Redeclaration of variable 'acpi_pci_disabled', originally seen at DIE 7a68. +warning: .debug_info: DIE 0x7ee1: Redeclaration of variable 'apic_verbosity', originally seen at DIE 7a75. +warning: .debug_info: DIE 0x7eee: Redeclaration of variable 'disable_apic', originally seen at DIE 7a82. +warning: .debug_info: DIE 0x7efb: Redeclaration of variable 'x2apic_phys', originally seen at DIE 7a8f. +warning: .debug_info: DIE 0x7f08: Redeclaration of variable 'apic', originally seen at DIE 7a9c. +warning: .debug_info: DIE 0x7f16: Redeclaration of variable 'per_cpu__x86_bios_cpu_apicid', originally seen at DIE 7ab0. +warning: .debug_info: DIE 0x7f23: Redeclaration of variable 'per_cpu__cpu_sibling_map', originally seen at DIE 7abe. +warning: .debug_info: DIE 0x7f30: Redeclaration of variable 'per_cpu__cpu_core_map', originally seen at DIE 7acb. +warning: .debug_info: DIE 0x7f3d: Redeclaration of variable 'per_cpu__cpu_number', originally seen at DIE 7ad8. +warning: .debug_info: DIE 0x7f4a: Redeclaration of variable 'smp_ops', originally seen at DIE 7ae5. +warning: .debug_info: DIE 0x7f57: Redeclaration of variable 'memnode', originally seen at DIE 7af2. +warning: .debug_info: DIE 0x7f64: Redeclaration of variable 'mem_section', originally seen at DIE 7b16. +warning: .debug_info: DIE 0x7f72: Redeclaration of variable 'per_cpu__x86_cpu_to_node_map', originally seen at DIE 7b24. +warning: .debug_info: DIE 0x7f7f: Redeclaration of variable 'x86_cpu_to_node_map_early_ptr', originally seen at DIE 7b31. +warning: .debug_info: DIE 0x7f8c: Redeclaration of variable 'per_cpu__node_number', originally seen at DIE 7b3e. +warning: .debug_info: DIE 0x7f99: Redeclaration of variable 'node_to_cpumask_map', originally seen at DIE 7b62. +warning: .debug_info: DIE 0x7fa6: Redeclaration of variable 'gfp_allowed_mask', originally seen at DIE 7b6f. +warning: .debug_info: DIE 0x7fb4: Redeclaration of variable '__tracepoint_kmalloc', originally seen at DIE 7b7d. +warning: .debug_info: DIE 0x7fc1: Redeclaration of variable '__tracepoint_kmem_cache_alloc', originally seen at DIE 7b8a. +warning: .debug_info: DIE 0x7fce: Redeclaration of variable '__tracepoint_kmalloc_node', originally seen at DIE 7b97. +warning: .debug_info: DIE 0x7fdb: Redeclaration of variable '__tracepoint_kmem_cache_alloc_node', originally seen at DIE 7ba4. +warning: .debug_info: DIE 0x7fe8: Redeclaration of variable '__tracepoint_kfree', originally seen at DIE 7bb1. +warning: .debug_info: DIE 0x7ff5: Redeclaration of variable '__tracepoint_kmem_cache_free', originally seen at DIE 7bbe. +warning: .debug_info: DIE 0x8002: Redeclaration of variable '__tracepoint_mm_page_free_direct', originally seen at DIE 7bcb. +warning: .debug_info: DIE 0x800f: Redeclaration of variable '__tracepoint_mm_pagevec_free', originally seen at DIE 7bd8. +warning: .debug_info: DIE 0x801c: Redeclaration of variable '__tracepoint_mm_page_alloc', originally seen at DIE 7be5. +warning: .debug_info: DIE 0x802a: Redeclaration of variable '__tracepoint_mm_page_alloc_zone_locked', originally seen at DIE 7bf3. +warning: .debug_info: DIE 0x8038: Redeclaration of variable '__tracepoint_mm_page_pcpu_drain', originally seen at DIE 7c01. +warning: .debug_info: DIE 0x8046: Redeclaration of variable '__tracepoint_mm_page_alloc_extfrag', originally seen at DIE 7c0f. +warning: .debug_info: DIE 0x8054: Redeclaration of variable 'kmalloc_caches', originally seen at DIE 7c2d. +warning: .debug_info: DIE 0x8061: Redeclaration of variable '__tracepoint_module_load', originally seen at DIE 7c3a. +warning: .debug_info: DIE 0x806e: Redeclaration of variable '__tracepoint_module_free', originally seen at DIE 7c47. +warning: .debug_info: DIE 0x807b: Redeclaration of variable '__tracepoint_module_get', originally seen at DIE 7c54. +warning: .debug_info: DIE 0x8088: Redeclaration of variable '__tracepoint_module_put', originally seen at DIE 7c61. +warning: .debug_info: DIE 0x8095: Redeclaration of variable '__tracepoint_module_request', originally seen at DIE 7c6e. +EOF diff --git a/dwarflint/tests/run-check_range_out_of_scope.sh b/dwarflint/tests/run-check_range_out_of_scope.sh new file mode 100755 index 00000000..fa3b2de2 --- /dev/null +++ b/dwarflint/tests/run-check_range_out_of_scope.sh @@ -0,0 +1,27 @@ +#! /bin/sh +# Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +testfiles check_range_out_of_scope-1 + +testrun_compare ./dwarflint --check=check_range_out_of_scope check_range_out_of_scope-1 <<EOF +error: .debug_info: DIE 0x8b: PC range [0x4004d0, 0x4004d1) is not a sub-range of containing scope. +error: .debug_info: DIE 0x7a: in this context: [0x4004d4, 0x4004db) +EOF diff --git a/dwarflint/tests/run-check_self_referential_die.sh b/dwarflint/tests/run-check_self_referential_die.sh new file mode 100755 index 00000000..7fe83c63 --- /dev/null +++ b/dwarflint/tests/run-check_self_referential_die.sh @@ -0,0 +1,26 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +testfiles check_self_referential_die + +testrun_compare ./dwarflint --check=check_self_referential_die --ignore-bloat check_self_referential_die <<EOF +warning: .debug_info: DIE 0x19dc2: structure_type attribute containing_type references DIE itself. +EOF diff --git a/dwarflint/tests/run-debug_abbrev-duplicate-attribute.sh b/dwarflint/tests/run-debug_abbrev-duplicate-attribute.sh new file mode 100755 index 00000000..b189bee4 --- /dev/null +++ b/dwarflint/tests/run-debug_abbrev-duplicate-attribute.sh @@ -0,0 +1,29 @@ +#! /bin/sh +# Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +testfiles debug_abbrev-duplicate-attribute + +testrun_compare ./dwarflint debug_abbrev-duplicate-attribute <<EOF +error: .debug_abbrev: abbr. attribute 0x19: duplicate attribute byte_size (first was at 0x13). +error: .debug_abbrev: abbr. attribute 0x1b: duplicate attribute decl_file (first was at 0x15). +error: .debug_abbrev: abbr. attribute 0x1d: duplicate attribute decl_line (first was at 0x17). +warning: .debug_info: DIE 0xb: DW_AT_low_pc value not below DW_AT_high_pc. +EOF diff --git a/dwarflint/tests/run-libdl-2.12.so.debug.sh b/dwarflint/tests/run-libdl-2.12.so.debug.sh new file mode 100755 index 00000000..f9f33635 --- /dev/null +++ b/dwarflint/tests/run-libdl-2.12.so.debug.sh @@ -0,0 +1,43 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +testfiles libdl-2.12.so.debug + +# Here we test that dwarflint can tolerate invalid attribute name. +testrun_compare ./dwarflint --check=@low --nognu --ignore-bloat libdl-2.12.so.debug <<EOF +warning: .debug_abbrev: abbr. attribute 0xbe: invalid or unknown name 0x2107. +warning: .debug_abbrev: abbr. attribute 0x330: invalid or unknown name 0x2107. +warning: .debug_abbrev: abbr. attribute 0xa28: invalid or unknown name 0x2107. +warning: .debug_abbrev: abbr. attribute 0x108e: invalid or unknown name 0x2107. +warning: .debug_abbrev: abbr. attribute 0x1300: invalid or unknown name 0x2107. +warning: .debug_info: DIE 0xd9a8: DW_AT_low_pc value not below DW_AT_high_pc. +warning: .debug_info: DIE 0xdcd7: DW_AT_low_pc value not below DW_AT_high_pc. +warning: .debug_info: CU 55709: no aranges table is associated with this CU. +warning: .debug_info: CU 56524: no aranges table is associated with this CU. +EOF + +# Here we test proper support for DW_AT_GNU_vector +testrun_compare ./dwarflint --check=@low --ignore-bloat libdl-2.12.so.debug <<EOF +warning: .debug_info: DIE 0xd9a8: DW_AT_low_pc value not below DW_AT_high_pc. +warning: .debug_info: DIE 0xdcd7: DW_AT_low_pc value not below DW_AT_high_pc. +warning: .debug_info: CU 55709: no aranges table is associated with this CU. +warning: .debug_info: CU 56524: no aranges table is associated with this CU. +EOF diff --git a/dwarflint/tests/run-location-leaks.sh b/dwarflint/tests/run-location-leaks.sh new file mode 100755 index 00000000..a8eb63f9 --- /dev/null +++ b/dwarflint/tests/run-location-leaks.sh @@ -0,0 +1,28 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +testfiles location-leaks + +testrun_compare ./dwarflint location-leaks <<EOF +warning: .debug_loc: loclist 0x38: entry covers no range. +error: .debug_info: DIE 0x62: attribute \`location': PC range [0x400495, 0x40049a) outside containing scope. +error: .debug_info: DIE 0x51: in this context: [0x400498, 0x4004b2). +EOF diff --git a/dwarflint/tests/run-nodebug.sh b/dwarflint/tests/run-nodebug.sh new file mode 100755 index 00000000..1b67a706 --- /dev/null +++ b/dwarflint/tests/run-nodebug.sh @@ -0,0 +1,73 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +testfiles nodebug null.o + +testrun_compare ./dwarflint nodebug <<EOF +error: .debug_abbrev: data not found. +error: .debug_info: data not found. +EOF + +testrun_compare ./dwarflint -i nodebug <<EOF +No errors +EOF + +testrun_compare ./dwarflint -q -i nodebug <<EOF +EOF + +# This has nothing to do with the nodebug test, but we can just as +# well stick it in there. +testrun_compare ./dwarflint --check=oentuh -q nodebug <<EOF +warning: the rule \`oentuh' never matched. +EOF + +# ... and since we are testing this here, also check that we don't get +# this message in situations where it makes no sense. +LANG=C testrun_compare ./dwarflint --check=oentuh -q noeuht <<EOF +error: Cannot open input file: No such file or directory. +EOF + +LANG=C testrun_compare ./dwarflint --check=oentuh -q noeuht nodebug <<EOF + +noeuht: +error: Cannot open input file: No such file or directory. + +nodebug: +warning: the rule \`oentuh' never matched. +EOF + +LANG=C testrun_compare ./dwarflint --check=oentuh -q nodebug nodebug <<EOF + +nodebug: + +nodebug: +warning: the rule \`oentuh' never matched. +EOF + +testrun_compare ./dwarflint null.o <<EOF +warning: .debug_abbrev: [0x0, 0x1): unnecessary padding with zero bytes. +warning: .debug_abbrev: no abbreviations. +error: .debug_info: data not found. +EOF + +testrun_compare ./dwarflint --ignore-bloat --nodebug:ignore null.o <<EOF +No errors +EOF diff --git a/dwarflint/tests/run-test-all-dies-it.sh b/dwarflint/tests/run-test-all-dies-it.sh new file mode 100755 index 00000000..fdcf63fd --- /dev/null +++ b/dwarflint/tests/run-test-all-dies-it.sh @@ -0,0 +1,30 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +testfiles hello.bad-2 + +../src/readelf -winfo ./tests/test-all-dies-it | grep '^ \[ *[0-9a-f]*\]' | + sed 's/ \[ *\([0-9a-f]\+\).*/0x\1/' | + testrun_compare ./tests/test-all-dies-it ./tests/test-all-dies-it + +testrun_compare ./tests/test-all-dies-it hello.bad-2 <<EOF +0xb +EOF diff --git a/dwarflint/tests/run-upper.sh b/dwarflint/tests/run-upper.sh new file mode 100755 index 00000000..d1ed96ac --- /dev/null +++ b/dwarflint/tests/run-upper.sh @@ -0,0 +1,48 @@ +#! /bin/sh +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +. $srcdir/../tests/test-subr.sh + +srcdir=$srcdir/tests + +# Following program compiled with "default" gcc settings, +# which is dwarf-2 + gnu extensions. Which will result in: +# +# [ a2] subrange_type +# type (ref4) [ ac] +# upper_bound (block1) +# [ 0] fbreg -24 +# [ 2] deref +# +# According to dwarf-2 DW_AT_upperbound cannot be encoded with block form. +# It can however with later versions of dwarf, which gcc will output as +# gnu extension (unless -gstrict-dwarf is given). +# +# int max_range = 42; +# +# int main (int argc, char **argv) +# { +# char chars[max_range]; +# chars[max_range -1] = 7; +# return 0; +# } +# +# This would crash the low-level check_debug_info in the past. +testfiles upper + +testrun_compare ./dwarflint --quiet --check=@low upper <<EOF +EOF diff --git a/dwarflint/tests/test-all-dies-it.cc b/dwarflint/tests/test-all-dies-it.cc new file mode 100644 index 00000000..ec242974 --- /dev/null +++ b/dwarflint/tests/test-all-dies-it.cc @@ -0,0 +1,46 @@ +/* + Copyright (C) 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 <http://www.gnu.org/licenses/>. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <cassert> + +#include "config.h" +#include "../all-dies-it.hh" +#include "../../libdw/c++/dwarf" + +using namespace elfutils; + +int +main (int argc, char ** argv) +{ + assert (argc == 2); + char const *fn = argv[1]; + assert (fn != NULL); + + int fd = open (fn, O_RDONLY); + assert (fd >= 0); + + Dwarf *cdw = dwarf_begin (fd, DWARF_C_READ); + assert (cdw != NULL); + + dwarf dw = cdw; + for (all_dies_iterator<dwarf> it = all_dies_iterator<dwarf> (dw); + it != all_dies_iterator<dwarf> (); ++it) + std::cout << std::hex << "0x" << (*it).offset () << std::endl; +} diff --git a/dwarflint/tests/test-coverage.cc b/dwarflint/tests/test-coverage.cc new file mode 100644 index 00000000..3d3add0a --- /dev/null +++ b/dwarflint/tests/test-coverage.cc @@ -0,0 +1,166 @@ +#include <iostream> +#include <cstdlib> +#include <cassert> +#include "../coverage.hh" +#include "../pri.hh" + +bool fail = false; + +void +cmpfmt (coverage const &cov, + std::string const &exp) +{ + std::string act = cov::format_ranges (cov); + if (act != exp) + std::cerr << "FAIL: expected: " << exp << std::endl + << " got: " << act << std::endl; +} + +void +cmpholes (coverage const &cov, + std::string const &exp) +{ + uint64_t start = cov.front ().start; + uint64_t len = cov.back ().end () - start; + std::string act = cov::format_holes (cov, start, len); + if (act != exp) + std::cerr << "FAIL: expected: " << exp << std::endl + << " got: " << act << std::endl; +} + +void +chkcov (coverage const &cov, uint64_t start, uint64_t length) +{ + assert (cov.is_covered (start, length)); + assert (cov.is_overlap (start, length)); + for (uint64_t i = start; i < start + length; ++i) + { + assert (cov.is_covered (i, 1)); + for (unsigned k = 0; k < 100; ++k) + { + assert (cov.is_overlap (i, k + 1)); + if (i >= k) + { + assert (cov.is_overlap (i - k, k + 1)); + assert (cov.is_overlap (i - k, 2*k + 1)); + } + } + } +} + +void +chkncov (coverage const &cov, uint64_t start, uint64_t length) +{ + assert (!cov.is_overlap (start, length)); + for (uint64_t i = start; i < start + length; ++i) + { + assert (!cov.is_covered (i, 1)); + assert (!cov.is_overlap (i, 1)); + } +} + +class check_assert_used +{ + bool _m_used; + +public: + check_assert_used () + : _m_used (false) + { + assert (_m_used = true); + if (!_m_used) + abort (); + } +}; + +int +main () +{ + check_assert_used (); + + coverage cov; + assert (cov.empty ()); + cmpfmt(cov, ""); + chkncov (cov, 0x0, 0x100); + + cov.add (0x10, 0x20); + chkncov (cov, 0x0, 0x10); + chkcov (cov, 0x10, 0x20); + chkncov (cov, 0x30, 0x100); + cmpfmt(cov, "[0x10, 0x30)"); + cmpholes(cov, ""); + + cov.add (0x40, 0x20); + chkncov (cov, 0x0, 0x10); + chkcov (cov, 0x10, 0x20); + chkncov (cov, 0x30, 0x10); + chkcov (cov, 0x40, 0x20); + chkncov (cov, 0x60, 0x100); + cmpfmt(cov, "[0x10, 0x30), [0x40, 0x60)"); + cmpholes(cov, "[0x30, 0x40)"); + + cov.add (0x50, 0x20); + cmpfmt(cov, "[0x10, 0x30), [0x40, 0x70)"); + cmpholes(cov, "[0x30, 0x40)"); + + cov.add (5, 1); + cmpfmt(cov, "[0x5, 0x6), [0x10, 0x30), [0x40, 0x70)"); + cmpholes(cov, "[0x6, 0x10), [0x30, 0x40)"); + + cov.add (5, 1); + cmpfmt(cov, "[0x5, 0x6), [0x10, 0x30), [0x40, 0x70)"); + cmpholes(cov, "[0x6, 0x10), [0x30, 0x40)"); + + cov.add (0, 5); + cmpfmt(cov, "[0x0, 0x6), [0x10, 0x30), [0x40, 0x70)"); + cmpholes(cov, "[0x6, 0x10), [0x30, 0x40)"); + + { + coverage cov2 = cov; + cov2.add (0, 0x40); + cmpfmt(cov2, "[0x0, 0x70)"); + } + cov.add (0, 0x30); + cmpfmt(cov, "[0x0, 0x30), [0x40, 0x70)"); + cov.add (0x31, 5); + cmpfmt(cov, "[0x0, 0x30), [0x31, 0x36), [0x40, 0x70)"); + + assert (cov.remove (0x40, 0x30)); + cmpfmt(cov, "[0x0, 0x30), [0x31, 0x36)"); + assert (!cov.remove (0x30, 1)); + cmpfmt(cov, "[0x0, 0x30), [0x31, 0x36)"); + assert (cov.remove (0x2f, 3)); + cmpfmt(cov, "[0x0, 0x2f), [0x32, 0x36)"); + assert (cov.remove (0x10, 0x10)); + cmpfmt(cov, "[0x0, 0x10), [0x20, 0x2f), [0x32, 0x36)"); + assert (cov.remove (0x2, 3)); + cmpfmt(cov, "[0x0, 0x2), [0x5, 0x10), [0x20, 0x2f), [0x32, 0x36)"); + cmpholes(cov, "[0x2, 0x5), [0x10, 0x20), [0x2f, 0x32)"); + assert (cov.remove (0x1, 0x40)); + cmpfmt(cov, "[0x0, 0x1)"); + assert (cov.remove (0x0, 0x40)); + assert (cov.empty ()); + cmpfmt(cov, ""); + + cov.add (0, 10); + assert (cov == cov); + assert (cov == coverage (cov)); + assert (cov == coverage (cov) + coverage (cov)); + assert ((coverage (cov) - coverage (cov)).empty ()); + + cov.add (20, 0); + cmpfmt (cov, "[0x0, 0xa), [0x14, 0x14)"); + chkcov (cov, 20, 0); + chkcov (cov, 0, 0); + chkcov (cov, 9, 0); + chkcov (cov, 10, 0); + chkncov (cov, 11, 0); + chkncov (cov, 19, 1); + chkncov (cov, 20, 1); + chkncov (cov, 30, 0); + cov.add (30, 10); + cmpholes(cov, "[0xa, 0x14), [0x14, 0x1e)"); + + if (fail) + std::exit (1); +} diff --git a/dwarflint/tests/test-wrap.cc b/dwarflint/tests/test-wrap.cc new file mode 100644 index 00000000..57bfda2c --- /dev/null +++ b/dwarflint/tests/test-wrap.cc @@ -0,0 +1,58 @@ +/* + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#include "../wrap.hh" +#include <cassert> + +std::string +wrap (char const *str, size_t width) +{ + return wrap_str (str, width).join (); +} + +std::string +sspaces (size_t i) +{ + return spaces (i); +} + +int main (void) +{ + assert (sspaces (0) == ""); + assert (sspaces (1) == " "); + assert (sspaces (2) == " "); + assert (sspaces (10) == " "); + assert (wrap ("a b c d", 1) == "a\nb\nc\nd\n"); + assert (wrap ("a bbbbb c d", 1) == "a\nbbbbb\nc\nd\n"); + assert (wrap ("a b", 3) == "a b\n"); + assert (wrap (" a b", 3) == " a\n b\n"); + assert (wrap (" a b", 4) == " a b\n"); + assert (wrap (" a b c d", 4) == " a b\n c d\n"); + assert (wrap ("ab cd ef gh ij", 2) == "ab\ncd\nef\ngh\nij\n"); + assert (wrap ("ab cd ef gh ij", 3) == "ab\ncd\nef\ngh\nij\n"); + assert (wrap ("ab cd ef gh ij", 4) == "ab\ncd\nef\ngh\nij\n"); + assert (wrap ("ab cd ef gh ij", 5) == "ab cd\nef gh\nij\n"); + assert (wrap ("", 5) == ""); + assert (wrap ("", 0) == ""); + assert (wrap ("\n", 5) == "\n"); + assert (wrap ("\n\n", 5) == "\n\n"); + assert (wrap ("\n\n", 0) == "\n\n"); + assert (wrap ("ab\ncd ef gh ij", 5) == "ab\ncd ef\ngh ij\n"); + assert (wrap (" - abcd abbb accc", 3) == " - abcd\n abbb\n accc\n"); + assert (wrap (" -abcd abbb accc", 3) == " -abcd\n abbb\n accc\n"); + assert (wrap (" abcd abbb accc", 3) == " abcd\n abbb\n accc\n"); +} diff --git a/dwarflint/tests/upper.bz2 b/dwarflint/tests/upper.bz2 Binary files differnew file mode 100755 index 00000000..88448869 --- /dev/null +++ b/dwarflint/tests/upper.bz2 diff --git a/dwarflint/wrap.cc b/dwarflint/wrap.cc new file mode 100644 index 00000000..6d40ee02 --- /dev/null +++ b/dwarflint/wrap.cc @@ -0,0 +1,168 @@ +/* Pedantic checking of DWARF files + + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#include "wrap.hh" +#include <cassert> +#include <cstring> + +namespace +{ + size_t + skip_blank (char const *str, size_t pos) + { + while (isblank (str[pos])) + pos++; + return pos; + } +} + +wrapline_t::wrapline_t (size_t start, size_t end, size_t indent) + : _m_start (start) + , _m_end (end) + , _m_indent (indent) +{ + assert (end >= start); +} + +std::string +wrapline_t::build (char const *image) const +{ + assert (_m_end <= std::strlen (image)); + std::string main (image, _m_start, _m_end - _m_start); + char const *padding = spaces (_m_indent); + return std::string (padding) + main; +} + +wrap_str::wrap_str (char const *str, unsigned width) + : _m_image (str) +{ + size_t pos = 0; + bool newline = true; + size_t indent = 0; + size_t str_size = std::strlen (str); + while (pos < str_size) + { + size_t last = pos; + + // This is how long a line we can allocate. + size_t length = width; + + // For newlines, i.e. right after hard EOL (\n) in input string, + // we look for indentation and bullets. + if (newline) + { + pos = skip_blank (str, pos); + if (pos < str_size && str[pos] == '-') + pos = skip_blank (str, pos + 1); + indent = pos - last; + } + length -= indent; + + // Take the remainder of the line, but don't cross hard EOLs. + for (; length > 0 && pos < str_size; --length) + if (str[pos] == '\n') + break; + else + pos++; + + // We may have ended mid-word. Look back to first white space. + // Look as far back as the end of previous line. + size_t space = pos; + for (; space > last; --space) + if (space == str_size || isspace (str[space])) + break; + + // While skipping back, we might end at the very beginning. If + // that's the case, we have a word that doesn't fit user limit. + // Include it whole anyway. For newline, account for + // freshly-introduced indent. + if (space <= last + (newline ? indent : 0)) + { + space = pos; + while (space < str_size && !isspace (str[space])) + space++; + } + + // We have a line! + push_back (wrapline_t (last, space, newline ? 0 : indent)); + + // Skip useless white space at the end of the line, up to EOL. + while (space < str_size && isspace (str[space]) && str[space] != '\n') + space++; + + if (str[space] == '\n') + { + space++; + indent = 0; + newline = true; + } + else + newline = false; + + pos = space; + } +} + +std::string +wrap_str::join () const +{ + std::string ret; + for (const_iterator it = begin (); it != end (); ++it) + ret += build (it) + "\n"; + return ret; +} + +std::string +wrap_str::build (wrap_str::const_iterator it) const +{ + return it->build (_m_image); +} + +namespace +{ + template <unsigned Max> + class spc + { + char *_m_buf; + char *_m_endp; + public: + spc () + : _m_buf (new char[Max]) + , _m_endp (_m_buf + Max - 1) + { + std::memset (_m_buf, ' ', Max - 1); + _m_buf[Max - 1] = 0; + } + ~spc () + { + delete [] _m_buf; + } + char const *get (size_t n) + { + assert (n < Max); + return _m_endp - n; + } + }; +} + +char const * +spaces (size_t n) +{ + static spc<128> spaces; + return spaces.get (n); +} diff --git a/dwarflint/wrap.hh b/dwarflint/wrap.hh new file mode 100644 index 00000000..d9e052bd --- /dev/null +++ b/dwarflint/wrap.hh @@ -0,0 +1,58 @@ +/* Pedantic checking of DWARF files + + Copyright (C) 2010 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 <http://www.gnu.org/licenses/>. */ + +#ifndef DWARFLINT_WRAP_HH +#define DWARFLINT_WRAP_HH + +#include <string> +#include <vector> + +class wrapline_t +{ + size_t _m_start; + size_t _m_end; + size_t _m_indent; + +public: + wrapline_t (size_t start, size_t end, size_t indent); + std::string build (char const *image) const; +}; + +class wrap_str + : private std::vector<wrapline_t> +{ + char const *_m_image; + +public: + typedef std::vector<wrapline_t> super_t; + using super_t::operator []; + using super_t::size; + using super_t::const_iterator; + using super_t::begin; + using super_t::end; + using super_t::empty; + + wrap_str (char const *str, unsigned width); + + std::string join () const; + std::string build (const_iterator it) const; +}; + +char const *spaces (size_t n); + +#endif//DWARFLINT_WRAP_HH |