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