/* 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 . */ #ifdef HAVE_CONFIG_H # include #endif #include "highlevel_check.hh" #include "all-dies-it.hh" #include "option.hh" #include "pri.hh" #include "files.hh" #include #include #include using elfutils::dwarf; #define DIE_OPTSTRING \ "}[,...]" global_opt 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 opt_dump ("Dump certain DIEs. For classes, see option 'ignore'.", "class[,...]", "dump"); global_opt 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 { 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 (&ptr), 10); if (*ptr == 0) step = 0; else { if (*ptr != ':') { step = 0; goto garbage; } else ptr++; step = std::strtol (ptr, const_cast (&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 { class invalid {}; std::pair 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 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 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 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 interested = ignore | dump; bool interested_mutability = interested.test (dt_mutable) || interested.test (dt_immutable); for (all_dies_iterator it = all_dies_iterator (dw); it != all_dies_iterator (); ++it) { std::bitset 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 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::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) ? "" : "???"); 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); }