1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- Mode: C++ -*-
3 //
4 // Copyright (C) 2017-2022 Red Hat, Inc.
5 //
6 // Author: Dodji Seketeli
7
8 /// @file
9 ///
10 /// The source code of the Kernel Module Interface Diff tool.
11
12 #include "config.h"
13 #include <sys/types.h>
14 #include <dirent.h>
15 #include <cstring>
16 #include <string>
17 #include <vector>
18 #include <iostream>
19
20 #include "abg-config.h"
21 #include "abg-tools-utils.h"
22 #include "abg-corpus.h"
23 #include "abg-dwarf-reader.h"
24 #include "abg-reader.h"
25 #include "abg-comparison.h"
26
27 using std::string;
28 using std::vector;
29 using std::ostream;
30 using std::cout;
31 using std::cerr;
32 using abg_compat::optional;
33
34 using namespace abigail::tools_utils;
35 using namespace abigail::ir;
36 using namespace abigail;
37
38 using abigail::comparison::diff_context_sptr;
39 using abigail::comparison::diff_context;
40 using abigail::comparison::translation_unit_diff_sptr;
41 using abigail::comparison::corpus_diff;
42 using abigail::comparison::corpus_diff_sptr;
43 using abigail::comparison::compute_diff;
44 using abigail::comparison::get_default_harmless_categories_bitmap;
45 using abigail::comparison::get_default_harmful_categories_bitmap;
46 using abigail::suppr::suppression_sptr;
47 using abigail::suppr::suppressions_type;
48 using abigail::suppr::read_suppressions;
49 using abigail::tools_utils::guess_file_type;
50 using abigail::tools_utils::file_type;
51
52 /// The options of this program.
53 struct options
54 {
55 bool display_usage;
56 bool display_version;
57 bool verbose;
58 bool missing_operand;
59 bool leaf_changes_only;
60 bool show_hexadecimal_values;
61 bool show_offsets_sizes_in_bits;
62 bool show_impacted_interfaces;
63 optional<bool> exported_interfaces_only;
64 #ifdef WITH_CTF
65 bool use_ctf;
66 #endif
67 string wrong_option;
68 string kernel_dist_root1;
69 string kernel_dist_root2;
70 string vmlinux1;
71 string vmlinux2;
72 vector<string> kabi_whitelist_paths;
73 vector<string> suppression_paths;
74 suppressions_type read_time_supprs;
75 suppressions_type diff_time_supprs;
76 shared_ptr<char> di_root_path1;
77 shared_ptr<char> di_root_path2;
78
optionsoptions79 options()
80 : display_usage(),
81 display_version(),
82 verbose(),
83 missing_operand(),
84 leaf_changes_only(true),
85 show_hexadecimal_values(true),
86 show_offsets_sizes_in_bits(false),
87 show_impacted_interfaces(false)
88 #ifdef WITH_CTF
89 ,
90 use_ctf(false)
91 #endif
92 {}
93 }; // end struct options.
94
95 /// Display the usage of the program.
96 ///
97 /// @param prog_name the name of this program.
98 ///
99 /// @param out the output stream the usage stream is sent to.
100 static void
display_usage(const string & prog_name,ostream & out)101 display_usage(const string& prog_name, ostream& out)
102 {
103 emit_prefix(prog_name, out)
104 << "usage: " << prog_name << " [options] kernel-modules-dir1 kernel-modules-dir2\n"
105 << " where options can be:\n"
106 << " --help|-h display this message\n"
107 << " --version|-v display program version information and exit\n"
108 << " --verbose display verbose messages\n"
109 << " --debug-info-dir1|--d1 <path> the root for the debug info of "
110 "the first kernel\n"
111 << " --debug-info-dir2|--d2 <path> the root for the debug info of "
112 "the second kernel\n"
113 << " --vmlinux1|--l1 <path> the path to the first vmlinux\n"
114 << " --vmlinux2|--l2 <path> the path to the second vmlinux\n"
115 << " --suppressions|--suppr <path> specify a suppression file\n"
116 << " --kmi-whitelist|-w <path> path to a kernel module interface "
117 "whitelist\n"
118 #ifdef WITH_CTF
119 << " --ctf use CTF instead of DWARF in ELF files\n"
120 #endif
121 << " --impacted-interfaces|-i show interfaces impacted by ABI changes\n"
122 << " --full-impact|-f show the full impact of changes on top-most "
123 "interfaces\n"
124 << " --exported-interfaces-only analyze exported interfaces only\n"
125 << " --allow-non-exported-interfaces analyze interfaces that "
126 "might not be exported\n"
127 << " --show-bytes show size and offsets in bytes\n"
128 << " --show-bits show size and offsets in bits\n"
129 << " --show-hex show size and offset in hexadecimal\n"
130 << " --show-dec show size and offset in decimal\n";
131 }
132
133 /// Parse the command line of the program.
134 ///
135 /// @param argc the number of arguments on the command line, including
136 /// the program name.
137 ///
138 /// @param argv the arguments on the command line, including the
139 /// program name.
140 ///
141 /// @param opts the options resulting from the command line parsing.
142 ///
143 /// @return true iff the command line parsing went fine.
144 bool
parse_command_line(int argc,char * argv[],options & opts)145 parse_command_line(int argc, char* argv[], options& opts)
146 {
147 if (argc < 2)
148 return false;
149
150 for (int i = 1; i < argc; ++i)
151 {
152 if (argv[i][0] != '-')
153 {
154 if (opts.kernel_dist_root1.empty())
155 opts.kernel_dist_root1 = argv[i];
156 else if (opts.kernel_dist_root2.empty())
157 opts.kernel_dist_root2 = argv[i];
158 else
159 return false;
160 }
161 else if (!strcmp(argv[i], "--verbose"))
162 opts.verbose = true;
163 else if (!strcmp(argv[i], "--version")
164 || !strcmp(argv[i], "-v"))
165 {
166 opts.display_version = true;
167 return true;
168 }
169 else if (!strcmp(argv[i], "--help")
170 || !strcmp(argv[i], "-h"))
171 {
172 opts.display_usage = true;
173 return true;
174 }
175 else if (!strcmp(argv[i], "--debug-info-dir1")
176 || !strcmp(argv[i], "--d1"))
177 {
178 int j = i + 1;
179 if (j >= argc)
180 {
181 opts.missing_operand = true;
182 opts.wrong_option = argv[i];
183 return true;
184 }
185 // elfutils wants the root path to the debug info to be
186 // absolute.
187 opts.di_root_path1 =
188 abigail::tools_utils::make_path_absolute(argv[j]);
189 ++i;
190 }
191 else if (!strcmp(argv[i], "--debug-info-dir2")
192 || !strcmp(argv[i], "--d2"))
193 {
194 int j = i + 1;
195 if (j >= argc)
196 {
197 opts.missing_operand = true;
198 opts.wrong_option = argv[i];
199 return true;
200 }
201 // elfutils wants the root path to the debug info to be
202 // absolute.
203 opts.di_root_path2 =
204 abigail::tools_utils::make_path_absolute(argv[j]);
205 ++i;
206 }
207 else if (!strcmp(argv[i], "--vmlinux1")
208 || !strcmp(argv[i], "--l1"))
209 {
210 int j = i + 1;
211 if (j >= argc)
212 {
213 opts.missing_operand = true;
214 opts.wrong_option = argv[i];
215 return false;
216 }
217 opts.vmlinux1 = argv[j];
218 ++i;
219 }
220 else if (!strcmp(argv[i], "--vmlinux2")
221 || !strcmp(argv[i], "--l2"))
222 {
223 int j = i + 1;
224 if (j >= argc)
225 {
226 opts.missing_operand = true;
227 opts.wrong_option = argv[i];
228 return false;
229 }
230 opts.vmlinux2 = argv[j];
231 ++i;
232 }
233 else if (!strcmp(argv[i], "--kmi-whitelist")
234 || !strcmp(argv[i], "-w"))
235 {
236 int j = i + 1;
237 if (j >= argc)
238 {
239 opts.missing_operand = true;
240 opts.wrong_option = argv[i];
241 return false;
242 }
243 opts.kabi_whitelist_paths.push_back(argv[j]);
244 ++i;
245 }
246 else if (!strcmp(argv[i], "--suppressions")
247 || !strcmp(argv[i], "--suppr"))
248 {
249 int j = i + 1;
250 if (j >= argc)
251 {
252 opts.missing_operand = true;
253 opts.wrong_option = argv[i];
254 return false;
255 }
256 opts.suppression_paths.push_back(argv[j]);
257 ++i;
258 }
259 #ifdef WITH_CTF
260 else if (!strcmp(argv[i], "--ctf"))
261 opts.use_ctf = true;
262 #endif
263 else if (!strcmp(argv[i], "--impacted-interfaces")
264 || !strcmp(argv[i], "-i"))
265 opts.show_impacted_interfaces = true;
266 else if (!strcmp(argv[i], "--full-impact")
267 || !strcmp(argv[i], "-f"))
268 opts.leaf_changes_only = false;
269 else if (!strcmp(argv[i], "--exported-interfaces-only"))
270 opts.exported_interfaces_only = true;
271 else if (!strcmp(argv[i], "--allow-non-exported-interfaces"))
272 opts.exported_interfaces_only = false;
273 else if (!strcmp(argv[i], "--show-bytes"))
274 opts.show_offsets_sizes_in_bits = false;
275 else if (!strcmp(argv[i], "--show-bits"))
276 opts.show_offsets_sizes_in_bits = true;
277 else if (!strcmp(argv[i], "--show-hex"))
278 opts.show_hexadecimal_values = true;
279 else if (!strcmp(argv[i], "--show-dec"))
280 opts.show_hexadecimal_values = false;
281 else
282 {
283 opts.wrong_option = argv[i];
284 return false;
285 }
286 }
287
288 return true;
289 }
290
291 /// Check that the suppression specification files supplied are
292 /// present. If not, emit an error on stderr.
293 ///
294 /// @param opts the options instance to use.
295 ///
296 /// @return true if all suppression specification files are present,
297 /// false otherwise.
298 static bool
maybe_check_suppression_files(const options & opts)299 maybe_check_suppression_files(const options& opts)
300 {
301 for (vector<string>::const_iterator i = opts.suppression_paths.begin();
302 i != opts.suppression_paths.end();
303 ++i)
304 if (!check_file(*i, cerr, "abidiff"))
305 return false;
306
307 for (vector<string>::const_iterator i =
308 opts.kabi_whitelist_paths.begin();
309 i != opts.kabi_whitelist_paths.end();
310 ++i)
311 if (!check_file(*i, cerr, "abidiff"))
312 return false;
313
314 return true;
315 }
316
317 /// Setup the diff context from the program's options.
318 ///
319 /// @param ctxt the diff context to consider.
320 ///
321 /// @param opts the options to set the context.
322 static void
set_diff_context(diff_context_sptr ctxt,const options & opts)323 set_diff_context(diff_context_sptr ctxt, const options& opts)
324 {
325 ctxt->default_output_stream(&cout);
326 ctxt->error_output_stream(&cerr);
327 ctxt->show_relative_offset_changes(true);
328 ctxt->show_redundant_changes(false);
329 ctxt->show_locs(true);
330 ctxt->show_linkage_names(false);
331 ctxt->show_symbols_unreferenced_by_debug_info
332 (true);
333 ctxt->show_leaf_changes_only(opts.leaf_changes_only);
334 ctxt->show_impacted_interfaces(opts.show_impacted_interfaces);
335 ctxt->show_hex_values(opts.show_hexadecimal_values);
336 ctxt->show_offsets_sizes_in_bits(opts.show_offsets_sizes_in_bits);
337
338 ctxt->switch_categories_off(get_default_harmless_categories_bitmap());
339
340 if (!opts.diff_time_supprs.empty())
341 ctxt->add_suppressions(opts.diff_time_supprs);
342 }
343
344 /// Print information about the kernel (and modules) binaries found
345 /// under a given directory.
346 ///
347 /// Note that this function actually look for the modules iff the
348 /// --verbose option was provided.
349 ///
350 /// @param root the directory to consider.
351 ///
352 /// @param opts the options to use during the search.
353 static void
print_kernel_dist_binary_paths_under(const string & root,const options & opts)354 print_kernel_dist_binary_paths_under(const string& root, const options &opts)
355 {
356 string vmlinux;
357 vector<string> modules;
358
359 if (opts.verbose)
360 if (get_binary_paths_from_kernel_dist(root, /*debug_info_root_path*/"",
361 vmlinux, modules))
362 {
363 cout << "Found kernel binaries under: '" << root << "'\n";
364 if (!vmlinux.empty())
365 cout << "[linux kernel binary]\n"
366 << " '" << vmlinux << "'\n";
367 if (!modules.empty())
368 {
369 cout << "[linux kernel module binaries]\n";
370 for (vector<string>::const_iterator p = modules.begin();
371 p != modules.end();
372 ++p)
373 cout << " '" << *p << "' \n";
374 }
375 cout << "\n";
376 }
377 }
378
379 int
main(int argc,char * argv[])380 main(int argc, char* argv[])
381 {
382 options opts;
383 if (!parse_command_line(argc, argv, opts))
384 {
385 emit_prefix(argv[0], cerr)
386 << "unrecognized option: "
387 << opts.wrong_option << "\n"
388 << "try the --help option for more information\n";
389 return 1;
390 }
391
392 if (opts.missing_operand)
393 {
394 emit_prefix(argv[0], cerr)
395 << "missing operand to option: " << opts.wrong_option <<"\n"
396 << "try the --help option for more information\n";
397 return 1;
398 }
399
400 if (!maybe_check_suppression_files(opts))
401 return 1;
402
403 if (opts.display_usage)
404 {
405 display_usage(argv[0], cout);
406 return 1;
407 }
408
409 if (opts.display_version)
410 {
411 emit_prefix(argv[0], cout)
412 << abigail::tools_utils::get_library_version_string()
413 << "\n";
414 return 0;
415 }
416
417 environment env;
418
419 if (opts.exported_interfaces_only.has_value())
420 env.analyze_exported_interfaces_only(*opts.exported_interfaces_only);
421
422 corpus_group_sptr group1, group2;
423 string debug_info_root_dir;
424 corpus::origin requested_fe_kind =
425 #ifdef WITH_CTF
426 opts.use_ctf ? corpus::CTF_ORIGIN :
427 #endif
428 corpus::DWARF_ORIGIN;
429
430 if (!opts.kernel_dist_root1.empty())
431 {
432 file_type ftype = guess_file_type(opts.kernel_dist_root1);
433 if (ftype == FILE_TYPE_DIR)
434 {
435 debug_info_root_dir = opts.di_root_path1.get()
436 ? opts.di_root_path1.get()
437 : "";
438
439 group1 =
440 build_corpus_group_from_kernel_dist_under(opts.kernel_dist_root1,
441 debug_info_root_dir,
442 opts.vmlinux1,
443 opts.suppression_paths,
444 opts.kabi_whitelist_paths,
445 opts.read_time_supprs,
446 opts.verbose, env,
447 requested_fe_kind);
448 print_kernel_dist_binary_paths_under(opts.kernel_dist_root1, opts);
449 }
450 else if (ftype == FILE_TYPE_XML_CORPUS_GROUP)
451 group1 =
452 abixml::read_corpus_group_from_abixml_file(opts.kernel_dist_root1,
453 env);
454
455 }
456
457 if (!opts.kernel_dist_root2.empty())
458 {
459 file_type ftype = guess_file_type(opts.kernel_dist_root2);
460 if (ftype == FILE_TYPE_DIR)
461 {
462 debug_info_root_dir = opts.di_root_path2.get()
463 ? opts.di_root_path2.get()
464 : "";
465 group2 =
466 build_corpus_group_from_kernel_dist_under(opts.kernel_dist_root2,
467 debug_info_root_dir,
468 opts.vmlinux2,
469 opts.suppression_paths,
470 opts.kabi_whitelist_paths,
471 opts.read_time_supprs,
472 opts.verbose, env,
473 requested_fe_kind);
474 print_kernel_dist_binary_paths_under(opts.kernel_dist_root2, opts);
475 }
476 else if (ftype == FILE_TYPE_XML_CORPUS_GROUP)
477 group2 =
478 abixml::read_corpus_group_from_abixml_file(opts.kernel_dist_root2,
479 env);
480 }
481
482 abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
483 if (group1 && group2)
484 {
485 diff_context_sptr diff_ctxt(new diff_context);
486 set_diff_context(diff_ctxt, opts);
487
488 corpus_diff_sptr diff= compute_diff(group1, group2, diff_ctxt);
489
490 if (diff->has_net_changes())
491 status = abigail::tools_utils::ABIDIFF_ABI_CHANGE;
492
493 if (diff->has_incompatible_changes())
494 status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE;
495
496 if (diff->has_changes())
497 diff->report(cout);
498 }
499 else
500 status = abigail::tools_utils::ABIDIFF_ERROR;
501
502 return status;
503 }
504