blob: 8df841b4404959b0b806d7b45117d31cfe8a5c8a [file] [log] [blame]
Mike Dodd8cfa7022010-11-17 11:12:26 -08001/**
2 * @file opannotate.cpp
3 * Implement opannotate utility
4 *
5 * @remark Copyright 2003 OProfile authors
6 * @remark Read the file COPYING
7 *
8 * @author John Levon
9 * @author Philippe Elie
10 */
11
12#include <iostream>
13#include <sstream>
14#include <algorithm>
15#include <iomanip>
16#include <fstream>
17#include <utility>
18
19#include "op_exception.h"
20#include "op_header.h"
21#include "profile.h"
22#include "populate.h"
23#include "op_sample_file.h"
24#include "cverb.h"
25#include "string_manip.h"
26#include "demangle_symbol.h"
27#include "child_reader.h"
28#include "op_file.h"
29#include "file_manip.h"
30#include "arrange_profiles.h"
31#include "opannotate_options.h"
32#include "profile_container.h"
33#include "symbol_sort.h"
34#include "image_errors.h"
35
36using namespace std;
37using namespace options;
38
39namespace {
40
41size_t nr_events;
42
43scoped_ptr<profile_container> samples;
44
45/// how opannotate was invoked
46string cmdline;
47
48/// empty annotation fill string
49string annotation_fill;
50
51/// string used as start / end comment to annotate source
52string const begin_comment("/* ");
53string const in_comment(" * ");
54string const end_comment(" */");
55
56/// field width for the sample count
57unsigned int const count_width = 6;
58
59string get_annotation_fill()
60{
61 string str;
62
63 for (size_t i = 0; i < nr_events; ++i) {
64 str += string(count_width, ' ') + ' ';
65 str += string(percent_width, ' ');
66 }
67
68 for (size_t i = 1; i < nr_events; ++i)
69 str += " ";
70
71 str += " :";
72 return str;
73}
74
75
76symbol_entry const * find_symbol(string const & image_name,
77 string const & str_vma)
78{
79 // do not use the bfd equivalent:
80 // - it does not skip space at begin
81 // - we does not need cross architecture compile so the native
82 // strtoull must work, assuming unsigned long long can contain a vma
83 // and on 32/64 bits box bfd_vma is 64 bits
84 bfd_vma vma = strtoull(str_vma.c_str(), NULL, 16);
85
86 return samples->find_symbol(image_name, vma);
87}
88
89
90void output_info(ostream & out)
91{
92 out << begin_comment << '\n';
93
94 out << in_comment << "Command line: " << cmdline << '\n'
95 << in_comment << '\n';
96
97 out << in_comment << "Interpretation of command line:" << '\n';
98
99 if (!assembly) {
100 out << in_comment
101 << "Output annotated source file with samples" << '\n';
102
103 if (options::threshold != 0) {
104 out << in_comment
105 << "Output files where samples count reach "
106 << options::threshold << "% of the samples\n";
107 } else {
108 out << in_comment << "Output all files" << '\n';
109 }
110 } else {
111 out << in_comment
112 << "Output annotated assembly listing with samples"
113 << '\n';
114
115 if (!objdump_params.empty()) {
116 out << in_comment << "Passing the following "
117 "additional arguments to objdump ; \"";
118 for (size_t i = 0 ; i < objdump_params.size() ; ++i)
119 out << objdump_params[i] << " ";
120 out << "\"" << '\n';
121 }
122 }
123
124 out << in_comment << '\n';
125
126 out << in_comment << classes.cpuinfo << endl;
127 if (!classes.event.empty())
128 out << in_comment << classes.event << endl;
129
130 for (size_t i = 0; i < classes.v.size(); ++i)
131 out << in_comment << classes.v[i].longname << endl;
132
133 out << end_comment << '\n';
134}
135
136
137string count_str(count_array_t const & count,
138 count_array_t const & total)
139{
140 ostringstream os;
141 for (size_t i = 0; i < nr_events; ++i) {
142 os << setw(count_width) << count[i] << ' ';
143
144 os << format_percent(op_ratio(count[i], total[i]) * 100.0,
145 percent_int_width, percent_fract_width);
146 }
147 return os.str();
148}
149
150
151/// NOTE: This function annotates a list<string> containing output from objdump.
152/// It uses a list iterator, and a sample_container iterator which iterates
153/// from the beginning to the end, and compare sample address
154/// against the instruction address on the asm line.
155///
156/// There are 2 cases of annotation:
157/// 1. If sample address matches current line address, annotate the current line.
158/// 2. If (previous line address < sample address < current line address),
159/// then we annotate previous line. This case happens when sample address
160/// is not aligned with the instruction address, which is seen when profile
161/// using the instruction fetch mode of AMD Instruction-Based Sampling (IBS).
162///
163int asm_list_annotation(symbol_entry const * last_symbol,
164 bfd_vma last_symbol_vma,
165 list<string>::iterator sit,
166 sample_container::samples_iterator & samp_it,
167 list<string> & asm_lines)
168{
169 int ret = 0;
170
171 sample_entry const * sample = NULL;
172
173 if (samp_it != samples->end())
174 sample = &samp_it->second;
175
176 // do not use the bfd equivalent:
177 // - it does not skip space at begin
178 // - we does not need cross architecture compile so the native
179 // strtoull must work, assuming unsigned long long can contain a vma
180 // and on 32/64 bits box bfd_vma is 64 bits
181 // gcc 2.91.66 workaround
182 bfd_vma vma = strtoull((*sit).c_str(), NULL, 16);
183
184 if (sample
185 && ((sample->vma < last_symbol_vma) || (sample->vma > vma))) {
186 *sit = annotation_fill + *sit;
187 } else if (sample && sample->vma == vma) {
188 // Case 1 : Sample address match current line address.
189 string str = count_str(sample->counts, samples->samples_count());
190
191 // For each events
192 for (size_t i = 1; i < nr_events; ++i)
193 str += " ";
194
195 *sit = str + " :" + *sit;
196 if (samp_it != samples->end())
197 ++samp_it;
198
199 } else if (sample && sample->vma < vma) {
200 // Case 2 : vma of the current line is greater than vma of the sample
201
202 // Get the string of previous assembly line
203 list<string>::iterator sit_prev = sit;
204 string prev_line, prev_vma_str;
205 string::size_type loc1 = string::npos, loc2 = string::npos;
206 while (sit_prev != asm_lines.begin()) {
207 --sit_prev;
208 prev_line = *sit_prev;
209
210 loc1 = prev_line.find(":", 0);
211 if (loc1 != string::npos) {
212 loc2 = prev_line.find(":", loc1+1);
213 if (loc2 != string::npos) {
214 prev_vma_str = prev_line.substr(loc1+1, loc2);
215 break;
216 }
217 }
218 }
219
220 bfd_vma prev_vma = strtoull(prev_vma_str.c_str(), NULL, 16);
221
222 // Need to check if prev_vma < sample->vma
223 if (prev_vma != 0 && prev_vma < sample->vma) {
224 string str;
225
226 // Get sample for previous line.
227 sample_entry * prev_sample = (sample_entry *)samples->
228 find_sample(last_symbol, prev_vma);
229 if (prev_sample) {
230 // Aggregate sample with previous line if it already has samples
231 prev_sample->counts += sample->counts;
232 str = count_str(prev_sample->counts, samples->samples_count());
233 } else {
234 str = count_str(sample->counts, samples->samples_count());
235 }
236
237 // For each events
238 for (size_t i = 1; i < nr_events; ++i)
239 str += " ";
240
241 *sit_prev = str + " :" + prev_line.substr(loc1+1);
242 if (samp_it != samples->end())
243 ++samp_it;
244 ret = -1;
245 } else {
246 // Failed to annotate the previous line. Skip sample.
247 *sit = annotation_fill + *sit;
248 if (samp_it != samples->end())
249 ++samp_it;
250 }
251 } else {
252 // In case sample is NULL
253 *sit = annotation_fill + *sit;
254 }
255
256 return ret;
257}
258
259
260string symbol_annotation(symbol_entry const * symbol)
261{
262 if (!symbol)
263 return string();
264
265 string annot = count_str(symbol->sample.counts,
266 samples->samples_count());
267
268 string const & symname = symbol_names.demangle(symbol->name);
269
270 string str = " ";
271 str += begin_comment + symname + " total: ";
272 str += count_str(symbol->sample.counts, samples->samples_count());
273 str += end_comment;
274 return str;
275}
276
277
278/// return true if this line contains a symbol name in objdump formatting
279/// symbol are on the form 08030434 <symbol_name>: we need to be strict
280/// here to avoid any interpretation of a source line as a symbol line
281bool is_symbol_line(string const & str, string::size_type pos)
282{
283 if (str[pos] != ' ' || str[pos + 1] != '<')
284 return false;
285
286 return str[str.length() - 1] == ':';
287}
288
289
290void annotate_objdump_str_list(string const & app_name,
291 symbol_collection const & symbols,
292 list<string> & asm_lines)
293{
294 symbol_entry const * last_symbol = 0;
295 bfd_vma last_symbol_vma = 0;
296 int ret = 0;
297
298 // to filter output of symbols (filter based on command line options)
299 bool do_output = true;
300
301 // We simultaneously walk the two structures (list and sample_container)
302 // which are sorted by address. and do address comparision.
303 list<string>::iterator sit = asm_lines.begin();
304 list<string>::iterator send = asm_lines.end();
305 sample_container::samples_iterator samp_it = samples->begin();
306
307 for (; sit != send; (!ret? sit++: sit)) {
308 // output of objdump is a human readable form and can contain some
309 // ambiguity so this code is dirty. It is also optimized a little bit
310 // so it is difficult to simplify it without breaking something ...
311
312 // line of interest are: "[:space:]*[:xdigit:]?[ :]", the last char of
313 // this regexp dis-ambiguate between a symbol line and an asm line. If
314 // source contain line of this form an ambiguity occur and we rely on
315 // the robustness of this code.
316 string str = *sit;
317 size_t pos = 0;
318 while (pos < str.length() && isspace(str[pos]))
319 ++pos;
320
321 if (pos == str.length() || !isxdigit(str[pos])) {
322 if (do_output) {
323 *sit = annotation_fill + str;
324 continue;
325 }
326 }
327
328 while (pos < str.length() && isxdigit(str[pos]))
329 ++pos;
330
331 if (pos == str.length() || (!isspace(str[pos]) && str[pos] != ':')) {
332 if (do_output) {
333 *sit = annotation_fill + str;
334 continue;
335 }
336 }
337
338 if (is_symbol_line(str, pos)) {
339
340 last_symbol = find_symbol(app_name, str);
341 last_symbol_vma = strtoull(str.c_str(), NULL, 16);
342
343 // ! complexity: linear in number of symbol must use sorted
344 // by address vector and lower_bound ?
345 // Note this use a pointer comparison. It work because symbols
346 // pointer are unique
347 if (find(symbols.begin(), symbols.end(), last_symbol)
348 != symbols.end())
349 do_output = true;
350 else
351 do_output = false;
352
353 if (do_output) {
354 *sit += symbol_annotation(last_symbol);
355
356 // Realign the sample iterator to
357 // the beginning of this symbols
358 samp_it = samples->begin(last_symbol);
359 }
360 } else {
361 // not a symbol, probably an asm line.
362 if (do_output)
363 ret = asm_list_annotation(last_symbol,
364 last_symbol_vma,
365 sit, samp_it,
366 asm_lines);
367 }
368
369 if (!do_output)
370 *sit = "";
371 }
372}
373
374
375void output_objdump_str_list(symbol_collection const & symbols,
376 string const & app_name,
377 list<string> & asm_lines)
378{
379
380 annotate_objdump_str_list(app_name, symbols, asm_lines);
381
382 // Printing objdump output to stdout
383 list<string>::iterator sit = asm_lines.begin();
384 list<string>::iterator send = asm_lines.end();
385 sit = asm_lines.begin();
386 for (; sit != send; ++sit) {
387 string str = *sit;
388 if (str.length() != 0)
389 cout << str << '\n';
390 }
391}
392
393
394void do_one_output_objdump(symbol_collection const & symbols,
395 string const & image_name, string const & app_name,
396 bfd_vma start, bfd_vma end)
397{
398 vector<string> args;
399 list<string> asm_lines;
400
401 args.push_back("-d");
402 args.push_back("--no-show-raw-insn");
403 if (source)
404 args.push_back("-S");
405
406 if (start || end != ~(bfd_vma)0) {
407 ostringstream arg1, arg2;
408 arg1 << "--start-address=" << start;
409 arg2 << "--stop-address=" << end;
410 args.push_back(arg1.str());
411 args.push_back(arg2.str());
412 }
413
414 if (!objdump_params.empty()) {
415 for (size_t i = 0 ; i < objdump_params.size() ; ++i)
416 args.push_back(objdump_params[i]);
417 }
418
419 args.push_back(image_name);
Ben Chenge4b944c2012-01-06 15:35:28 -0800420#if defined(ANDROID)
421 child_reader reader("arm-eabi-objdump", args);
422#else
Mike Dodd8cfa7022010-11-17 11:12:26 -0800423 child_reader reader("objdump", args);
Ben Chenge4b944c2012-01-06 15:35:28 -0800424#endif
Mike Dodd8cfa7022010-11-17 11:12:26 -0800425 if (reader.error()) {
426 cerr << "An error occur during the execution of objdump:\n\n";
427 cerr << reader.error_str() << endl;
428 return;
429 }
430
431 // Read each output line from objdump and store in a list.
432 string str;
433 while (reader.getline(str))
434 asm_lines.push_back(str);
435
436 output_objdump_str_list(symbols, app_name, asm_lines);
437
438 // objdump always returns SUCCESS so we must rely on the stderr state
439 // of objdump. If objdump error message is cryptic our own error
440 // message will be probably also cryptic
441 ostringstream std_err;
442 ostringstream std_out;
443 reader.get_data(std_out, std_err);
444 if (std_err.str().length()) {
445 cerr << "An error occur during the execution of objdump:\n\n";
446 cerr << std_err.str() << endl;
447 return ;
448 }
449
450 // force error code to be acquired
451 reader.terminate_process();
452
453 // required because if objdump stop by signal all above things suceeed
454 // (signal error message are not output through stdout/stderr)
455 if (reader.error()) {
456 cerr << "An error occur during the execution of objdump:\n\n";
457 cerr << reader.error_str() << endl;
458 return;
459 }
460}
461
462
463void output_objdump_asm(symbol_collection const & symbols,
464 string const & app_name)
465{
466 image_error error;
467 string image =
468 classes.extra_found_images.find_image_path(app_name, error,
469 true);
470
471 // this is only an optimisation, we can either filter output by
472 // directly calling objdump and rely on the symbol filtering or
473 // we can call objdump with the right parameter to just disassemble
474 // the needed part. This is a real win only when calling objdump
475 // a medium number of times, I dunno if the used threshold is optimal
476 // but it is a conservative value.
477 size_t const max_objdump_exec = 50;
478 if (symbols.size() <= max_objdump_exec || error != image_ok) {
479 symbol_collection::const_iterator cit = symbols.begin();
480 symbol_collection::const_iterator end = symbols.end();
481 for (; cit != end; ++cit) {
482 bfd_vma start = (*cit)->sample.vma;
483 bfd_vma end = start + (*cit)->size;
484 do_one_output_objdump(symbols, image, app_name,
485 start, end);
486 }
487 } else {
488 do_one_output_objdump(symbols, image,
489 app_name, 0, ~bfd_vma(0));
490 }
491}
492
493
494bool output_asm(string const & app_name)
495{
496 profile_container::symbol_choice choice;
497 choice.threshold = options::threshold;
498 choice.image_name = app_name;
499 choice.match_image = true;
500 symbol_collection symbols = samples->select_symbols(choice);
501
502 if (!symbols.empty()) {
503 sort_options options;
504 options.add_sort_option(sort_options::sample);
505 options.sort(symbols, false, false);
506
507 output_info(cout);
508
509 output_objdump_asm(symbols, app_name);
510
511 return true;
512 }
513
514 return false;
515}
516
517
518string const source_line_annotation(debug_name_id filename, size_t linenr)
519{
520 string str;
521
522 count_array_t counts = samples->samples_count(filename, linenr);
523 if (!counts.zero()) {
524 str += count_str(counts, samples->samples_count());
525 for (size_t i = 1; i < nr_events; ++i)
526 str += " ";
527 str += " :";
528 } else {
529 str = annotation_fill;
530 }
531
532 return str;
533}
534
535
536string source_symbol_annotation(debug_name_id filename, size_t linenr)
537{
538 symbol_collection const symbols = samples->find_symbol(filename, linenr);
539
540 if (symbols.empty())
541 return string();
542
543 string str = " " + begin_comment;
544
545 count_array_t counts;
546 for (size_t i = 0; i < symbols.size(); ++i) {
547 str += symbol_names.demangle(symbols[i]->name);
548 if (symbols.size() == 1)
549 str += " total: ";
550 else
551 str += " ";
552 str += count_str(symbols[i]->sample.counts,
553 samples->samples_count());
554 if (symbols.size() != 1)
555 str += ", ";
556
557 counts += symbols[i]->sample.counts;
558 }
559
560 if (symbols.size() > 1)
561 str += "total: " + count_str(counts, samples->samples_count());
562 str += end_comment;
563
564 return str;
565}
566
567
568void output_per_file_info(ostream & out, debug_name_id filename,
569 count_array_t const & total_file_count)
570{
571 out << begin_comment << '\n'
572 << in_comment << "Total samples for file : "
573 << '"' << debug_names.name(filename) << '"'
574 << '\n';
575 out << in_comment << '\n' << in_comment
576 << count_str(total_file_count, samples->samples_count())
577 << '\n';
578 out << end_comment << '\n' << '\n';
579}
580
581
582string const line0_info(debug_name_id filename)
583{
584 string annotation = source_line_annotation(filename, 0);
585 if (trim(annotation, " \t:").empty())
586 return string();
587
588 string str = "<credited to line zero> ";
589 str += annotation;
590 return str;
591}
592
593
594void do_output_one_file(ostream & out, istream & in, debug_name_id filename,
595 bool header)
596{
597 count_array_t count = samples->samples_count(filename);
598
599 if (header) {
600 output_per_file_info(out, filename, count);
601 out << line0_info(filename) << '\n';
602 }
603
604
605 if (in) {
606 string str;
607
608 for (size_t linenr = 1 ; getline(in, str) ; ++linenr) {
609 out << source_line_annotation(filename, linenr) << str
610 << source_symbol_annotation(filename, linenr)
611 << '\n';
612 }
613
614 } else {
615 // source is not available but we can at least output all the
616 // symbols belonging to this file. This make more visible the
617 // problem of having less samples for a given file than the
618 // sum of all symbols samples for this file due to inlining
619 symbol_collection const symbols = samples->select_symbols(filename);
620 for (size_t i = 0; i < symbols.size(); ++i)
621 out << symbol_annotation(symbols[i]) << endl;
622 }
623
624 if (!header) {
625 output_per_file_info(out, filename, count);
626 out << line0_info(filename) << '\n';
627 }
628}
629
630
631void output_one_file(istream & in, debug_name_id filename,
632 string const & source)
633{
634 if (output_dir.empty()) {
635 do_output_one_file(cout, in, filename, true);
636 return;
637 }
638
639 string const out_file = op_realpath(output_dir + source);
640
641 /* Just because you're paranoid doesn't mean they're not out to
642 * get you ...
643 *
644 * This is just a lame final safety check. If we found the
645 * source, then "source" should be canonical already, and
646 * can't escape from the output dir. We can't use op_realpath()
647 * alone as that needs the file to exist already.
648 *
649 * Let's not complain again if we couldn't find the file anyway.
650 */
651 if (out_file.find("/../") != string::npos) {
652 if (in) {
653 cerr << "refusing to create non-canonical filename "
654 << out_file << endl;
655 }
656 return;
657 } else if (!is_prefix(out_file, output_dir)) {
658 if (in) {
659 cerr << "refusing to create file " << out_file
660 << " outside of output directory " << output_dir
661 << endl;
662 }
663 return;
664 }
665
666 if (is_files_identical(out_file, source)) {
667 cerr << "input and output files are identical: "
668 << out_file << endl;
669 return;
670 }
671
672 if (create_path(out_file.c_str())) {
673 cerr << "unable to create file: "
674 << '"' << op_dirname(out_file) << '"' << endl;
675 return;
676 }
677
678 ofstream out(out_file.c_str());
679 if (!out) {
680 cerr << "unable to open output file "
681 << '"' << out_file << '"' << endl;
682 } else {
683 do_output_one_file(out, in, filename, false);
684 output_info(out);
685 }
686}
687
688
689/* Locate a source file from debug info, which may be relative */
690string const locate_source_file(debug_name_id filename_id)
691{
692 string const origfile = debug_names.name(filename_id);
693 string file = origfile;
694
695 if (file.empty())
696 return file;
697
698 /* Allow absolute paths to be relocated to a different directory */
699 if (file[0] == '/') {
700 vector<string>::const_iterator cit = base_dirs.begin();
701 vector<string>::const_iterator end = base_dirs.end();
702 for (; cit != end; ++cit) {
703 string path = op_realpath(*cit);
704
705 if (is_prefix(file, path)) {
706 file = file.substr(path.length());
707 break;
708 }
709 }
710 }
711
712 vector<string>::const_iterator cit = search_dirs.begin();
713 vector<string>::const_iterator end = search_dirs.end();
714
715 for (; cit != end; ++cit) {
716 string const absfile = op_realpath(*cit + "/" + file);
717
718 if (op_file_readable(absfile))
719 return absfile;
720 }
721
722 /* We didn't find a relocated absolute file, or a relative file,
723 * assume the original is correct, accounting for the
724 * possibility it's relative the cwd
725 */
726 return op_realpath(origfile);
727}
728
729
730void output_source(path_filter const & filter)
731{
732 bool const separate_file = !output_dir.empty();
733
734 if (!separate_file)
735 output_info(cout);
736
737 vector<debug_name_id> filenames =
738 samples->select_filename(options::threshold);
739
740 for (size_t i = 0 ; i < filenames.size() ; ++i) {
741 string const & source = locate_source_file(filenames[i]);
742
743 if (!filter.match(source))
744 continue;
745
746 ifstream in(source.c_str());
747
748 // it is common to have empty filename due to the lack
749 // of debug info (eg _init function) so warn only
750 // if the filename is non empty. The case: no debug
751 // info at all has already been checked.
752 if (!in && source.length()) {
753 cerr << "opannotate (warning): unable to open for "
754 "reading: " << source << endl;
755 }
756
757 if (source.length())
758 output_one_file(in, filenames[i], source);
759 }
760}
761
762
763bool annotate_source(list<string> const & images)
764{
765 annotation_fill = get_annotation_fill();
766
767 if (!output_dir.empty()) {
768
769 if (create_path(output_dir.c_str())) {
770 cerr << "unable to create " << output_dir
771 << " directory: " << endl;
772 return false;
773 }
774
775 // Make sure we have an absolute path.
776 output_dir = op_realpath(output_dir);
777 if (output_dir.length() &&
778 output_dir[output_dir.length() - 1] != '/')
779 output_dir += '/';
780
781 /* Don't let the user stomp on their sources */
782 if (output_dir == "/") {
783 cerr << "Output path of / would over-write the "
784 "source files" << endl;
785 return false;
786 }
787 }
788
789 if (assembly) {
790 bool some_output = false;
791
792 list<string>::const_iterator it = images.begin();
793 list<string>::const_iterator const end = images.end();
794
795 for (; it != end; ++it) {
796 if (output_asm(*it))
797 some_output = true;
798 }
799
800 if (!some_output) {
801 // It's the only case we must care since we know the
802 // selected image set is not empty
803 cerr << "selected image set doesn't contain any of "
804 << "the selected symbol\n";
805 }
806 } else {
807 output_source(file_filter);
808 }
809
810 return true;
811}
812
813
814int opannotate(options::spec const & spec)
815{
816 handle_options(spec);
817
818 nr_events = classes.v.size();
819
820 samples.reset(new profile_container(true, true,
821 classes.extra_found_images));
822
823 list<string> images;
824
825 list<inverted_profile> iprofiles = invert_profiles(classes);
826
827 report_image_errors(iprofiles, classes.extra_found_images);
828
829 list<inverted_profile>::iterator it = iprofiles.begin();
830 list<inverted_profile>::iterator const end = iprofiles.end();
831
832 bool debug_info = false;
833 for (; it != end; ++it) {
834 bool tmp = false;
835 populate_for_image(*samples, *it,
836 options::symbol_filter, &tmp);
837 images.push_back(it->image);
838 if (tmp)
839 debug_info = true;
840 }
841
842 if (!debug_info && !options::assembly) {
843 cerr << "opannotate (warning): no debug information available for binary "
844 << it->image << ", and --assembly not requested\n";
845 }
846
847 annotate_source(images);
848
849 return 0;
850}
851
852} // anonymous namespace
853
854
855int main(int argc, char const * argv[])
856{
857 // set the invocation, for the file headers later
858 for (int i = 0 ; i < argc ; ++i)
859 cmdline += string(argv[i]) + " ";
860
861 return run_pp_tool(argc, argv, opannotate);
862}