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