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