operator<(const imported_unit_point& l, const imported_unit_point& r)
{return l.offset_of_import < r.offset_of_import;}
+static void
+add_symbol_to_map(const elf_symbol_sptr& sym,
+ string_elf_symbols_map_type& map);
+
static bool
find_symbol_table_section(Elf* elf_handle, Elf_Scn*& section);
/// get some important data from it.
class read_context
{
+public:
+ struct options_type
+ {
+ environment* env;
+ bool load_in_linux_kernel_mode;
+ bool load_all_types;
+ bool ignore_symbol_table;
+ bool show_stats;
+ bool do_log;
+
+ options_type()
+ : env(),
+ load_in_linux_kernel_mode(),
+ load_all_types(),
+ ignore_symbol_table(),
+ show_stats(),
+ do_log()
+ {}
+ };// read_context::options_type
+
/// A set of containers that contains one container per kind of @ref
/// die_source. This allows to associate DIEs to things, depending
/// on the source of the DIE.
return const_cast<die_source_dependant_container_set*>(this)->
get_container(die);
}
+
+ /// Clear the container set.
+ void
+ clear()
+ {
+ primary_debug_info_container_.clear();
+ alt_debug_info_container_.clear();
+ type_unit_container_.clear();
+ }
}; // end die_dependant_container_set
- environment* env_;
suppr::suppressions_type supprs_;
unsigned short dwarf_version_;
Dwfl_Callbacks offline_callbacks_;
Dwfl_Module* elf_module_;
mutable Elf* elf_handle_;
const string elf_path_;
+ mutable Elf_Scn* bss_section_;
+ mutable Elf_Scn* text_section_;
+ mutable Elf_Scn* rodata_section_;
+ mutable Elf_Scn* data_section_;
+ mutable Elf_Scn* data1_section_;
+ mutable Elf_Scn* symtab_section_;
+ // The "Official procedure descriptor section, aka .opd", used in
+ // ppc64 elf v1 binaries. This section contains the procedure
+ // descriptors on that platform.
+ Elf_Scn* opd_section_;
+ /// The special __ksymtab and __ksymtab_gpl sections from linux
+ /// kernel or module binaries. The former is used to store
+ /// references to symbols exported using the EXPORT_SYMBOL macro
+ /// from the linux kernel. The latter is used to store references
+ /// to symbols exported using the EXPORT_SYMBOL_GPL macro from the
+ /// linux kernel.
+ Elf_Scn* ksymtab_section_;
+ Elf_Scn* ksymtab_gpl_section_;
+ Elf_Scn* versym_section_;
+ Elf_Scn* verdef_section_;
+ Elf_Scn* verneed_section_;
+ bool symbol_versionning_sections_loaded_;
+ bool symbol_versionning_sections_found_;
Dwarf_Die* cur_tu_die_;
istring_type_or_decl_base_sptr_map_type name_artefacts_map_;
istring_type_or_decl_base_sptr_map_type per_tu_name_artefacts_map_;
vector<Dwarf_Off> type_unit_types_to_canonicalize_;
string_classes_map decl_only_classes_map_;
die_tu_map_type die_tu_map_;
+ corpus_group_sptr cur_corpus_group_;
corpus_sptr cur_corpus_;
translation_unit_sptr cur_tu_;
scope_decl_sptr nil_scope_;
offset_offset_map alternate_die_parent_map_;
offset_offset_map type_section_die_parent_map_;
list<var_decl_sptr> var_decls_to_add_;
- Elf_Scn* symtab_section_;
- // The "Official procedure descriptor section, aka .opd", used in
- // ppc64 elf v1 binaries. This section contains the procedure
- // descriptors on that platform.
- Elf_Scn* opd_section_;
- /// The special __ksymtab and __ksymtab_gpl sections from linux
- /// kernel or module binaries. The former is used to store
- /// references to symbols exported using the EXPORT_SYMBOL macro
- /// from the linux kernel. The latter is used to store references
- /// to symbols exported using the EXPORT_SYMBOL_GPL macro from the
- /// linux kernel.
- Elf_Scn* ksymtab_section_;
- Elf_Scn* ksymtab_gpl_section_;
- bool symbol_versionning_sections_loaded_;
- bool symbol_versionning_sections_found_;
- Elf_Scn* versym_section_;
- Elf_Scn* verdef_section_;
- Elf_Scn* verneed_section_;
addr_elf_symbol_sptr_map_sptr fun_addr_sym_map_;
// On PPC64, the function entry point address is different from the
// GElf_Sym::st_value value, which is the address of the descriptor
string dt_soname_;
string elf_architecture_;
corpus::exported_decls_builder* exported_decls_builder_;
- bool load_in_linux_kernel_mode_;
- bool load_all_types_;
- bool show_stats_;
- bool do_log_;
-
+ options_type options_;
read_context();
public:
- read_context(const string& elf_path)
- : env_(),
- dwarf_version_(),
+
+ /// Constructor of read_context.
+ ///
+ /// @param elf_path the path to the elf file the context is to be
+ /// used for.
+ ///
+ /// @param a pointer to the path to the root directory under which
+ /// the debug info is to be found for @p elf_path. Leave this to
+ /// NULL if the debug info is not in a split file.
+ ///
+ /// @param environment the environment used by the current context.
+ /// This environment contains resources needed by the reader and by
+ /// the types and declarations that are to be created later. Note
+ /// that ABI artifacts that are to be compared all need to be
+ /// created within the same environment.
+ ///
+ /// Please also note that the life time of this environment object
+ /// must be greater than the life time of the resulting @ref
+ /// read_context the context uses resources that are allocated in
+ /// the environment.
+ ///
+ /// @param load_all_types if set to false only the types that are
+ /// reachable from publicly exported declarations (of functions and
+ /// variables) are read. If set to true then all types found in the
+ /// debug information are loaded.
+ read_context(const string& elf_path,
+ char** debug_info_root_path,
+ ir::environment* environment,
+ bool load_all_types,
+ bool linux_kernel_mode)
+ : dwarf_version_(),
handle_(),
dwarf_(),
alt_fd_(),
elf_module_(),
elf_handle_(),
elf_path_(elf_path),
- cur_tu_die_(),
+ bss_section_(),
+ text_section_(),
+ rodata_section_(),
+ data_section_(),
+ data1_section_(),
symtab_section_(),
opd_section_(),
ksymtab_section_(),
ksymtab_gpl_section_(),
- symbol_versionning_sections_loaded_(),
- symbol_versionning_sections_found_(),
versym_section_(),
verdef_section_(),
verneed_section_(),
- exported_decls_builder_(),
- load_in_linux_kernel_mode_(),
- load_all_types_(),
- show_stats_(),
- do_log_()
+ symbol_versionning_sections_loaded_(),
+ symbol_versionning_sections_found_(),
+ cur_tu_die_(),
+ exported_decls_builder_()
{
memset(&offline_callbacks_, 0, sizeof(offline_callbacks_));
+ create_default_dwfl(debug_info_root_path);
+ options_.env = environment;
+ options_.load_in_linux_kernel_mode = linux_kernel_mode;
+ options_.load_all_types = load_all_types;
+ load_in_linux_kernel_mode(linux_kernel_mode);
+ env(environment);
}
- /// Detructor of the @ref read_context type.
- ~read_context()
+ /// Clear the resources related to the alternate DWARF data.
+ void
+ clear_alt_debug_info_data()
{
if (alt_fd_)
{
close(alt_fd_);
+ alt_fd_ = 0;
if (alt_dwarf_)
- dwarf_end(alt_dwarf_);
+ {
+ dwarf_end(alt_dwarf_);
+ alt_dwarf_ = 0;
+ }
+ alt_debug_info_path_.clear();
}
}
+ /// Detructor of the @ref read_context type.
+ ~read_context()
+ {
+ clear_alt_debug_info_data();
+ }
+
/// Clear the data that is relevant only for the current translation
/// unit being read. The rest of the data is relevant for the
/// entire ABI corpus.
void
clear_per_corpus_data()
{
+ name_artefacts_map_.clear();
+ die_qualified_name_maps_.clear();
+ die_pretty_repr_maps_.clear();
+ die_pretty_type_repr_maps_.clear();
die_decl_map().clear();
alternate_die_decl_map().clear();
clear_die_type_maps();
clear_types_to_canonicalize();
}
+ /// Getter of the options of the read context.
+ ///
+ /// @return the options of the read context.
+ options_type&
+ options()
+ {return options_;}
+
+ /// Getter of the options of the read context.
+ ///
+ /// @return the options of the read context.
+ const options_type&
+ options() const
+ {return options_;}
+
+ /// Getter of the options of the read context.
+ ///
+ /// @return the options of the read context.
+ void
+ options(const options_type& o)
+ {options_ = o;}
+
/// Getter for the current environment.
///
/// @return the current environment.
const ir::environment*
env() const
- {return env_;}
+ {return options_.env;}
/// Getter for the current environment.
///
/// @return the current environment.
ir::environment*
env()
- {return env_;}
+ {return options_.env;}
/// Setter for the current environment.
///
/// @param env the new current environment.
void
env(ir::environment* env)
- {env_ = env;}
+ {options_.env = env;}
/// Getter of the suppression specifications to be used during
/// ELF/DWARF parsing.
alt_debug_info_path() const
{return alt_debug_info_path_;}
+ /// Return the path to the ELF path we are reading.
+ ///
+ /// @return the elf path.
const string&
elf_path() const
{return elf_path_;}
+ /// Return the bss section of the ELF file we are reading.
+ ///
+ /// The first time this function is called, the ELF file is scanned
+ /// to look for the section we are looking for. Once the section is
+ /// found, it's cached.
+ ///
+ /// Subsequent calls to this function just return the cached
+ /// version.
+ ///
+ /// @return the bss section.
+ Elf_Scn*
+ bss_section() const
+ {
+ if (!bss_section_)
+ bss_section_ = find_bss_section(elf_handle());
+ return bss_section_;
+ }
+
+ /// Return the text section of the ELF file we are reading.
+ ///
+ /// The first time this function is called, the ELF file is scanned
+ /// to look for the section we are looking for. Once the section is
+ /// found, it's cached.
+ ///
+ /// Subsequent calls to this function just return the cached
+ /// version.
+ ///
+ /// return the text section.
+ Elf_Scn*
+ text_section() const
+ {
+ if (!text_section_)
+ text_section_ = find_text_section(elf_handle());
+ return text_section_;
+ }
+
+ /// Return the rodata section of the ELF file we are reading.
+ ///
+ /// The first time this function is called, the ELF file is scanned
+ /// to look for the section we are looking for. Once the section is
+ /// found, it's cached.
+ ///
+ /// Subsequent calls to this function just return the cached
+ /// version.
+ ///
+ /// return the rodata section.
+ Elf_Scn*
+ rodata_section() const
+ {
+ if (!rodata_section_)
+ rodata_section_ =find_rodata_section(elf_handle());
+ return rodata_section_;
+ }
+
+ /// Return the data section of the ELF file we are reading.
+ ///
+ /// The first time this function is called, the ELF file is scanned
+ /// to look for the section we are looking for. Once the section is
+ /// found, it's cached.
+ ///
+ /// Subsequent calls to this function just return the cached
+ /// version.
+ ///
+ /// return the data section.
+ Elf_Scn*
+ data_section() const
+ {
+ if (!data_section_)
+ data_section_ = find_data_section(elf_handle());
+ return data_section_;
+ }
+
+ /// Return the data1 section of the ELF file we are reading.
+ ///
+ /// The first time this function is called, the ELF file is scanned
+ /// to look for the section we are looking for. Once the section is
+ /// found, it's cached.
+ ///
+ /// Subsequent calls to this function just return the cached
+ /// version.
+ ///
+ /// return the data1 section.
+ Elf_Scn*
+ data1_section() const
+ {
+ if (!data1_section_)
+ data1_section_ = find_data1_section(elf_handle());
+ return data1_section_;
+ }
+
const Dwarf_Die*
cur_tu_die() const
{return cur_tu_die_;}
get_die_qualified_type_name(Dwarf_Die *die, size_t where_offset) const
{
assert(die);
+
+ // The name of the translation unit die is "".
+ if (die == cur_tu_die())
+ return env()->intern("");
+
die_istring_map_type& map =
die_qualified_name_maps_.get_container(*const_cast<read_context*>(this),
die);
return tu_die_imported_unit_points_map_;
}
+ /// Getter of the current corpus being constructed.
+ ///
+ /// @return the current corpus.
const corpus_sptr
current_corpus() const
{return cur_corpus_;}
+ /// Getter of the current corpus being constructed.
+ ///
+ /// @return the current corpus.
corpus_sptr
current_corpus()
{return cur_corpus_;}
+ /// Setter of the current corpus being constructed.
+ ///
+ /// @param c the new corpus.
void
- current_corpus(corpus_sptr c)
+ current_corpus(const corpus_sptr& c)
{
if (c)
cur_corpus_ = c;
}
+ /// Reset the current corpus being constructed.
+ ///
+ /// This actually deletes the current corpus being constructed.
void
reset_current_corpus()
{cur_corpus_.reset();}
+ /// Getter of the current corpus group being constructed.
+ ///
+ /// @return current the current corpus being constructed, if any, or
+ /// nil.
+ const corpus_group_sptr
+ current_corpus_group() const
+ {return cur_corpus_group_;}
+
+ /// Getter of the current corpus group being constructed.
+ ///
+ /// @return current the current corpus being constructed, if any, or
+ /// nil.
+ corpus_group_sptr
+ current_corpus_group()
+ {return cur_corpus_group_;}
+
+ /// Setter of the current corpus group being constructed.
+ ///
+ /// @param g the new corpus group.
+ void
+ current_corpus_group(const corpus_group_sptr& g)
+ {
+ if (g)
+ cur_corpus_group_ = g;
+ }
+
+ /// Test if there is a corpus group being built.
+ ///
+ /// @return if there is a corpus group being built, false otherwise.
+ bool
+ has_corpus_group() const
+ {return cur_corpus_group_;}
+
+ /// Return the main corpus from the current corpus group, if any.
+ ///
+ /// @return the main corpus of the current corpus group, if any, nil
+ /// if no corpus group is being constructed.
+ corpus_sptr
+ main_corpus_from_current_group()
+ {
+ if (cur_corpus_group_ && !cur_corpus_group_->get_corpora().empty())
+ return cur_corpus_group_->get_corpora().front();
+
+ return corpus_sptr();
+ }
+
+ /// Return the main corpus from the current corpus group, if any.
+ ///
+ /// @return the main corpus of the current corpus group, if any, nil
+ /// if no corpus group is being constructed.
+ const corpus_sptr
+ main_corpus_from_current_group() const
+ {return const_cast<read_context*>(this)->main_corpus_from_current_group();}
+
+ /// Test if the current corpus being built is the main corpus of the
+ /// current corpus group.
+ ///
+ /// @return return true iff the current corpus being built is the
+ /// main corpus of the current corpus group.
+ bool
+ current_corpus_is_main_corpus_from_current_group() const
+ {
+ corpus_sptr main_corpus = main_corpus_from_current_group();
+
+ if (main_corpus && main_corpus.get() == cur_corpus_.get())
+ return true;
+
+ return false;
+ }
+
+ /// Return true if the current corpus is part of a corpus group
+ /// being built and if it's not the main corpus of the group.
+ ///
+ /// For instance, this would return true if we are loading a linux
+ /// kernel *module* that is part of the current corpus group that is
+ /// being built. In this case, it means we should re-use types
+ /// coming from the "vmlinux" binary that is the main corpus of the
+ /// group.
+ ///
+ /// @return true if the current corpus is part of a corpus group
+ /// being built and if it's not the main corpus of the group.
+ corpus_sptr
+ should_reuse_type_from_corpus_group() const
+ {
+ if (has_corpus_group() && is_c_language(cur_transl_unit()->get_language()))
+ if (corpus_sptr main_corpus = main_corpus_from_current_group())
+ if (!current_corpus_is_main_corpus_from_current_group())
+ return main_corpus;
+
+ return corpus_sptr();
+ }
+
/// Get the map that associates each DIE to its parent DIE. This is
/// for DIEs coming from the main debug info sections.
///
if (!undefined_var_syms_)
undefined_var_syms_.reset(new string_elf_symbols_map_type);
- if (load_symbol_maps_from_symtab_section(load_fun_map,
- load_var_map,
- load_undefined_fun_map,
- load_undefined_var_map))
+ if (!options_.ignore_symbol_table)
{
- if (load_in_linux_kernel_mode() && is_linux_kernel_binary())
- return load_linux_specific_exported_symbol_maps();
- return true;
+ if (load_symbol_maps_from_symtab_section(load_fun_map,
+ load_var_map,
+ load_undefined_fun_map,
+ load_undefined_var_map))
+ {
+ if (load_in_linux_kernel_mode() && is_linux_kernel_binary())
+ return load_linux_specific_exported_symbol_maps();
+ return true;
+ }
+ return false;
}
- return false;
+ return true;
}
/// Return true if an address is in the ".opd" section that is
return false;
}
- /// Get the section which a global variable address comes from.
- ///
- /// @param elf the elf handle to consider.
- ///
+ /// Get the section which a global variable address comes from.
+ ///
/// @param var_addr the address for the variable.
///
/// @return the ELF section the @p var_addr comes from, or nil if no
/// section was found for that variable address.
Elf_Scn*
- get_data_section_for_variable_address(Elf* elf, Dwarf_Addr var_addr) const
+ get_data_section_for_variable_address(Dwarf_Addr var_addr) const
{
- // There are several potential 'data sections" from which an
+ // There are several potential 'data sections" from which a
// variable address can come from: .data, .data1 and .rodata.
// Let's try to try them all in sequence.
- Elf_Scn* data_section = find_bss_section(elf);
- if (!address_is_in_section(var_addr, data_section))
+ Elf_Scn* data_scn = bss_section();
+ if (!address_is_in_section(var_addr, data_scn))
{
- data_section = find_data_section(elf);
- if (!address_is_in_section(var_addr, data_section))
+ data_scn = data_section();
+ if (!address_is_in_section(var_addr, data_scn))
{
- data_section = find_data1_section(elf);
- if (!address_is_in_section(var_addr, data_section))
+ data_scn = data1_section();
+ if (!address_is_in_section(var_addr, data_scn))
{
- data_section = find_rodata_section(elf);
- if (!address_is_in_section(var_addr, data_section))
+ data_scn = rodata_section();
+ if (!address_is_in_section(var_addr, data_scn))
return 0;
}
}
}
- return data_section;
+ return data_scn;
}
/// For a relocatable (*.o) elf file, this function expects an
if (elf_header->e_type == ET_REL)
{
- Elf_Scn* data_section =
- get_data_section_for_variable_address(elf, addr);
+ Elf_Scn* data_section = get_data_section_for_variable_address(addr);
if (!data_section)
// It's likely that this address doesn't come from any
// data section.
/// @return the load_all_types flag.
bool
load_all_types() const
- {return load_all_types_;}
+ {return options_.load_all_types;}
/// Setter of the "load_all_types" flag. This flag tells if all the
/// types (including those not reachable by public declarations) are
/// @param f the new load_all_types flag.
void
load_all_types(bool f)
- {load_all_types_ = f;}
+ {options_.load_all_types = f;}
bool
load_in_linux_kernel_mode() const
- {return load_in_linux_kernel_mode_;}
+ {return options_.load_in_linux_kernel_mode;}
void
load_in_linux_kernel_mode(bool f)
- {load_in_linux_kernel_mode_ = f;}
+ {options_.load_in_linux_kernel_mode = f;}
/// Guess if the current binary is a Linux Kernel or a Linux Kernel module.
///
/// @return the value of the flag.
bool
show_stats() const
- {return show_stats_;}
+ {return options_.show_stats;}
/// Setter of the "show_stats" flag.
///
/// @param f the value of the flag.
void
show_stats(bool f)
- {show_stats_ = f;}
+ {options_.show_stats = f;}
/// Getter of the "do_log" flag.
///
/// return the "do_log" flag.
bool
do_log() const
- {return do_log_;}
+ {return options_.do_log;}
/// Setter of the "do_log" flag.
///
/// @param f the new value of the flag.
void
do_log(bool f)
- {do_log_ = f;}
+ {options_.do_log = f;}
/// If a given function decl is suitable for the set of exported
/// functions of the current corpus, this function adds it to that
set_do_log(read_context& ctxt, bool f)
{ctxt.do_log(f);}
+/// Setter of the "set_ignore_symbol_table" flag.
+///
+/// This flag tells if we should load information about ELF symbol
+/// tables. Not loading the symbol tables is a speed optimization
+/// that is done when the set of symbols we care about is provided
+/// off-hand. This is the case when we are supposed to analyze a
+/// Linux kernel binary. In that case, because we have the white list
+/// of functions/variable symbols we care about, we don't need to
+/// analyze the symbol table; things are thus faster in that case.
+///
+/// By default, the symbol table is analyzed so this boolean is set to
+/// false.
+///
+/// @param ctxt the read context to consider.
+///
+/// @param f the new value of the flag.
+void
+set_ignore_symbol_table(read_context &ctxt, bool f)
+{ctxt.options_.ignore_symbol_table = f;}
+
+/// Getter of the "set_ignore_symbol_table" flag.
+///
+/// This flag tells if we should load information about ELF symbol
+/// tables. Not loading the symbol tables is a speed optimization
+/// that is done when the set of symbols we care about is provided
+/// off-hand. This is the case when we are supposed to analyze a
+/// Linux kernel binary. In that case, because we have the white list
+/// of functions/variable symbols we care about, we don't need to
+/// analyze the symbol table; things are thus faster in that case.
+///
+/// By default, the symbol table is analyzed so this boolean is set to
+/// false.
+///
+/// @param ctxt the read context to consider.
+///
+/// @return the value of the flag.
+bool
+get_ignore_symbol_table(read_context& ctxt)
+{return ctxt.options_.ignore_symbol_table;}
+
/// Get the value of an attribute that is supposed to be a string, or
/// an empty string if the attribute could not be found.
///
return result;
}
+ if (corpus_sptr corp = ctxt.should_reuse_type_from_corpus_group())
+ {
+ string normalized_type_name = type_name;
+ integral_type int_type;
+ if (parse_integral_type(type_name, int_type))
+ normalized_type_name = int_type.to_string();
+ result = lookup_basic_type(normalized_type_name, *corp);
+ }
+
if (!result)
if (corpus_sptr corp = ctxt.current_corpus())
result = lookup_basic_type(type_name, *corp);
is_enum_type(ctxt.lookup_artifact_from_die(die, where_offset)))
result = pre_existing_enum;
}
+ else if (corpus_sptr corp = ctxt.should_reuse_type_from_corpus_group())
+ {
+ if (loc)
+ result = lookup_enum_type_per_location(loc.expand(), *corp);
+ }
else if (loc)
{
if (enum_type_decl_sptr pre_existing_enum =
return result;
}
}
+ // TODO: for anymous enums, maybe have a map of loc -> enums so that
+ // we can look them up?
uint64_t size = 0;
if (die_unsigned_constant_attribute(die, DW_AT_byte_size, size))
string n, m;
location l;
- die_loc_and_name(ctxt, &child, loc, n, m);
+ die_loc_and_name(ctxt, &child, l, n, m);
uint64_t val = 0;
die_unsigned_constant_attribute(&child, DW_AT_const_value, val);
enms.push_back(enum_type_decl::enumerator(ctxt.env(), n, val));
string name, linkage_name;
location loc;
die_loc_and_name(ctxt, die, loc, name, linkage_name);
+ bool is_declaration_only = die_is_declaration_only(die);
bool is_anonymous = false;
if (name.empty())
is_class_type(ctxt.lookup_artifact_from_die(die, where_offset)))
klass = pre_existing_class;
}
+ else if (corpus_sptr corp = ctxt.should_reuse_type_from_corpus_group())
+ {
+ if (loc)
+ result = lookup_class_type_per_location(loc.expand(), *corp);
+ else
+ result = lookup_class_type(name, *corp);
+ if (result)
+ {
+ ctxt.associate_die_to_type(die, result, where_offset,
+ associate_die_to_repr);
+ return result;
+ }
+ }
else if (loc)
{
// The current type we are looking at does have a location.
Dwarf_Die child;
bool has_child = (dwarf_child(die, &child) == 0);
- bool is_declaration_only = die_is_declaration_only(die);
decl_base_sptr res;
if (klass)
string name, linkage_name;
location loc;
die_loc_and_name(ctxt, die, loc, name, linkage_name);
+ bool is_declaration_only = die_is_declaration_only(die);
bool is_anonymous = false;
if (name.empty())
is_union_type(ctxt.lookup_artifact_from_die(die, where_offset)))
union_type = pre_existing_union;
}
+ else if (corpus_sptr corp = ctxt.should_reuse_type_from_corpus_group())
+ {
+ if (loc)
+ result = lookup_union_type_per_location(loc.expand(), *corp);
+ else
+ result = lookup_union_type(name, *corp);
+
+ if (result)
+ {
+ ctxt.associate_die_to_type(die, result, where_offset,
+ associate_die_to_repr);
+ return result;
+ }
+ }
else if (loc)
{
// The current type we are looking at does have a location.
uint64_t size = 0;
die_size_in_bits(die, size);
- bool is_declaration_only = die_is_declaration_only(die);
-
if (union_type)
{
result = union_type;
type_base_sptr utype = is_type(utype_decl);
assert(utype);
+ qualified_type_def::CV qual = qualified_type_def::CV_NONE;
if (tag == DW_TAG_const_type)
- result.reset(new qualified_type_def(utype,
- qualified_type_def::CV_CONST,
- location()));
+ qual |= qualified_type_def::CV_CONST;
else if (tag == DW_TAG_volatile_type)
- result.reset(new qualified_type_def(utype,
- qualified_type_def::CV_VOLATILE,
- location()));
+ qual |= qualified_type_def::CV_VOLATILE;
else if (tag == DW_TAG_restrict_type)
- result.reset(new qualified_type_def(utype,
- qualified_type_def::CV_RESTRICT,
- location()));
+ qual |= qualified_type_def::CV_RESTRICT;
+ else
+ ABG_ASSERT_NOT_REACHED;
+
+ if (!result)
+ result.reset(new qualified_type_def(utype, qual, location()));
- if (result)
- if (corpus_sptr corp = ctxt.current_corpus())
- if (qualified_type_def_sptr t =
- lookup_qualified_type(*is_qualified_type(result), *corp))
- result = t;
ctxt.associate_die_to_type(die, result, where_offset);
return result;
if (tag != DW_TAG_typedef)
return result;
- Dwarf_Die underlying_type_die;
- if (!die_die_attribute(die, DW_AT_type, underlying_type_die))
- return result;
+ string name, linkage_name;
+ location loc;
+ die_loc_and_name(ctxt, die, loc, name, linkage_name);
- type_base_sptr utype = is_type(build_ir_node_from_die(ctxt,
- &underlying_type_die,
- called_from_public_decl,
- where_offset));
- if (!utype)
- return result;
+ if (corpus_sptr corp = ctxt.should_reuse_type_from_corpus_group())
+ if (loc)
+ result = lookup_typedef_type_per_location(loc.expand(), *corp);
- // The call to build_ir_node_from_die() could have triggered the
- // creation of the type for this DIE. In that case, just return it.
- if (type_base_sptr t = ctxt.lookup_type_from_die(die))
+ if (!result)
{
- result = is_typedef(t);
- assert(result);
- return result;
- }
+ Dwarf_Die underlying_type_die;
+ if (!die_die_attribute(die, DW_AT_type, underlying_type_die))
+ return result;
- assert(utype);
+ type_base_sptr utype =
+ is_type(build_ir_node_from_die(ctxt,
+ &underlying_type_die,
+ called_from_public_decl,
+ where_offset));
+ if (!utype)
+ return result;
- string name, linkage_name;
- location loc;
- die_loc_and_name(ctxt, die, loc, name, linkage_name);
+ // The call to build_ir_node_from_die() could have triggered the
+ // creation of the type for this DIE. In that case, just return it.
+ if (type_base_sptr t = ctxt.lookup_type_from_die(die))
+ {
+ result = is_typedef(t);
+ assert(result);
+ return result;
+ }
- result.reset(new typedef_decl(name, utype, loc, linkage_name));
+ assert(utype);
+ result.reset(new typedef_decl(name, utype, loc, linkage_name));
+
+ if (class_decl_sptr klass = is_class_type(utype))
+ if (is_anonymous_type(klass))
+ klass->set_naming_typedef(result);
+ }
ctxt.associate_die_to_type(die, result, where_offset,
/*do_associate_by_repr=*/false);
- if (class_decl_sptr klass = is_class_type(utype))
- if (is_anonymous_type(klass))
- klass->set_naming_typedef(result);
-
return result;
}
return var;
}
+/// Create a variable symbol with a given name.
+///
+/// @param sym_name the name of the variable symbol.
+///
+/// @param env the environment to create the default symbol in.
+///
+/// @return the newly created symbol.
+static elf_symbol_sptr
+create_default_var_sym(const string& sym_name, const environment *env)
+{
+ elf_symbol::version ver;
+ elf_symbol_sptr result =
+ elf_symbol::create(env,
+ /*symbol index=*/ 0,
+ /*symbol size=*/ 0,
+ sym_name,
+ /*symbol type=*/ elf_symbol::OBJECT_TYPE,
+ /*symbol binding=*/ elf_symbol::GLOBAL_BINDING,
+ /*symbol is defined=*/ true,
+ /*symbol is common=*/ false,
+ /*symbol version=*/ ver);
+ return result;
+}
+
/// Build a @ref var_decl out of a DW_TAG_variable DIE.
///
/// @param ctxt the read context to use.
// not set already.
if (!result->get_symbol())
{
- Dwarf_Addr var_addr;
elf_symbol_sptr var_sym;
- if (ctxt.get_variable_address(die, var_addr)
- && (var_sym = ctxt.variable_symbol_is_exported(var_addr)))
+ if (get_ignore_symbol_table(ctxt))
+ {
+ string var_name =
+ result->get_linkage_name().empty()
+ ? result->get_name()
+ : result->get_linkage_name();
+
+ var_sym = create_default_var_sym(var_name, ctxt.env());
+ assert(var_sym);
+ add_symbol_to_map(var_sym, ctxt.var_syms());
+ }
+ else
+ {
+ Dwarf_Addr var_addr;
+ if (ctxt.get_variable_address(die, var_addr))
+ var_sym = var_sym = ctxt.variable_symbol_is_exported(var_addr);
+ }
+
+ if (var_sym)
{
result->set_symbol(var_sym);
// If the linkage name is not set or is wrong, set it to
/*require_drop_property=*/true);
}
+/// Create a function symbol with a given name.
+///
+/// @param sym_name the name of the symbol to create.
+///
+/// @param env the environment to create the symbol in.
+///
+/// @return the newly created symbol.
+elf_symbol_sptr
+create_default_fn_sym(const string& sym_name, const environment *env)
+{
+ elf_symbol::version ver;
+ elf_symbol_sptr result =
+ elf_symbol::create(env,
+ /*symbol index=*/ 0,
+ /*symbol size=*/ 0,
+ sym_name,
+ /*symbol type=*/ elf_symbol::FUNC_TYPE,
+ /*symbol binding=*/ elf_symbol::GLOBAL_BINDING,
+ /*symbol is defined=*/ true,
+ /*symbol is common=*/ false,
+ /*symbol version=*/ ver);
+ return result;
+}
+
/// Build a @ref function_decl our of a DW_TAG_subprogram DIE.
///
/// @param ctxt the read context to use
// or is wrong, set it to the name of the underlying symbol.
if (!result->get_symbol())
{
- Dwarf_Addr fn_addr;
elf_symbol_sptr fn_sym;
- if (ctxt.get_function_address(die, fn_addr)
- && (fn_sym = ctxt.function_symbol_is_exported(fn_addr)))
+ if (get_ignore_symbol_table(ctxt))
+ {
+ string fn_name =
+ result->get_linkage_name().empty()
+ ? result->get_name()
+ : result->get_linkage_name();
+
+ fn_sym = create_default_fn_sym(fn_name, ctxt.env());
+ assert(fn_sym);
+ add_symbol_to_map(fn_sym, ctxt.fun_syms());
+ }
+ else
+ {
+ Dwarf_Addr fn_addr;
+ if (ctxt.get_function_address(die, fn_addr))
+ fn_sym = ctxt.function_symbol_is_exported(fn_addr);
+ }
+
+ if (fn_sym)
{
result->set_symbol(fn_sym);
string linkage_name = result->get_linkage_name();
- if (linkage_name.empty() || !fn_sym->get_alias_from_name(linkage_name))
+ if (linkage_name.empty()
+ || !fn_sym->get_alias_from_name(linkage_name))
result->set_linkage_name(fn_sym->get_name());
result->set_is_in_public_symbol_table(true);
}
}
}
+/// Add a symbol to a symbol map.
+///
+/// @param sym the symbol to add.
+///
+/// @param map the symbol map to add the symbol into.
+static void
+add_symbol_to_map(const elf_symbol_sptr& sym,
+ string_elf_symbols_map_type& map)
+{
+ if (!sym)
+ return;
+
+ string_elf_symbols_map_type::iterator it = map.find(sym->get_name());
+ if (it == map.end())
+ {
+ elf_symbols syms;
+ syms.push_back(sym);
+ map[sym->get_name()] = syms;
+ }
+ else
+ it->second.push_back(sym);
+}
+
/// Add a set of addresses (representing variable symbols) to a
/// variable symbol name -> symbol map.
///
ctxt.current_corpus()->set_architecture_name(ctxt.elf_architecture());
// Set symbols information to the corpus.
- if (ctxt.load_in_linux_kernel_mode() && ctxt.is_linux_kernel_binary())
- {
- string_elf_symbols_map_sptr exported_fn_symbols_map
- (new string_elf_symbols_map_type);
- add_fn_symbols_to_map(*ctxt.linux_exported_fn_syms(),
- *exported_fn_symbols_map,
- ctxt);
- add_fn_symbols_to_map(*ctxt.linux_exported_gpl_fn_syms(),
- *exported_fn_symbols_map,
- ctxt);
- ctxt.current_corpus()->set_fun_symbol_map(exported_fn_symbols_map);
-
- string_elf_symbols_map_sptr exported_var_symbols_map
- (new string_elf_symbols_map_type);
- add_var_symbols_to_map(*ctxt.linux_exported_var_syms(),
- *exported_var_symbols_map,
- ctxt);
- add_var_symbols_to_map(*ctxt.linux_exported_gpl_var_syms(),
- *exported_var_symbols_map,
- ctxt);
- ctxt.current_corpus()->set_var_symbol_map(exported_var_symbols_map);
+ if (!get_ignore_symbol_table(ctxt))
+ {
+ if (ctxt.load_in_linux_kernel_mode() && ctxt.is_linux_kernel_binary())
+ {
+ string_elf_symbols_map_sptr exported_fn_symbols_map
+ (new string_elf_symbols_map_type);
+ add_fn_symbols_to_map(*ctxt.linux_exported_fn_syms(),
+ *exported_fn_symbols_map,
+ ctxt);
+ add_fn_symbols_to_map(*ctxt.linux_exported_gpl_fn_syms(),
+ *exported_fn_symbols_map,
+ ctxt);
+ ctxt.current_corpus()->set_fun_symbol_map(exported_fn_symbols_map);
+
+ string_elf_symbols_map_sptr exported_var_symbols_map
+ (new string_elf_symbols_map_type);
+ add_var_symbols_to_map(*ctxt.linux_exported_var_syms(),
+ *exported_var_symbols_map,
+ ctxt);
+ add_var_symbols_to_map(*ctxt.linux_exported_gpl_var_syms(),
+ *exported_var_symbols_map,
+ ctxt);
+ ctxt.current_corpus()->set_var_symbol_map(exported_var_symbols_map);
+ }
+ else
+ {
+ ctxt.current_corpus()->set_fun_symbol_map(ctxt.fun_syms_sptr());
+ ctxt.current_corpus()->set_var_symbol_map(ctxt.var_syms_sptr());
+ }
+
+ ctxt.current_corpus()->set_undefined_fun_symbol_map
+ (ctxt.undefined_fun_syms_sptr());
+ ctxt.current_corpus()->set_undefined_var_symbol_map
+ (ctxt.undefined_var_syms_sptr());
}
else
{
ctxt.current_corpus()->set_var_symbol_map(ctxt.var_syms_sptr());
}
- ctxt.current_corpus()->set_undefined_fun_symbol_map
- (ctxt.undefined_fun_syms_sptr());
- ctxt.current_corpus()->set_undefined_var_symbol_map
- (ctxt.undefined_var_syms_sptr());
-
// Get out now if no debug info is found.
if (!ctxt.dwarf())
return ctxt.current_corpus();
{
Dwarf_Die spec_die;
bool var_is_cloned = false;
+
+ if (tag == DW_TAG_member)
+ assert(!is_c_language(ctxt.cur_transl_unit()->get_language()));
+
if (die_die_attribute(die, DW_AT_specification, spec_die,false)
|| (var_is_cloned = die_die_attribute(die, DW_AT_abstract_origin,
spec_die, false)))
die, where_offset, fn);
if (result && !fn)
- result = add_decl_to_scope(is_decl(result), scope);
+ result = add_decl_to_scope(is_decl(result), logical_scope);
fn = is_function_decl(result);
if (fn && is_member_function(fn))
{
- class_decl_sptr klass(static_cast<class_decl*>(scope),
+ class_decl_sptr klass(static_cast<class_decl*>(logical_scope),
sptr_utils::noop_deleter());
assert(klass);
finish_member_function_reading(die, fn, klass, ctxt);
if (!die)
return decl_base_sptr();
+ if (is_c_language(ctxt.cur_transl_unit()->get_language()))
+ {
+ const scope_decl_sptr& scop = ctxt.global_scope();
+ return build_ir_node_from_die(ctxt, die, scop.get(),
+ called_from_public_decl,
+ where_offset);
+ }
+
scope_decl_sptr scope = get_scope_for_die(ctxt, die,
called_from_public_decl,
where_offset);
{
// Create a DWARF Front End Library handle to be used by functions
// of that library.
- read_context_sptr result(new read_context(elf_path));
- result->create_default_dwfl(debug_info_root_path);
- result->load_all_types(load_all_types);
- result->load_in_linux_kernel_mode(linux_kernel_mode);
- result->env(environment);
+ read_context_sptr result(new read_context(elf_path, debug_info_root_path,
+ environment, load_all_types,
+ linux_kernel_mode));
return result;
}
ctxt.get_suppressions().push_back(*i);
}
+/// Set the @ref corpus_group being created to the current read context.
+///
+/// @param ctxt the read_context to consider.
+///
+/// @param group the @ref corpus_group to set.
+void
+set_read_context_corpus_group(read_context& ctxt,
+ corpus_group_sptr& group)
+{
+ ctxt.cur_corpus_group_ = group;
+}
+
/// Read all @ref abigail::translation_unit possible from the debug info
/// accessible from an elf file, stuff them into a libabigail ABI
/// Corpus and return it.
if (!ctxt.load_debug_info())
status |= STATUS_DEBUG_INFO_NOT_FOUND;
- ctxt.load_elf_properties();
-
- // First read the symbols for publicly defined decls
- if (!ctxt.load_symbol_maps())
- status |= STATUS_NO_SYMBOLS_FOUND;
+ if (!get_ignore_symbol_table(ctxt))
+ {
+ ctxt.load_elf_properties();
+ // Read the symbols for publicly defined decls
+ if (!ctxt.load_symbol_maps())
+ status |= STATUS_NO_SYMBOLS_FOUND;
+ }
if (status & STATUS_NO_SYMBOLS_FOUND)
return corpus_sptr();
return corp;
}
+/// Read a corpus and add it to a given @ref corpus_group.
+///
+/// @param ctxt the reading context to consider.
+///
+/// @param group the @ref corpus_group to add the new corpus to.
+///
+/// @param status output parameter. The status of the read. It is set
+/// by this function upon its completion.
+corpus_sptr
+read_and_add_corpus_to_group_from_elf(read_context& ctxt,
+ corpus_group& group,
+ status& status)
+{
+ corpus_sptr result;
+ corpus_sptr corp = read_corpus_from_elf(ctxt, status);
+ if (status & STATUS_OK)
+ {
+ group.add_corpus(corp);
+ result = corp;
+ }
+
+ return result;
+}
+
/// Read all @ref abigail::translation_unit possible from the debug info
/// accessible from an elf file, stuff them into a libabigail ABI
/// Corpus and return it.
--- /dev/null
+// -*- Mode: C++ -*-
+//
+// Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
+//
+// Author: Dodji Seketeli
+
+/// @file
+///
+/// The source code of the Kernel Module Interface Diff tool.
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <fts.h>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <iostream>
+
+#include "abg-config.h"
+#include "abg-tools-utils.h"
+#include "abg-corpus.h"
+#include "abg-dwarf-reader.h"
+#include "abg-comparison.h"
+
+using std::string;
+using std::vector;
+using std::ostream;
+using std::cout;
+using std::cerr;
+
+using namespace abigail::tools_utils;
+using namespace abigail::dwarf_reader;
+using namespace abigail::ir;
+
+using abigail::comparison::diff_context_sptr;
+using abigail::comparison::diff_context;
+using abigail::comparison::translation_unit_diff_sptr;
+using abigail::comparison::corpus_diff;
+using abigail::comparison::corpus_diff_sptr;
+using abigail::comparison::compute_diff;
+using abigail::suppr::suppression_sptr;
+using abigail::suppr::suppressions_type;
+using abigail::suppr::read_suppressions;
+using abigail::tools_utils::gen_suppr_spec_from_kernel_abi_whitelist;
+
+/// The options of this program.
+struct options
+{
+ bool display_usage;
+ bool display_version;
+ bool verbose;
+ bool missing_operand;
+ string wrong_option;
+ string kernel_dist_root1;
+ string kernel_dist_root2;
+ vector<string> kabi_whitelist_paths;
+ vector<string> suppression_paths;
+ suppressions_type read_time_supprs;
+ suppressions_type diff_time_supprs;
+
+ options()
+ : display_usage(),
+ display_version(),
+ verbose(),
+ missing_operand()
+ {}
+}; // end struct options.
+
+/// Display the usage of the program.
+///
+/// @param prog_name the name of this program.
+///
+/// @param out the output stream the usage stream is sent to.
+static void
+display_usage(const string& prog_name, ostream& out)
+{
+ emit_prefix(prog_name, out)
+ << "usage: " << prog_name << " [options] kernel-package1 kernel-package2\n"
+ << " where options can be:\n"
+ << " --help|-h display this message\n"
+ << " --version|-v display program version information and exit\n"
+ << " --verbose display verbose messages\n"
+ << " --suppressions|--suppr <path> specify a suppression file\n"
+ << " --kmi-whitelist|-w <path> path to a kernel module interface "
+ "whitelist\n";
+}
+
+/// Parse the command line of the program.
+///
+/// @param argc the number of arguments on the command line, including
+/// the program name.
+///
+/// @param argv the arguments on the command line, including the
+/// program name.
+///
+/// @param opts the options resulting from the command line parsing.
+///
+/// @return true iff the command line parsing went fine.
+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.kernel_dist_root1.empty())
+ opts.kernel_dist_root1 = argv[i];
+ else if (opts.kernel_dist_root2.empty())
+ opts.kernel_dist_root2 = argv[i];
+ else
+ return false;
+ }
+ else if (!strcmp(argv[i], "--verbose"))
+ opts.verbose = true;
+ else if (!strcmp(argv[i], "--version")
+ || !strcmp(argv[i], "-v"))
+ {
+ opts.display_version = true;
+ return true;
+ }
+ else if (!strcmp(argv[i], "--help")
+ || !strcmp(argv[i], "-h"))
+ {
+ opts.display_usage = true;
+ return true;
+ }
+ else if (!strcmp(argv[i], "--kmi-whitelist")
+ || !strcmp(argv[i], "-w"))
+ {
+ int j = i + 1;
+ if (j >= argc)
+ {
+ opts.missing_operand = true;
+ opts.wrong_option = argv[i];
+ return false;
+ }
+ opts.kabi_whitelist_paths.push_back(argv[j]);
+ ++i;
+ }
+ else if (!strcmp(argv[i], "--suppressions")
+ || !strcmp(argv[i], "--suppr"))
+ {
+ int j = i + 1;
+ if (j >= argc)
+ {
+ opts.missing_operand = true;
+ opts.wrong_option = argv[i];
+ return false;
+ }
+ opts.suppression_paths.push_back(argv[j]);
+ ++i;
+ }
+ }
+
+ return true;
+}
+
+/// Check that the suppression specification files supplied are
+/// present. If not, emit an error on stderr.
+///
+/// @param opts the options instance to use.
+///
+/// @return true if all suppression specification files are present,
+/// false otherwise.
+static bool
+maybe_check_suppression_files(const options& opts)
+{
+ for (vector<string>::const_iterator i = opts.suppression_paths.begin();
+ i != opts.suppression_paths.end();
+ ++i)
+ if (!check_file(*i, cerr, "abidiff"))
+ return false;
+
+ for (vector<string>::const_iterator i =
+ opts.kabi_whitelist_paths.begin();
+ i != opts.kabi_whitelist_paths.end();
+ ++i)
+ if (!check_file(*i, cerr, "abidiff"))
+ return false;
+
+ return true;
+}
+
+/// If the user specified suppression specification files, this
+/// function parses those, set them to the options of the program and
+/// set the read context with the suppression specification.
+///
+/// @param read_ctxt the read context to consider.
+///
+/// @param opts the options to consider.
+static void
+set_suppressions(read_context &read_ctxt, options& opts)
+{
+ if (opts.read_time_supprs.empty())
+ {
+ for (vector<string>::const_iterator i = opts.suppression_paths.begin();
+ i != opts.suppression_paths.end();
+ ++i)
+ read_suppressions(*i, opts.read_time_supprs);
+
+ for (vector<string>::const_iterator i =
+ opts.kabi_whitelist_paths.begin();
+ i != opts.kabi_whitelist_paths.end();
+ ++i)
+ gen_suppr_spec_from_kernel_abi_whitelist(*i, opts.read_time_supprs);
+ }
+
+ abigail::dwarf_reader::add_read_context_suppressions(read_ctxt,
+ opts.read_time_supprs);
+}
+
+/// Setup the diff context from the program's options.
+///
+/// @param ctxt the diff context to consider.
+///
+/// @param opts the options to set the context.
+static void
+set_diff_context(diff_context_sptr ctxt, const options& opts)
+{
+ ctxt->default_output_stream(&cout);
+ ctxt->error_output_stream(&cerr);
+ ctxt->show_relative_offset_changes(true);
+ ctxt->show_redundant_changes(false);
+ ctxt->show_locs(true);
+ ctxt->show_linkage_names(false);
+ ctxt->show_added_fns(false);
+ ctxt->show_added_vars(false);
+ ctxt->show_added_symbols_unreferenced_by_debug_info
+ (false);
+ ctxt->show_symbols_unreferenced_by_debug_info
+ (true);
+
+ ctxt->switch_categories_off
+ (abigail::comparison::ACCESS_CHANGE_CATEGORY
+ | abigail::comparison::COMPATIBLE_TYPE_CHANGE_CATEGORY
+ | abigail::comparison::HARMLESS_DECL_NAME_CHANGE_CATEGORY
+ | abigail::comparison::NON_VIRT_MEM_FUN_CHANGE_CATEGORY
+ | abigail::comparison::STATIC_DATA_MEMBER_CHANGE_CATEGORY
+ | abigail::comparison::HARMLESS_ENUM_CHANGE_CATEGORY
+ | abigail::comparison::HARMLESS_SYMBOL_ALIAS_CHANGE_CATEORY);
+
+ if (!opts.diff_time_supprs.empty())
+ ctxt->add_suppressions(opts.diff_time_supprs);
+}
+
+/// Test if an FTSENT pointer (resulting from fts_read) represents the
+/// vmlinux binary.
+///
+/// @param entry the FTSENT to consider.
+///
+/// @return true iff @p entry is for a vmlinux binary.
+static bool
+is_vmlinux(const FTSENT *entry)
+{
+ if (entry == NULL
+ || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
+ || entry->fts_info == FTS_ERR
+ || entry->fts_info == FTS_NS)
+ return false;
+
+ string fname = entry->fts_name;
+
+ if (fname == "vmlinux")
+ {
+ string dirname;
+ dir_name(entry->fts_path, dirname);
+ if (string_ends_with(dirname, "compressed"))
+ return false;
+
+ return true;
+ }
+
+ return false;
+}
+
+/// Test if an FTSENT pointer (resulting from fts_read) represents a a
+/// linux kernel module binary.
+///
+/// @param entry the FTSENT to consider.
+///
+/// @return true iff @p entry is for a linux kernel module binary.
+static bool
+is_kernel_module(const FTSENT *entry)
+{
+ if (entry == NULL
+ || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
+ || entry->fts_info == FTS_ERR
+ || entry->fts_info == FTS_NS)
+ return false;
+
+ string fname = entry->fts_name;
+ if (string_ends_with(fname, ".ko")
+ || string_ends_with(fname, ".ko.xz")
+ || string_ends_with(fname, ".ko.gz"))
+ return true;
+
+ return false;
+}
+
+/// Find a vmlinux and its kernel modules in a given directory tree.
+///
+/// @param from the directory tree to start looking from.
+///
+/// @param vmlinux_path output parameter. This is set to the path
+/// where the vmlinux binary is found. This is set iff the returns
+/// true.
+///
+/// @param module_paths output parameter. This is set to the paths of
+/// the linux kernel module binaries.
+///
+/// @return true iff at least the vmlinux binary was found.
+static bool
+find_vmlinux_and_module_paths(const string& from,
+ string &vmlinux_path,
+ vector<string> &module_paths)
+{
+ char* path[] = {const_cast<char*>(from.c_str()), 0};
+
+ FTS *file_hierarchy = fts_open(path, FTS_PHYSICAL|FTS_NOCHDIR|FTS_XDEV, 0);
+ if (!file_hierarchy)
+ return false;
+
+ bool found_vmlinux = false;
+ FTSENT *entry;
+ while ((entry = fts_read(file_hierarchy)))
+ {
+ // Skip descendents of symbolic links.
+ if (entry->fts_info == FTS_SL || entry->fts_info == FTS_SLNONE)
+ {
+ fts_set(file_hierarchy, entry, FTS_SKIP);
+ continue;
+ }
+
+ if (!found_vmlinux && is_vmlinux(entry))
+ {
+ vmlinux_path = entry->fts_path;
+ found_vmlinux = true;
+ }
+ else if (is_kernel_module(entry))
+ module_paths.push_back(entry->fts_path);
+ }
+
+ fts_close(file_hierarchy);
+
+ return found_vmlinux;
+}
+
+/// Get the paths of the vmlinux and kernel module binaries under
+/// given directory.
+///
+/// @param dist_root the directory under which to look for.
+///
+/// @param vmlinux_path output parameter. The path of the vmlinux
+/// binary that was found.
+///
+/// @param module_paths output parameter. The paths of the kernel
+/// module binaries that were found.
+///
+/// @param debug_info_root_path output parameter. If a debug info
+/// sub-directory is found under @p dist_root, it's set to this
+/// parameter.
+///
+/// @return true if at least the path to the vmlinux binary was found.
+static bool
+get_binary_paths_from_kernel_dist(const string& dist_root,
+ string& vmlinux_path,
+ vector<string>& module_paths,
+ string& debug_info_root_path)
+{
+ if (!dir_exists(dist_root))
+ return false;
+
+ // For now, we assume either an Enterprise Linux or a Fedora kernel
+ // distribution directory.
+ //
+ // We also take into account split debug info package for these. In
+ // this case, the content split debug info package is installed
+ // under the 'dist_root' directory as well, and its content is
+ // accessible from <dist_root>/usr/lib/debug directory.
+
+ string kernel_modules_root;
+ string debug_info_root;
+ if (dir_exists(dist_root + "/lib/modules"))
+ {
+ dist_root + "/lib/modules";
+ debug_info_root = dist_root + "/usr/lib/debug";
+ }
+
+ if (dir_is_empty(debug_info_root))
+ debug_info_root.clear();
+
+ bool found = false;
+ string from = !debug_info_root.empty() ? debug_info_root : dist_root;
+ if (find_vmlinux_and_module_paths(from, vmlinux_path, module_paths))
+ found = true;
+
+ debug_info_root_path = debug_info_root;
+
+ return found;
+}
+
+/// Get the paths of the vmlinux and kernel module binaries under
+/// given directory.
+///
+/// @param dist_root the directory under which to look for.
+///
+/// @param vmlinux_path output parameter. The path of the vmlinux
+/// binary that was found.
+///
+/// @param module_paths output parameter. The paths of the kernel
+/// module binaries that were found.
+///
+/// @return true if at least the path to the vmlinux binary was found.
+static bool
+get_binary_paths_from_kernel_dist(const string& dist_root,
+ string& vmlinux_path,
+ vector<string>& module_paths)
+{
+ string debug_info_root_path;
+ return get_binary_paths_from_kernel_dist(dist_root,
+ vmlinux_path,
+ module_paths,
+ debug_info_root_path);
+}
+
+/// Walk a given directory and build an instance of @ref corpus_group
+/// from the vmlinux kernel binary and the linux kernel modules found
+/// under that directory and under its sub-directories, recursively.
+///
+/// The main corpus of the @ref corpus_group is made of the vmlinux
+/// binary. The other corpora are made of the linux kernel binaries.
+///
+/// @param root the path of the directory under which vmlinux and its
+/// kernel modules are to be found.
+///
+/// @param opts the options to use during the search.
+///
+/// @param env the environment to create the corpus_group in.
+static corpus_group_sptr
+build_corpus_group_from_kernel_dist_under(const string& root,
+ options& opts,
+ environment_sptr& env)
+{
+ corpus_group_sptr result;
+ string vmlinux;
+ vector<string> modules;
+ string debug_info_root_path;
+
+ if (opts.verbose)
+ cout << "Analysing kernel dist root '" << root << "' ... " << std::flush;
+
+ bool got_binary_paths =
+ get_binary_paths_from_kernel_dist(root, vmlinux, modules,
+ debug_info_root_path);
+
+ if (opts.verbose)
+ cout << "DONE\n";
+
+ if (got_binary_paths)
+ {
+ shared_ptr<char> di_root =
+ make_path_absolute(debug_info_root_path.c_str());
+ char *di_root_ptr = di_root.get();
+ abigail::dwarf_reader::status status = abigail::dwarf_reader::STATUS_OK;
+ corpus_group_sptr group;
+ if (!vmlinux.empty())
+ {
+ read_context_sptr ctxt =
+ create_read_context(vmlinux, &di_root_ptr, env.get(),
+ /*read_all_types=*/false,
+ /*linux_kernel_mode=*/true);
+
+ set_suppressions(*ctxt, opts);
+
+ // If we have been given a whitelist of functions and
+ // variable symbols to look at, then we can avoid loading
+ // and analyzing the ELF symbol table.
+ bool do_ignore_symbol_table = !opts.read_time_supprs.empty();
+ set_ignore_symbol_table(*ctxt, do_ignore_symbol_table);
+
+ group.reset(new corpus_group(env.get(), root));
+
+ set_read_context_corpus_group(*ctxt, group);
+
+ if (opts.verbose)
+ cout << "reading kernel binary '"
+ << vmlinux << "' ... " << std::flush;
+
+ // Read the vmlinux corpus and add it to the group.
+ read_and_add_corpus_to_group_from_elf(*ctxt, *group, status);
+
+ if (opts.verbose)
+ cout << " DONE\n";
+ }
+
+ if (!group->is_empty())
+ {
+ // Now add the corpora of the modules to the corpus group.
+ int total_nb_modules = modules.size();
+ int cur_module_index = 1;
+ for (vector<string>::const_iterator m = modules.begin();
+ m != modules.end();
+ ++m, ++cur_module_index)
+ {
+ if (opts.verbose)
+ cout << "reading module '"
+ << *m << "' ("
+ << cur_module_index
+ << "/" << total_nb_modules
+ << ") ... " << std::flush;
+
+ read_context_sptr module_ctxt =
+ create_read_context(*m, &di_root_ptr, env.get(),
+ /*read_all_types=*/false,
+ /*linux_kernel_mode=*/true);
+
+ // If we have been given a whitelist of functions and
+ // variable symbols to look at, then we can avoid loading
+ // and analyzing the ELF symbol table.
+ bool do_ignore_symbol_table = !opts.read_time_supprs.empty();
+
+ set_ignore_symbol_table(*module_ctxt, do_ignore_symbol_table);
+
+ set_suppressions(*module_ctxt, opts);
+
+ set_read_context_corpus_group(*module_ctxt, group);
+
+ read_and_add_corpus_to_group_from_elf(*module_ctxt,
+ *group, status);
+ if (opts.verbose)
+ cout << " DONE\n";
+ }
+
+ result = group;
+ }
+ }
+
+ return result;
+}
+
+/// Print information about the kernel (and modules) binaries found
+/// under a given directory.
+///
+/// Note that this function actually look for the modules iff the
+/// --verbose option was provided.
+///
+/// @param root the directory to consider.
+///
+/// @param opts the options to use during the search.
+static void
+print_kernel_dist_binary_paths_under(const string& root, const options &opts)
+{
+ string vmlinux;
+ vector<string> modules;
+
+ if (opts.verbose)
+ if (get_binary_paths_from_kernel_dist(root, vmlinux, modules))
+ {
+ cout << "Found kernel binaries under: '" << root << "'\n";
+ if (!vmlinux.empty())
+ cout << "[linux kernel binary]\n"
+ << " '" << vmlinux << "'\n";
+ if (!modules.empty())
+ {
+ cout << "[linux kernel module binaries]\n";
+ for (vector<string>::const_iterator p = modules.begin();
+ p != modules.end();
+ ++p)
+ cout << " '" << *p << "' \n";
+ }
+ cout << "\n";
+ }
+}
+
+int
+main(int argc, char* argv[])
+{
+ options opts;
+ if (!parse_command_line(argc, argv, opts))
+ {
+ emit_prefix(argv[0], cerr)
+ << "unrecognized option: "
+ << opts.wrong_option << "\n"
+ << "try the --help option for more information\n";
+ return 1;
+ }
+
+ if (opts.missing_operand)
+ {
+ emit_prefix(argv[0], cerr)
+ << "missing operand to option: " << opts.wrong_option <<"\n"
+ << "try the --help option for more information\n";
+ return 1;
+ }
+
+ if (!maybe_check_suppression_files(opts))
+ return 1;
+
+ if (opts.display_usage)
+ {
+ display_usage(argv[0], cout);
+ return 1;
+ }
+
+ if (opts.display_version)
+ {
+ string major, minor, revision;
+ abigail::abigail_get_library_version(major, minor, revision);
+ emit_prefix(argv[0], cout)
+ << major << "." << minor << "." << revision << "\n";
+ return 0;
+ }
+
+ environment_sptr env(new environment);
+
+ corpus_group_sptr group1, group2;
+ if (!opts.kernel_dist_root1.empty())
+ {
+ group1 =
+ build_corpus_group_from_kernel_dist_under(opts.kernel_dist_root1,
+ opts, env);
+ print_kernel_dist_binary_paths_under(opts.kernel_dist_root1, opts);
+ }
+
+ if (!opts.kernel_dist_root2.empty())
+ {
+ group2 =
+ build_corpus_group_from_kernel_dist_under(opts.kernel_dist_root2,
+ opts, env);
+ print_kernel_dist_binary_paths_under(opts.kernel_dist_root2, opts);
+ }
+
+ abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
+ if (group1 && group2)
+ {
+ diff_context_sptr diff_ctxt(new diff_context);
+ set_diff_context(diff_ctxt, opts);
+
+ corpus_diff_sptr diff= compute_diff(group1, group2, diff_ctxt);
+
+ if (diff->has_net_changes())
+ status = abigail::tools_utils::ABIDIFF_ABI_CHANGE;
+
+ if (diff->has_incompatible_changes())
+ status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE;
+
+ if (diff->has_changes())
+ diff->report(cout);
+ }
+ else
+ status = abigail::tools_utils::ABIDIFF_ERROR;
+
+ return status;
+}