1 /** 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 36 using namespace std; 37 using namespace options; 38 39 namespace { 40 41 size_t nr_events; 42 43 scoped_ptr<profile_container> samples; 44 45 /// how opannotate was invoked 46 string cmdline; 47 48 /// empty annotation fill string 49 string annotation_fill; 50 51 /// string used as start / end comment to annotate source 52 string const begin_comment("/* "); 53 string const in_comment(" * "); 54 string const end_comment(" */"); 55 56 /// field width for the sample count 57 unsigned int const count_width = 6; 58 59 string 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 76 symbol_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 90 void 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 137 string 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 /// 163 int 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 260 string 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 281 bool 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 290 void 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 375 void 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 394 void 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); 420 child_reader reader("objdump", args); 421 if (reader.error()) { 422 cerr << "An error occur during the execution of objdump:\n\n"; 423 cerr << reader.error_str() << endl; 424 return; 425 } 426 427 // Read each output line from objdump and store in a list. 428 string str; 429 while (reader.getline(str)) 430 asm_lines.push_back(str); 431 432 output_objdump_str_list(symbols, app_name, asm_lines); 433 434 // objdump always returns SUCCESS so we must rely on the stderr state 435 // of objdump. If objdump error message is cryptic our own error 436 // message will be probably also cryptic 437 ostringstream std_err; 438 ostringstream std_out; 439 reader.get_data(std_out, std_err); 440 if (std_err.str().length()) { 441 cerr << "An error occur during the execution of objdump:\n\n"; 442 cerr << std_err.str() << endl; 443 return ; 444 } 445 446 // force error code to be acquired 447 reader.terminate_process(); 448 449 // required because if objdump stop by signal all above things suceeed 450 // (signal error message are not output through stdout/stderr) 451 if (reader.error()) { 452 cerr << "An error occur during the execution of objdump:\n\n"; 453 cerr << reader.error_str() << endl; 454 return; 455 } 456 } 457 458 459 void output_objdump_asm(symbol_collection const & symbols, 460 string const & app_name) 461 { 462 image_error error; 463 string image = 464 classes.extra_found_images.find_image_path(app_name, error, 465 true); 466 467 // this is only an optimisation, we can either filter output by 468 // directly calling objdump and rely on the symbol filtering or 469 // we can call objdump with the right parameter to just disassemble 470 // the needed part. This is a real win only when calling objdump 471 // a medium number of times, I dunno if the used threshold is optimal 472 // but it is a conservative value. 473 size_t const max_objdump_exec = 50; 474 if (symbols.size() <= max_objdump_exec || error != image_ok) { 475 symbol_collection::const_iterator cit = symbols.begin(); 476 symbol_collection::const_iterator end = symbols.end(); 477 for (; cit != end; ++cit) { 478 bfd_vma start = (*cit)->sample.vma; 479 bfd_vma end = start + (*cit)->size; 480 do_one_output_objdump(symbols, image, app_name, 481 start, end); 482 } 483 } else { 484 do_one_output_objdump(symbols, image, 485 app_name, 0, ~bfd_vma(0)); 486 } 487 } 488 489 490 bool output_asm(string const & app_name) 491 { 492 profile_container::symbol_choice choice; 493 choice.threshold = options::threshold; 494 choice.image_name = app_name; 495 choice.match_image = true; 496 symbol_collection symbols = samples->select_symbols(choice); 497 498 if (!symbols.empty()) { 499 sort_options options; 500 options.add_sort_option(sort_options::sample); 501 options.sort(symbols, false, false); 502 503 output_info(cout); 504 505 output_objdump_asm(symbols, app_name); 506 507 return true; 508 } 509 510 return false; 511 } 512 513 514 string const source_line_annotation(debug_name_id filename, size_t linenr) 515 { 516 string str; 517 518 count_array_t counts = samples->samples_count(filename, linenr); 519 if (!counts.zero()) { 520 str += count_str(counts, samples->samples_count()); 521 for (size_t i = 1; i < nr_events; ++i) 522 str += " "; 523 str += " :"; 524 } else { 525 str = annotation_fill; 526 } 527 528 return str; 529 } 530 531 532 string source_symbol_annotation(debug_name_id filename, size_t linenr) 533 { 534 symbol_collection const symbols = samples->find_symbol(filename, linenr); 535 536 if (symbols.empty()) 537 return string(); 538 539 string str = " " + begin_comment; 540 541 count_array_t counts; 542 for (size_t i = 0; i < symbols.size(); ++i) { 543 str += symbol_names.demangle(symbols[i]->name); 544 if (symbols.size() == 1) 545 str += " total: "; 546 else 547 str += " "; 548 str += count_str(symbols[i]->sample.counts, 549 samples->samples_count()); 550 if (symbols.size() != 1) 551 str += ", "; 552 553 counts += symbols[i]->sample.counts; 554 } 555 556 if (symbols.size() > 1) 557 str += "total: " + count_str(counts, samples->samples_count()); 558 str += end_comment; 559 560 return str; 561 } 562 563 564 void output_per_file_info(ostream & out, debug_name_id filename, 565 count_array_t const & total_file_count) 566 { 567 out << begin_comment << '\n' 568 << in_comment << "Total samples for file : " 569 << '"' << debug_names.name(filename) << '"' 570 << '\n'; 571 out << in_comment << '\n' << in_comment 572 << count_str(total_file_count, samples->samples_count()) 573 << '\n'; 574 out << end_comment << '\n' << '\n'; 575 } 576 577 578 string const line0_info(debug_name_id filename) 579 { 580 string annotation = source_line_annotation(filename, 0); 581 if (trim(annotation, " \t:").empty()) 582 return string(); 583 584 string str = "<credited to line zero> "; 585 str += annotation; 586 return str; 587 } 588 589 590 void do_output_one_file(ostream & out, istream & in, debug_name_id filename, 591 bool header) 592 { 593 count_array_t count = samples->samples_count(filename); 594 595 if (header) { 596 output_per_file_info(out, filename, count); 597 out << line0_info(filename) << '\n'; 598 } 599 600 601 if (in) { 602 string str; 603 604 for (size_t linenr = 1 ; getline(in, str) ; ++linenr) { 605 out << source_line_annotation(filename, linenr) << str 606 << source_symbol_annotation(filename, linenr) 607 << '\n'; 608 } 609 610 } else { 611 // source is not available but we can at least output all the 612 // symbols belonging to this file. This make more visible the 613 // problem of having less samples for a given file than the 614 // sum of all symbols samples for this file due to inlining 615 symbol_collection const symbols = samples->select_symbols(filename); 616 for (size_t i = 0; i < symbols.size(); ++i) 617 out << symbol_annotation(symbols[i]) << endl; 618 } 619 620 if (!header) { 621 output_per_file_info(out, filename, count); 622 out << line0_info(filename) << '\n'; 623 } 624 } 625 626 627 void output_one_file(istream & in, debug_name_id filename, 628 string const & source) 629 { 630 if (output_dir.empty()) { 631 do_output_one_file(cout, in, filename, true); 632 return; 633 } 634 635 string const out_file = op_realpath(output_dir + source); 636 637 /* Just because you're paranoid doesn't mean they're not out to 638 * get you ... 639 * 640 * This is just a lame final safety check. If we found the 641 * source, then "source" should be canonical already, and 642 * can't escape from the output dir. We can't use op_realpath() 643 * alone as that needs the file to exist already. 644 * 645 * Let's not complain again if we couldn't find the file anyway. 646 */ 647 if (out_file.find("/../") != string::npos) { 648 if (in) { 649 cerr << "refusing to create non-canonical filename " 650 << out_file << endl; 651 } 652 return; 653 } else if (!is_prefix(out_file, output_dir)) { 654 if (in) { 655 cerr << "refusing to create file " << out_file 656 << " outside of output directory " << output_dir 657 << endl; 658 } 659 return; 660 } 661 662 if (is_files_identical(out_file, source)) { 663 cerr << "input and output files are identical: " 664 << out_file << endl; 665 return; 666 } 667 668 if (create_path(out_file.c_str())) { 669 cerr << "unable to create file: " 670 << '"' << op_dirname(out_file) << '"' << endl; 671 return; 672 } 673 674 ofstream out(out_file.c_str()); 675 if (!out) { 676 cerr << "unable to open output file " 677 << '"' << out_file << '"' << endl; 678 } else { 679 do_output_one_file(out, in, filename, false); 680 output_info(out); 681 } 682 } 683 684 685 /* Locate a source file from debug info, which may be relative */ 686 string const locate_source_file(debug_name_id filename_id) 687 { 688 string const origfile = debug_names.name(filename_id); 689 string file = origfile; 690 691 if (file.empty()) 692 return file; 693 694 /* Allow absolute paths to be relocated to a different directory */ 695 if (file[0] == '/') { 696 vector<string>::const_iterator cit = base_dirs.begin(); 697 vector<string>::const_iterator end = base_dirs.end(); 698 for (; cit != end; ++cit) { 699 string path = op_realpath(*cit); 700 701 if (is_prefix(file, path)) { 702 file = file.substr(path.length()); 703 break; 704 } 705 } 706 } 707 708 vector<string>::const_iterator cit = search_dirs.begin(); 709 vector<string>::const_iterator end = search_dirs.end(); 710 711 for (; cit != end; ++cit) { 712 string const absfile = op_realpath(*cit + "/" + file); 713 714 if (op_file_readable(absfile)) 715 return absfile; 716 } 717 718 /* We didn't find a relocated absolute file, or a relative file, 719 * assume the original is correct, accounting for the 720 * possibility it's relative the cwd 721 */ 722 return op_realpath(origfile); 723 } 724 725 726 void output_source(path_filter const & filter) 727 { 728 bool const separate_file = !output_dir.empty(); 729 730 if (!separate_file) 731 output_info(cout); 732 733 vector<debug_name_id> filenames = 734 samples->select_filename(options::threshold); 735 736 for (size_t i = 0 ; i < filenames.size() ; ++i) { 737 string const & source = locate_source_file(filenames[i]); 738 739 if (!filter.match(source)) 740 continue; 741 742 ifstream in(source.c_str()); 743 744 // it is common to have empty filename due to the lack 745 // of debug info (eg _init function) so warn only 746 // if the filename is non empty. The case: no debug 747 // info at all has already been checked. 748 if (!in && source.length()) { 749 cerr << "opannotate (warning): unable to open for " 750 "reading: " << source << endl; 751 } 752 753 if (source.length()) 754 output_one_file(in, filenames[i], source); 755 } 756 } 757 758 759 bool annotate_source(list<string> const & images) 760 { 761 annotation_fill = get_annotation_fill(); 762 763 if (!output_dir.empty()) { 764 765 if (create_path(output_dir.c_str())) { 766 cerr << "unable to create " << output_dir 767 << " directory: " << endl; 768 return false; 769 } 770 771 // Make sure we have an absolute path. 772 output_dir = op_realpath(output_dir); 773 if (output_dir.length() && 774 output_dir[output_dir.length() - 1] != '/') 775 output_dir += '/'; 776 777 /* Don't let the user stomp on their sources */ 778 if (output_dir == "/") { 779 cerr << "Output path of / would over-write the " 780 "source files" << endl; 781 return false; 782 } 783 } 784 785 if (assembly) { 786 bool some_output = false; 787 788 list<string>::const_iterator it = images.begin(); 789 list<string>::const_iterator const end = images.end(); 790 791 for (; it != end; ++it) { 792 if (output_asm(*it)) 793 some_output = true; 794 } 795 796 if (!some_output) { 797 // It's the only case we must care since we know the 798 // selected image set is not empty 799 cerr << "selected image set doesn't contain any of " 800 << "the selected symbol\n"; 801 } 802 } else { 803 output_source(file_filter); 804 } 805 806 return true; 807 } 808 809 810 int opannotate(options::spec const & spec) 811 { 812 handle_options(spec); 813 814 nr_events = classes.v.size(); 815 816 samples.reset(new profile_container(true, true, 817 classes.extra_found_images)); 818 819 list<string> images; 820 821 list<inverted_profile> iprofiles = invert_profiles(classes); 822 823 report_image_errors(iprofiles, classes.extra_found_images); 824 825 list<inverted_profile>::iterator it = iprofiles.begin(); 826 list<inverted_profile>::iterator const end = iprofiles.end(); 827 828 bool debug_info = false; 829 for (; it != end; ++it) { 830 bool tmp = false; 831 populate_for_image(*samples, *it, 832 options::symbol_filter, &tmp); 833 images.push_back(it->image); 834 if (tmp) 835 debug_info = true; 836 } 837 838 if (!debug_info && !options::assembly) { 839 cerr << "opannotate (warning): no debug information available for binary " 840 << it->image << ", and --assembly not requested\n"; 841 } 842 843 annotate_source(images); 844 845 return 0; 846 } 847 848 } // anonymous namespace 849 850 851 int main(int argc, char const * argv[]) 852 { 853 // set the invocation, for the file headers later 854 for (int i = 0 ; i < argc ; ++i) 855 cmdline += string(argv[i]) + " "; 856 857 return run_pp_tool(argc, argv, opannotate); 858 } 859