From 558cab8e4f0d1793e7be21cc118aed5b293c022a Mon Sep 17 00:00:00 2001 From: Chris Johns Date: Tue, 8 May 2018 15:09:38 +1000 Subject: rtemstoolkit: Add libdwarf C++ interface. Provide a C++ interface to libdwarf to: - Manage DWARF debug data - Manage CU - Manage DIE - Handle CU line addresses - Handle CU source files Update #3417 --- rtemstoolkit/rld-dwarf.cpp | 938 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 938 insertions(+) create mode 100644 rtemstoolkit/rld-dwarf.cpp (limited to 'rtemstoolkit/rld-dwarf.cpp') diff --git a/rtemstoolkit/rld-dwarf.cpp b/rtemstoolkit/rld-dwarf.cpp new file mode 100644 index 0000000..ed135d9 --- /dev/null +++ b/rtemstoolkit/rld-dwarf.cpp @@ -0,0 +1,938 @@ +/* + * Copyright (c) 2018, Chris Johns + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/** + * @file + * + * @ingroup rtems-ld + * + * @brief RTEMS Linker DWARF module manages the DWARF format images. + * + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace rld +{ + namespace dwarf + { + /** + * The libdwarf error. + */ + void libdwarf_error_check (const std::string where, + int result, + dwarf_error& error) + { + if (result != DW_DLV_OK) + { + std::ostringstream exe_where; + std::string what; + exe_where << "dwarf:" << where; + what = dwarf_errmsg (error); + throw rld::error (what, exe_where.str ()); + } + } + + address::address (const sources& source, dwarf_line& line) + : addr (0), + source (&source), + source_index (-1), + source_line (-1), + begin_statement (false), + block (false), + end_sequence (false) + { + dwarf_error de; + int dr; + dr = ::dwarf_lineaddr(line, &addr, &de); + libdwarf_error_check ("address::address", dr, de); + dr = ::dwarf_line_srcfileno(line, &source_index, &de); + libdwarf_error_check ("address::address", dr, de); + dr = ::dwarf_lineno(line, &source_line, &de); + libdwarf_error_check ("address::address", dr, de); + dwarf_bool b; + dr = ::dwarf_linebeginstatement(line, &b, &de); + libdwarf_error_check ("address::address", dr, de); + begin_statement = b ? true : false; + dr = ::dwarf_lineblock(line, &b, &de); + libdwarf_error_check ("address::address", dr, de); + block = b ? true : false; + dr = ::dwarf_lineendsequence(line, &b, &de); + libdwarf_error_check ("address::address", dr, de); + end_sequence = b ? true : false; + } + + address::address (const address& orig) + : addr (orig.addr), + source (orig.source), + source_index (orig.source_index), + source_line (orig.source_line), + begin_statement (orig.begin_statement), + block (orig.block), + end_sequence (orig.end_sequence) + { + } + + address::address () + : addr (0), + source (nullptr), + source_index (-1), + source_line (-1), + begin_statement (false), + block (false), + end_sequence (false) + { + } + + address::~address () + { + source = nullptr; + } + + bool + address::valid () const + { + return source_line > 0; + } + + dwarf_address + address::location () const + { + return addr; + } + + std::string + address::path () const + { + if (source == nullptr) + throw rld::error ("invalid source pointer", "dwarf:address:path"); + return (*source)[source_index]; + } + + int + address::line () const + { + return source_line; + } + + bool + address::is_a_begin_statement () const + { + return begin_statement; + } + + bool + address::is_in_a_block () const + { + return block; + } + + bool + address::is_an_end_sequence () const + { + return end_sequence; + } + + address& + address::operator = (address& rhs) + { + if (this != &rhs) + { + addr = rhs.addr; + source = rhs.source; + source_index = rhs.source_index; + source_line = rhs.source_line; + begin_statement = rhs.begin_statement; + block = rhs.block; + end_sequence = rhs.end_sequence; + } + return *this; + } + + line_addresses::line_addresses (file& debug, + debug_info_entry& die) + : debug (debug), + lines (nullptr), + count_ (0) + { + if (!die.source_lines (lines, count_)) + { + lines = nullptr; + count_ = 0; + } + } + + line_addresses::line_addresses (line_addresses&& orig) + : debug (orig.debug), + lines (orig.lines), + count_ (orig.count_) + { + orig.lines = nullptr; + orig.count_ = 0; + } + + line_addresses::~line_addresses () + { + if (lines && count_ > 0) + { + ::dwarf_srclines_dealloc (debug, lines, count_); + lines = nullptr; + count_ = 0; + } + } + + size_t + line_addresses::count () const + { + return count_; + } + + dwarf_line& + line_addresses::operator [] (const int index) + { + if (index < 0 || index >= count_) + throw rld::error ("index out of range", + "line_addresses:indexing"); + return lines[index]; + } + + sources::sources (file& debug, debug_info_entry& die) + : debug (debug), + source (nullptr), + count (0), + die_offset (die.offset ()) + { + die.source_files (source, count); + } + + sources::sources (const sources& orig) + : debug (orig.debug), + source (nullptr), + count (0), + die_offset (orig.die_offset) + { + /* + * In a copy constructor we need to get our own copy of the strings. To + * do that we need to get the DIE at the offset in the original. + */ + debug_info_entry die (debug, die_offset); + die.source_files (source, count); + } + + sources::sources (sources&& orig) + : debug (orig.debug), + source (orig.source), + count (orig.count), + die_offset (orig.die_offset) + { + orig.source = nullptr; + orig.count = 0; + } + + sources::~sources () + { + dealloc (); + } + + std::string + sources::operator [] (const int index) const + { + if (index <= 0 || index > count) + return "unknown"; + return source[index - 1]; + } + + void + sources::dealloc () + { + if (source && count > 0) + { + /* + * The elftoolchain cleans the memory up and there is no compatible + * call we can put here so adding the required code causes is a double + * free results in a crash. + */ + if (false) + { + for (int s = 0; s < count; ++s) + ::dwarf_dealloc (debug, source[s], DW_DLA_STRING); + ::dwarf_dealloc (debug, source, DW_DLA_LIST); + } + source = nullptr; + count = 0; + } + } + + sources& + sources::operator = (sources&& rhs) + { + if (this != &rhs) + { + debug = rhs.debug; + source = rhs.source; + count = rhs.count; + die_offset = rhs.die_offset; + rhs.source = nullptr; + rhs.count = 0; + } + return *this; + } + + debug_info_entry::debug_info_entry (file& debug) + : debug (debug), + die (nullptr), + tag_ (0), + offset_ (0) + { + } + + debug_info_entry::debug_info_entry (file& debug, dwarf_die& die) + : debug (debug), + die (die), + tag_ (0), + offset_ (0) + { + } + + debug_info_entry::debug_info_entry (file& debug, dwarf_offset offset__) + : debug (debug), + die (nullptr), + tag_ (0), + offset_ (offset__) + { + dwarf_error de; + int dr; + dr = ::dwarf_offdie (debug, offset_, &die, &de); + libdwarf_error_check ("debug_info_entry:debug_info_entry", dr, de); + } + + debug_info_entry::debug_info_entry (debug_info_entry&& orig) + : debug (orig.debug), + die (orig.die), + tag_ (orig.tag_), + offset_ (orig.offset_) + { + orig.die = nullptr; + orig.tag_ = 0; + orig.offset_ = 0; + } + + debug_info_entry::~debug_info_entry () + { + if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG) + std::cout << "dwarf::debug_info_entry::~debug_info_entry" << std::endl; + dealloc (); + } + + dwarf_die + debug_info_entry::get () const + { + return die; + } + + debug_info_entry::operator dwarf_die& () + { + return die; + } + + debug_info_entry::operator dwarf_die* () + { + return ¨ + } + + debug_info_entry& + debug_info_entry::operator = (debug_info_entry& rhs) + { + if (this != &rhs) + { + if (debug != rhs.debug) + throw rld::error ("DIE debug info mismatch", + "dwarf:debug_info_entry:operator="); + dealloc (); + die = rhs.die; + tag_ = rhs.tag_; + offset_ = rhs.offset_; + rhs.die = nullptr; + } + return *this; + } + + bool + debug_info_entry::operator == (debug_info_entry& rhs) const + { + return debug == rhs.debug && die == rhs.die && + tag_ == rhs.tag_ && offset_ == rhs.offset_; + } + + bool + debug_info_entry::operator == (const dwarf_die rhs) const + { + return die == rhs; + } + + dwarf_tag + debug_info_entry::tag () + { + if (tag_ == 0) + { + dwarf_error de; + int dr; + dr = ::dwarf_tag(die, &tag_, &de); + libdwarf_error_check ("debug_info_entry:debug_info_entry", dr, de); + } + return tag_; + } + + dwarf_offset + debug_info_entry::offset () + { + if (offset_ == 0) + { + dwarf_error de; + int dr; + dr = ::dwarf_dieoffset (die, &offset_, &de); + libdwarf_error_check ("debug_info_entry:debug_info_entry", dr, de); + } + return offset_; + } + + bool + debug_info_entry::attribute (dwarf_attr attr, + dwarf_unsigned& value, + bool error) const + { + dwarf_error de; + int dr; + dr = ::dwarf_attrval_unsigned (die, attr, &value, &de); + if (error) + libdwarf_error_check ("debug_info_entry:attribute ", dr, de); + return dr == DW_DLV_OK; + } + + bool + debug_info_entry::attribute (dwarf_attr attr, + std::string& value, + bool error) const + { + dwarf_error de; + int dr; + const char* s = nullptr; + value.clear (); + dr = ::dwarf_attrval_string (die, attr, &s, &de); + if (error) + libdwarf_error_check ("debug_info_entry:attribute ", dr, de); + if (s != nullptr) + value = s; + return dr == DW_DLV_OK; + } + + bool + debug_info_entry::source_lines (dwarf_line*& lines, + dwarf_signed& linecount) const + { + dwarf_error de; + int dr; + if (lines != nullptr) + throw rld::error ("lines is not null", "debug_info_entry:source_lines"); + linecount = 0; + dr = ::dwarf_srclines (die, &lines, &linecount, &de); + if (dr == DW_DLV_NO_ENTRY) + return false; + libdwarf_error_check ("debug_info_entry:source_lines ", dr, de); + return true; + } + + void + debug_info_entry::source_files (char**& sources, + dwarf_signed& sourcecount) const + { + dwarf_error de; + int dr; + dr = ::dwarf_srcfiles (die, &sources, &sourcecount, &de); + libdwarf_error_check ("debug_info_entry:source_files ", dr, de); + } + + void + debug_info_entry::dealloc () + { + if (die != nullptr) { + ::dwarf_dealloc (debug, die, DW_DLA_DIE); + die = nullptr; + } + } + + compilation_unit::compilation_unit (file& debug, + debug_info_entry& die, + dwarf_unsigned offset) + : debug (debug), + offset_ (offset), + pc_low_ (0), + pc_high_ (0), + source_ (debug, die), + die_offset (die.offset ()) + { + die.attribute (DW_AT_name, name_); + name_ = name_; + + die.attribute (DW_AT_producer, producer_); + + die.attribute (DW_AT_low_pc, pc_low_, false); + + if (!die.attribute (DW_AT_high_pc, pc_high_, false)) + pc_high_ = ~0U; + + if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG) + { + std::cout << std::hex << std::setfill ('0') + << "dwarf::compilation_unit: " + << rld::path::basename (name_) + << ": (0x" << std::setw (8) << offset_ << ") "; + if (pc_low_ != 0 && pc_high_ != ~0U) + std::cout << "pc_low = " << std::setw (8) << pc_low_ + << " pc_high = " << std::setw (8) << pc_high_; + std::cout << std::setfill (' ') << std::dec + << std::endl + << " ] " << producer_ + << std::endl; + } + + line_addresses lines (debug, die); + dwarf_address pc = 0; + + for (size_t l = 0; l < lines.count (); ++l) + { + address addr (source_, lines[l]); + dwarf_address loc = addr.location (); + if (inside (loc) && loc >= pc) + { + pc = loc; + addr_lines_[addr.location ()] = addr; + if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG) + { + std::cout << "dwarf::compilation_unit: " + << std::hex << std::setw (8) << addr.location () << std::dec + << " - " + << (char) (addr.is_a_begin_statement () ? 'B' : '.') + << (char) (addr.is_in_a_block () ? 'I' : '.') + << (char) (addr.is_an_end_sequence () ? 'E' : '.') + << " - " + << rld::path::basename (addr.path ()) + << ':' << addr.line () + << std::endl; + } + } + } + } + + compilation_unit::compilation_unit (compilation_unit&& orig) + : debug (orig.debug), + offset_ (orig.offset_), + name_ (orig.name_), + producer_ (orig.producer_), + pc_low_ (orig.pc_low_), + pc_high_ (orig.pc_high_), + source_ (std::move (orig.source_)), + addr_lines_ (orig.addr_lines_), + die_offset (orig.die_offset) + { + orig.name_.clear (); + orig.producer_.clear (); + orig.offset_ = 0; + orig.die_offset = 0; + orig.pc_low_ = 0; + orig.pc_high_ = 0; + } + + compilation_unit::compilation_unit (const compilation_unit& orig) + : debug (orig.debug), + offset_ (orig.offset_), + name_ (orig.name_), + producer_ (orig.producer_), + pc_low_ (orig.pc_low_), + pc_high_ (orig.pc_high_), + source_ (orig.source_), + addr_lines_ (orig.addr_lines_), + die_offset (orig.die_offset) + { + } + + compilation_unit::~compilation_unit () + { + if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG) + std::cout << "dwarf::compilation_unit::~compilation_unit: " << name_ << std::endl; + } + + std::string + compilation_unit::name () const + { + return name_; + } + + std::string + compilation_unit::producer () const + { + return producer_; + } + + unsigned int + compilation_unit::pc_low () const + { + return pc_low_; + } + + unsigned int + compilation_unit::pc_high () const + { + return pc_high_; + } + + bool + compilation_unit::get_source (const dwarf_address addr, + address& addr_line) + { + if (addr_lines_.find (addr) == addr_lines_.end ()) + return false; + addr_line = addr_lines_[addr]; + return true; + } + + bool + compilation_unit::inside (dwarf_unsigned addr) const + { + return addr >= pc_low_ && addr < pc_high_; + } + + compilation_unit& + compilation_unit::operator = (compilation_unit&& rhs) + { + if (this != &rhs) + { + debug = rhs.debug; + offset_ = rhs.offset_; + name_ = rhs.name_; + producer_ = rhs.producer_; + source_ = std::move (rhs.source_); + addr_lines_ = std::move (rhs.addr_lines_), + pc_low_ = rhs.pc_low_; + pc_high_ = rhs.pc_high_; + die_offset = rhs.die_offset; + rhs.offset_ = 0; + rhs.name_.clear (); + rhs.pc_low_ = -1; + rhs.pc_high_ = -1; + rhs.die_offset = 0; + } + return *this; + } + + compilation_unit& + compilation_unit::operator = (const compilation_unit& rhs) + { + if (this != &rhs) + { + debug = rhs.debug; + + /* + * This is a copy operator so we need to get a new copy of the strings, + * we cannot steal the other copy. + */ + offset_ = rhs.offset_; + name_ = rhs.name_; + producer_ = rhs.producer_; + debug_info_entry die (debug, rhs.offset_); + source_ = sources (debug, die); + addr_lines_ = addresses (rhs.addr_lines_); + pc_low_ = rhs.pc_low_; + pc_high_ = rhs.pc_high_; + die_offset = rhs.die_offset; + } + return *this; + } + + source_flags::source_flags (const std::string& source) + : source (source) + { + } + + bool + source_flags_compare::operator () (const source_flags& a, + const source_flags& b) const + { + if (by_basename) + return rld::path::basename (a.source) < rld::path::basename (b.source); + return a.source < b.source; + } + + source_flags_compare:: source_flags_compare (bool by_basename) + : by_basename (by_basename) + { + } + + producer_source::producer_source (const std::string& producer) + : producer (producer) + { + } + + producer_source::producer_source () + { + } + + file::file () + : debug (nullptr), + elf_ (nullptr) + { + } + + file::~file () + { + try + { + end (); + } + catch (rld::error re) + { + std::cerr << "error: rld::dwarf::file::~file: " + << re.where << ": " << re.what + << std::endl; + } + catch (...) + { + std::cerr << "error: rld::dwarf::file::~file: unhandled exception" + << std::endl; + } + } + + void + file::begin (rld::elf::file& elf__) + { + if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG) + std::cout << "dwarf::begin: " << elf__.name () << std::endl; + + if (debug != nullptr || elf_ != nullptr) + throw rld::error ("Already called", "dwarf:file:begin"); + + /* + * DWARF data is not writable. + */ + if (elf__.is_writable ()) + throw rld::error ("Cannot write DWARF info", "dwarf:file:begin"); + + /* + * Initialise the DWARF instance. + */ + dwarf_error de; + int dr = ::dwarf_elf_init (elf__.get_elf (), + DW_DLC_READ, + nullptr, + this, + &debug, + &de); + libdwarf_error_check ("file:begin", dr, de); + + /* + * Record the ELF instance and obtain a reference to it. The ELF file + * cannot end while the DWARF file has not ended. + */ + elf__.reference_obtain (); + elf_ = &elf__; + } + + void + file::end () + { + if (debug) + { + if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG) + std::cout << "dwarf::end: " << name () << std::endl; + + cus.clear (); + + ::dwarf_finish (debug, 0); + if (elf_) + elf_->reference_release (); + elf_ = nullptr; + debug = nullptr; + } + } + + void + file::load_debug () + { + dwarf_unsigned cu_offset = 0; + + while (true) + { + dwarf_unsigned cu_next_offset = 0; + dwarf_error de; + int dr; + + dr = ::dwarf_next_cu_header_c(debug, 1, + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, + &cu_next_offset, &de); + if (dr != DW_DLV_OK) + break; + + /* + * Fnd the CU DIE. + */ + debug_info_entry die (*this); + debug_info_entry ret_die (*this); + + while (true) + { + dr = ::dwarf_siblingof(debug, die, ret_die, &de); + if (dr != DW_DLV_OK) + break; + + if (ret_die.tag () == DW_TAG_compile_unit) + { + cus.push_back (compilation_unit (*this, ret_die, cu_offset)); + break; + } + + die = ret_die; + } + + cu_offset = cu_next_offset; + } + } + + bool + file::get_source (const unsigned int addr, + std::string& source_file, + int& source_line) + { + bool r = false; + + /* + * Search the CU's collecting the addresses. An address can appear in + * more than one CU. It may be the last address and the first. + */ + source_file = "unknown"; + source_line = -1; + + address match; + + for (auto& cu : cus) + { + address line; + r = cu.get_source (addr, line); + if (r) + { + if (match.valid ()) + { + if (match.is_an_end_sequence ()) + { + match = line; + } + else if (!line.is_an_end_sequence ()) + { + match = line; + } + } + } + } + + if (match.valid ()) + { + source_file = match.path (); + source_line = match.line (); + r = true; + } + + return r; + } + + void + file::get_producer_sources (producer_sources& producers) + { + for (auto& cu : cus) + { + std::string producer = cu.producer (); + producer_source new_producer; + source_flags sf (cu.name ()); + rld::strings parts; + + rld::split (parts, producer); + + for (auto& s : parts) + { + if (s[0] == '-') + sf.flags.push_back (s); + else + new_producer.producer += ' ' + s; + } + + bool add = true; + + for (auto& p : producers) + { + if (p.producer == new_producer.producer) + { + p.sources.push_back (sf); + add = false; + break; + } + } + if (add) + { + new_producer.sources.push_back (sf); + producers.push_back (new_producer); + } + } + } + + dwarf& + file::get_debug () + { + return debug; + } + + compilation_units& + file::get_cus () + { + return cus; + } + + const std::string& + file::name () const + { + if (!elf_) + throw rld::error ("No begin called", "dwarf:fie:name"); + return elf_->name (); + } + + void + file::check (const char* where) const + { + if (!debug || !elf_) + { + std::string w = where; + throw rld::error ("No DWARF or ELF file", "dwarf:file:" + w); + } + } + + } +} -- cgit v1.2.3