1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- Mode: C++ -*-
3 //
4 // Copyright (C) 2013-2021 Red Hat, Inc.
5 //
6 // Author: Dodji Seketeli
7
8 /// @file
9 ///
10 /// This is a program aimed at checking that a binary instrumentation
11 /// (bi) file is well formed and valid enough. It acts by loading an
12 /// input bi file and saving it back to a temporary file. It then
13 /// runs a diff on the two files and expects the result of the diff to
14 /// be empty.
15
16 #include <cstdio>
17 #include <cstdlib>
18 #include <cstring>
19 #include <fstream>
20 #include <iostream>
21 #include <memory>
22 #include <string>
23 #include <vector>
24 #include "abg-config.h"
25 #include "abg-tools-utils.h"
26 #include "abg-ir.h"
27 #include "abg-corpus.h"
28 #include "abg-reader.h"
29 #include "abg-dwarf-reader.h"
30 #include "abg-writer.h"
31 #include "abg-suppression.h"
32
33 using std::string;
34 using std::cerr;
35 using std::cin;
36 using std::cout;
37 using std::ostream;
38 using std::ofstream;
39 using std::vector;
40 using abigail::tools_utils::emit_prefix;
41 using abigail::tools_utils::check_file;
42 using abigail::tools_utils::file_type;
43 using abigail::tools_utils::guess_file_type;
44 using abigail::suppr::suppression_sptr;
45 using abigail::suppr::suppressions_type;
46 using abigail::suppr::read_suppressions;
47 using abigail::corpus;
48 using abigail::corpus_sptr;
49 using abigail::xml_reader::read_translation_unit_from_file;
50 using abigail::xml_reader::read_translation_unit_from_istream;
51 using abigail::xml_reader::read_corpus_from_native_xml;
52 using abigail::xml_reader::read_corpus_from_native_xml_file;
53 using abigail::xml_reader::read_corpus_group_from_input;
54 using abigail::dwarf_reader::read_corpus_from_elf;
55 using abigail::xml_writer::write_translation_unit;
56 using abigail::xml_writer::write_context_sptr;
57 using abigail::xml_writer::create_write_context;
58 using abigail::xml_writer::write_corpus;
59 using abigail::xml_writer::write_corpus_to_archive;
60
61 struct options
62 {
63 string wrong_option;
64 string file_path;
65 bool display_version;
66 bool read_from_stdin;
67 bool read_tu;
68 bool diff;
69 bool noout;
70 std::shared_ptr<char> di_root_path;
71 vector<string> suppression_paths;
72 string headers_dir;
73 vector<string> header_files;
74
optionsoptions75 options()
76 : display_version(false),
77 read_from_stdin(false),
78 read_tu(false),
79 diff(false),
80 noout(false)
81 {}
82 };//end struct options;
83
84 static void
display_usage(const string & prog_name,ostream & out)85 display_usage(const string& prog_name, ostream& out)
86 {
87 emit_prefix(prog_name, out)
88 << "usage: " << prog_name << " [options] [<abi-file1>]\n"
89 << " where options can be:\n"
90 << " --help display this message\n"
91 << " --version|-v display program version information and exit\n"
92 << " --debug-info-dir <path> the path under which to look for "
93 << " --headers-dir|--hd <patch> the path to headers of the elf file\n"
94 "debug info for the elf <abi-file>\n"
95 << " --header-file|--hf <path> the path to one header of the elf file\n"
96 "debug info for the elf <abi-file>\n"
97 << " --suppressions|--suppr <path> specify a suppression file\n"
98 << " --diff for xml inputs, perform a text diff between "
99 "the input and the memory model saved back to disk\n"
100 << " --noout do not display anything on stdout\n"
101 << " --stdin|-- read abi-file content from stdin\n"
102 << " --tu expect a single translation unit file\n";
103 }
104
105 bool
parse_command_line(int argc,char * argv[],options & opts)106 parse_command_line(int argc, char* argv[], options& opts)
107 {
108 if (argc < 2)
109 {
110 opts.read_from_stdin = true;
111 return true;
112 }
113
114 for (int i = 1; i < argc; ++i)
115 {
116 if (argv[i][0] != '-')
117 {
118 if (opts.file_path.empty())
119 opts.file_path = argv[i];
120 else
121 return false;
122 }
123 else if (!strcmp(argv[i], "--help"))
124 return false;
125 else if (!strcmp(argv[i], "--version")
126 || !strcmp(argv[i], "-v"))
127 {
128 opts.display_version = true;
129 return true;
130 }
131 else if (!strcmp(argv[i], "--debug-info-dir"))
132 {
133 if (argc <= i + 1
134 || argv[i + 1][0] == '-')
135 return false;
136 // elfutils wants the root path to the debug info to be
137 // absolute.
138 opts.di_root_path =
139 abigail::tools_utils::make_path_absolute(argv[i + 1]);
140 ++i;
141 }
142 else if (!strcmp(argv[i], "--headers-dir")
143 || !strcmp(argv[i], "--hd"))
144 {
145 int j = i + 1;
146 if (j >= argc)
147 return false;
148 opts.headers_dir = argv[j];
149 ++i;
150 }
151 else if (!strcmp(argv[i], "--header-file")
152 || !strcmp(argv[i], "--hf"))
153 {
154 int j = i + 1;
155 if (j >= argc)
156 return false;
157 opts.header_files.push_back(argv[j]);
158 ++i;
159 }
160 else if (!strcmp(argv[i], "--suppressions")
161 || !strcmp(argv[i], "--suppr"))
162 {
163 int j = i + 1;
164 if (j >= argc)
165 {
166 opts.wrong_option = argv[i];
167 return true;
168 }
169 opts.suppression_paths.push_back(argv[j]);
170 ++i;
171 }
172 else if (!strcmp(argv[i], "--stdin"))
173 opts.read_from_stdin = true;
174 else if (!strcmp(argv[i], "--tu"))
175 opts.read_tu = true;
176 else if (!strcmp(argv[i], "--diff"))
177 opts.diff = true;
178 else if (!strcmp(argv[i], "--noout"))
179 opts.noout = true;
180 else
181 {
182 if (strlen(argv[i]) >= 2 && argv[i][0] == '-' && argv[i][1] == '-')
183 opts.wrong_option = argv[i];
184 return false;
185 }
186 }
187
188 if (opts.file_path.empty())
189 opts.read_from_stdin = true;
190 return true;
191 }
192
193 /// Check that the suppression specification files supplied are
194 /// present. If not, emit an error on stderr.
195 ///
196 /// @param opts the options instance to use.
197 ///
198 /// @return true if all suppression specification files are present,
199 /// false otherwise.
200 static bool
maybe_check_suppression_files(const options & opts)201 maybe_check_suppression_files(const options& opts)
202 {
203 for (vector<string>::const_iterator i = opts.suppression_paths.begin();
204 i != opts.suppression_paths.end();
205 ++i)
206 if (!check_file(*i, cerr, "abidiff"))
207 return false;
208
209 return true;
210 }
211
212 /// Set suppression specifications to the @p read_context used to load
213 /// the ABI corpus from the ELF/DWARF file.
214 ///
215 /// These suppression specifications are going to be applied to drop
216 /// some ABI artifacts on the floor (while reading the ELF/DWARF file
217 /// or the native XML ABI file) and thus minimize the size of the
218 /// resulting ABI corpus.
219 ///
220 /// @param read_ctxt the read context to apply the suppression
221 /// specifications to. Note that the type of this parameter is
222 /// generic (class template) because in practise, it can be either an
223 /// abigail::dwarf_reader::read_context type or an
224 /// abigail::xml_reader::read_context type.
225 ///
226 /// @param opts the options where to get the suppression
227 /// specifications from.
228 template<class ReadContextType>
229 static void
set_suppressions(ReadContextType & read_ctxt,const options & opts)230 set_suppressions(ReadContextType& read_ctxt, const options& opts)
231 {
232 suppressions_type supprs;
233 for (vector<string>::const_iterator i = opts.suppression_paths.begin();
234 i != opts.suppression_paths.end();
235 ++i)
236 read_suppressions(*i, supprs);
237
238 suppression_sptr suppr =
239 abigail::tools_utils::gen_suppr_spec_from_headers(opts.headers_dir,
240 opts.header_files);
241 if (suppr)
242 supprs.push_back(suppr);
243
244 add_read_context_suppressions(read_ctxt, supprs);
245 }
246
247 /// Reads a bi (binary instrumentation) file, saves it back to a
248 /// temporary file and run a diff on the two versions.
249 int
main(int argc,char * argv[])250 main(int argc, char* argv[])
251 {
252 options opts;
253 if (!parse_command_line(argc, argv, opts))
254 {
255 if (!opts.wrong_option.empty())
256 emit_prefix(argv[0], cerr)
257 << "unrecognized option: " << opts.wrong_option << "\n";
258 display_usage(argv[0], cerr);
259 return 1;
260 }
261
262 if (opts.display_version)
263 {
264 emit_prefix(argv[0], cout)
265 << abigail::tools_utils::get_library_version_string();
266 return 0;
267 }
268
269 if (!maybe_check_suppression_files(opts))
270 return 1;
271
272 abigail::ir::environment_sptr env(new abigail::ir::environment);
273 if (opts.read_from_stdin)
274 {
275 if (!cin.good())
276 return 1;
277
278 if (opts.read_tu)
279 {
280 abigail::translation_unit_sptr tu =
281 read_translation_unit_from_istream(&cin, env.get());
282
283 if (!tu)
284 {
285 emit_prefix(argv[0], cerr)
286 << "failed to read the ABI instrumentation from stdin\n";
287 return 1;
288 }
289
290 if (!opts.noout)
291 {
292 const write_context_sptr& ctxt
293 = create_write_context(tu->get_environment(), cout);
294 write_translation_unit(*ctxt, *tu, 0);
295 }
296 return 0;
297 }
298 else
299 {
300 abigail::xml_reader::read_context_sptr ctxt =
301 abigail::xml_reader::create_native_xml_read_context(&cin,
302 env.get());
303 assert(ctxt);
304 set_suppressions(*ctxt, opts);
305 corpus_sptr corp = abigail::xml_reader::read_corpus_from_input(*ctxt);
306 if (!opts.noout)
307 {
308 const write_context_sptr& ctxt
309 = create_write_context(corp->get_environment(), cout);
310 write_corpus(*ctxt, corp, /*indent=*/0);
311 }
312 return 0;
313 }
314 }
315 else if (!opts.file_path.empty())
316 {
317 if (!check_file(opts.file_path, cerr, argv[0]))
318 return 1;
319 abigail::translation_unit_sptr tu;
320 abigail::corpus_sptr corp;
321 abigail::corpus_group_sptr group;
322 abigail::dwarf_reader::status s = abigail::dwarf_reader::STATUS_OK;
323 char* di_root_path = 0;
324 file_type type = guess_file_type(opts.file_path);
325
326 switch (type)
327 {
328 case abigail::tools_utils::FILE_TYPE_UNKNOWN:
329 emit_prefix(argv[0], cerr)
330 << "Unknown file type given in input: " << opts.file_path;
331 return 1;
332 case abigail::tools_utils::FILE_TYPE_NATIVE_BI:
333 tu = read_translation_unit_from_file(opts.file_path, env.get());
334 break;
335 case abigail::tools_utils::FILE_TYPE_ELF:
336 case abigail::tools_utils::FILE_TYPE_AR:
337 {
338 di_root_path = opts.di_root_path.get();
339 vector<char**> di_roots;
340 di_roots.push_back(&di_root_path);
341 abigail::dwarf_reader::read_context_sptr ctxt =
342 abigail::dwarf_reader::create_read_context(opts.file_path,
343 di_roots, env.get(),
344 /*load_all_types=*/false);
345 assert(ctxt);
346 set_suppressions(*ctxt, opts);
347 corp = read_corpus_from_elf(*ctxt, s);
348 }
349 break;
350 case abigail::tools_utils::FILE_TYPE_XML_CORPUS:
351 {
352 abigail::xml_reader::read_context_sptr ctxt =
353 abigail::xml_reader::create_native_xml_read_context(opts.file_path,
354 env.get());
355 assert(ctxt);
356 set_suppressions(*ctxt, opts);
357 corp = read_corpus_from_input(*ctxt);
358 break;
359 }
360 case abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP:
361 {
362 abigail::xml_reader::read_context_sptr ctxt =
363 abigail::xml_reader::create_native_xml_read_context(opts.file_path,
364 env.get());
365 assert(ctxt);
366 set_suppressions(*ctxt, opts);
367 group = read_corpus_group_from_input(*ctxt);
368 }
369 break;
370 case abigail::tools_utils::FILE_TYPE_RPM:
371 break;
372 case abigail::tools_utils::FILE_TYPE_SRPM:
373 break;
374 case abigail::tools_utils::FILE_TYPE_DEB:
375 break;
376 case abigail::tools_utils::FILE_TYPE_DIR:
377 break;
378 case abigail::tools_utils::FILE_TYPE_TAR:
379 break;
380 }
381
382 if (!tu && !corp && !group)
383 {
384 emit_prefix(argv[0], cerr)
385 << "failed to read " << opts.file_path << "\n";
386 if (!(s & abigail::dwarf_reader::STATUS_OK))
387 {
388 if (s & abigail::dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND)
389 {
390 cerr << "could not find the debug info";
391 if(di_root_path == 0)
392 emit_prefix(argv[0], cerr)
393 << " Maybe you should consider using the "
394 "--debug-info-dir1 option to tell me about the "
395 "root directory of the debuginfo? "
396 "(e.g, --debug-info-dir1 /usr/lib/debug)\n";
397 else
398 emit_prefix(argv[0], cerr)
399 << "Maybe the root path to the debug "
400 "information is wrong?\n";
401 }
402 if (s & abigail::dwarf_reader::STATUS_NO_SYMBOLS_FOUND)
403 emit_prefix(argv[0], cerr)
404 << "could not find the ELF symbols in the file "
405 << opts.file_path
406 << "\n";
407 }
408 return 1;
409 }
410
411 using abigail::tools_utils::temp_file;
412 using abigail::tools_utils::temp_file_sptr;
413
414 temp_file_sptr tmp_file = temp_file::create();
415 if (!tmp_file)
416 {
417 emit_prefix(argv[0], cerr) << "failed to create temporary file\n";
418 return 1;
419 }
420
421 std::ostream& of = opts.diff ? tmp_file->get_stream() : cout;
422 const abigail::ir::environment* env = 0;
423 if (tu)
424 env = tu->get_environment();
425 else if (corp)
426 env = corp->get_environment();
427 else if (group)
428 env = group->get_environment();
429
430 ABG_ASSERT(env);
431 const write_context_sptr ctxt = create_write_context(env, of);
432
433 bool is_ok = true;
434
435 if (tu)
436 {
437 if (!opts.noout)
438 is_ok = write_translation_unit(*ctxt, *tu, 0);
439 }
440 else
441 {
442 if (type == abigail::tools_utils::FILE_TYPE_XML_CORPUS
443 ||type == abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP
444 || type == abigail::tools_utils::FILE_TYPE_ELF)
445 {
446 if (!opts.noout)
447 is_ok = write_corpus(*ctxt, corp, 0);
448 }
449 }
450
451 if (!is_ok)
452 {
453 string output =
454 (type == abigail::tools_utils::FILE_TYPE_NATIVE_BI)
455 ? "translation unit"
456 : "ABI corpus";
457 emit_prefix(argv[0], cerr)
458 << "failed to write the translation unit "
459 << opts.file_path << " back\n";
460 }
461
462 if (is_ok
463 && opts.diff
464 && ((type == abigail::tools_utils::FILE_TYPE_XML_CORPUS)
465 ||type == abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP
466 || type == abigail::tools_utils::FILE_TYPE_NATIVE_BI))
467 {
468 string cmd = "diff -u " + opts.file_path + " " + tmp_file->get_path();
469 if (system(cmd.c_str()))
470 is_ok = false;
471 }
472
473 return is_ok ? 0 : 1;
474 }
475
476 return 1;
477 }
478