blob: 7602e7fdac2f29202581043154f9c119ae04120e [file] [log] [blame]
/*
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);
}