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