// -*- Mode: C++ -*- // // Copyright (C) 2013-2015 Red Hat, Inc. // // This file is part of the GNU Application Binary Interface Generic // Analysis and Instrumentation Library (libabigail). This library is // free software; you can redistribute it and/or modify it under the // terms of the GNU Lesser General Public License as published by the // Free Software Foundation; either version 3, or (at your option) any // later version. // This library 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 Lesser Public License for more details. // You should have received a copy of the GNU Lesser General Public // License along with this program; see the file COPYING-LGPLV3. If // not, see . // // Author: Dodji Seketeli /// @file /// /// This program reads an elf file, try to load its debug info (in /// DWARF format) and emit it back in a set of "text sections" in native /// libabigail XML format. #include #include #include #include #include #include #include #include #include #include "abg-tools-utils.h" #include "abg-corpus.h" #include "abg-dwarf-reader.h" #include "abg-writer.h" #include "abg-reader.h" #include "abg-comparison.h" using std::string; using std::cerr; using std::cout; using std::ostream; using std::ofstream; using std::tr1::shared_ptr; using abigail::tools_utils::temp_file; using abigail::tools_utils::temp_file_sptr; using abigail::comparison::corpus_diff; using abigail::comparison::corpus_diff_sptr; using abigail::comparison::compute_diff; using abigail::comparison::diff_context_sptr; using abigail::comparison::diff_context; using abigail::xml_writer::write_corpus_to_native_xml; using abigail::xml_reader::read_corpus_from_native_xml_file; struct options { string in_file_path; string out_file_path; shared_ptr di_root_path; bool check_alt_debug_info_path; bool show_base_name_alt_debug_info_path; bool write_architecture; bool load_all_types; bool show_stats; bool noout; bool abidiff; options() : check_alt_debug_info_path(), show_base_name_alt_debug_info_path(), write_architecture(true), load_all_types(), show_stats(), noout(), abidiff() {} }; static void display_usage(const string& prog_name, ostream& out) { out << "usage: " << prog_name << " [options] []\n" << " where options can be: \n" << " --help|-h display this message\n" << " --debug-info-dir|-d look for debug info under 'dir-path'\n" << " --out-file write the output to 'file-path'\n" << " --noout do not emit anything after reading the binary\n" << " --no-architecture do not emit architecture info in the output\n" << " --check-alternate-debug-info check alternate debug info " "of \n" << " --check-alternate-debug-info-base-name check alternate " "debug info of , and show its base name\n" << " --load-all-types read all types including those not reachable from" "exported declarations\n" << " --abidiff compare the loaded ABI against itself\n" << " --stats show statistics about various internal stuff\n"; ; } static bool parse_command_line(int argc, char* argv[], options& opts) { if (argc < 2) return false; for (int i = 1; i < argc; ++i) { if (argv[i][0] != '-') { if (opts.in_file_path.empty()) opts.in_file_path = argv[i]; else return false; } else if (!strcmp(argv[i], "--debug-info-dir") || !strcmp(argv[i], "-d")) { if (argc <= i + 1 || argv[i + 1][0] == '-' || !opts.out_file_path.empty()) return false; // elfutils wants the root path to the debug info to be // absolute. opts.di_root_path = abigail::tools_utils::make_path_absolute(argv[i + 1]); ++i; } else if (!strcmp(argv[i], "--out-file")) { if (argc <= i + 1 || argv[i + 1][0] == '-' || !opts.out_file_path.empty()) return false; opts.out_file_path = argv[i + 1]; ++i; } else if (!strcmp(argv[i], "--noout")) opts.noout = true; else if (!strcmp(argv[i], "--no-architecture")) opts.write_architecture = false; else if (!strcmp(argv[i], "--check-alternate-debug-info") || !strcmp(argv[i], "--check-alternate-debug-info-base-name")) { if (argc <= i + 1 || argv[i + 1][0] == '-' || !opts.in_file_path.empty()) return false; if (!strcmp(argv[i], "--check-alternate-debug-info-base-name")) opts.show_base_name_alt_debug_info_path = true; opts.check_alt_debug_info_path = true; opts.in_file_path = argv[i + 1]; ++i; } else if (!strcmp(argv[i], "--load-all-types")) opts.load_all_types = true; else if (!strcmp(argv[i], "--abidiff")) opts.abidiff = true; else if (!strcmp(argv[i], "--stats")) opts.show_stats = true; else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "--h")) return false; else return false; } return true; } /// Initialize the context use for driving ABI comparison. /// /// @param ctxt the context to initialize. static void set_diff_context(diff_context_sptr& ctxt) { ctxt->default_output_stream(&cerr); ctxt->error_output_stream(&cerr); // Filter out changes that are not meaningful from an ABI // standpoint, from the diff output. ctxt->switch_categories_off (abigail::comparison::ACCESS_CHANGE_CATEGORY | abigail::comparison::COMPATIBLE_TYPE_CHANGE_CATEGORY | abigail::comparison::HARMLESS_DECL_NAME_CHANGE_CATEGORY); } int main(int argc, char* argv[]) { options opts; if (!parse_command_line(argc, argv, opts) || opts.in_file_path.empty()) { display_usage(argv[0], cerr); return 1; } assert(!opts.in_file_path.empty()); if (!abigail::tools_utils::check_file(opts.in_file_path, cerr)) return 1; abigail::tools_utils::file_type type = abigail::tools_utils::guess_file_type(opts.in_file_path); if (type != abigail::tools_utils::FILE_TYPE_ELF && type != abigail::tools_utils::FILE_TYPE_AR) { cerr << opts.in_file_path << " is not an ELF file\n"; return 1; } using abigail::ir::environment_sptr; using abigail::ir::environment; using abigail::corpus; using abigail::corpus_sptr; using abigail::translation_units; using abigail::dwarf_reader::read_context; using abigail::dwarf_reader::read_context_sptr; using abigail::dwarf_reader::read_corpus_from_elf; using abigail::dwarf_reader::create_read_context; using namespace abigail; char* p = opts.di_root_path.get(); environment_sptr env(new environment); corpus_sptr corp; read_context_sptr c = create_read_context(opts.in_file_path, &p, env.get(), opts.load_all_types); read_context& ctxt = *c; set_show_stats(ctxt, opts.show_stats); if (opts.check_alt_debug_info_path) { bool has_alt_di = false; string alt_di_path; abigail::dwarf_reader::status status = abigail::dwarf_reader::has_alt_debug_info(ctxt, has_alt_di, alt_di_path); if (status & abigail::dwarf_reader::STATUS_OK) { if (alt_di_path.empty()) ; else { if (opts.show_base_name_alt_debug_info_path) tools_utils::base_name(alt_di_path, alt_di_path); cout << "found the alternate debug info file '" << alt_di_path << "'\n"; } return 0; } else { cerr << "could not find alternate debug info file\n"; return 1; } } dwarf_reader::status s = dwarf_reader::STATUS_UNKNOWN; corp = read_corpus_from_elf(ctxt, s); if (!corp) { if (s == dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND) { if (p == 0) { cerr << "Could not read debug info from " << opts.in_file_path << "\n"; cerr << "You might want to supply the root directory where " "to search debug info from, using the " "--debug-info-dir option " "(e.g --debug-info-dir /usr/lib/debug)\n"; } else { cerr << "Could not read debug info for '" << opts.in_file_path << "' from debug info root directory '" << p << "'\n"; } } else if (s == dwarf_reader::STATUS_NO_SYMBOLS_FOUND) cerr << "Could not read ELF symbol information from " << opts.in_file_path << "\n"; return 1; } else { if (opts.abidiff) { // Save the abi in abixml format in a temporary file, read // it back, and compare the ABI of what we've read back // against the ABI of the input ELF file. temp_file_sptr tmp_file = temp_file::create(); write_corpus_to_native_xml(corp, 0, tmp_file->get_stream()); tmp_file->get_stream().flush(); corpus_sptr corp2 = read_corpus_from_native_xml_file(tmp_file->get_path(), env.get()); if (!corp2) { cerr << "Could not read temporary XML representation of " "elf file back\n"; return 1; } diff_context_sptr ctxt(new diff_context); set_diff_context(ctxt); corpus_diff_sptr diff = compute_diff(corp, corp2, ctxt); bool has_error = diff->has_incompatible_changes(); if (has_error) { diff->report(cerr); return 1; } return 0; } if (opts.noout) return 0; if (!opts.write_architecture) corp->set_architecture_name(""); if (!opts.out_file_path.empty()) { ofstream of(opts.out_file_path.c_str(), std::ios_base::trunc); if (!of.is_open()) { cerr << "could not open output file '" << opts.out_file_path << "'\n"; return 1; } abigail::xml_writer::write_corpus_to_native_xml(corp, 0, of); of.close(); return 0; } else abigail::xml_writer::write_corpus_to_native_xml(corp, 0, cout); } return 0; }