/** * @file opgprof.cpp * Implement opgprof utility * * @remark Copyright 2003 OProfile authors * @remark Read the file COPYING * * @author John Levon * @author Philippe Elie */ #include <iostream> #include <cstdio> #include "op_header.h" #include "profile.h" #include "op_libiberty.h" #include "op_fileio.h" #include "string_filter.h" #include "profile_container.h" #include "arrange_profiles.h" #include "image_errors.h" #include "opgprof_options.h" #include "cverb.h" #include "op_file.h" using namespace std; extern profile_classes classes; namespace { #define GMON_VERSION 1 #define GMON_TAG_TIME_HIST 0 #define GMON_TAG_CG_ARC 1 struct gmon_hdr { char cookie[4]; u32 version; u32 spare[3]; }; void op_write_vma(FILE * fp, op_bfd const & abfd, bfd_vma vma) { // bfd vma write size is a per binary property not a bfd // configuration property switch (abfd.bfd_arch_bits_per_address()) { case 32: op_write_u32(fp, vma); break; case 64: op_write_u64(fp, vma); break; default: cerr << "oprofile: unknown vma size for this binary\n"; exit(EXIT_FAILURE); } } void get_vma_range(bfd_vma & min, bfd_vma & max, profile_container const & samples) { min = bfd_vma(-1); max = 0; sample_container::samples_iterator it = samples.begin(); sample_container::samples_iterator end = samples.end(); for (; it != end ; ++it) { if (it->second.vma < min) min = it->second.vma; if (it->second.vma > max) max = it->second.vma; } if (min == bfd_vma(-1)) min = 0; // we must return a range [min, max) not a range [min, max] if (max != 0) max += 1; } /** * @param abfd bfd object * @param samples_files profile container to act on * @param gap a power of 2 * * return true if all sample in samples_files are at least aligned on gap. This * function is used to get at runtime the right size of gprof bin size * reducing gmon.out on arch with fixed size instruction length * */ bool aligned_samples(profile_container const & samples, int gap) { sample_container::samples_iterator it = samples.begin(); sample_container::samples_iterator end = samples.end(); for (; it != end ; ++it) { if (it->second.vma % gap) return false; } return true; } void output_cg(FILE * fp, op_bfd const & abfd, profile_t const & cg_db) { opd_header const & header = cg_db.get_header(); bfd_vma offset = 0; if (header.is_kernel) offset = abfd.get_start_offset(0); else offset = header.anon_start; profile_t::iterator_pair p_it = cg_db.samples_range(); for (; p_it.first != p_it.second; ++p_it.first) { bfd_vma from = p_it.first.vma() >> 32; bfd_vma to = p_it.first.vma() & 0xffffffff; op_write_u8(fp, GMON_TAG_CG_ARC); op_write_vma(fp, abfd, abfd.offset_to_pc(from + offset)); op_write_vma(fp, abfd, abfd.offset_to_pc(to + offset)); u32 count = p_it.first.count(); if (count != p_it.first.count()) { count = (u32)-1; cerr << "Warning: capping sample count by " << p_it.first.count() - count << endl; } op_write_u32(fp, p_it.first.count()); } } void output_gprof(op_bfd const & abfd, profile_container const & samples, profile_t const & cg_db, string const & gmon_filename) { static gmon_hdr hdr = { { 'g', 'm', 'o', 'n' }, GMON_VERSION, {0, 0, 0 } }; bfd_vma low_pc; bfd_vma high_pc; /* FIXME worth to try more multiplier ? */ int multiplier = 2; if (aligned_samples(samples, 4)) multiplier = 8; cverb << vdebug << "opgrof multiplier: " << multiplier << endl; get_vma_range(low_pc, high_pc, samples); cverb << vdebug << "low_pc: " << hex << low_pc << " " << "high_pc: " << high_pc << dec << endl; // round-down low_pc to ensure bin number is correct in the inner loop low_pc = (low_pc / multiplier) * multiplier; // round-up high_pc to ensure a correct histsize calculus high_pc = ((high_pc + multiplier - 1) / multiplier) * multiplier; cverb << vdebug << "low_pc: " << hex << low_pc << " " << "high_pc: " << high_pc << dec << endl; size_t histsize = (high_pc - low_pc) / multiplier; // FIXME: must we skip the flat profile write if histsize == 0 ? // (this can occur with callgraph w/o samples to the binary) but in // this case user must gprof --no-flat-profile which is a bit boring // and result *seems* weirds. FILE * fp = op_open_file(gmon_filename.c_str(), "w"); op_write_file(fp, &hdr, sizeof(gmon_hdr)); op_write_u8(fp, GMON_TAG_TIME_HIST); op_write_vma(fp, abfd, low_pc); op_write_vma(fp, abfd, high_pc); /* size of histogram */ op_write_u32(fp, histsize); /* profiling rate */ op_write_u32(fp, 1); op_write_file(fp, "samples\0\0\0\0\0\0\0\0", 15); /* abbreviation */ op_write_u8(fp, '1'); u16 * hist = (u16*)xcalloc(histsize, sizeof(u16)); profile_container::symbol_choice choice; choice.threshold = options::threshold; symbol_collection symbols = samples.select_symbols(choice); symbol_collection::const_iterator sit = symbols.begin(); symbol_collection::const_iterator send = symbols.end(); for (; sit != send; ++sit) { sample_container::samples_iterator it = samples.begin(*sit); sample_container::samples_iterator end = samples.end(*sit); for (; it != end ; ++it) { u32 pos = (it->second.vma - low_pc) / multiplier; count_type count = it->second.counts[0]; if (pos >= histsize) { cerr << "Bogus histogram bin " << pos << ", larger than " << pos << " !\n"; continue; } if (hist[pos] + count > (u16)-1) { hist[pos] = (u16)-1; cerr << "Warning: capping sample count by " << hist[pos] + count - ((u16)-1) << endl; } else { hist[pos] += (u16)count; } } } op_write_file(fp, hist, histsize * sizeof(u16)); if (!cg_db.empty()) output_cg(fp, abfd, cg_db); op_close_file(fp); free(hist); } void load_samples(op_bfd const & abfd, list<profile_sample_files> const & files, string const & image, profile_container & samples) { list<profile_sample_files>::const_iterator it = files.begin(); list<profile_sample_files>::const_iterator const end = files.end(); for (; it != end; ++it) { // we can get call graph w/o any samples to the binary if (it->sample_filename.empty()) continue; cverb << vsfile << "loading flat samples files : " << it->sample_filename << endl; profile_t profile; profile.add_sample_file(it->sample_filename); profile.set_offset(abfd); check_mtime(abfd.get_filename(), profile.get_header()); samples.add(profile, abfd, image, 0); } } void load_cg(profile_t & cg_db, list<profile_sample_files> const & files) { list<profile_sample_files>::const_iterator it = files.begin(); list<profile_sample_files>::const_iterator const end = files.end(); /* the list of non cg files is a super set of the list of cg file * (module always log a samples to non-cg files before logging * call stack) so by using the list of non-cg file we are sure to get * all existing cg files. */ for (; it != end; ++it) { list<string>::const_iterator cit; list<string>::const_iterator const cend = it->cg_files.end(); for (cit = it->cg_files.begin(); cit != cend; ++cit) { // FIXME: do we need filtering ? /* We can't handle start_offset now but after splitting * data in from/to eip. */ cverb << vsfile << "loading cg samples file : " << *cit << endl; cg_db.add_sample_file(*cit); } } } int opgprof(options::spec const & spec) { handle_options(spec); profile_container samples(false, true, classes.extra_found_images); bool ok = image_profile.error == image_ok; // FIXME: symbol_filter would be allowed through option op_bfd abfd(image_profile.image, string_filter(), classes.extra_found_images, ok); if (!ok && image_profile.error == image_ok) image_profile.error = image_format_failure; if (image_profile.error != image_ok) { report_image_error(image_profile, true, classes.extra_found_images); exit(EXIT_FAILURE); } profile_t cg_db; image_group_set const & groups = image_profile.groups[0]; image_group_set::const_iterator it; for (it = groups.begin(); it != groups.end(); ++it) { load_samples(abfd, it->files, image_profile.image, samples); load_cg(cg_db, it->files); } output_gprof(abfd, samples, cg_db, options::gmon_filename); return 0; } } // anonymous namespace int main(int argc, char const * argv[]) { return run_pp_tool(argc, argv, opgprof); }