Mike Dodd | 8cfa702 | 2010-11-17 11:12:26 -0800 | [diff] [blame^] | 1 | /** |
| 2 | * @file opgprof.cpp |
| 3 | * Implement opgprof 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 <cstdio> |
| 14 | |
| 15 | #include "op_header.h" |
| 16 | #include "profile.h" |
| 17 | #include "op_libiberty.h" |
| 18 | #include "op_fileio.h" |
| 19 | #include "string_filter.h" |
| 20 | #include "profile_container.h" |
| 21 | #include "arrange_profiles.h" |
| 22 | #include "image_errors.h" |
| 23 | #include "opgprof_options.h" |
| 24 | #include "cverb.h" |
| 25 | #include "op_file.h" |
| 26 | |
| 27 | using namespace std; |
| 28 | |
| 29 | extern profile_classes classes; |
| 30 | |
| 31 | namespace { |
| 32 | |
| 33 | #define GMON_VERSION 1 |
| 34 | #define GMON_TAG_TIME_HIST 0 |
| 35 | #define GMON_TAG_CG_ARC 1 |
| 36 | |
| 37 | struct gmon_hdr { |
| 38 | char cookie[4]; |
| 39 | u32 version; |
| 40 | u32 spare[3]; |
| 41 | }; |
| 42 | |
| 43 | |
| 44 | void op_write_vma(FILE * fp, op_bfd const & abfd, bfd_vma vma) |
| 45 | { |
| 46 | // bfd vma write size is a per binary property not a bfd |
| 47 | // configuration property |
| 48 | switch (abfd.bfd_arch_bits_per_address()) { |
| 49 | case 32: |
| 50 | op_write_u32(fp, vma); |
| 51 | break; |
| 52 | case 64: |
| 53 | op_write_u64(fp, vma); |
| 54 | break; |
| 55 | default: |
| 56 | cerr << "oprofile: unknown vma size for this binary\n"; |
| 57 | exit(EXIT_FAILURE); |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | |
| 62 | void get_vma_range(bfd_vma & min, bfd_vma & max, |
| 63 | profile_container const & samples) |
| 64 | { |
| 65 | min = bfd_vma(-1); |
| 66 | max = 0; |
| 67 | |
| 68 | sample_container::samples_iterator it = samples.begin(); |
| 69 | sample_container::samples_iterator end = samples.end(); |
| 70 | for (; it != end ; ++it) { |
| 71 | if (it->second.vma < min) |
| 72 | min = it->second.vma; |
| 73 | if (it->second.vma > max) |
| 74 | max = it->second.vma; |
| 75 | } |
| 76 | |
| 77 | if (min == bfd_vma(-1)) |
| 78 | min = 0; |
| 79 | // we must return a range [min, max) not a range [min, max] |
| 80 | if (max != 0) |
| 81 | max += 1; |
| 82 | } |
| 83 | |
| 84 | |
| 85 | /** |
| 86 | * @param abfd bfd object |
| 87 | * @param samples_files profile container to act on |
| 88 | * @param gap a power of 2 |
| 89 | * |
| 90 | * return true if all sample in samples_files are at least aligned on gap. This |
| 91 | * function is used to get at runtime the right size of gprof bin size |
| 92 | * reducing gmon.out on arch with fixed size instruction length |
| 93 | * |
| 94 | */ |
| 95 | bool aligned_samples(profile_container const & samples, int gap) |
| 96 | { |
| 97 | sample_container::samples_iterator it = samples.begin(); |
| 98 | sample_container::samples_iterator end = samples.end(); |
| 99 | for (; it != end ; ++it) { |
| 100 | if (it->second.vma % gap) |
| 101 | return false; |
| 102 | } |
| 103 | |
| 104 | return true; |
| 105 | } |
| 106 | |
| 107 | |
| 108 | void output_cg(FILE * fp, op_bfd const & abfd, profile_t const & cg_db) |
| 109 | { |
| 110 | opd_header const & header = cg_db.get_header(); |
| 111 | bfd_vma offset = 0; |
| 112 | if (header.is_kernel) |
| 113 | offset = abfd.get_start_offset(0); |
| 114 | else |
| 115 | offset = header.anon_start; |
| 116 | |
| 117 | profile_t::iterator_pair p_it = cg_db.samples_range(); |
| 118 | for (; p_it.first != p_it.second; ++p_it.first) { |
| 119 | bfd_vma from = p_it.first.vma() >> 32; |
| 120 | bfd_vma to = p_it.first.vma() & 0xffffffff; |
| 121 | |
| 122 | op_write_u8(fp, GMON_TAG_CG_ARC); |
| 123 | op_write_vma(fp, abfd, abfd.offset_to_pc(from + offset)); |
| 124 | op_write_vma(fp, abfd, abfd.offset_to_pc(to + offset)); |
| 125 | u32 count = p_it.first.count(); |
| 126 | if (count != p_it.first.count()) { |
| 127 | count = (u32)-1; |
| 128 | cerr << "Warning: capping sample count by " |
| 129 | << p_it.first.count() - count << endl; |
| 130 | } |
| 131 | op_write_u32(fp, p_it.first.count()); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | |
| 136 | void output_gprof(op_bfd const & abfd, profile_container const & samples, |
| 137 | profile_t const & cg_db, string const & gmon_filename) |
| 138 | { |
| 139 | static gmon_hdr hdr = { { 'g', 'm', 'o', 'n' }, GMON_VERSION, {0, 0, 0 } }; |
| 140 | |
| 141 | bfd_vma low_pc; |
| 142 | bfd_vma high_pc; |
| 143 | |
| 144 | /* FIXME worth to try more multiplier ? */ |
| 145 | int multiplier = 2; |
| 146 | if (aligned_samples(samples, 4)) |
| 147 | multiplier = 8; |
| 148 | |
| 149 | cverb << vdebug << "opgrof multiplier: " << multiplier << endl; |
| 150 | |
| 151 | get_vma_range(low_pc, high_pc, samples); |
| 152 | |
| 153 | cverb << vdebug << "low_pc: " << hex << low_pc << " " << "high_pc: " |
| 154 | << high_pc << dec << endl; |
| 155 | |
| 156 | // round-down low_pc to ensure bin number is correct in the inner loop |
| 157 | low_pc = (low_pc / multiplier) * multiplier; |
| 158 | // round-up high_pc to ensure a correct histsize calculus |
| 159 | high_pc = ((high_pc + multiplier - 1) / multiplier) * multiplier; |
| 160 | |
| 161 | cverb << vdebug << "low_pc: " << hex << low_pc << " " << "high_pc: " |
| 162 | << high_pc << dec << endl; |
| 163 | |
| 164 | size_t histsize = (high_pc - low_pc) / multiplier; |
| 165 | |
| 166 | // FIXME: must we skip the flat profile write if histsize == 0 ? |
| 167 | // (this can occur with callgraph w/o samples to the binary) but in |
| 168 | // this case user must gprof --no-flat-profile which is a bit boring |
| 169 | // and result *seems* weirds. |
| 170 | |
| 171 | FILE * fp = op_open_file(gmon_filename.c_str(), "w"); |
| 172 | |
| 173 | op_write_file(fp, &hdr, sizeof(gmon_hdr)); |
| 174 | op_write_u8(fp, GMON_TAG_TIME_HIST); |
| 175 | |
| 176 | op_write_vma(fp, abfd, low_pc); |
| 177 | op_write_vma(fp, abfd, high_pc); |
| 178 | /* size of histogram */ |
| 179 | op_write_u32(fp, histsize); |
| 180 | /* profiling rate */ |
| 181 | op_write_u32(fp, 1); |
| 182 | op_write_file(fp, "samples\0\0\0\0\0\0\0\0", 15); |
| 183 | /* abbreviation */ |
| 184 | op_write_u8(fp, '1'); |
| 185 | |
| 186 | u16 * hist = (u16*)xcalloc(histsize, sizeof(u16)); |
| 187 | |
| 188 | profile_container::symbol_choice choice; |
| 189 | choice.threshold = options::threshold; |
| 190 | symbol_collection symbols = samples.select_symbols(choice); |
| 191 | |
| 192 | symbol_collection::const_iterator sit = symbols.begin(); |
| 193 | symbol_collection::const_iterator send = symbols.end(); |
| 194 | |
| 195 | for (; sit != send; ++sit) { |
| 196 | sample_container::samples_iterator it = samples.begin(*sit); |
| 197 | sample_container::samples_iterator end = samples.end(*sit); |
| 198 | for (; it != end ; ++it) { |
| 199 | u32 pos = (it->second.vma - low_pc) / multiplier; |
| 200 | count_type count = it->second.counts[0]; |
| 201 | |
| 202 | if (pos >= histsize) { |
| 203 | cerr << "Bogus histogram bin " << pos |
| 204 | << ", larger than " << pos << " !\n"; |
| 205 | continue; |
| 206 | } |
| 207 | |
| 208 | if (hist[pos] + count > (u16)-1) { |
| 209 | hist[pos] = (u16)-1; |
| 210 | cerr << "Warning: capping sample count by " |
| 211 | << hist[pos] + count - ((u16)-1) << endl; |
| 212 | } else { |
| 213 | hist[pos] += (u16)count; |
| 214 | } |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | op_write_file(fp, hist, histsize * sizeof(u16)); |
| 219 | |
| 220 | if (!cg_db.empty()) |
| 221 | output_cg(fp, abfd, cg_db); |
| 222 | |
| 223 | op_close_file(fp); |
| 224 | |
| 225 | free(hist); |
| 226 | } |
| 227 | |
| 228 | |
| 229 | void |
| 230 | load_samples(op_bfd const & abfd, list<profile_sample_files> const & files, |
| 231 | string const & image, profile_container & samples) |
| 232 | { |
| 233 | list<profile_sample_files>::const_iterator it = files.begin(); |
| 234 | list<profile_sample_files>::const_iterator const end = files.end(); |
| 235 | |
| 236 | for (; it != end; ++it) { |
| 237 | // we can get call graph w/o any samples to the binary |
| 238 | if (it->sample_filename.empty()) |
| 239 | continue; |
| 240 | |
| 241 | cverb << vsfile << "loading flat samples files : " |
| 242 | << it->sample_filename << endl; |
| 243 | |
| 244 | profile_t profile; |
| 245 | |
| 246 | profile.add_sample_file(it->sample_filename); |
| 247 | profile.set_offset(abfd); |
| 248 | |
| 249 | check_mtime(abfd.get_filename(), profile.get_header()); |
| 250 | |
| 251 | samples.add(profile, abfd, image, 0); |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | |
| 256 | void load_cg(profile_t & cg_db, list<profile_sample_files> const & files) |
| 257 | { |
| 258 | list<profile_sample_files>::const_iterator it = files.begin(); |
| 259 | list<profile_sample_files>::const_iterator const end = files.end(); |
| 260 | |
| 261 | /* the list of non cg files is a super set of the list of cg file |
| 262 | * (module always log a samples to non-cg files before logging |
| 263 | * call stack) so by using the list of non-cg file we are sure to get |
| 264 | * all existing cg files. |
| 265 | */ |
| 266 | for (; it != end; ++it) { |
| 267 | list<string>::const_iterator cit; |
| 268 | list<string>::const_iterator const cend = it->cg_files.end(); |
| 269 | for (cit = it->cg_files.begin(); cit != cend; ++cit) { |
| 270 | // FIXME: do we need filtering ? |
| 271 | /* We can't handle start_offset now but after splitting |
| 272 | * data in from/to eip. */ |
| 273 | cverb << vsfile << "loading cg samples file : " |
| 274 | << *cit << endl; |
| 275 | cg_db.add_sample_file(*cit); |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | |
| 281 | int opgprof(options::spec const & spec) |
| 282 | { |
| 283 | handle_options(spec); |
| 284 | |
| 285 | profile_container samples(false, true, classes.extra_found_images); |
| 286 | |
| 287 | bool ok = image_profile.error == image_ok; |
| 288 | // FIXME: symbol_filter would be allowed through option |
| 289 | op_bfd abfd(image_profile.image, string_filter(), |
| 290 | classes.extra_found_images, ok); |
| 291 | if (!ok && image_profile.error == image_ok) |
| 292 | image_profile.error = image_format_failure; |
| 293 | |
| 294 | if (image_profile.error != image_ok) { |
| 295 | report_image_error(image_profile, true, |
| 296 | classes.extra_found_images); |
| 297 | exit(EXIT_FAILURE); |
| 298 | } |
| 299 | |
| 300 | profile_t cg_db; |
| 301 | |
| 302 | image_group_set const & groups = image_profile.groups[0]; |
| 303 | image_group_set::const_iterator it; |
| 304 | for (it = groups.begin(); it != groups.end(); ++it) { |
| 305 | load_samples(abfd, it->files, image_profile.image, samples); |
| 306 | |
| 307 | load_cg(cg_db, it->files); |
| 308 | } |
| 309 | |
| 310 | output_gprof(abfd, samples, cg_db, options::gmon_filename); |
| 311 | |
| 312 | return 0; |
| 313 | } |
| 314 | |
| 315 | |
| 316 | } // anonymous namespace |
| 317 | |
| 318 | |
| 319 | int main(int argc, char const * argv[]) |
| 320 | { |
| 321 | return run_pp_tool(argc, argv, opgprof); |
| 322 | } |