1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- Mode: C++ -*-
3 //
4 // Copyright (C) 2015-2022 Red Hat, Inc.
5 //
6 // Author: Sinny Kumari
7
8 /// @file
9
10 /// This program compares the ABIs of binaries inside two packages.
11 ///
12 /// For now, the supported package formats are Deb and RPM, but
13 /// support for other formats would be greatly appreciated.
14 ///
15 /// The program takes the two packages to compare as well as their
16 /// associated debug info packages.
17 ///
18 /// The program extracts the content of the two packages into a
19 /// temporary directory , looks for the ELF binaries in there,
20 /// compares their ABIs and emit a report about the changes.
21 /// As this program uses libpthread to perform several tasks
22 /// concurrently, here is a coarse grain description of the sequence
23 /// of actions performed, including where things are done
24 /// concurrently.
25 ///
26 /// (steps 1/ and 2/ are performed concurrently. Then steps 3/ and 4/
27 /// are performed in sequence)
28 ///
29 /// 1/ the first package and its ancillary packages (debug info and
30 /// devel packages) are extracted concurrently.
31 /// There is one thread per package being extracted. So if there are
32 /// 3 thread packages (one package, one debug info package and one
33 /// devel package), then there are 3 threads to extracts them. Then
34 /// when the extracting is done, another thread performs the analysis
35 /// of th1 extracted content.
36 ///
37 /// 2/ A similar thing is done for the second package.
38 ///
39 /// 3/ comparisons are performed concurrently.
40 ///
41 /// 4/ the reports are then emitted to standard output, always in the same
42 /// order.
43
44
45 // In case we have a bad fts we include this before config.h because
46 // it can't handle _FILE_OFFSET_BITS. Everything we need here is fine
47 // if its declarations just come first. Also, include sys/types.h
48 // before fts. On some systems fts.h is not self contained.
49 #ifdef BAD_FTS
50 #include <sys/types.h>
51 #include <fts.h>
52 #endif
53
54 // For package configuration macros.
55 #include "config.h"
56
57 #include <assert.h>
58 #include <fcntl.h>
59 #include <sys/stat.h>
60
61 // If fts.h is included before config.h, its indirect inclusions may
62 // not give us the right LFS aliases of these functions, so map them
63 // manually.
64 #ifdef BAD_FTS
65 #ifdef _FILE_OFFSET_BITS
66 #define open open64
67 #define fopen fopen64
68 #endif
69 #else
70 #include <sys/types.h>
71 #include <fts.h>
72 #endif
73
74 #include <algorithm>
75 #include <cstdlib>
76 #include <cstring>
77 #include <fstream>
78 #include <iostream>
79 #include <map>
80 #include <memory>
81 #include <string>
82 #include <unordered_set>
83 #include <vector>
84
85 #include "abg-workers.h"
86 #include "abg-config.h"
87 #include "abg-tools-utils.h"
88 #include "abg-comparison.h"
89 #include "abg-suppression.h"
90 #include "abg-dwarf-reader.h"
91 #include "abg-reader.h"
92 #include "abg-writer.h"
93 #ifdef WITH_CTF
94 #include "abg-ctf-reader.h"
95 #endif
96
97 using std::cout;
98 using std::cerr;
99 using std::string;
100 using std::ostream;
101 using std::ofstream;
102 using std::vector;
103 using std::map;
104 using std::unordered_set;
105 using std::set;
106 using std::ostringstream;
107 using std::shared_ptr;
108 using std::dynamic_pointer_cast;
109 using abg_compat::optional;
110 using abigail::workers::task;
111 using abigail::workers::task_sptr;
112 using abigail::workers::queue;
113 using abigail::tools_utils::maybe_get_symlink_target_file_path;
114 using abigail::tools_utils::file_exists;
115 using abigail::tools_utils::is_dir;
116 using abigail::tools_utils::emit_prefix;
117 using abigail::tools_utils::check_file;
118 using abigail::tools_utils::ensure_dir_path_created;
119 using abigail::tools_utils::guess_file_type;
120 using abigail::tools_utils::string_ends_with;
121 using abigail::tools_utils::dir_name;
122 using abigail::tools_utils::real_path;
123 using abigail::tools_utils::string_suffix;
124 using abigail::tools_utils::sorted_strings_common_prefix;
125 using abigail::tools_utils::file_type;
126 using abigail::tools_utils::make_path_absolute;
127 using abigail::tools_utils::base_name;
128 using abigail::tools_utils::get_rpm_arch;
129 using abigail::tools_utils::file_is_kernel_package;
130 using abigail::tools_utils::gen_suppr_spec_from_headers;
131 using abigail::tools_utils::get_default_system_suppression_file_path;
132 using abigail::tools_utils::get_default_user_suppression_file_path;
133 using abigail::tools_utils::get_vmlinux_path_from_kernel_dist;
134 using abigail::tools_utils::get_dsos_provided_by_rpm;
135 using abigail::tools_utils::build_corpus_group_from_kernel_dist_under;
136 using abigail::tools_utils::load_default_system_suppressions;
137 using abigail::tools_utils::load_default_user_suppressions;
138 using abigail::tools_utils::abidiff_status;
139 using abigail::tools_utils::create_best_elf_based_reader;
140 using abigail::ir::corpus_sptr;
141 using abigail::ir::corpus_group_sptr;
142 using abigail::comparison::diff_context;
143 using abigail::comparison::diff_context_sptr;
144 using abigail::comparison::compute_diff;
145 using abigail::comparison::corpus_diff_sptr;
146 using abigail::comparison::get_default_harmless_categories_bitmap;
147 using abigail::comparison::get_default_harmful_categories_bitmap;
148 using abigail::suppr::suppression_sptr;
149 using abigail::suppr::suppressions_type;
150 using abigail::suppr::read_suppressions;
151 using abigail::elf::get_soname_of_elf_file;
152 using abigail::elf::get_type_of_elf_file;
153 using abigail::xml_writer::create_write_context;
154 using abigail::xml_writer::write_context_sptr;
155 using abigail::xml_writer::write_corpus;
156
157 using namespace abigail;
158
159 class package;
160
161 /// Convenience typedef for a shared pointer to a @ref package.
162 typedef shared_ptr<package> package_sptr;
163
164 /// The options passed to the current program.
165 class options
166 {
167 options();
168
169 public:
170 string wrong_option;
171 string wrong_arg;
172 string prog_name;
173 bool display_usage;
174 bool display_version;
175 bool missing_operand;
176 bool nonexistent_file;
177 bool abignore;
178 bool parallel;
179 string package1;
180 string package2;
181 vector<string> debug_packages1;
182 vector<string> debug_packages2;
183 string devel_package1;
184 string devel_package2;
185 size_t num_workers;
186 bool verbose;
187 bool drop_private_types;
188 bool show_relative_offset_changes;
189 bool no_default_suppression;
190 bool keep_tmp_files;
191 bool compare_dso_only;
192 bool compare_private_dsos;
193 bool leaf_changes_only;
194 bool show_all_types;
195 bool show_hexadecimal_values;
196 bool show_offsets_sizes_in_bits;
197 bool show_impacted_interfaces;
198 bool show_full_impact_report;
199 bool show_linkage_names;
200 bool show_redundant_changes;
201 bool show_harmless_changes;
202 bool show_locs;
203 bool show_added_syms;
204 bool show_symbols_not_referenced_by_debug_info;
205 bool show_added_binaries;
206 bool fail_if_no_debug_info;
207 bool show_identical_binaries;
208 bool leverage_dwarf_factorization;
209 bool assume_odr_for_cplusplus;
210 bool self_check;
211 optional<bool> exported_interfaces_only;
212 #ifdef WITH_CTF
213 bool use_ctf;
214 #endif
215
216 vector<string> kabi_whitelist_packages;
217 vector<string> suppression_paths;
218 vector<string> kabi_whitelist_paths;
219 suppressions_type kabi_suppressions;
220 package_sptr pkg1;
221 package_sptr pkg2;
222
options(const string & program_name)223 options(const string& program_name)
224 : prog_name(program_name),
225 display_usage(),
226 display_version(),
227 missing_operand(),
228 nonexistent_file(),
229 abignore(true),
230 parallel(true),
231 verbose(),
232 drop_private_types(),
233 show_relative_offset_changes(true),
234 no_default_suppression(),
235 keep_tmp_files(),
236 compare_dso_only(),
237 compare_private_dsos(),
238 leaf_changes_only(),
239 show_all_types(),
240 show_hexadecimal_values(),
241 show_offsets_sizes_in_bits(true),
242 show_impacted_interfaces(),
243 show_full_impact_report(),
244 show_linkage_names(true),
245 show_redundant_changes(),
246 show_harmless_changes(),
247 show_locs(true),
248 show_added_syms(true),
249 show_symbols_not_referenced_by_debug_info(true),
250 show_added_binaries(true),
251 fail_if_no_debug_info(),
252 show_identical_binaries(),
253 leverage_dwarf_factorization(true),
254 assume_odr_for_cplusplus(true),
255 self_check()
256 #ifdef WITH_CTF
257 ,
258 use_ctf()
259 #endif
260 {
261 // set num_workers to the default number of threads of the
262 // underlying maching. This is the default value for the number
263 // of workers to use in workers queues throughout the code.
264 num_workers = abigail::workers::get_number_of_threads();
265 }
266 };
267
268 static bool
269 get_interesting_files_under_dir(const string dir,
270 const string& file_name_to_look_for,
271 options& opts,
272 vector<string>& interesting_files);
273
274 /// Abstract ELF files from the packages which ABIs ought to be
275 /// compared
276 class elf_file
277 {
278 private:
279 elf_file();
280
281 public:
282 string path;
283 string name;
284 string soname;
285 off_t size;
286 abigail::elf::elf_type type;
287
288 /// The path to the elf file.
289 ///
290 /// @param path the path to the elf file.
elf_file(const string & path)291 elf_file(const string& path)
292 : path(path)
293 {
294 abigail::tools_utils::base_name(path, name);
295 get_soname_of_elf_file(path, soname);
296 get_type_of_elf_file(path, type);
297 struct stat estat;
298 stat(path.c_str(), &estat);
299 size = estat.st_size;
300 }
301 };
302
303 /// A convenience typedef for a shared pointer to elf_file.
304 typedef shared_ptr<elf_file> elf_file_sptr;
305
306 /// Abstract the result of comparing two packages.
307 ///
308 /// This contains the the paths of the set of added binaries, removed
309 /// binaries, and binaries whic ABI changed.
310 struct abi_diff
311 {
312 vector<elf_file_sptr> added_binaries;
313 vector<elf_file_sptr> removed_binaries;
314 vector<string> changed_binaries;
315
316 /// Test if the current diff carries changes.
317 ///
318 /// @return true iff the current diff carries changes.
319 bool
has_changesabi_diff320 has_changes()
321 {
322 return (!added_binaries.empty()
323 || !removed_binaries.empty()
324 ||!changed_binaries.empty());
325 }
326 };
327
328 /// Abstracts a package.
329 class package
330 {
331 public:
332
333 /// The kind of package we are looking at.
334 enum kind
335 {
336 /// Main package. Contains binaries to ABI-compare.
337 KIND_MAIN = 0,
338 /// Devel package. Contains public headers files in which public
339 /// types are defined.
340 KIND_DEVEL,
341 /// Debug info package. Contains the debug info for the binaries
342 /// int he main packge.
343 KIND_DEBUG_INFO,
344 /// Contains kernel ABI whitelists
345 KIND_KABI_WHITELISTS,
346 /// Source package. Contains the source of the binaries in the
347 /// main package.
348 KIND_SRC
349 };
350
351 private:
352 string path_;
353 string extracted_dir_path_;
354 string common_paths_prefix_;
355 abigail::tools_utils::file_type type_;
356 kind kind_;
357 map<string, elf_file_sptr> path_elf_file_sptr_map_;
358 vector<package_sptr> debug_info_packages_;
359 package_sptr devel_package_;
360 package_sptr kabi_whitelist_package_;
361 vector<string> elf_file_paths_;
362 set<string> public_dso_sonames_;
363
364 public:
365 /// Constructor for the @ref package type.
366 ///
367 /// @param path the path to the package.
368 ///
369 /// @parm dir the temporary directory where to extract the content
370 /// of the package.
371 ///
372 /// @param pkg_kind the kind of package.
package(const string & path,const string & dir,kind pkg_kind=package::KIND_MAIN)373 package(const string& path,
374 const string& dir,
375 kind pkg_kind = package::KIND_MAIN)
376 : path_(path),
377 kind_(pkg_kind)
378 {
379 type_ = guess_file_type(path);
380 if (type_ == abigail::tools_utils::FILE_TYPE_DIR)
381 extracted_dir_path_ = path;
382 else
383 extracted_dir_path_ = extracted_packages_parent_dir() + "/" + dir;
384 }
385
386 /// Getter of the path of the package.
387 ///
388 /// @return the path of the package.
389 const string&
path() const390 path() const
391 {return path_;}
392
393 /// Setter of the path of the package.
394 ///
395 /// @param s the new path.
396 void
path(const string & s)397 path(const string& s)
398 {path_ = s;}
399
400 /// Getter of the base name of the package.
401 ///
402 /// @return the base name of the package.
403 string
base_name() const404 base_name() const
405 {
406 string name;
407 abigail::tools_utils::base_name(path(), name);
408 return name;
409 }
410
411 /// Getter for the path to the root dir where the packages are
412 /// extracted.
413 ///
414 /// @return the path to the root dir where the packages are
415 /// extracted.
416 static const string&
417 extracted_packages_parent_dir();
418
419 /// Getter for the path to the directory where the packages are
420 /// extracted for the current thread.
421 ///
422 /// @return the path to the directory where the packages are
423 /// extracted for the current thread.
424 const string&
extracted_dir_path() const425 extracted_dir_path() const
426 {return extracted_dir_path_;}
427
428 /// Setter for the path to the directory where the packages are
429 /// extracted for the current thread.
430 ///
431 /// @param p the new path.
432 void
extracted_dir_path(const string & p)433 extracted_dir_path(const string& p)
434 {extracted_dir_path_ = p;}
435
436 /// Getter of the the prefix that is common to all the paths of all
437 /// the elements of the package.
438 ///
439 /// @return the common path prefix of package elements.
440 const string&
common_paths_prefix() const441 common_paths_prefix() const
442 {return common_paths_prefix_;}
443
444 /// Getter of the the prefix that is common to all the paths of all
445 /// the elements of the package.
446 ///
447 /// @return the common path prefix of package elements.
448 string&
common_paths_prefix()449 common_paths_prefix()
450 {return common_paths_prefix_;}
451
452 /// Setter of the the prefix that is common to all the paths of all
453 /// the elements of the package.
454 ///
455 ///
456 ///@param p the new prefix.
457 void
common_paths_prefix(const string & p)458 common_paths_prefix(const string& p)
459 {common_paths_prefix_ = p;}
460
461 /// Getter for the file type of the current package.
462 ///
463 /// @return the file type of the current package.
464 abigail::tools_utils::file_type
type() const465 type() const
466 {return type_;}
467
468 /// Setter for the file type of the current package.
469 ///
470 /// @param t the new file type.
type(abigail::tools_utils::file_type t)471 void type(abigail::tools_utils::file_type t)
472 {type_ = t;}
473
474 /// Get the package kind
475 ///
476 /// @return the package kind
477 kind
get_kind() const478 get_kind() const
479 {return kind_;}
480
481 /// Set the package kind
482 ///
483 /// @param k the package kind.
484 void
set_kind(kind k)485 set_kind(kind k)
486 {kind_ = k;}
487
488 /// Getter for the path <-> elf_file map.
489 ///
490 /// @return the the path <-> elf_file map.
491 const map<string, elf_file_sptr>&
path_elf_file_sptr_map() const492 path_elf_file_sptr_map() const
493 {return path_elf_file_sptr_map_;}
494
495 /// Getter for the path <-> elf_file map.
496 ///
497 /// @return the the path <-> elf_file map.
498 map<string, elf_file_sptr>&
path_elf_file_sptr_map()499 path_elf_file_sptr_map()
500 {return path_elf_file_sptr_map_;}
501
502 /// Getter for the debug info packages associated to the current
503 /// package.
504 ///
505 /// There can indeed be several debug info packages needed for one
506 /// input package, as the debug info for that input package can be
507 /// split across several debuginfo packages.
508 ///
509 /// @return the debug info packages associated to the current
510 /// package.
511 const vector<package_sptr>&
debug_info_packages() const512 debug_info_packages() const
513 {return debug_info_packages_;}
514
515 /// Getter for the debug info packages associated to the current
516 /// package.
517 ///
518 /// There can indeed be several debug info packages needed for one
519 /// input package, as the debug info for that input package can be
520 /// split across several debuginfo packages.
521 ///
522 /// @return the debug info packages associated to the current
523 /// package.
524 vector<package_sptr>&
debug_info_packages()525 debug_info_packages()
526 {return debug_info_packages_;}
527
528 /// Setter for the debug info packages associated to the current
529 /// package.
530 ///
531 /// There can indeed be several debug info packages needed for one
532 /// input package, as the debug info for that input package can be
533 /// split across several debuginfo packages.
534 ///
535 /// @param p the new debug info package.
536 void
debug_info_packages(const vector<package_sptr> & p)537 debug_info_packages(const vector<package_sptr> &p)
538 {debug_info_packages_ = p;}
539
540 /// Getter for the devel package associated to the current package.
541 ///
542 /// @return the devel package associated to the current package.
543 const package_sptr&
devel_package() const544 devel_package() const
545 {return devel_package_;}
546
547 /// Setter of the devel package associated to the current package.
548 ///
549 /// @param p the new devel package associated to the current package.
550 void
devel_package(const package_sptr & p)551 devel_package(const package_sptr& p)
552 {devel_package_ = p;}
553
554 /// Getter of the associated kernel abi whitelist package, if any.
555 ///
556 /// @return the associated kernel abi whitelist package.
557 const package_sptr
kabi_whitelist_package() const558 kabi_whitelist_package() const
559 {return kabi_whitelist_package_;}
560
561 /// Setter of the associated kernel abi whitelist package.
562 ///
563 /// @param p the new kernel abi whitelist package.
564 void
kabi_whitelist_package(const package_sptr & p)565 kabi_whitelist_package(const package_sptr& p)
566 {kabi_whitelist_package_ = p;}
567
568 /// Getter of the path to the elf files of the package.
569 ///
570 /// @return the path tothe elf files of the package.
571 const vector<string>&
elf_file_paths() const572 elf_file_paths() const
573 {return elf_file_paths_;}
574
575 /// Getter of the path to the elf files of the package.
576 ///
577 /// @return the path tothe elf files of the package.
578 vector<string>&
elf_file_paths()579 elf_file_paths()
580 {return elf_file_paths_;}
581
582 /// Getter of the SONAMEs of the public DSOs carried by this
583 /// package.
584 ///
585 /// This is relevant only if the --private-dso option was *NOT*
586 /// provided.
587 ///
588 /// @return the SONAMEs of the public DSOs carried by this package.
589 const set<string>&
public_dso_sonames() const590 public_dso_sonames() const
591 {return public_dso_sonames_;}
592
593 /// Getter of the SONAMEs of the public DSOs carried by this
594 /// package.
595 ///
596 /// This is relevant only if the --private-dso option was *NOT*
597 /// provided.
598 ///
599 /// @return the SONAMEs of the public DSOs carried by this package.
600 set<string>&
public_dso_sonames()601 public_dso_sonames()
602 {return public_dso_sonames_;}
603
604 /// Convert the absolute path of an element of this package into a
605 /// path relative to the root path pointing to this package.
606 ///
607 /// That is, suppose the content of a package named 'pkg' is located
608 /// at /root/path/pkg. Suppose an element of that package is named
609 /// is at '/root/path/pkg/somewhere/inside/element'.
610 ///
611 /// This function will return the path:
612 /// /pkg/somewhere/inside/element.
613 ///
614 /// @param path the path to consider.
615 ///
616 /// @param converted_path the resulting converted path. This is set
617 /// iff the function returns true.
618 ///
619 /// @return true if the path could be converted to being relative to
620 /// the extracted directory.
621 bool
convert_path_to_relative(const string & path,string & converted_path) const622 convert_path_to_relative(const string& path, string& converted_path) const
623 {
624 string root = extracted_dir_path_;
625 real_path(root, root);
626 string p = path;
627 real_path(p, p);
628 return string_suffix(p, root, converted_path);
629 }
630
631 // Convert the absolute path of an element of this package into a
632 // path relative to the prefix common to the paths of all elements
633 // of the package.
634 //
635 // @param path the path to conver.
636 //
637 // @param converted_path the resulting converted path. This is set
638 // iff the function returns true.
639 //
640 // @return true iff the function could successfully convert @p path
641 // and put the result into @p converted_path.
642 bool
convert_path_to_unique_suffix(const string & path,string & converted_path) const643 convert_path_to_unique_suffix(const string& path,
644 string& converted_path) const
645 {return string_suffix(path, common_paths_prefix(), converted_path);}
646
647 /// Retrieve the set of "interesting" package element paths by
648 /// walking the package.
649 ///
650 /// And then compute the path prefix that is common to all the
651 /// collected elements.
652 ///
653 /// @param the options of this application.
654 void
load_elf_file_paths(options & opts)655 load_elf_file_paths(options& opts)
656 {
657 if (!common_paths_prefix().empty()
658 || !elf_file_paths().empty())
659 // We have already loaded the elf file paths, don't do it again.
660 return;
661
662 get_interesting_files_under_dir(extracted_dir_path(),
663 /*file_name_to_look_for=*/"",
664 opts, elf_file_paths());
665 std::sort(elf_file_paths().begin(), elf_file_paths().end());
666 string common_prefix;
667 sorted_strings_common_prefix(elf_file_paths(), common_paths_prefix());
668 }
669
670 /// Create the path of an ABI file to be associated with a given
671 /// binary.
672 ///
673 /// @param elf_file_path the path to the binary to consider.
674 ///
675 /// @param abi_file_path the resulting ABI file path. This is set
676 /// iff the function return true.
677 ///
678 /// @return true if the ABI file path could be constructed and the
679 /// directory tree containing it could be created. In that case,
680 /// the resulting ABI file path is set to the @p abi_file_path
681 /// output parameter.
682 bool
create_abi_file_path(const string & elf_file_path,string & abi_file_path) const683 create_abi_file_path(const string &elf_file_path,
684 string &abi_file_path) const
685 {
686 string abi_path, dir, parent;
687 if (!convert_path_to_relative(elf_file_path, abi_path))
688 return false;
689 abi_path = extracted_dir_path() + "/abixml" + abi_path + ".abi";
690 if (!abigail::tools_utils::ensure_parent_dir_created(abi_path))
691 return false;
692 abi_file_path = abi_path;
693 return true;
694 }
695
696 /// Erase the content of the temporary extraction directory that has
697 /// been populated by the @ref extract_package() function;
698 ///
699 /// @param opts the options passed to the current program.
700 void
erase_extraction_directory(const options & opts) const701 erase_extraction_directory(const options &opts) const
702 {
703 if (type() == abigail::tools_utils::FILE_TYPE_DIR)
704 // If we are comparing two directories, do not erase the
705 // directory as it was provided by the user; it's not a
706 // temporary directory we created ourselves.
707 return;
708
709 if (opts.verbose)
710 emit_prefix("abipkgdiff", cerr)
711 << "Erasing temporary extraction directory "
712 << extracted_dir_path()
713 << " ...";
714
715 string cmd = "rm -rf " + extracted_dir_path();
716 if (system(cmd.c_str()))
717 {
718 if (opts.verbose)
719 emit_prefix("abipkgdiff", cerr) << " FAILED\n";
720 }
721 else
722 {
723 if (opts.verbose)
724 emit_prefix("abipkgdiff", cerr) << " DONE\n";
725 }
726 }
727
728 /// Erase the content of all the temporary extraction directories.
729 ///
730 /// @param opts the options passed to the current program.
731 void
erase_extraction_directories(const options & opts) const732 erase_extraction_directories(const options &opts) const
733 {
734 erase_extraction_directory(opts);
735 if (!debug_info_packages().empty())
736 debug_info_packages().front()->erase_extraction_directory(opts);
737 if (devel_package())
738 devel_package()->erase_extraction_directory(opts);
739 if (kabi_whitelist_package())
740 kabi_whitelist_package()->erase_extraction_directory(opts);
741 }
742 }; // end class package.
743
744 /// Arguments passed to the comparison tasks.
745 struct compare_args
746 {
747 const elf_file elf1;
748 const string& debug_dir1;
749 const suppressions_type private_types_suppr1;
750 const elf_file elf2;
751 const string& debug_dir2;
752 const suppressions_type private_types_suppr2;
753 const options& opts;
754
755 /// Constructor for compare_args, which is used to pass
756 /// information to the comparison threads.
757 ///
758 /// @param elf1 the first elf file to consider.
759 ///
760 /// @param debug_dir1 the directory where the debug info file for @p
761 /// elf1 is stored.
762 ///
763 /// @param elf2 the second elf file to consider.
764 ///
765 /// @param debug_dir2 the directory where the debug info file for @p
766 /// elf2 is stored.
767 ///
768 /// @param opts the options the current program has been called with.
compare_argscompare_args769 compare_args(const elf_file &elf1, const string& debug_dir1,
770 const suppressions_type& priv_types_suppr1,
771 const elf_file &elf2, const string& debug_dir2,
772 const suppressions_type& priv_types_suppr2,
773 const options& opts)
774 : elf1(elf1), debug_dir1(debug_dir1),
775 private_types_suppr1(priv_types_suppr1),
776 elf2(elf2), debug_dir2(debug_dir2),
777 private_types_suppr2(priv_types_suppr2),
778 opts(opts)
779 {}
780 }; // end struct compare_args
781
782 /// A convenience typedef for arguments passed to the comparison workers.
783 typedef shared_ptr<compare_args> compare_args_sptr;
784
785 static bool extract_package_and_map_its_content(const package_sptr &pkg,
786 options &opts);
787
788 /// Getter for the path to the parent directory under which packages
789 /// extracted by the current thread are placed.
790 ///
791 /// @return the path to the parent directory under which packages
792 /// extracted by the current thread are placed.
793 const string&
extracted_packages_parent_dir()794 package::extracted_packages_parent_dir()
795 {
796 // I tried to declare this in thread-local storage, but GCC 4.4.7
797 // won't let me. So for now, I am just making it static. I'll deal
798 // with this later when I have to.
799
800 //static __thread string p;
801 static string p;
802
803 if (p.empty())
804 {
805 const char *cachedir = getenv("XDG_CACHE_HOME");
806
807 if (cachedir != NULL)
808 p = cachedir;
809 else
810 {
811 const char* s = getenv("HOME");
812 if (s != NULL)
813 p = s;
814 if (p.empty())
815 {
816 s = getenv("TMPDIR");
817 if (s != NULL)
818 p = s;
819 else
820 p = "/tmp";
821 }
822 p += "/.cache/libabigail";
823 }
824
825 // Create the cache directory if it doesn't exist
826 ABG_ASSERT(ensure_dir_path_created(p));
827
828 string libabigail_tmp_dir_template = p;
829 libabigail_tmp_dir_template += "/abipkgdiff-tmp-dir-XXXXXX";
830
831 if (!mkdtemp(const_cast<char*>(libabigail_tmp_dir_template.c_str())))
832 abort();
833
834 p = libabigail_tmp_dir_template;
835 }
836
837 return p;
838 }
839
840 /// A convenience typedef for shared_ptr of package.
841 typedef shared_ptr<package> package_sptr;
842
843 /// Show the usage of this program.
844 ///
845 /// @param prog_name the name of the program.
846 ///
847 /// @param out the output stream to emit the usage to .
848 static void
display_usage(const string & prog_name,ostream & out)849 display_usage(const string& prog_name, ostream& out)
850 {
851 emit_prefix(prog_name, out)
852 << "usage: " << prog_name << " [options] <package1> <package2>\n"
853 << " where options can be:\n"
854 << " --debug-info-pkg1|--d1 <path> path of debug-info package of package1\n"
855 << " --debug-info-pkg2|--d2 <path> path of debug-info package of package2\n"
856 << " --devel-pkg1|--devel1 <path> path of devel package of pakage1\n"
857 << " --devel-pkg2|--devel2 <path> path of devel package of pakage1\n"
858 << " --drop-private-types drop private types from "
859 "internal representation\n"
860 << " --no-default-suppression don't load any default "
861 "suppression specifications\n"
862 << " --suppressions|--suppr <path> specify supression specification path\n"
863 << " --linux-kernel-abi-whitelist|-w path to a "
864 "linux kernel abi whitelist\n"
865 << " --wp <path> path to a linux kernel abi whitelist package\n"
866 << " --keep-tmp-files don't erase created temporary files\n"
867 << " --dso-only compare shared libraries only\n"
868 << " --private-dso compare DSOs that are private "
869 "to the package as well\n"
870 << " --leaf-changes-only|-l only show leaf changes, "
871 "so no change impact analysis (implies --redundant)\n"
872 << " --impacted-interfaces|-i display interfaces impacted by leaf changes\n"
873 << " --full-impact|-f when comparing kernel packages, show the "
874 "full impact analysis report rather than the default leaf changes reports\n"
875 << " --non-reachable-types|-t consider types non reachable"
876 " from public interfaces\n"
877 << " --exported-interfaces-only analyze exported interfaces only\n"
878 << " --allow-non-exported-interfaces analyze interfaces that "
879 "might not be exported\n"
880 << " --no-linkage-name do not display linkage names of "
881 "added/removed/changed\n"
882 << " --redundant display redundant changes\n"
883 << " --harmless display the harmless changes\n"
884 << " --no-show-locs do not show location information\n"
885 << " --show-bytes show size and offsets in bytes\n"
886 << " --show-bits show size and offsets in bits\n"
887 << " --show-hex show size and offset in hexadecimal\n"
888 << " --show-dec show size and offset in decimal\n"
889 << " --no-show-relative-offset-changes do not show relative"
890 " offset changes\n"
891 << " --no-added-syms do not display added functions or variables\n"
892 << " --no-unreferenced-symbols do not display changes "
893 "about symbols not referenced by debug info\n"
894 << " --no-added-binaries do not display added binaries\n"
895 << " --no-abignore do not look for *.abignore files\n"
896 << " --no-parallel do not execute in parallel\n"
897 << " --fail-no-dbg fail if no debug info was found\n"
898 << " --show-identical-binaries show the names of identical binaries\n"
899 << " --no-leverage-dwarf-factorization do not use DWZ optimisations to "
900 "speed-up the analysis of the binary\n"
901 << " --no-assume-odr-for-cplusplus do not assume the ODR to speed-up the"
902 "analysis of the binary\n"
903 << " --verbose emit verbose progress messages\n"
904 << " --self-check perform a sanity check by comparing "
905 "binaries inside the input package against their ABIXML representation\n"
906 #ifdef WITH_CTF
907 << " --ctf use CTF instead of DWARF in ELF files\n"
908 #endif
909 << " --help|-h display this help message\n"
910 << " --version|-v display program version information"
911 " and exit\n";
912 }
913
914 #ifdef WITH_RPM
915
916 /// Extract an RPM package.
917 ///
918 /// @param package_path the path to the package to extract.
919 ///
920 /// @param extracted_package_dir_path the path where to extract the
921 /// package to.
922 ///
923 /// @param opts the options passed to the current program.
924 ///
925 /// @return true upon successful completion, false otherwise.
926 static bool
extract_rpm(const string & package_path,const string & extracted_package_dir_path,const options & opts)927 extract_rpm(const string& package_path,
928 const string& extracted_package_dir_path,
929 const options &opts)
930 {
931 if (opts.verbose)
932 emit_prefix("abipkgdiff", cerr)
933 << "Extracting package "
934 << package_path
935 << " to "
936 << extracted_package_dir_path
937 << " ...";
938
939 string cmd = "test -d " + extracted_package_dir_path
940 + " || mkdir -p " + extracted_package_dir_path + " ; cd " +
941 extracted_package_dir_path + " && rpm2cpio " + package_path +
942 " | cpio -dium --quiet";
943
944 if (system(cmd.c_str()))
945 {
946 if (opts.verbose)
947 emit_prefix("abipkgdiff", cerr) << " FAILED\n";
948 return false;
949 }
950
951 if (opts.verbose)
952 emit_prefix("abipkgdiff", cerr) << " DONE\n";
953
954 return true;
955 }
956
957 #endif // WITH_RPM
958
959 #ifdef WITH_DEB
960
961 /// Extract a Debian binary package.
962 ///
963 /// @param package_path the path to the package to extract.
964 ///
965 /// @param extracted_package_dir_path the path where to extract the
966 /// package to.
967 ///
968 /// @param opts the options passed to the current program.
969 ///
970 /// @return true upon successful completion, false otherwise.
971 static bool
extract_deb(const string & package_path,const string & extracted_package_dir_path,const options & opts)972 extract_deb(const string& package_path,
973 const string& extracted_package_dir_path,
974 const options &opts)
975 {
976 if (opts.verbose)
977 emit_prefix("abipkgdiff", cerr)
978 << "Extracting package "
979 << package_path
980 << " to "
981 << extracted_package_dir_path
982 << " ...\n";
983
984 string cmd = "mkdir -p " + extracted_package_dir_path + " && dpkg -x " +
985 package_path + " " + extracted_package_dir_path;
986
987 if (system(cmd.c_str()))
988 {
989 if (opts.verbose)
990 emit_prefix("abipkgdiff", cerr) << " FAILED\n";
991 return false;
992 }
993
994 if (opts.verbose)
995 emit_prefix("abipkgdiff", cerr) << " DONE\n";
996
997 return true;
998 }
999
1000 #endif // WITH_DEB
1001
1002 #ifdef WITH_TAR
1003
1004 /// Extract a GNU Tar archive.
1005 ///
1006 /// @param package_path the path to the archive to extract.
1007 ///
1008 /// @param extracted_package_dir_path the path where to extract the
1009 /// archive to.
1010 ///
1011 /// @param opts the options passed to the current program.
1012 ///
1013 /// @return true upon successful completion, false otherwise.
1014 static bool
extract_tar(const string & package_path,const string & extracted_package_dir_path,const options & opts)1015 extract_tar(const string& package_path,
1016 const string& extracted_package_dir_path,
1017 const options &opts)
1018 {
1019 if (opts.verbose)
1020 emit_prefix("abipkgdiff", cerr)
1021 << "Extracting tar archive "
1022 << package_path
1023 << " to "
1024 << extracted_package_dir_path
1025 << " ...";
1026
1027 string cmd = "test -d " +
1028 extracted_package_dir_path +
1029 " && rm -rf " + extracted_package_dir_path;
1030
1031 if (system(cmd.c_str()))
1032 {
1033 if (opts.verbose)
1034 emit_prefix("abipkgdiff", cerr) << "command " << cmd << " FAILED\n";
1035 }
1036
1037 cmd = "mkdir -p " + extracted_package_dir_path + " && cd " +
1038 extracted_package_dir_path + " && tar -xf " + package_path;
1039
1040 if (system(cmd.c_str()))
1041 {
1042 if (opts.verbose)
1043 emit_prefix("abipkgdiff", cerr) << " FAILED\n";
1044 return false;
1045 }
1046
1047 if (opts.verbose)
1048 emit_prefix("abipkgdiff", cerr) << " DONE\n";
1049
1050 return true;
1051 }
1052
1053 #endif // WITH_TAR
1054
1055 /// Erase the temporary directories created for the extraction of two
1056 /// packages.
1057 ///
1058 /// @param first_package the first package to consider.
1059 ///
1060 /// @param opts the options passed to the current program.
1061 ///
1062 /// @param second_package the second package to consider.
1063 static void
erase_created_temporary_directories(const package & first_package,const package & second_package,const options & opts)1064 erase_created_temporary_directories(const package& first_package,
1065 const package& second_package,
1066 const options &opts)
1067 {
1068 first_package.erase_extraction_directories(opts);
1069 second_package.erase_extraction_directories(opts);
1070 }
1071
1072 /// Erase the root of all the temporary directories created by the
1073 /// current thread.
1074 static void
erase_created_temporary_directories_parent(const options & opts)1075 erase_created_temporary_directories_parent(const options &opts)
1076 {
1077 if (opts.verbose)
1078 emit_prefix("abipkgdiff", cerr)
1079 << "Erasing temporary extraction parent directory "
1080 << package::extracted_packages_parent_dir()
1081 << " ...";
1082
1083 string cmd = "rm -rf " + package::extracted_packages_parent_dir();
1084 if (system(cmd.c_str()))
1085 {
1086 if (opts.verbose)
1087 emit_prefix("abipkgdiff", cerr) << "FAILED\n";
1088 }
1089 else
1090 {
1091 if (opts.verbose)
1092 emit_prefix("abipkgdiff", cerr) << "DONE\n";
1093 }
1094 }
1095
1096 /// Extract the content of a package.
1097 ///
1098 /// @param package the package we are looking at.
1099 ///
1100 /// @param opts the options passed to the current program.
1101 static bool
extract_package(const package & package,const options & opts)1102 extract_package(const package& package,
1103 const options &opts)
1104 {
1105 switch(package.type())
1106 {
1107 case abigail::tools_utils::FILE_TYPE_RPM:
1108 #ifdef WITH_RPM
1109 if (!extract_rpm(package.path(), package.extracted_dir_path(), opts))
1110 {
1111 emit_prefix("abipkgdiff", cerr)
1112 << "Error while extracting package " << package.path() << "\n";
1113 return false;
1114 }
1115 return true;
1116 #else
1117 emit_prefix("abipkgdiff", cerr)
1118 << "Support for rpm hasn't been enabled. Please consider "
1119 "enabling it at package configure time\n";
1120 return false;
1121 #endif // WITH_RPM
1122 break;
1123 case abigail::tools_utils::FILE_TYPE_DEB:
1124 #ifdef WITH_DEB
1125 if (!extract_deb(package.path(), package.extracted_dir_path(), opts))
1126 {
1127 emit_prefix("abipkgdiff", cerr)
1128 << "Error while extracting package" << package.path() << "\n";
1129 return false;
1130 }
1131 return true;
1132 #else
1133 emit_prefix("abipkgdiff", cerr)
1134 << "Support for deb hasn't been enabled. Please consider "
1135 "enabling it at package configure time\n";
1136 return false;
1137 #endif // WITH_DEB
1138 break;
1139
1140 case abigail::tools_utils::FILE_TYPE_DIR:
1141 // The input package is just a directory that contains binaries,
1142 // there is nothing to extract.
1143 break;
1144
1145 case abigail::tools_utils::FILE_TYPE_TAR:
1146 #ifdef WITH_TAR
1147 if (!extract_tar(package.path(), package.extracted_dir_path(), opts))
1148 {
1149 emit_prefix("abipkgdiff", cerr)
1150 << "Error while extracting GNU tar archive "
1151 << package.path() << "\n";
1152 return false;
1153 }
1154 return true;
1155 #else
1156 emit_prefix("abipkgdiff", cerr)
1157 << "Support for GNU tar hasn't been enabled. Please consider "
1158 "enabling it at package configure time\n";
1159 return false;
1160 #endif // WITH_TAR
1161 break;
1162
1163 default:
1164 return false;
1165 }
1166 return true;
1167 }
1168
1169 /// Check that the suppression specification files supplied are
1170 /// present. If not, emit an error on stderr.
1171 ///
1172 /// @param opts the options instance to use.
1173 ///
1174 /// @return true if all suppression specification files are present,
1175 /// false otherwise.
1176 static bool
maybe_check_suppression_files(const options & opts)1177 maybe_check_suppression_files(const options& opts)
1178 {
1179 for (vector<string>::const_iterator i = opts.suppression_paths.begin();
1180 i != opts.suppression_paths.end();
1181 ++i)
1182 if (!check_file(*i, cerr, opts.prog_name))
1183 return false;
1184
1185 for (vector<string>::const_iterator i =
1186 opts.kabi_whitelist_paths.begin();
1187 i != opts.kabi_whitelist_paths.end();
1188 ++i)
1189 if (!check_file(*i, cerr, "abidiff"))
1190 return false;
1191
1192 return true;
1193 }
1194
1195 /// Update the diff context from the @ref options data structure.
1196 ///
1197 /// @param ctxt the diff context to update.
1198 ///
1199 /// @param opts the instance of @ref options to consider.
1200 static void
set_diff_context_from_opts(diff_context_sptr ctxt,const options & opts)1201 set_diff_context_from_opts(diff_context_sptr ctxt,
1202 const options& opts)
1203 {
1204 ctxt->default_output_stream(&cout);
1205 ctxt->error_output_stream(&cerr);
1206 // See comment in abidiff.cc's set_diff_context_from_opts.
1207 ctxt->show_redundant_changes(opts.show_redundant_changes
1208 || opts.leaf_changes_only);
1209 ctxt->show_leaf_changes_only(opts.leaf_changes_only);
1210 ctxt->show_impacted_interfaces(opts.show_impacted_interfaces);
1211 ctxt->show_unreachable_types(opts.show_all_types);
1212 ctxt->show_hex_values(opts.show_hexadecimal_values);
1213 ctxt->show_offsets_sizes_in_bits(opts.show_offsets_sizes_in_bits);
1214 ctxt->show_relative_offset_changes(opts.show_relative_offset_changes);
1215 ctxt->show_locs(opts.show_locs);
1216 ctxt->show_linkage_names(opts.show_linkage_names);
1217 ctxt->show_added_fns(opts.show_added_syms);
1218 ctxt->show_added_vars(opts.show_added_syms);
1219 ctxt->show_added_symbols_unreferenced_by_debug_info
1220 (opts.show_added_syms);
1221 ctxt->show_symbols_unreferenced_by_debug_info
1222 (opts.show_symbols_not_referenced_by_debug_info);
1223
1224 if (!opts.show_harmless_changes)
1225 ctxt->switch_categories_off(get_default_harmless_categories_bitmap());
1226
1227 suppressions_type supprs;
1228 for (vector<string>::const_iterator i = opts.suppression_paths.begin();
1229 i != opts.suppression_paths.end();
1230 ++i)
1231 read_suppressions(*i, supprs);
1232 ctxt->add_suppressions(supprs);
1233 }
1234
1235 /// Set a bunch of tunable buttons on the ELF-based reader from the
1236 /// command-line options.
1237 ///
1238 /// @param rdr the reader to tune.
1239 ///
1240 /// @param opts the command line options.
1241 static void
set_generic_options(abigail::elf_based_reader & rdr,const options & opts)1242 set_generic_options(abigail::elf_based_reader& rdr, const options& opts)
1243 {
1244 if (!opts.kabi_suppressions.empty())
1245 rdr.add_suppressions(opts.kabi_suppressions);
1246
1247 rdr.options().leverage_dwarf_factorization =
1248 opts.leverage_dwarf_factorization;
1249 rdr.options().assume_odr_for_cplusplus =
1250 opts.assume_odr_for_cplusplus;
1251 }
1252
1253 /// Compare the ABI two elf files, using their associated debug info.
1254 ///
1255 /// The result of the comparison is emitted to standard output.
1256 ///
1257 /// @param elf1 the first elf file to consider.
1258 ///
1259 /// @param debug_dir1 the directory where the debug info file for @p
1260 /// elf1 is stored.
1261 /// The result of the comparison is saved to a global corpus map.
1262 ///
1263 /// @param elf2 the second eld file to consider.
1264 /// @args the list of argument sets used for comparison
1265 ///
1266 /// @param debug_dir2 the directory where the debug info file for @p
1267 /// elf2 is stored.
1268 ///
1269 /// @param opts the options the current program has been called with.
1270 ///
1271 /// @param env the environment encapsulating the entire comparison.
1272 ///
1273 /// @param diff the shared pointer to be set to the result of the comparison.
1274 ///
1275 /// @param detailed_error_status is this pointer is non-null and if
1276 /// the function returns ABIDIFF_ERROR, then the function sets the
1277 /// pointed-to parameter to the abigail::fe_iface::status value
1278 /// that gives details about the rror.
1279 ///
1280 /// @return the status of the comparison.
1281 static abidiff_status
compare(const elf_file & elf1,const string & debug_dir1,const suppressions_type & priv_types_supprs1,const elf_file & elf2,const string & debug_dir2,const suppressions_type & priv_types_supprs2,const options & opts,abigail::ir::environment & env,corpus_diff_sptr & diff,diff_context_sptr & ctxt,abigail::fe_iface::status * detailed_error_status=0)1282 compare(const elf_file& elf1,
1283 const string& debug_dir1,
1284 const suppressions_type& priv_types_supprs1,
1285 const elf_file& elf2,
1286 const string& debug_dir2,
1287 const suppressions_type& priv_types_supprs2,
1288 const options& opts,
1289 abigail::ir::environment& env,
1290 corpus_diff_sptr& diff,
1291 diff_context_sptr& ctxt,
1292 abigail::fe_iface::status* detailed_error_status = 0)
1293 {
1294 char *di_dir1 = (char*) debug_dir1.c_str(),
1295 *di_dir2 = (char*) debug_dir2.c_str();
1296
1297 vector<char**> di_dirs1, di_dirs2;
1298 di_dirs1.push_back(&di_dir1);
1299 di_dirs2.push_back(&di_dir2);
1300
1301 if (opts.verbose)
1302 emit_prefix("abipkgdiff", cerr)
1303 << "Comparing the ABIs of file "
1304 << elf1.path
1305 << " and "
1306 << elf2.path
1307 << "...\n";
1308
1309 abigail::fe_iface::status c1_status = abigail::fe_iface::STATUS_OK,
1310 c2_status = abigail::fe_iface::STATUS_OK;
1311
1312 ctxt.reset(new diff_context);
1313 set_diff_context_from_opts(ctxt, opts);
1314 suppressions_type& supprs = ctxt->suppressions();
1315 bool files_suppressed = (file_is_suppressed(elf1.path, supprs)
1316 ||file_is_suppressed(elf2.path, supprs));
1317
1318 if (files_suppressed)
1319 {
1320 if (opts.verbose)
1321 emit_prefix("abipkgdiff", cerr)
1322 << " input file "
1323 << elf1.path << " or " << elf2.path
1324 << " has been suppressed by a suppression specification.\n"
1325 << " Not reading any of them\n";
1326 return abigail::tools_utils::ABIDIFF_OK;
1327 }
1328
1329 // Add the first private type suppressions set to the set of
1330 // suppressions.
1331 for (suppressions_type::const_iterator i = priv_types_supprs1.begin();
1332 i != priv_types_supprs1.end();
1333 ++i)
1334 supprs.push_back(*i);
1335
1336 // Add the second private type suppressions set to the set of
1337 // suppressions.
1338 for (suppressions_type::const_iterator i = priv_types_supprs2.begin();
1339 i != priv_types_supprs2.end();
1340 ++i)
1341 supprs.push_back(*i);
1342
1343 if (opts.verbose)
1344 emit_prefix("abipkgdiff", cerr)
1345 << "Reading file "
1346 << elf1.path
1347 << " ...\n";
1348
1349 abigail::elf_based_reader_sptr reader;
1350 corpus_sptr corpus1;
1351 {
1352 corpus::origin requested_fe_kind = corpus::DWARF_ORIGIN;
1353 #ifdef WITH_CTF
1354 if (opts.use_ctf)
1355 requested_fe_kind = corpus::CTF_ORIGIN;
1356 #endif
1357 abigail::elf_based_reader_sptr reader =
1358 create_best_elf_based_reader(elf1.path,
1359 di_dirs1,
1360 env, requested_fe_kind,
1361 opts.show_all_types);
1362 ABG_ASSERT(reader);
1363
1364 reader->add_suppressions(priv_types_supprs1);
1365 set_generic_options(*reader, opts);
1366
1367 corpus1 = reader->read_corpus(c1_status);
1368
1369 bool bail_out = false;
1370 if (!(c1_status & abigail::fe_iface::STATUS_OK))
1371 {
1372 if (opts.verbose)
1373 emit_prefix("abipkgdiff", cerr)
1374 << "Could not read file '"
1375 << elf1.path
1376 << "' properly\n";
1377
1378 if (detailed_error_status)
1379 *detailed_error_status = c1_status;
1380
1381 bail_out = true;
1382 }
1383
1384 if (opts.fail_if_no_debug_info)
1385 {
1386 bool debug_info_error = false;
1387 if (c1_status & abigail::fe_iface::STATUS_DEBUG_INFO_NOT_FOUND)
1388 {
1389 if (opts.verbose)
1390 emit_prefix("abipkgdiff", cerr)
1391 << "while reading file" << elf1.path << "\n";
1392
1393 emit_prefix("abipkgdiff", cerr) << "Could not find debug info file";
1394 if (di_dir1 && strcmp(di_dir1, ""))
1395 cerr << " under " << di_dir1 << "\n";
1396 else
1397 cerr << "\n";
1398
1399 if (detailed_error_status)
1400 *detailed_error_status = c1_status;
1401 debug_info_error = true;
1402 }
1403
1404 if (c1_status & abigail::fe_iface::STATUS_ALT_DEBUG_INFO_NOT_FOUND)
1405 {
1406 if (opts.verbose)
1407 emit_prefix("abipkgdiff", cerr)
1408 << "while reading file" << elf1.path << "\n";
1409
1410 emit_prefix("abipkgdiff", cerr)
1411 << "Could not find alternate debug info file";
1412 string alt_di_path;
1413 #ifdef WITH_CTF
1414 if (opts.use_ctf)
1415 ;
1416 else
1417 #endif
1418 reader->refers_to_alt_debug_info(alt_di_path);
1419 if (!alt_di_path.empty())
1420 cerr << ": " << alt_di_path << "\n";
1421 else
1422 cerr << "\n";
1423
1424 if (detailed_error_status)
1425 *detailed_error_status = c1_status;
1426 debug_info_error = true;
1427 }
1428
1429 if (debug_info_error)
1430 bail_out = true;
1431 }
1432
1433 if (bail_out)
1434 return abigail::tools_utils::ABIDIFF_ERROR;
1435 }
1436
1437 if (opts.verbose)
1438 emit_prefix("abipkgdiff", cerr)
1439 << "DONE reading file "
1440 << elf1.path
1441 << "\n";
1442
1443 if (opts.verbose)
1444 emit_prefix("abipkgdiff", cerr)
1445 << "Reading file "
1446 << elf2.path
1447 << " ...\n";
1448
1449 corpus_sptr corpus2;
1450 {
1451 corpus::origin requested_fe_kind = corpus::DWARF_ORIGIN;
1452 #ifdef WITH_CTF
1453 if (opts.use_ctf)
1454 requested_fe_kind = corpus::CTF_ORIGIN;
1455 #endif
1456 abigail::elf_based_reader_sptr reader =
1457 create_best_elf_based_reader(elf2.path,
1458 di_dirs2,
1459 env, requested_fe_kind,
1460 opts.show_all_types);
1461 ABG_ASSERT(reader);
1462
1463 reader->add_suppressions(priv_types_supprs2);
1464 set_generic_options(*reader, opts);
1465
1466 corpus2 = reader->read_corpus(c2_status);
1467
1468 bool bail_out = false;
1469 if (!(c2_status & abigail::fe_iface::STATUS_OK))
1470 {
1471 if (opts.verbose)
1472 emit_prefix("abipkgdiff", cerr)
1473 << "Could not find the read file '"
1474 << elf2.path
1475 << "' properly\n";
1476
1477 if (detailed_error_status)
1478 *detailed_error_status = c2_status;
1479
1480 bail_out = true;
1481 }
1482
1483 if (opts.fail_if_no_debug_info)
1484 {
1485 bool debug_info_error = false;
1486 if (c2_status & abigail::fe_iface::STATUS_DEBUG_INFO_NOT_FOUND)
1487 {
1488 if (opts.verbose)
1489 emit_prefix("abipkgdiff", cerr)
1490 << "while reading file" << elf2.path << "\n";
1491
1492 emit_prefix("abipkgdiff", cerr) << "Could not find debug info file";
1493 if (di_dir2 && strcmp(di_dir2, ""))
1494 cerr << " under " << di_dir2 << "\n";
1495 else
1496 cerr << "\n";
1497
1498 if (detailed_error_status)
1499 *detailed_error_status = c2_status;
1500 debug_info_error = true;
1501 }
1502
1503 if (c2_status & abigail::fe_iface::STATUS_ALT_DEBUG_INFO_NOT_FOUND)
1504 {
1505 if (opts.verbose)
1506 emit_prefix("abipkgdiff", cerr)
1507 << "while reading file" << elf2.path << "\n";
1508
1509 emit_prefix("abipkgdiff", cerr)
1510 << "Could not find alternate debug info file";
1511 string alt_di_path;
1512 #ifdef WITH_CTF
1513 if (opts.use_ctf)
1514 ;
1515 else
1516 #endif
1517 reader->refers_to_alt_debug_info(alt_di_path);
1518 if (!alt_di_path.empty())
1519 cerr << ": " << alt_di_path << "\n";
1520 else
1521 cerr << "\n";
1522
1523 if (detailed_error_status)
1524 *detailed_error_status = c2_status;
1525 debug_info_error = true;
1526 }
1527
1528 if (debug_info_error)
1529 bail_out = true;
1530 }
1531
1532 if (bail_out)
1533 return abigail::tools_utils::ABIDIFF_ERROR;
1534 }
1535
1536 if (opts.verbose)
1537 emit_prefix("abipkgdiff", cerr)
1538 << " DONE reading file " << elf2.path << "\n";
1539
1540 if (opts.verbose)
1541 emit_prefix("abipkgdiff", cerr)
1542 << " Comparing the ABIs of: \n"
1543 << " " << elf1.path << "\n"
1544 << " " << elf2.path << "\n";
1545
1546 diff = compute_diff(corpus1, corpus2, ctxt);
1547
1548 if (opts.verbose)
1549 emit_prefix("abipkgdiff", cerr)
1550 << "Comparing the ABIs of file "
1551 << elf1.path
1552 << " and "
1553 << elf2.path
1554 << " is DONE\n";
1555
1556 abidiff_status s = abigail::tools_utils::ABIDIFF_OK;
1557 if (diff->has_net_changes())
1558 s |= abigail::tools_utils::ABIDIFF_ABI_CHANGE;
1559 if (diff->has_incompatible_changes())
1560 s |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE;
1561
1562 return s;
1563 }
1564
1565 /// Compare an ELF file to its ABIXML representation.
1566 ///
1567 /// @param elf the ELF file to compare.
1568 ///
1569 /// @param debug_dir the debug directory of the ELF file.
1570 ///
1571 /// @param opts the options passed the user.
1572 ///
1573 /// @param env the environment to use for the comparison.
1574 ///
1575 /// @param diff the diff object resulting from the comparison of @p
1576 /// elf against its ABIXML representation.
1577 ///
1578 /// @param ctxt the resulting diff context used for the comparison
1579 /// that yielded @p diff.
1580 ///
1581 /// @param detailed_error_status the detailed error satus returned by
1582 /// this function.
1583 ///
1584 /// @return the status of the self comparison.
1585 static abidiff_status
compare_to_self(const elf_file & elf,const string & debug_dir,const options & opts,abigail::ir::environment & env,corpus_diff_sptr & diff,diff_context_sptr & ctxt,abigail::fe_iface::status * detailed_error_status=0)1586 compare_to_self(const elf_file& elf,
1587 const string& debug_dir,
1588 const options& opts,
1589 abigail::ir::environment& env,
1590 corpus_diff_sptr& diff,
1591 diff_context_sptr& ctxt,
1592 abigail::fe_iface::status* detailed_error_status = 0)
1593 {
1594 char *di_dir = (char*) debug_dir.c_str();
1595
1596 vector<char**> di_dirs;
1597 di_dirs.push_back(&di_dir);
1598
1599 abigail::fe_iface::status c_status = abigail::fe_iface::STATUS_OK;
1600
1601 if (opts.verbose)
1602 emit_prefix("abipkgdiff", cerr)
1603 << "Comparing the ABI of file '"
1604 << elf.path
1605 << "' against itself ...\n";
1606
1607 if (opts.verbose)
1608 emit_prefix("abipkgdiff", cerr)
1609 << "Reading file "
1610 << elf.path
1611 << " ...\n";
1612
1613 corpus_sptr corp;
1614 abigail::elf_based_reader_sptr reader;
1615 {
1616 corpus::origin requested_fe_kind = corpus::DWARF_ORIGIN;
1617 #ifdef WITH_CTF
1618 if (opts.use_ctf)
1619 requested_fe_kind = corpus::CTF_ORIGIN;
1620 #endif
1621 abigail::elf_based_reader_sptr reader =
1622 create_best_elf_based_reader(elf.path,
1623 di_dirs,
1624 env, requested_fe_kind,
1625 opts.show_all_types);
1626 ABG_ASSERT(reader);
1627
1628 corp = reader->read_corpus(c_status);
1629
1630 if (!(c_status & abigail::fe_iface::STATUS_OK))
1631 {
1632 if (opts.verbose)
1633 emit_prefix("abipkgdiff", cerr)
1634 << "Could not read file '"
1635 << elf.path
1636 << "' propertly\n";
1637
1638 if (detailed_error_status)
1639 *detailed_error_status = c_status;
1640
1641 return abigail::tools_utils::ABIDIFF_ERROR;
1642 }
1643
1644 if (opts.verbose)
1645 emit_prefix("abipkgdiff", cerr)
1646 << "Read file '"
1647 << elf.path
1648 << "' OK\n";
1649
1650
1651 ABG_ASSERT(corp);
1652 }
1653
1654 corpus_sptr reread_corp;
1655 string abi_file_path;
1656 {
1657 if (!opts.pkg1->create_abi_file_path(elf.path, abi_file_path))
1658 {
1659 if (opts.verbose)
1660 emit_prefix("abipkgdiff", cerr)
1661 << "Could not create the directory tree to store the abi for '"
1662 << elf.path
1663 << "'\n";
1664
1665 return abigail::tools_utils::ABIDIFF_ERROR;
1666 }
1667 ofstream of(abi_file_path.c_str(), std::ios_base::trunc);
1668
1669 {
1670 const abigail::xml_writer::write_context_sptr c =
1671 abigail::xml_writer::create_write_context(env, of);
1672
1673 if (opts.verbose)
1674 emit_prefix("abipkgdiff", cerr)
1675 << "Writting ABIXML file '"
1676 << abi_file_path
1677 << "' ...\n";
1678
1679 if (!write_corpus(*c, corp, 0))
1680 {
1681 if (opts.verbose)
1682 emit_prefix("abipkgdiff", cerr)
1683 << "Could not write the ABIXML file to '"
1684 << abi_file_path << "'\n";
1685
1686 return abigail::tools_utils::ABIDIFF_ERROR;
1687 }
1688
1689 of.flush();
1690 of.close();
1691
1692 if (opts.verbose)
1693 emit_prefix("abipkgdiff", cerr)
1694 << "Wrote ABIXML file '"
1695 << abi_file_path
1696 << "' OK\n";
1697 }
1698
1699 {
1700 abigail::fe_iface_sptr rdr = abixml::create_reader(abi_file_path, env);
1701 if (!rdr)
1702 {
1703 if (opts.verbose)
1704 emit_prefix("abipkgdiff", cerr)
1705 << "Could not create read context for ABIXML file '"
1706 << abi_file_path << "'\n";
1707
1708 return abigail::tools_utils::ABIDIFF_ERROR;
1709 }
1710
1711 if (opts.verbose)
1712 emit_prefix("abipkgdiff", cerr)
1713 << "Reading ABIXML file '"
1714 << abi_file_path
1715 << "' ...\n";
1716
1717 abigail::fe_iface::status sts;
1718 reread_corp = rdr->read_corpus(sts);
1719 if (!reread_corp)
1720 {
1721 if (opts.verbose)
1722 emit_prefix("abipkgdiff", cerr)
1723 << "Could not read temporary ABIXML file '"
1724 << abi_file_path << "'\n";
1725
1726 return abigail::tools_utils::ABIDIFF_ERROR;
1727 }
1728
1729 if (opts.verbose)
1730 emit_prefix("abipkgdiff", cerr)
1731 << "Read file '"
1732 << abi_file_path
1733 << "' OK\n";
1734 }
1735 }
1736
1737 ctxt.reset(new diff_context);
1738 set_diff_context_from_opts(ctxt, opts);
1739
1740 if (opts.verbose)
1741 emit_prefix("abipkgdiff", cerr)
1742 << "Comparing the ABIs of: \n"
1743 << " '" << corp->get_path() << "' against \n"
1744 << " '" << abi_file_path << "'...\n";
1745
1746 diff = compute_diff(corp, reread_corp, ctxt);
1747 if (opts.verbose)
1748 emit_prefix("abipkgdfiff", cerr)
1749 << "... Comparing the ABIs: DONE\n";
1750
1751 abidiff_status s = abigail::tools_utils::ABIDIFF_OK;
1752 if (diff->has_changes())
1753 s |= abigail::tools_utils::ABIDIFF_ABI_CHANGE;
1754 if (diff->has_incompatible_changes())
1755 s |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE;
1756
1757 if (opts.verbose)
1758 emit_prefix("abipkgdfiff", cerr)
1759 << "Comparison against self "
1760 << (s == abigail::tools_utils::ABIDIFF_OK ? "SUCCEEDED" : "FAILED")
1761 << '\n';
1762
1763 return s;
1764 }
1765
1766 /// If devel packages were associated to the main package we are
1767 /// looking at, use the names of the header files (extracted from the
1768 /// package) to generate suppression specification to filter out types
1769 /// that are not defined in those header files.
1770 ///
1771 /// Filtering out types not defined in publi headers amounts to filter
1772 /// out types that are deemed private to the package we are looking
1773 /// at.
1774 ///
1775 /// If the function succeeds, it returns a non-empty vector of
1776 /// suppression specifications.
1777 ///
1778 /// @param pkg the main package we are looking at.
1779 ///
1780 /// @param opts the options of the current program.
1781 ///
1782 /// @return a vector of suppression_sptr. If no suppressions
1783 /// specification were constructed, the returned vector is empty.
1784 static suppressions_type
create_private_types_suppressions(const package & pkg,const options & opts)1785 create_private_types_suppressions(const package& pkg, const options &opts)
1786 {
1787 suppressions_type supprs;
1788
1789 package_sptr devel_pkg = pkg.devel_package();
1790 if (!devel_pkg
1791 || !file_exists(devel_pkg->extracted_dir_path())
1792 || !is_dir(devel_pkg->extracted_dir_path()))
1793 return supprs;
1794
1795 string headers_path = devel_pkg->extracted_dir_path();
1796 if (devel_pkg->type() == abigail::tools_utils::FILE_TYPE_RPM
1797 ||devel_pkg->type() == abigail::tools_utils::FILE_TYPE_DEB)
1798 // For RPM and DEB packages, header files are under the
1799 // /usr/include sub-directories.
1800 headers_path += "/usr/include";
1801
1802 if (!is_dir(headers_path))
1803 return supprs;
1804
1805 suppression_sptr suppr =
1806 gen_suppr_spec_from_headers(headers_path);
1807
1808 if (suppr)
1809 {
1810 if (opts.drop_private_types)
1811 suppr->set_drops_artifact_from_ir(true);
1812 supprs.push_back(suppr);
1813 }
1814
1815 return supprs;
1816 }
1817
1818 /// If the user wants to avoid comparing DSOs that are private to this
1819 /// package, then we build the set of public DSOs as advertised in the
1820 /// package's "provides" property.
1821 ///
1822 /// Note that at the moment this function only works for RPMs. It
1823 /// doesn't yet support other packaging formats.
1824 ///
1825 /// @param pkg the package to consider.
1826 ///
1827 /// @param opts the options of this program.
1828 ///
1829 /// @return true iff the set of public DSOs was built.
1830 static bool
maybe_create_public_dso_sonames_set(package & pkg,const options & opts)1831 maybe_create_public_dso_sonames_set(package& pkg, const options &opts)
1832 {
1833 if (opts.compare_private_dsos || !pkg.public_dso_sonames().empty())
1834 return false;
1835
1836 if (pkg.type() == abigail::tools_utils::FILE_TYPE_RPM)
1837 return get_dsos_provided_by_rpm(pkg.path(), pkg.public_dso_sonames());
1838
1839 // We don't support this yet for non-RPM packages.
1840 return false;
1841 }
1842
1843 /// Test if we should only compare the public DSOs of a given package.
1844 ///
1845 /// @param pkg the package to consider.
1846 ///
1847 /// @param opts the options of this program
1848 static bool
must_compare_public_dso_only(package & pkg,options & opts)1849 must_compare_public_dso_only(package& pkg, options& opts)
1850 {
1851 if (pkg.type() == abigail::tools_utils::FILE_TYPE_RPM
1852 && !opts.compare_private_dsos)
1853 return true;
1854
1855 return false;
1856 }
1857
1858 /// While walking a file directory, check if a directory entry is a
1859 /// kabi whitelist of a particular architecture.
1860 ///
1861 /// If it is, then save its file path in a vector of whitelists.
1862 ///
1863 /// @param entry the directory entry to consider.
1864 ///
1865 /// @param arch the architecture to consider.
1866 ///
1867 /// @param whitelists out parameter. If @p entry is the whitelist we
1868 /// are looking for, add its path to this output parameter.
1869 static void
maybe_collect_kabi_whitelists(const FTSENT * entry,const string arch,vector<string> & whitelists)1870 maybe_collect_kabi_whitelists(const FTSENT *entry,
1871 const string arch,
1872 vector<string> &whitelists)
1873 {
1874 if (entry == NULL
1875 || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
1876 || entry->fts_info == FTS_ERR
1877 || entry->fts_info == FTS_NS)
1878 return;
1879
1880 string path = entry->fts_path;
1881 maybe_get_symlink_target_file_path(path, path);
1882
1883 string kabi_whitelist_name = "kabi_whitelist_" + arch;
1884
1885 if (string_ends_with(path, kabi_whitelist_name))
1886 whitelists.push_back(path);
1887 }
1888
1889 /// Get the kabi whitelist for a particular architecture under a given
1890 /// directory.
1891 ///
1892 /// @param dir the directory to look at.
1893 ///
1894 /// @param arch the architecture to consider.
1895 ///
1896 /// @param whitelist_paths the vector where to add the whitelists
1897 /// found. Note that a whitelist is added to this parameter iff the
1898 /// function returns true.
1899 ///
1900 /// @return true iff the function found a whitelist at least.
1901 static bool
get_kabi_whitelists_from_arch_under_dir(const string & dir,const string & arch,vector<string> & whitelist_paths)1902 get_kabi_whitelists_from_arch_under_dir(const string& dir,
1903 const string& arch,
1904 vector<string>& whitelist_paths)
1905 {
1906 bool is_ok = false;
1907 char* paths[] = {const_cast<char*>(dir.c_str()), 0};
1908
1909 FTS *file_hierarchy = fts_open(paths, FTS_LOGICAL|FTS_NOCHDIR, NULL);
1910 if (!file_hierarchy)
1911 return is_ok;
1912
1913 FTSENT *entry;
1914 while ((entry = fts_read(file_hierarchy)))
1915 maybe_collect_kabi_whitelists(entry, arch, whitelist_paths);
1916
1917 fts_close(file_hierarchy);
1918
1919 return true;
1920 }
1921
1922 /// Find a kabi whitelist in a linux kernel RPM package.
1923 ///
1924 /// Note that the linux kernel RPM package must have been extracted
1925 /// somewhere already.
1926 ///
1927 /// This function then looks for the whitelist under the /lib/modules
1928 /// directory inside the extracted content of the package. If it
1929 /// finds it and saves its file path in the
1930 /// options::kabi_whitelist_paths data member.
1931 ///
1932 /// @param pkg the linux kernel package to consider.
1933 ///
1934 /// @param opts the options the program was invoked with.
1935 static bool
maybe_handle_kabi_whitelist_pkg(const package & pkg,options & opts)1936 maybe_handle_kabi_whitelist_pkg(const package& pkg, options &opts)
1937 {
1938 if (opts.kabi_whitelist_packages.empty()
1939 || !opts.kabi_whitelist_paths.empty()
1940 || !pkg.kabi_whitelist_package())
1941 return false;
1942
1943 if (pkg.type() != abigail::tools_utils::FILE_TYPE_RPM)
1944 return false;
1945
1946 string pkg_name = pkg.base_name();
1947 bool is_linux_kernel_package = file_is_kernel_package(pkg_name, pkg.type());
1948
1949 if (!is_linux_kernel_package)
1950 return false;
1951
1952 package_sptr kabi_wl_pkg = pkg.kabi_whitelist_package();
1953 assert(kabi_wl_pkg);
1954
1955 if (!file_exists(kabi_wl_pkg->extracted_dir_path())
1956 || !is_dir(kabi_wl_pkg->extracted_dir_path()))
1957 return false;
1958
1959 string rpm_arch;
1960 if (!get_rpm_arch(pkg_name, rpm_arch))
1961 return false;
1962
1963 string kabi_wl_path = kabi_wl_pkg->extracted_dir_path();
1964 kabi_wl_path += "/lib/modules";
1965 vector<string> whitelist_paths;
1966
1967 get_kabi_whitelists_from_arch_under_dir(kabi_wl_path, rpm_arch,
1968 whitelist_paths);
1969
1970 if (!whitelist_paths.empty())
1971 {
1972 std::sort(whitelist_paths.begin(), whitelist_paths.end());
1973 opts.kabi_whitelist_paths.push_back(whitelist_paths.back());
1974 }
1975
1976 return true;
1977 }
1978
1979 /// The task that performs the extraction of the content of several
1980 /// packages into a temporary directory.
1981 ///
1982 /// If this task has several packages to extract, then it extracts
1983 /// them in sequence.
1984 ///
1985 /// Note that several instances of tasks can perform their jobs (i.e
1986 /// extract packages in sequence) in parallel.
1987 class pkg_extraction_task : public task
1988 {
1989 pkg_extraction_task();
1990
1991 public:
1992 vector<package_sptr> pkgs;
1993 const options &opts;
1994 bool is_ok;
1995
pkg_extraction_task(const package_sptr & p,const options & o)1996 pkg_extraction_task(const package_sptr &p, const options &o)
1997 : opts(o), is_ok(true)
1998 {pkgs.push_back(p);}
1999
pkg_extraction_task(const vector<package_sptr> & packages,const options & o)2000 pkg_extraction_task(const vector<package_sptr> &packages, const options &o)
2001 : pkgs(packages), opts(o), is_ok(true)
2002 {}
2003
2004 /// The job performed by the current task, which is to extract its
2005 /// packages in sequence. This job is to be performed in parallel
2006 /// with other jobs of other tasks.
2007 virtual void
perform()2008 perform()
2009 {
2010 for (vector<package_sptr>::const_iterator p = pkgs.begin();
2011 p != pkgs.end();
2012 ++p)
2013 is_ok &= extract_package(**p, opts);
2014 }
2015 }; //end class pkg_extraction_task
2016
2017 /// A convenience typedef for a shared pointer to @f pkg_extraction_task.
2018 typedef shared_ptr<pkg_extraction_task> pkg_extraction_task_sptr;
2019
2020 /// The worker task which job is to prepares a package.
2021 ///
2022 /// Preparing a package means:
2023 ///
2024 /// 1/ Extract the package and its ancillary packages.
2025 ///
2026 /// 2/ Analyze the extracted content, map that content so that we
2027 /// determine what the ELF files to be analyze are.
2028 class pkg_prepare_task : public abigail::workers::task
2029 {
2030 pkg_prepare_task();
2031
2032 public:
2033 package_sptr pkg;
2034 options &opts;
2035 bool is_ok;
2036
pkg_prepare_task(package_sptr & p,options & o)2037 pkg_prepare_task(package_sptr &p, options &o)
2038 : pkg(p), opts(o), is_ok(false)
2039 {}
2040
2041 /// The job performed by this task.
2042 virtual void
perform()2043 perform()
2044 {
2045 is_ok = pkg && extract_package_and_map_its_content(pkg, opts);
2046 }
2047 }; //end class pkg_prepare_task
2048
2049 /// A convenience typedef for a shared_ptr to @ref pkg_prepare_task
2050 typedef shared_ptr<pkg_prepare_task> pkg_prepare_task_sptr;
2051
2052 /// The worker task which job is to compare two ELF binaries
2053 class compare_task : public abigail::workers::task
2054 {
2055 public:
2056
2057 compare_args_sptr args;
2058 abidiff_status status;
2059 ostringstream out;
2060 string pretty_output;
2061
compare_task()2062 compare_task()
2063 : status(abigail::tools_utils::ABIDIFF_OK)
2064 {}
2065
compare_task(const compare_args_sptr & a)2066 compare_task(const compare_args_sptr& a)
2067 : args(a),
2068 status(abigail::tools_utils::ABIDIFF_OK)
2069 {}
2070
2071 /// The job performed by the task.
2072 ///
2073 /// This compares two ELF files, gets the resulting test report and
2074 /// stores it in an output stream.
2075 virtual void
perform()2076 perform()
2077 {
2078 abigail::ir::environment env;
2079 diff_context_sptr ctxt;
2080 corpus_diff_sptr diff;
2081
2082 abigail::fe_iface::status detailed_status =
2083 abigail::fe_iface::STATUS_UNKNOWN;
2084
2085 if (args->opts.exported_interfaces_only.has_value())
2086 env.analyze_exported_interfaces_only
2087 (*args->opts.exported_interfaces_only);
2088
2089 status |= compare(args->elf1, args->debug_dir1, args->private_types_suppr1,
2090 args->elf2, args->debug_dir2, args->private_types_suppr2,
2091 args->opts, env, diff, ctxt, &detailed_status);
2092
2093 // If there is an ABI change, tell the user about it.
2094 if ((status & abigail::tools_utils::ABIDIFF_ABI_CHANGE)
2095 ||( diff && diff->has_net_changes()))
2096 {
2097 diff->report(out, /*prefix=*/" ");
2098 string name = args->elf1.name;
2099
2100 pretty_output +=
2101 string("================ changes of '") + name + "'===============\n"
2102 + out.str()
2103 + "================ end of changes of '"
2104 + name + "'===============\n\n";
2105 }
2106 else
2107 {
2108 if (args->opts.show_identical_binaries)
2109 out << "No ABI change detected\n";
2110 }
2111
2112 // If an error happened while comparing the two binaries, tell the
2113 // user about it.
2114 if (status & abigail::tools_utils::ABIDIFF_ERROR)
2115 {
2116 string diagnostic =
2117 abigail::status_to_diagnostic_string(detailed_status);
2118 if (diagnostic.empty())
2119 diagnostic =
2120 "Unknown error. Please run the tool again with --verbose\n";
2121
2122 string name = args->elf1.name;
2123 pretty_output +=
2124 "==== Error happened during processing of '" + name + "' ====\n";
2125 pretty_output += diagnostic;
2126 pretty_output +=
2127 "==== End of error for '" + name + "' ====\n";
2128 }
2129 }
2130 }; // end class compare_task
2131
2132 /// Convenience typedef for a shared_ptr of @ref compare_task.
2133 typedef shared_ptr<compare_task> compare_task_sptr;
2134
2135 /// The worker task which job is to compare an ELF binary to its ABI
2136 /// representation.
2137 class self_compare_task : public compare_task
2138 {
2139 public:
self_compare_task(const compare_args_sptr & a)2140 self_compare_task(const compare_args_sptr& a)
2141 : compare_task(a)
2142 {}
2143
2144 /// The job performed by the task.
2145 ///
2146 /// This compares an ELF file to its ABIXML representation and
2147 /// expects the result to be the empty set.
2148 virtual void
perform()2149 perform()
2150 {
2151 abigail::ir::environment env;
2152 diff_context_sptr ctxt;
2153 corpus_diff_sptr diff;
2154
2155 if (args->opts.exported_interfaces_only.has_value())
2156 env.analyze_exported_interfaces_only
2157 (*args->opts.exported_interfaces_only);
2158
2159 abigail::fe_iface::status detailed_status =
2160 abigail::fe_iface::STATUS_UNKNOWN;
2161
2162 status |= compare_to_self(args->elf1, args->debug_dir1,
2163 args->opts, env, diff, ctxt,
2164 &detailed_status);
2165
2166 string name = args->elf1.name;
2167 if (status == abigail::tools_utils::ABIDIFF_OK)
2168 pretty_output += "==== SELF CHECK SUCCEEDED for '"+ name + "' ====\n";
2169 else if ((status & abigail::tools_utils::ABIDIFF_ABI_CHANGE)
2170 ||( diff && diff->has_net_changes()))
2171 {
2172 // There is an ABI change, tell the user about it.
2173 diff->report(out, /*indent=*/" ");
2174
2175 pretty_output +=
2176 string("======== comparing'") + name +
2177 "' to itself wrongly yielded result: ===========\n"
2178 + out.str()
2179 + "===SELF CHECK FAILED for '"+ name + "'\n";
2180 }
2181
2182 // If an error happened while comparing the two binaries, tell the
2183 // user about it.
2184 if (status & abigail::tools_utils::ABIDIFF_ERROR)
2185 {
2186 string diagnostic =
2187 abigail::status_to_diagnostic_string(detailed_status);
2188
2189 if (diagnostic.empty())
2190 diagnostic =
2191 "Unknown error. Please run the tool again with --verbose\n";
2192
2193 string name = args->elf1.name;
2194 pretty_output +=
2195 "==== Error happened during self check of '" + name + "' ====\n";
2196 pretty_output += diagnostic;
2197 pretty_output +=
2198 "==== SELF CHECK FAILED for '" + name + "' ====\n";
2199
2200 }
2201 }
2202 }; // end class self_compare
2203
2204 /// Convenience typedef for a shared_ptr of @ref compare_task.
2205 typedef shared_ptr<self_compare_task> self_compare_task_sptr;
2206
2207 /// This function is a sub-routine of create_maps_of_package_content.
2208 ///
2209 /// It's called during the walking of the directory tree containing
2210 /// the extracted content of package. It's called with an entry of
2211 /// that directory tree.
2212 ///
2213 /// Depending on the kind of file this function is called on, it
2214 /// updates the vector of paths of the directory and the set of
2215 /// suppression paths found.
2216 ///
2217 /// @param entry the directory entry to analyze.
2218 ///
2219 /// @param opts the options of the current program.
2220 ///
2221 /// @param file_name_to_look_for if this parameter is set, the
2222 /// function only looks for a file name which name is the same as the
2223 /// value of this parameter.
2224 ///
2225 /// @param paths out parameter. This is the set of meaningful paths
2226 /// of the current directory tree being analyzed. These paths are
2227 /// those that are going to be involved in ABI comparison.
2228 static void
maybe_update_package_content(const FTSENT * entry,options & opts,const string & file_name_to_look_for,unordered_set<string> & paths)2229 maybe_update_package_content(const FTSENT *entry,
2230 options &opts,
2231 const string& file_name_to_look_for,
2232 unordered_set<string>& paths)
2233 {
2234 if (entry == NULL
2235 || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
2236 || entry->fts_info == FTS_ERR
2237 || entry->fts_info == FTS_NS)
2238 return;
2239
2240 string path = entry->fts_path;
2241 maybe_get_symlink_target_file_path(path, path);
2242
2243 if (!file_name_to_look_for.empty())
2244 {
2245 string name;
2246 abigail::tools_utils::base_name(path, name);
2247 if (name == file_name_to_look_for)
2248 paths.insert(path);
2249 return;
2250 }
2251
2252 if (guess_file_type(path) == abigail::tools_utils::FILE_TYPE_ELF)
2253 paths.insert(path);
2254 else if (opts.abignore && string_ends_with(path, ".abignore"))
2255 opts.suppression_paths.push_back(path);
2256 }
2257
2258 /// Walk a given directory to collect files that are "interesting" to
2259 /// analyze. By default, "interesting" means interesting from either
2260 /// a kernel package or a userspace binary analysis point of view.
2261 ///
2262 /// @param dir the directory to walk.
2263 ///
2264 /// @param file_name_to_look_for if this parameter is set, only a file
2265 /// with this name is going to be collected.
2266 ///
2267 /// @param interesting_files out parameter. This parameter is
2268 /// populated with the interesting files found by the function iff the
2269 /// function returns true.
2270 ///
2271 /// @return true iff the function completed successfully.
2272 static bool
get_interesting_files_under_dir(const string dir,const string & file_name_to_look_for,options & opts,vector<string> & interesting_files)2273 get_interesting_files_under_dir(const string dir,
2274 const string& file_name_to_look_for,
2275 options& opts,
2276 vector<string>& interesting_files)
2277 {
2278 bool is_ok = false;
2279 string root;
2280 real_path(dir, root);
2281 if (root.empty())
2282 root = dir;
2283
2284 char* paths[] = {const_cast<char*>(root.c_str()), 0};
2285
2286 FTS *file_hierarchy = fts_open(paths, FTS_LOGICAL|FTS_NOCHDIR, NULL);
2287 if (!file_hierarchy)
2288 return is_ok;
2289
2290 FTSENT *entry;
2291 unordered_set<string> files;
2292 while ((entry = fts_read(file_hierarchy)))
2293 maybe_update_package_content(entry, opts, file_name_to_look_for, files);
2294
2295 for (unordered_set<string>::const_iterator i = files.begin();
2296 i != files.end();
2297 ++i)
2298 interesting_files.push_back(*i);
2299
2300 fts_close(file_hierarchy);
2301
2302 is_ok = true;
2303
2304 return is_ok;
2305 }
2306
2307 /// Create maps of the content of a given package.
2308 ///
2309 /// The maps contain relevant metadata about the content of the
2310 /// files. These maps are used afterwards during the comparison of
2311 /// the content of the package. Note that the maps are stored in the
2312 /// object that represents that package.
2313 ///
2314 /// @param package the package to consider.
2315 ///
2316 /// @param opts the options the current program has been called with.
2317 ///
2318 /// @param true upon successful completion, false otherwise.
2319 static bool
create_maps_of_package_content(package & package,options & opts)2320 create_maps_of_package_content(package& package, options& opts)
2321 {
2322 if (opts.verbose)
2323 emit_prefix("abipkgdiff", cerr)
2324 << "Analyzing the content of package "
2325 << package.path()
2326 << " extracted to "
2327 << package.extracted_dir_path()
2328 << " ...\n";
2329
2330 bool is_ok = true;
2331 vector<string> elf_file_paths;
2332
2333 // if package is linux kernel package and its associated debug
2334 // info package looks like a kernel debuginfo package, then try to
2335 // go find the vmlinux file in that debug info file.
2336 string pkg_name = package.base_name();
2337 bool is_linux_kernel_package = file_is_kernel_package(pkg_name,
2338 package.type());
2339 if (is_linux_kernel_package)
2340 {
2341 // For a linux kernel package, no analysis is done. It'll be
2342 // done later at comparison time by
2343 // compare_prepared_linux_kernel_packages
2344 is_ok = true;
2345 if (opts.verbose)
2346 emit_prefix("abipkgdiff", cerr)
2347 << " Analysis of " << package.path() << " DONE\n";
2348 return is_ok;
2349 }
2350
2351 is_ok &= get_interesting_files_under_dir(package.extracted_dir_path(),
2352 /*file_name_to_look_for=*/"",
2353 opts, elf_file_paths);
2354
2355 if (opts.verbose)
2356 emit_prefix("abipkgdiff", cerr)
2357 << "Found " << elf_file_paths.size() << " files in "
2358 << package.extracted_dir_path() << "\n";
2359
2360 // determine if all files have the same prefix. Compute that prefix
2361 // and stick it into the package! That prefix is going to be used
2362 // later by the package::convert_path_to_unique_suffix method.
2363 package.load_elf_file_paths(opts);
2364
2365 maybe_create_public_dso_sonames_set(package, opts);
2366
2367 for (vector<string>::const_iterator file = elf_file_paths.begin();
2368 file != elf_file_paths.end();
2369 ++file)
2370 {
2371 elf_file_sptr e (new elf_file(*file));
2372 if (opts.compare_dso_only)
2373 {
2374 if (e->type != abigail::elf::ELF_TYPE_DSO)
2375 {
2376 if (opts.verbose)
2377 emit_prefix("abipkgdiff", cerr)
2378 << "skipping non-DSO file " << e->path << "\n";
2379 continue;
2380 }
2381 }
2382 else
2383 {
2384 if (e->type != abigail::elf::ELF_TYPE_DSO
2385 && e->type != abigail::elf::ELF_TYPE_EXEC
2386 && e->type != abigail::elf::ELF_TYPE_PI_EXEC)
2387 {
2388 if (is_linux_kernel_package)
2389 {
2390 if (e->type == abigail::elf::ELF_TYPE_RELOCATABLE)
2391 {
2392 // This is a Linux Kernel module.
2393 ;
2394 }
2395 }
2396 else if (opts.verbose)
2397 {
2398 emit_prefix("abipkgdiff", cerr)
2399 << "skipping non-DSO non-executable file "
2400 << e->path
2401 << "\n";
2402 continue;
2403 }
2404 }
2405 }
2406
2407 if (e->soname.empty())
2408 {
2409 if (e->type == abigail::elf::ELF_TYPE_DSO
2410 && must_compare_public_dso_only(package, opts))
2411 {
2412 // We are instructed to compare public DSOs only. Yet
2413 // this DSO does not have a soname. so it can not be a
2414 // public DSO. Let's skip it.
2415 if (opts.verbose)
2416 emit_prefix("abipkgdiff", cerr)
2417 << "DSO " << e->path
2418 << " does not have a soname so it's private. Skipping it\n";
2419 continue;
2420 }
2421
2422 // Several binaries at different paths can have the same
2423 // base name. So let's consider the full path of the binary
2424 // inside the extracted directory.
2425 string key = e->name;
2426 package.convert_path_to_unique_suffix(e->path, key);
2427 package.path_elf_file_sptr_map()[key] = e;
2428 if (opts.verbose)
2429 emit_prefix("abipkgdiff", cerr)
2430 << "mapped binary with key '" << key << "'"
2431 << "\n";
2432 }
2433 else
2434 {
2435 // Several binaries at different paths can have the same
2436 // soname. So let's *also* consider the full path of the
2437 // binary inside the extracted directory, not just the
2438 // soname.
2439 string key = e->soname;
2440
2441 if (must_compare_public_dso_only(package, opts))
2442 {
2443 if (package.public_dso_sonames().find(key)
2444 == package.public_dso_sonames().end())
2445 {
2446 // We are instructed to compare public DSOs only and
2447 // this one seems to be private. So skip it.
2448 if (opts.verbose)
2449 emit_prefix("abipkgdiff", cerr)
2450 << "DSO " << e->path << " of soname " << key
2451 << " seems to be private. Skipping it\n";
2452 continue;
2453 }
2454 }
2455
2456 if (package.convert_path_to_unique_suffix(e->path, key))
2457 {
2458 dir_name(key, key);
2459 key += string("/@soname:") + e->soname;
2460 }
2461 package.path_elf_file_sptr_map()[key] = e;
2462 if (opts.verbose)
2463 emit_prefix("abipkgdiff", cerr)
2464 << "mapped binary with key '" << key << "'"
2465 << "\n";
2466 }
2467 }
2468
2469 if (opts.verbose)
2470 emit_prefix("abipkgdiff", cerr)
2471 << " Analysis of " << package.path() << " DONE\n";
2472
2473 is_ok = true;
2474
2475 return is_ok;
2476 }
2477
2478 /// Extract the content of a package (and its ancillary packages) and
2479 /// map its content.
2480 ///
2481 /// First, the content of the package and its ancillary packages are
2482 /// extracted, in parallel.
2483 ///
2484 /// Then, after that extraction is done, the content of the package if
2485 /// walked and analyzed.
2486 ///
2487 /// @param pkg the package to extract and to analyze.
2488 ///
2489 /// @param opts the options of the current program.
2490 ///
2491 /// @return true iff the extraction and analyzing went well.
2492 static bool
extract_package_and_map_its_content(const package_sptr & pkg,options & opts)2493 extract_package_and_map_its_content(const package_sptr &pkg, options &opts)
2494 {
2495 assert(pkg);
2496
2497 pkg_extraction_task_sptr main_pkg_extraction;
2498 pkg_extraction_task_sptr dbg_extraction;
2499 pkg_extraction_task_sptr devel_extraction;
2500 pkg_extraction_task_sptr kabi_whitelist_extraction;
2501
2502 size_t NUM_EXTRACTIONS = 1;
2503
2504 main_pkg_extraction.reset(new pkg_extraction_task(pkg, opts));
2505
2506 if (!pkg->debug_info_packages().empty())
2507 {
2508 dbg_extraction.reset(new pkg_extraction_task(pkg->debug_info_packages(),
2509 opts));
2510 ++NUM_EXTRACTIONS;
2511 }
2512
2513 if (package_sptr devel_pkg = pkg->devel_package())
2514 {
2515 devel_extraction.reset(new pkg_extraction_task(devel_pkg, opts));
2516 ++NUM_EXTRACTIONS;
2517 }
2518
2519 if (package_sptr kabi_wl_pkg = pkg->kabi_whitelist_package())
2520 {
2521 kabi_whitelist_extraction.reset(new pkg_extraction_task(kabi_wl_pkg,
2522 opts));
2523 ++NUM_EXTRACTIONS;
2524 }
2525
2526 size_t num_workers = (opts.parallel
2527 ? std::min(opts.num_workers, NUM_EXTRACTIONS)
2528 : 1);
2529 abigail::workers::queue extraction_queue(num_workers);
2530
2531 // Perform the extraction of the NUM_WORKERS packages in parallel.
2532 extraction_queue.schedule_task(dbg_extraction);
2533 extraction_queue.schedule_task(main_pkg_extraction);
2534 extraction_queue.schedule_task(devel_extraction);
2535 extraction_queue.schedule_task(kabi_whitelist_extraction);
2536
2537 // Wait for the extraction to be done.
2538 extraction_queue.wait_for_workers_to_complete();
2539
2540 // Analyze and map the content of the extracted package.
2541 bool is_ok = false;
2542 if (main_pkg_extraction->is_ok)
2543 is_ok = create_maps_of_package_content(*pkg, opts);
2544
2545 if (is_ok)
2546 maybe_handle_kabi_whitelist_pkg(*pkg, opts);
2547
2548 return is_ok;
2549 }
2550
2551 /// Extract the two packages (and their ancillary packages) and
2552 /// analyze their content, so that we later know what files from the
2553 /// first package to compare against what files from the second
2554 /// package.
2555 ///
2556 /// Note that preparing the first package and its ancillary packages
2557 /// happens in parallel with preparing the second package and its
2558 /// ancillary packages. The function then waits for the two
2559 /// preparations to complete before returning.
2560 ///
2561 /// @param first_package the first package to consider.
2562 ///
2563 /// @param second_package the second package to consider.
2564 ///
2565 /// @param opts the options of the current program.
2566 ///
2567 /// @return true iff the preparation went well.
2568 static bool
prepare_packages(package_sptr & first_package,package_sptr & second_package,options & opts)2569 prepare_packages(package_sptr &first_package,
2570 package_sptr &second_package,
2571 options &opts)
2572 {
2573 pkg_prepare_task_sptr first_pkg_prepare;
2574 pkg_prepare_task_sptr second_pkg_prepare;
2575 size_t NUM_PREPARATIONS = 2;
2576
2577 first_pkg_prepare.reset(new pkg_prepare_task(first_package, opts));
2578 second_pkg_prepare.reset(new pkg_prepare_task(second_package, opts));
2579
2580 size_t num_workers = (opts.parallel
2581 ? std::min(opts.num_workers, NUM_PREPARATIONS)
2582 : 1);
2583 abigail::workers::queue preparation_queue(num_workers);
2584
2585 preparation_queue.schedule_task(first_pkg_prepare);
2586 preparation_queue.schedule_task(second_pkg_prepare);
2587
2588 preparation_queue.wait_for_workers_to_complete();
2589
2590 return first_pkg_prepare->is_ok && second_pkg_prepare->is_ok;
2591 }
2592
2593 /// Prepare one package for the sake of comparing it to its ABIXML
2594 /// representation.
2595 ///
2596 /// The preparation entails unpacking the content of the package into
2597 /// a temporary directory and mapping its content.
2598 ///
2599 /// @param pkg the package to prepare.
2600 ///
2601 /// @param opts the options provided by the user.
2602 ///
2603 /// @return true iff the preparation succeeded.
2604 static bool
prepare_package(package_sptr & pkg,options & opts)2605 prepare_package(package_sptr& pkg, options &opts)
2606 {return extract_package_and_map_its_content(pkg, opts);}
2607
2608 /// Compare the added sizes of an ELF pair (specified by a comparison
2609 /// task that compares two ELF files) against the added sizes of a
2610 /// second ELF pair.
2611 ///
2612 /// Larger filesize strongly raises the possibility of larger debug-info,
2613 /// hence longer diff time. For a package containing several relatively
2614 /// large and small ELFs, it is often more efficient to start working on
2615 /// the larger ones first. This function is used to order the pairs by
2616 /// size, starting from the largest.
2617 ///
2618 /// @param t1 the first comparison task that compares a pair of ELF
2619 /// files.
2620 ///
2621 /// @param t2 the second comparison task that compares a pair of ELF
2622 /// files.
2623 ///
2624 /// @return true if @p task1 is greater than @p task2.
2625 bool
elf_size_is_greater(const task_sptr & task1,const task_sptr & task2)2626 elf_size_is_greater(const task_sptr &task1,
2627 const task_sptr &task2)
2628 {
2629 compare_task_sptr t1 = dynamic_pointer_cast<compare_task>(task1);
2630 compare_task_sptr t2 = dynamic_pointer_cast<compare_task>(task2);
2631
2632 ABG_ASSERT(t1->args && t2->args);
2633 off_t s1 = t1->args->elf1.size + t1->args->elf2.size;
2634 off_t s2 = t2->args->elf1.size + t2->args->elf2.size;
2635
2636 if (s1 != s2)
2637 return s1 > s2;
2638
2639 // The sizes of the compared binaries are the same. So sort them
2640 // lexicographically.
2641 return t1->args->elf1.name < t2->args->elf1.name;
2642
2643 }
2644
2645 /// This type is used to notify the calling thread that the comparison
2646 /// of two ELF files is done.
2647 class comparison_done_notify : public abigail::workers::queue::task_done_notify
2648 {
2649 comparison_done_notify();
2650
2651 public:
2652 abi_diff& diff;
2653 abidiff_status status;
2654
comparison_done_notify(abi_diff & d)2655 comparison_done_notify(abi_diff &d)
2656 : diff(d),
2657 status(abigail::tools_utils::ABIDIFF_OK)
2658 {}
2659
2660 /// This operator is invoked by the worker queue whenever a
2661 /// comparison task is done.
2662 ///
2663 /// The operator collects the status of the job of the task and also
2664 /// updates the the count of binaries that have ABI changes.
2665 ///
2666 /// @param task_done the task that is done.
2667 virtual void
operator ()(const task_sptr & task_done)2668 operator()(const task_sptr& task_done)
2669 {
2670 compare_task_sptr comp_task = dynamic_pointer_cast<compare_task>(task_done);
2671 assert(comp_task);
2672
2673 status |= comp_task->status;
2674
2675 if (status != abigail::tools_utils::ABIDIFF_OK)
2676 {
2677 string name = comp_task->args->elf1.name;
2678
2679 if (status & abigail::tools_utils::ABIDIFF_ABI_CHANGE)
2680 diff.changed_binaries.push_back(name);
2681 }
2682 }
2683 }; // end struct comparison_done_notify
2684
2685 /// Erase the temporary directories that might have been created while
2686 /// handling two packages, unless the user asked to keep the temporary
2687 /// directories around.
2688 ///
2689 /// @param first_package the first package to consider.
2690 ///
2691 /// @param second_package the second package to consider.
2692 ///
2693 /// @param opts the options passed to the program.
2694 static void
maybe_erase_temp_dirs(package & first_package,package & second_package,options & opts)2695 maybe_erase_temp_dirs(package& first_package, package& second_package,
2696 options& opts)
2697 {
2698 if (opts.keep_tmp_files)
2699 return;
2700
2701 erase_created_temporary_directories(first_package, second_package, opts);
2702 erase_created_temporary_directories_parent(opts);
2703 }
2704
2705 /// Compare the ABI of two prepared packages that contain userspace
2706 /// binaries.
2707 ///
2708 /// A prepared package is a package which content has been extracted
2709 /// and mapped.
2710 ///
2711 /// @param first_package the first package to consider.
2712 ///
2713 /// @param second_package the second package to consider.
2714 ///
2715 /// @param options the options the current program has been called
2716 /// with.
2717 ///
2718 /// @param diff out parameter. If this function returns true, then
2719 /// this parameter is set to the result of the comparison.
2720 ///
2721 /// @param opts the options of the current program.
2722 ///
2723 /// @return the status of the comparison.
2724 static abidiff_status
compare_prepared_userspace_packages(package & first_package,package & second_package,abi_diff & diff,options & opts)2725 compare_prepared_userspace_packages(package& first_package,
2726 package& second_package,
2727 abi_diff& diff, options& opts)
2728 {
2729 abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
2730 abigail::workers::queue::tasks_type compare_tasks;
2731 string pkg_name = first_package.base_name();
2732
2733 // Setting debug-info path of libraries
2734 string debug_dir1, debug_dir2, relative_debug_path = "/usr/lib/debug/";
2735 if (!first_package.debug_info_packages().empty()
2736 && !second_package.debug_info_packages().empty())
2737 {
2738 debug_dir1 =
2739 first_package.debug_info_packages().front()->extracted_dir_path() +
2740 relative_debug_path;
2741 debug_dir2 =
2742 second_package.debug_info_packages().front()->extracted_dir_path() +
2743 relative_debug_path;
2744 }
2745
2746 for (map<string, elf_file_sptr>::iterator it =
2747 first_package.path_elf_file_sptr_map().begin();
2748 it != first_package.path_elf_file_sptr_map().end();
2749 ++it)
2750 {
2751 map<string, elf_file_sptr>::iterator iter =
2752 second_package.path_elf_file_sptr_map().find(it->first);
2753
2754 if (iter != second_package.path_elf_file_sptr_map().end()
2755 && (iter->second->type == abigail::elf::ELF_TYPE_DSO
2756 || iter->second->type == abigail::elf::ELF_TYPE_EXEC
2757 || iter->second->type == abigail::elf::ELF_TYPE_PI_EXEC
2758 || iter->second->type == abigail::elf::ELF_TYPE_RELOCATABLE))
2759 {
2760 if (iter->second->type != abigail::elf::ELF_TYPE_RELOCATABLE)
2761 {
2762 if (opts.verbose)
2763 emit_prefix("abipkgdiff", cerr)
2764 << "Going to compare files '"
2765 << it->first << "' and '" << iter->first << "'\n";
2766 compare_args_sptr args
2767 (new compare_args(*it->second,
2768 debug_dir1,
2769 create_private_types_suppressions
2770 (first_package, opts),
2771 *iter->second,
2772 debug_dir2,
2773 create_private_types_suppressions
2774 (second_package, opts), opts));
2775 compare_task_sptr t(new compare_task(args));
2776 compare_tasks.push_back(t);
2777 }
2778 second_package.path_elf_file_sptr_map().erase(iter);
2779 }
2780 else if (iter == second_package.path_elf_file_sptr_map().end())
2781 {
2782 if (opts.verbose)
2783 emit_prefix("abipkgdiff", cerr)
2784 << "Detected removed file: '"
2785 << it->first << "'\n";
2786 diff.removed_binaries.push_back(it->second);
2787 status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE;
2788 status |= abigail::tools_utils::ABIDIFF_ABI_CHANGE;
2789 }
2790 }
2791
2792 comparison_done_notify notifier(diff);
2793 if (!compare_tasks.empty())
2794 {
2795 // Larger elfs are processed first, since it's usually safe to assume
2796 // their debug-info is larger as well, but the results are still
2797 // in a map ordered by looked up in elf.name order.
2798 std::sort(compare_tasks.begin(), compare_tasks.end(), elf_size_is_greater);
2799
2800 // There's no reason to spawn more workers than there are ELF pairs
2801 // to be compared.
2802 size_t num_workers = (opts.parallel
2803 ? std::min(opts.num_workers, compare_tasks.size())
2804 : 1);
2805 assert(num_workers >= 1);
2806
2807 abigail::workers::queue comparison_queue(num_workers, notifier);
2808
2809 // Compare all the binaries, in parallel and then wait for the
2810 // comparisons to complete.
2811 comparison_queue.schedule_tasks(compare_tasks);
2812 comparison_queue.wait_for_workers_to_complete();
2813
2814 // Get the set of comparison tasks that were perform and sort them.
2815 queue::tasks_type& done_tasks = comparison_queue.get_completed_tasks();
2816 std::sort(done_tasks.begin(), done_tasks.end(), elf_size_is_greater);
2817
2818 // Print the reports of the comparison to standard output.
2819 for (queue::tasks_type::const_iterator i = done_tasks.begin();
2820 i != done_tasks.end();
2821 ++i)
2822 {
2823 compare_task_sptr t = dynamic_pointer_cast<compare_task>(*i);
2824 cout << t->pretty_output;
2825 }
2826 }
2827
2828 // Update the count of added binaries.
2829 for (map<string, elf_file_sptr>::iterator it =
2830 second_package.path_elf_file_sptr_map().begin();
2831 it != second_package.path_elf_file_sptr_map().end();
2832 ++it)
2833 diff.added_binaries.push_back(it->second);
2834
2835 // Print information about removed binaries on standard output.
2836 if (diff.removed_binaries.size())
2837 {
2838 cout << "Removed binaries:\n";
2839 for (vector<elf_file_sptr>::iterator it = diff.removed_binaries.begin();
2840 it != diff.removed_binaries.end(); ++it)
2841 {
2842 string relative_path;
2843 first_package.convert_path_to_relative((*it)->path, relative_path);
2844 cout << " [D] " << relative_path << ", ";
2845 string soname;
2846 get_soname_of_elf_file((*it)->path, soname);
2847 if (!soname.empty())
2848 cout << "SONAME: " << soname;
2849 else
2850 cout << "no SONAME";
2851 cout << "\n";
2852 }
2853 }
2854
2855 // Print information about added binaries on standard output.
2856 if (opts.show_added_binaries && diff.added_binaries.size())
2857 {
2858 cout << "Added binaries:\n";
2859 for (vector<elf_file_sptr>::iterator it = diff.added_binaries.begin();
2860 it != diff.added_binaries.end(); ++it)
2861 {
2862 string relative_path;
2863 second_package.convert_path_to_relative((*it)->path, relative_path);
2864 cout << " [A] " << relative_path << ", ";
2865 string soname;
2866 get_soname_of_elf_file((*it)->path, soname);
2867 if (!soname.empty())
2868 cout << "SONAME: " << soname;
2869 else
2870 cout << "no SONAME";
2871 cout << "\n";
2872 }
2873 }
2874
2875 // Erase temporary directory tree we might have left behind.
2876 maybe_erase_temp_dirs(first_package, second_package, opts);
2877
2878 status = notifier.status;
2879
2880 return status;
2881 }
2882
2883 /// In the context of the unpacked content of a given package, compare
2884 /// the binaries inside the package against their ABIXML
2885 /// representation. This should yield the empty set.
2886 ///
2887 /// @param pkg (unpacked) package
2888 ///
2889 /// @param diff the representation of the changes between the binaries
2890 /// and their ABIXML. This should obviously be the empty set.
2891 ///
2892 /// @param diff a textual representation of the diff.
2893 ///
2894 /// @param opts the options provided by the user.
2895 static abidiff_status
self_compare_prepared_userspace_package(package & pkg,abi_diff & diff,options & opts)2896 self_compare_prepared_userspace_package(package& pkg,
2897 abi_diff& diff,
2898 options& opts)
2899 {
2900 abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
2901 abigail::workers::queue::tasks_type self_compare_tasks;
2902 string pkg_name = pkg.base_name();
2903
2904 // Setting debug-info path of libraries
2905 string debug_dir, relative_debug_path = "/usr/lib/debug/";
2906 if (!pkg.debug_info_packages().empty())
2907 debug_dir =
2908 pkg.debug_info_packages().front()->extracted_dir_path() +
2909 relative_debug_path;
2910
2911 suppressions_type supprs;
2912 for (map<string, elf_file_sptr>::iterator it =
2913 pkg.path_elf_file_sptr_map().begin();
2914 it != pkg.path_elf_file_sptr_map().end();
2915 ++it)
2916 {
2917 if (it != pkg.path_elf_file_sptr_map().end()
2918 && (it->second->type == abigail::elf::ELF_TYPE_DSO
2919 || it->second->type == abigail::elf::ELF_TYPE_EXEC
2920 || it->second->type == abigail::elf::ELF_TYPE_PI_EXEC
2921 || it->second->type == abigail::elf::ELF_TYPE_RELOCATABLE))
2922 {
2923 if (it->second->type != abigail::elf::ELF_TYPE_RELOCATABLE)
2924 {
2925 compare_args_sptr args
2926 (new compare_args(*it->second,
2927 debug_dir,
2928 supprs,
2929 *it->second,
2930 debug_dir,
2931 supprs,
2932 opts));
2933 self_compare_task_sptr t(new self_compare_task(args));
2934 self_compare_tasks.push_back(t);
2935 }
2936 }
2937 }
2938
2939 if (self_compare_tasks.empty())
2940 {
2941 maybe_erase_temp_dirs(pkg, pkg, opts);
2942 return abigail::tools_utils::ABIDIFF_OK;
2943 }
2944
2945 // Larger elfs are processed first, since it's usually safe to assume
2946 // their debug-info is larger as well, but the results are still
2947 // in a map ordered by looked up in elf.name order.
2948 std::sort(self_compare_tasks.begin(),
2949 self_compare_tasks.end(),
2950 elf_size_is_greater);
2951
2952 // There's no reason to spawn more workers than there are ELF pairs
2953 // to be compared.
2954 size_t num_workers = (opts.parallel
2955 ? std::min(opts.num_workers, self_compare_tasks.size())
2956 : 1);
2957 assert(num_workers >= 1);
2958
2959 comparison_done_notify notifier(diff);
2960 abigail::workers::queue comparison_queue(num_workers, notifier);
2961
2962 // Compare all the binaries, in parallel and then wait for the
2963 // comparisons to complete.
2964 comparison_queue.schedule_tasks(self_compare_tasks);
2965 comparison_queue.wait_for_workers_to_complete();
2966
2967 // Get the set of comparison tasks that were perform and sort them.
2968 queue::tasks_type& done_tasks = comparison_queue.get_completed_tasks();
2969 std::sort(done_tasks.begin(), done_tasks.end(), elf_size_is_greater);
2970
2971 // Print the reports of the comparison to standard output.
2972 for (queue::tasks_type::const_iterator i = done_tasks.begin();
2973 i != done_tasks.end();
2974 ++i)
2975 {
2976 self_compare_task_sptr t = dynamic_pointer_cast<self_compare_task>(*i);
2977 if (t)
2978 cout << t->pretty_output;
2979 }
2980
2981 // Erase temporary directory tree we might have left behind.
2982 maybe_erase_temp_dirs(pkg, pkg, opts);
2983
2984 status = notifier.status;
2985
2986 return status;
2987 }
2988
2989 /// Compare the ABI of two prepared packages that contain linux kernel
2990 /// binaries.
2991 ///
2992 /// A prepared package is a package which content has been extracted
2993 /// and mapped.
2994 ///
2995 /// @param first_package the first package to consider.
2996 ///
2997 /// @param second_package the second package to consider.
2998 ///
2999 /// @param options the options the current program has been called
3000 /// with.
3001 ///
3002 /// @param diff out parameter. If this function returns true, then
3003 /// this parameter is set to the result of the comparison.
3004 ///
3005 /// @param opts the options of the current program.
3006 ///
3007 /// @return the status of the comparison.
3008 static abidiff_status
compare_prepared_linux_kernel_packages(package & first_package,package & second_package,options & opts)3009 compare_prepared_linux_kernel_packages(package& first_package,
3010 package& second_package,
3011 options& opts)
3012 {
3013 abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
3014 string pkg_name = first_package.base_name();
3015
3016 // Setting debug-info path of binaries
3017 string debug_dir1, debug_dir2, relative_debug_path = "/usr/lib/debug/";
3018 if (!first_package.debug_info_packages().empty()
3019 && !second_package.debug_info_packages().empty())
3020 {
3021 debug_dir1 =
3022 first_package.debug_info_packages().front()->extracted_dir_path() +
3023 relative_debug_path;
3024 debug_dir2 =
3025 second_package.debug_info_packages().front()->extracted_dir_path() +
3026 relative_debug_path;
3027 }
3028
3029 string vmlinux_path1, vmlinux_path2;
3030
3031 if (!get_vmlinux_path_from_kernel_dist(debug_dir1, vmlinux_path1))
3032 return abigail::tools_utils::ABIDIFF_ERROR;
3033
3034 if (!get_vmlinux_path_from_kernel_dist(debug_dir2, vmlinux_path2))
3035 return abigail::tools_utils::ABIDIFF_ERROR;
3036
3037 string dist_root1 = first_package.extracted_dir_path();
3038 string dist_root2 = second_package.extracted_dir_path();
3039
3040 abigail::ir::environment env;
3041 if (opts.exported_interfaces_only.has_value())
3042 env.analyze_exported_interfaces_only
3043 (*opts.exported_interfaces_only);
3044
3045 suppressions_type supprs;
3046 corpus_group_sptr corpus1, corpus2;
3047 corpus1 = build_corpus_group_from_kernel_dist_under(dist_root1,
3048 debug_dir1,
3049 vmlinux_path1,
3050 opts.suppression_paths,
3051 opts.kabi_whitelist_paths,
3052 supprs,
3053 opts.verbose,
3054 env);
3055
3056 if (!corpus1)
3057 return abigail::tools_utils::ABIDIFF_ERROR;
3058
3059 corpus2 = build_corpus_group_from_kernel_dist_under(dist_root2,
3060 debug_dir2,
3061 vmlinux_path2,
3062 opts.suppression_paths,
3063 opts.kabi_whitelist_paths,
3064 supprs,
3065 opts.verbose,
3066 env);
3067
3068 if (!corpus2)
3069 return abigail::tools_utils::ABIDIFF_ERROR;
3070
3071 diff_context_sptr diff_ctxt(new diff_context);
3072 set_diff_context_from_opts(diff_ctxt, opts);
3073
3074 corpus_diff_sptr diff = compute_diff(corpus1, corpus2, diff_ctxt);
3075
3076 if (diff->has_net_changes())
3077 status |= abigail::tools_utils::ABIDIFF_ABI_CHANGE;
3078 if (diff->has_incompatible_changes())
3079 status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE;
3080
3081 if (status & abigail::tools_utils::ABIDIFF_ABI_CHANGE)
3082 {
3083 cout << "== Kernel ABI changes between packages '"
3084 << first_package.path() << "' and '"
3085 << second_package.path() << "' are: ===\n";
3086 diff->report(cout);
3087 cout << "== End of kernel ABI changes between packages '"
3088 << first_package.path()
3089 << "' and '"
3090 << second_package.path() << "' ===\n\n";
3091 }
3092
3093 return status;
3094 }
3095
3096 /// Compare the ABI of two prepared packages.
3097 ///
3098 /// A prepared package is a package which content has been extracted
3099 /// and mapped.
3100 ///
3101 /// @param first_package the first package to consider.
3102 ///
3103 /// @param second_package the second package to consider.
3104 ///
3105 /// @param options the options the current program has been called
3106 /// with.
3107 ///
3108 /// @param diff out parameter. If this function returns true, then
3109 /// this parameter is set to the result of the comparison.
3110 ///
3111 /// @param opts the options of the current program.
3112 ///
3113 /// @return the status of the comparison.
3114 static abidiff_status
compare_prepared_package(package & first_package,package & second_package,abi_diff & diff,options & opts)3115 compare_prepared_package(package& first_package, package& second_package,
3116 abi_diff& diff, options& opts)
3117 {
3118 abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
3119
3120 if (abigail::tools_utils::file_is_kernel_package(first_package.base_name(),
3121 first_package.type()))
3122 {
3123 opts.show_symbols_not_referenced_by_debug_info = false;
3124 status = compare_prepared_linux_kernel_packages(first_package,
3125 second_package,
3126 opts);
3127 }
3128 else
3129 status = compare_prepared_userspace_packages(first_package,
3130 second_package,
3131 diff, opts);
3132
3133 return status;
3134 }
3135
3136 /// Compare binaries in a package against their ABIXML
3137 /// representations.
3138 ///
3139 /// @param pkg the package to consider.
3140 ///
3141 /// @param diff the textual representation of the resulting
3142 /// comparison.
3143 ///
3144 /// @param opts the options provided by the user
3145 ///
3146 /// @return the status of the comparison.
3147 static abidiff_status
self_compare_prepared_package(package & pkg,abi_diff & diff,options & opts)3148 self_compare_prepared_package(package& pkg,
3149 abi_diff& diff,
3150 options& opts)
3151 {
3152 abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
3153
3154 status = self_compare_prepared_userspace_package(pkg, diff, opts);
3155
3156 return status;
3157 }
3158
3159 /// Compare the ABI of two packages
3160 ///
3161 /// @param first_package the first package to consider.
3162 ///
3163 /// @param second_package the second package to consider.
3164 ///
3165 /// @param options the options the current program has been called
3166 /// with.
3167 ///
3168 /// @param diff out parameter. If this function returns true, then
3169 /// this parameter is set to the result of the comparison.
3170 ///
3171 /// @param opts the options of the current program.
3172 ///
3173 /// @return the status of the comparison.
3174 static abidiff_status
compare(package_sptr & first_package,package_sptr & second_package,abi_diff & diff,options & opts)3175 compare(package_sptr& first_package, package_sptr& second_package,
3176 abi_diff& diff, options& opts)
3177 {
3178 // Prepare (extract and analyze the contents) the packages and their
3179 // ancillary packages.
3180 //
3181 // Note that the package preparations happens in parallel.
3182 if (!prepare_packages(first_package, second_package, opts))
3183 {
3184 maybe_erase_temp_dirs(*first_package, *second_package, opts);
3185 return abigail::tools_utils::ABIDIFF_ERROR;
3186 }
3187
3188 return compare_prepared_package(*first_package, *second_package, diff, opts);
3189 }
3190
3191 /// Compare binaries in a package against their ABIXML
3192 /// representations.
3193 ///
3194 /// @param pkg the package to consider.
3195 ///
3196 /// @param opts the options provided by the user
3197 ///
3198 /// @return the status of the comparison.
3199 static abidiff_status
compare_to_self(package_sptr & pkg,options & opts)3200 compare_to_self(package_sptr& pkg, options& opts)
3201 {
3202 if (!prepare_package(pkg, opts))
3203 return abigail::tools_utils::ABIDIFF_ERROR;
3204
3205 abi_diff diff;
3206 return self_compare_prepared_package(*pkg, diff, opts);
3207 }
3208
3209 /// Compare the ABI of two packages.
3210 ///
3211 /// @param first_package the first package to consider.
3212 ///
3213 /// @param second_package the second package to consider.
3214 ///
3215 /// @param opts the options the current program has been called with.
3216 ///
3217 /// @return the status of the comparison.
3218 static abidiff_status
compare(package_sptr & first_package,package_sptr & second_package,options & opts)3219 compare(package_sptr& first_package,
3220 package_sptr& second_package,
3221 options& opts)
3222 {
3223 abi_diff diff;
3224 return compare(first_package, second_package, diff, opts);
3225 }
3226
3227 /// Parse the command line of the current program.
3228 ///
3229 /// @param argc the number of arguments in the @p argv parameter.
3230 ///
3231 /// @param argv the array of arguemnts passed to the function. The
3232 /// first argument is the name of this program.
3233 ///
3234 /// @param opts the resulting options.
3235 ///
3236 /// @return true upon successful parsing.
3237 static bool
parse_command_line(int argc,char * argv[],options & opts)3238 parse_command_line(int argc, char* argv[], options& opts)
3239 {
3240 if (argc < 2)
3241 return false;
3242
3243 for (int i = 1; i < argc; ++i)
3244 {
3245 if (argv[i][0] != '-')
3246 {
3247 if (opts.package1.empty())
3248 {
3249 opts.package1 = make_path_absolute(argv[i]).get();
3250 opts.nonexistent_file = !file_exists(opts.package1);
3251 }
3252 else if (opts.package2.empty())
3253 {
3254 opts.package2 = make_path_absolute(argv[i]).get();
3255 opts.nonexistent_file = !file_exists(opts.package2);
3256 }
3257 else
3258 {
3259 opts.wrong_arg = argv[i];
3260 return false;
3261 }
3262
3263 if (opts.nonexistent_file)
3264 {
3265 opts.wrong_option = argv[i];
3266 return true;
3267 }
3268 }
3269 else if (!strcmp(argv[i], "--debug-info-pkg1")
3270 || !strcmp(argv[i], "--d1"))
3271 {
3272 int j = i + 1;
3273 if (j >= argc)
3274 {
3275 opts.missing_operand = true;
3276 opts.wrong_option = argv[i];
3277 return true;
3278 }
3279 opts.debug_packages1.push_back
3280 (abigail::tools_utils::make_path_absolute(argv[j]).get());
3281 ++i;
3282 }
3283 else if (!strcmp(argv[i], "--debug-info-pkg2")
3284 || !strcmp(argv[i], "--d2"))
3285 {
3286 int j = i + 1;
3287 if (j >= argc)
3288 {
3289 opts.missing_operand = true;
3290 opts.wrong_option = argv[i];
3291 return true;
3292 }
3293 opts.debug_packages2.push_back
3294 (abigail::tools_utils::make_path_absolute(argv[j]).get());
3295 ++i;
3296 }
3297 else if (!strcmp(argv[i], "--devel-pkg1")
3298 || !strcmp(argv[i], "--devel1"))
3299 {
3300 int j = i + 1;
3301 if (j >= argc)
3302 {
3303 opts.missing_operand = true;
3304 opts.wrong_option = argv[i];
3305 return true;
3306 }
3307 opts.devel_package1 =
3308 abigail::tools_utils::make_path_absolute(argv[j]).get();
3309 ++i;
3310 }
3311 else if (!strcmp(argv[i], "--devel-pkg2")
3312 || !strcmp(argv[i], "--devel2"))
3313 {
3314 int j = i + 1;
3315 if (j >= argc)
3316 {
3317 opts.missing_operand = true;
3318 opts.wrong_option = argv[i];
3319 return true;
3320 }
3321 opts.devel_package2 =
3322 abigail::tools_utils::make_path_absolute(argv[j]).get();
3323 ++i;
3324 }
3325 else if (!strcmp(argv[i], "--drop-private-types"))
3326 opts.drop_private_types = true;
3327 else if (!strcmp(argv[i], "--no-default-suppression"))
3328 opts.no_default_suppression = true;
3329 else if (!strcmp(argv[i], "--keep-tmp-files"))
3330 opts.keep_tmp_files = true;
3331 else if (!strcmp(argv[i], "--dso-only"))
3332 opts.compare_dso_only = true;
3333 else if (!strcmp(argv[i], "--private-dso"))
3334 opts.compare_private_dsos = true;
3335 else if (!strcmp(argv[i], "--leaf-changes-only")
3336 ||!strcmp(argv[i], "-l"))
3337 opts.leaf_changes_only = true;
3338 else if (!strcmp(argv[i], "--impacted-interfaces")
3339 ||!strcmp(argv[i], "-i"))
3340 opts.show_impacted_interfaces = true;
3341 else if (!strcmp(argv[i], "--non-reachable-types")
3342 ||!strcmp(argv[i], "-t"))
3343 opts.show_all_types = true;
3344 else if (!strcmp(argv[i], "--full-impact")
3345 ||!strcmp(argv[i], "-f"))
3346 opts.show_full_impact_report = true;
3347 else if (!strcmp(argv[i], "--exported-interfaces-only"))
3348 opts.exported_interfaces_only = true;
3349 else if (!strcmp(argv[i], "--allow-non-exported-interfaces"))
3350 opts.exported_interfaces_only = false;
3351 else if (!strcmp(argv[i], "--no-linkage-name"))
3352 opts.show_linkage_names = false;
3353 else if (!strcmp(argv[i], "--redundant"))
3354 opts.show_redundant_changes = true;
3355 else if (!strcmp(argv[i], "--harmless"))
3356 opts.show_harmless_changes = true;
3357 else if (!strcmp(argv[i], "--no-show-locs"))
3358 opts.show_locs = false;
3359 else if (!strcmp(argv[i], "--show-bytes"))
3360 opts.show_offsets_sizes_in_bits = false;
3361 else if (!strcmp(argv[i], "--show-bits"))
3362 opts.show_offsets_sizes_in_bits = true;
3363 else if (!strcmp(argv[i], "--show-hex"))
3364 opts.show_hexadecimal_values = true;
3365 else if (!strcmp(argv[i], "--show-dec"))
3366 opts.show_hexadecimal_values = false;
3367 else if (!strcmp(argv[i], "--no-show-relative-offset-changes"))
3368 opts.show_relative_offset_changes = false;
3369 else if (!strcmp(argv[i], "--no-added-syms"))
3370 opts.show_added_syms = false;
3371 else if (!strcmp(argv[i], "--no-unreferenced-symbols"))
3372 opts.show_symbols_not_referenced_by_debug_info = false;
3373 else if (!strcmp(argv[i], "--no-added-binaries"))
3374 opts.show_added_binaries = false;
3375 else if (!strcmp(argv[i], "--fail-no-dbg"))
3376 opts.fail_if_no_debug_info = true;
3377 else if (!strcmp(argv[i], "--no-leverage-dwarf-factorization"))
3378 opts.leverage_dwarf_factorization = false;
3379 else if (!strcmp(argv[i], "--no-assume-odr-for-cplusplus"))
3380 opts.assume_odr_for_cplusplus = false;
3381 else if (!strcmp(argv[i], "--verbose"))
3382 opts.verbose = true;
3383 else if (!strcmp(argv[i], "--no-abignore"))
3384 opts.abignore = false;
3385 else if (!strcmp(argv[i], "--no-parallel"))
3386 opts.parallel = false;
3387 else if (!strcmp(argv[i], "--show-identical-binaries"))
3388 opts.show_identical_binaries = true;
3389 else if (!strcmp(argv[i], "--self-check"))
3390 opts.self_check = true;
3391 else if (!strcmp(argv[i], "--suppressions")
3392 || !strcmp(argv[i], "--suppr"))
3393 {
3394 int j = i + 1;
3395 if (j >= argc)
3396 return false;
3397 opts.suppression_paths.push_back(argv[j]);
3398 ++i;
3399 }
3400 else if (!strcmp(argv[i], "--linux-kernel-abi-whitelist")
3401 || !strcmp(argv[i], "-w"))
3402 {
3403 int j = i + 1;
3404 if (j >= argc)
3405 {
3406 opts.missing_operand = true;
3407 opts.wrong_option = argv[i];
3408 return true;
3409 }
3410 if (guess_file_type(argv[j]) == abigail::tools_utils::FILE_TYPE_RPM)
3411 // The kernel abi whitelist is actually a whitelist
3412 // *package*. Take that into account.
3413 opts.kabi_whitelist_packages.push_back
3414 (make_path_absolute(argv[j]).get());
3415 else
3416 // We assume the kernel abi whitelist is a white list
3417 // file.
3418 opts.kabi_whitelist_paths.push_back(argv[j]);
3419 ++i;
3420 }
3421 else if (!strcmp(argv[i], "--wp"))
3422 {
3423 int j = i + 1;
3424 if (j >= argc)
3425 {
3426 opts.missing_operand = true;
3427 opts.wrong_option = argv[i];
3428 return true;
3429 }
3430 opts.kabi_whitelist_packages.push_back
3431 (make_path_absolute(argv[j]).get());
3432 ++i;
3433 }
3434 #ifdef WITH_CTF
3435 else if (!strcmp(argv[i], "--ctf"))
3436 opts.use_ctf = true;
3437 #endif
3438 else if (!strcmp(argv[i], "--help")
3439 || !strcmp(argv[i], "-h"))
3440 {
3441 opts.display_usage = true;
3442 return true;
3443 }
3444 else if (!strcmp(argv[i], "--version")
3445 || !strcmp(argv[i], "-v"))
3446 {
3447 opts.display_version = true;
3448 return true;
3449 }
3450 else
3451 {
3452 if (strlen(argv[i]) >= 2 && argv[i][0] == '-' && argv[i][1] == '-')
3453 opts.wrong_option = argv[i];
3454 return false;
3455 }
3456 }
3457
3458 return true;
3459 }
3460
3461 int
main(int argc,char * argv[])3462 main(int argc, char* argv[])
3463 {
3464 options opts(argv[0]);
3465
3466 if (!parse_command_line(argc, argv, opts))
3467 {
3468 if (!opts.wrong_option.empty())
3469 emit_prefix("abipkgdiff", cerr)
3470 << "unrecognized option: " << opts.wrong_option
3471 << "\ntry the --help option for more information\n";
3472 else
3473 emit_prefix("abipkgdiff", cerr)
3474 << "unrecognized argument: " << opts.wrong_arg
3475 << "\ntry the --help option for more information\n";
3476 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3477 | abigail::tools_utils::ABIDIFF_ERROR);
3478 }
3479
3480 if (opts.missing_operand)
3481 {
3482 emit_prefix("abipkgdiff", cerr)
3483 << "missing operand\n"
3484 "try the --help option for more information\n";
3485 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3486 | abigail::tools_utils::ABIDIFF_ERROR);
3487 }
3488
3489 if (opts.nonexistent_file)
3490 {
3491 string input_file;
3492 base_name(opts.wrong_option, input_file);
3493 emit_prefix("abipkgdiff", cerr)
3494 << "The input file " << input_file << " doesn't exist\n"
3495 "try the --help option for more information\n";
3496 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3497 | abigail::tools_utils::ABIDIFF_ERROR);
3498 }
3499
3500 if (opts.kabi_whitelist_packages.size() > 2)
3501 {
3502 emit_prefix("abipkgdiff", cerr)
3503 << "no more than 2 Linux kernel white list packages can be provided\n";
3504 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3505 | abigail::tools_utils::ABIDIFF_ERROR);
3506 }
3507
3508 if (opts.display_usage)
3509 {
3510 display_usage(argv[0], cout);
3511 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3512 | abigail::tools_utils::ABIDIFF_ERROR);
3513 }
3514
3515 if (opts.display_version)
3516 {
3517 emit_prefix(argv[0], cout)
3518 << abigail::tools_utils::get_library_version_string()
3519 << "\n";
3520 return 0;
3521 }
3522
3523 if (!opts.no_default_suppression && opts.suppression_paths.empty())
3524 {
3525 // Load the default system and user suppressions.
3526 string default_system_suppr_file =
3527 get_default_system_suppression_file_path();
3528 if (file_exists(default_system_suppr_file))
3529 opts.suppression_paths.push_back(default_system_suppr_file);
3530
3531 string default_user_suppr_file =
3532 get_default_user_suppression_file_path();
3533 if (file_exists(default_user_suppr_file))
3534 opts.suppression_paths.push_back(default_user_suppr_file);
3535 }
3536
3537 if (!maybe_check_suppression_files(opts))
3538 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3539 | abigail::tools_utils::ABIDIFF_ERROR);
3540
3541 bool need_just_one_input_package = opts.self_check;
3542
3543 if (need_just_one_input_package)
3544 {
3545 bool bail_out = false;
3546 if (!opts.package2.empty())
3547 {
3548 // We don't need the second package, we'll ignore it later
3549 // down below.
3550 ;
3551 }
3552 if (opts.package1.empty())
3553 {
3554 // We need at least one package to work with!
3555 emit_prefix("abipkgdiff", cerr)
3556 << "missing input package\n";
3557 if (bail_out)
3558 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3559 | abigail::tools_utils::ABIDIFF_ERROR);
3560 }
3561 }
3562 else if(opts.package1.empty() || opts.package2.empty())
3563 {
3564 emit_prefix("abipkgdiff", cerr)
3565 << "please enter two packages to compare" << "\n";
3566 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3567 | abigail::tools_utils::ABIDIFF_ERROR);
3568 }
3569
3570 package_sptr first_package(new package(opts.package1, "package1"));
3571
3572 package_sptr second_package(new package(opts.package2, "package2"));
3573 opts.pkg1 = first_package;
3574 opts.pkg2 = second_package;
3575
3576 for (vector<string>::const_iterator p = opts.debug_packages1.begin();
3577 p != opts.debug_packages1.end();
3578 ++p)
3579 first_package->debug_info_packages().push_back
3580 (package_sptr(new package(*p,
3581 "debug_package1",
3582 /*pkg_kind=*/package::KIND_DEBUG_INFO)));
3583
3584 for (vector<string>::const_iterator p = opts.debug_packages2.begin();
3585 p != opts.debug_packages2.end();
3586 ++p)
3587 second_package->debug_info_packages().push_back
3588 (package_sptr(new package(*p,
3589 "debug_package2",
3590 /*pkg_kind=*/package::KIND_DEBUG_INFO)));
3591
3592 if (!opts.devel_package1.empty())
3593 first_package->devel_package
3594 (package_sptr(new package(opts.devel_package1,
3595 "devel_package1",
3596 /*pkg_kind=*/package::KIND_DEVEL)));
3597 ;
3598
3599 if (!opts.devel_package2.empty())
3600 second_package->devel_package
3601 (package_sptr(new package(opts.devel_package2,
3602 "devel_package2",
3603 /*pkg_kind=*/package::KIND_DEVEL)));
3604
3605 if (!opts.kabi_whitelist_packages.empty())
3606 {
3607 first_package->kabi_whitelist_package
3608 (package_sptr(new package
3609 (opts.kabi_whitelist_packages[0],
3610 "kabi_whitelist_package1",
3611 /*pkg_kind=*/package::KIND_KABI_WHITELISTS)));
3612 if (opts.kabi_whitelist_packages.size() >= 2)
3613 second_package->kabi_whitelist_package
3614 (package_sptr(new package
3615 (opts.kabi_whitelist_packages[1],
3616 "kabi_whitelist_package2",
3617 /*pkg_kind=*/package::KIND_KABI_WHITELISTS)));
3618 }
3619
3620 string package_name;
3621 switch (first_package->type())
3622 {
3623 case abigail::tools_utils::FILE_TYPE_RPM:
3624 if (!second_package->path().empty()
3625 && second_package->type() != abigail::tools_utils::FILE_TYPE_RPM)
3626 {
3627 base_name(opts.package2, package_name);
3628 emit_prefix("abipkgdiff", cerr)
3629 << package_name << " should be an RPM file\n";
3630 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3631 | abigail::tools_utils::ABIDIFF_ERROR);
3632 }
3633
3634 if (file_is_kernel_package(first_package->base_name(),
3635 abigail::tools_utils::FILE_TYPE_RPM)
3636 || file_is_kernel_package(second_package->base_name(),
3637 abigail::tools_utils::FILE_TYPE_RPM))
3638 {
3639 if (file_is_kernel_package(first_package->base_name(),
3640 abigail::tools_utils::FILE_TYPE_RPM)
3641 != file_is_kernel_package(second_package->base_name(),
3642 abigail::tools_utils::FILE_TYPE_RPM))
3643 {
3644 emit_prefix("abipkgdiff", cerr)
3645 << "a Linux kernel package can only be compared to another "
3646 "Linux kernel package\n";
3647 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3648 | abigail::tools_utils::ABIDIFF_ERROR);
3649 }
3650
3651 if (first_package->debug_info_packages().empty()
3652 || (!second_package->path().empty()
3653 && second_package->debug_info_packages().empty()))
3654 {
3655 emit_prefix("abipkgdiff", cerr)
3656 << "a Linux Kernel package must be accompanied with its "
3657 "debug info package\n";
3658 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3659 | abigail::tools_utils::ABIDIFF_ERROR);
3660 }
3661 // We are looking at kernel packages. If the user provided
3662 // the --full-impact option then it means we want to display
3663 // the default libabigail report format where a full impact
3664 // analysis is done for each ABI change.
3665 //
3666 // Otherwise, let's just emit the leaf change report.
3667 if (opts.show_full_impact_report)
3668 opts.leaf_changes_only = false;
3669 else
3670 opts.leaf_changes_only = true;
3671 }
3672
3673 break;
3674
3675 case abigail::tools_utils::FILE_TYPE_DEB:
3676 if (!second_package->path().empty()
3677 && second_package->type() != abigail::tools_utils::FILE_TYPE_DEB)
3678 {
3679 base_name(opts.package2, package_name);
3680 emit_prefix("abipkgdiff", cerr)
3681 << package_name << " should be a DEB file\n";
3682 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3683 | abigail::tools_utils::ABIDIFF_ERROR);
3684 }
3685 break;
3686
3687 case abigail::tools_utils::FILE_TYPE_DIR:
3688 if (!second_package->path().empty()
3689 && second_package->type() != abigail::tools_utils::FILE_TYPE_DIR)
3690 {
3691 base_name(opts.package2, package_name);
3692 emit_prefix("abipkgdiff", cerr)
3693 << package_name << " should be a directory\n";
3694 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3695 | abigail::tools_utils::ABIDIFF_ERROR);
3696 }
3697 break;
3698
3699 case abigail::tools_utils::FILE_TYPE_TAR:
3700 if (!second_package->path().empty()
3701 && second_package->type() != abigail::tools_utils::FILE_TYPE_TAR)
3702 {
3703 base_name(opts.package2, package_name);
3704 emit_prefix("abipkgdiff", cerr)
3705 << package_name << " should be a GNU tar archive\n";
3706 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3707 | abigail::tools_utils::ABIDIFF_ERROR);
3708 }
3709 break;
3710
3711 default:
3712 base_name(opts.package1, package_name);
3713 emit_prefix("abipkgdiff", cerr)
3714 << package_name << " should be a valid package file \n";
3715 return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
3716 | abigail::tools_utils::ABIDIFF_ERROR);
3717 }
3718
3719 if (opts.self_check)
3720 return compare_to_self(first_package, opts);
3721
3722 return compare(first_package, second_package, opts);
3723 }
3724