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