• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#! /usr/bin/env perl
2
3# Copyright (c) 1998-2007, Google Inc.
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are
8# met:
9#
10#     * Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12#     * Redistributions in binary form must reproduce the above
13# copyright notice, this list of conditions and the following disclaimer
14# in the documentation and/or other materials provided with the
15# distribution.
16#     * Neither the name of Google Inc. nor the names of its
17# contributors may be used to endorse or promote products derived from
18# this software without specific prior written permission.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32# ---
33# Program for printing the profile generated by common/profiler.cc,
34# or by the heap profiler (common/debugallocation.cc)
35#
36# The profile contains a sequence of entries of the form:
37#       <count> <stack trace>
38# This program parses the profile, and generates user-readable
39# output.
40#
41# Examples:
42#
43# % tools/jeprof "program" "profile"
44#   Enters "interactive" mode
45#
46# % tools/jeprof --text "program" "profile"
47#   Generates one line per procedure
48#
49# % tools/jeprof --gv "program" "profile"
50#   Generates annotated call-graph and displays via "gv"
51#
52# % tools/jeprof --gv --focus=Mutex "program" "profile"
53#   Restrict to code paths that involve an entry that matches "Mutex"
54#
55# % tools/jeprof --gv --focus=Mutex --ignore=string "program" "profile"
56#   Restrict to code paths that involve an entry that matches "Mutex"
57#   and does not match "string"
58#
59# % tools/jeprof --list=IBF_CheckDocid "program" "profile"
60#   Generates disassembly listing of all routines with at least one
61#   sample that match the --list=<regexp> pattern.  The listing is
62#   annotated with the flat and cumulative sample counts at each line.
63#
64# % tools/jeprof --disasm=IBF_CheckDocid "program" "profile"
65#   Generates disassembly listing of all routines with at least one
66#   sample that match the --disasm=<regexp> pattern.  The listing is
67#   annotated with the flat and cumulative sample counts at each PC value.
68#
69# TODO: Use color to indicate files?
70
71use strict;
72use warnings;
73use Getopt::Long;
74
75my $JEPROF_VERSION = "@jemalloc_version@";
76my $PPROF_VERSION = "2.0";
77
78# These are the object tools we use which can come from a
79# user-specified location using --tools, from the JEPROF_TOOLS
80# environment variable, or from the environment.
81my %obj_tool_map = (
82  "objdump" => "objdump",
83  "nm" => "nm",
84  "addr2line" => "addr2line",
85  "c++filt" => "c++filt",
86  ## ConfigureObjTools may add architecture-specific entries:
87  #"nm_pdb" => "nm-pdb",       # for reading windows (PDB-format) executables
88  #"addr2line_pdb" => "addr2line-pdb",                                # ditto
89  #"otool" => "otool",         # equivalent of objdump on OS X
90);
91# NOTE: these are lists, so you can put in commandline flags if you want.
92my @DOT = ("dot");          # leave non-absolute, since it may be in /usr/local
93my @GV = ("gv");
94my @EVINCE = ("evince");    # could also be xpdf or perhaps acroread
95my @KCACHEGRIND = ("kcachegrind");
96my @PS2PDF = ("ps2pdf");
97# These are used for dynamic profiles
98my @URL_FETCHER = ("curl", "-s", "--fail");
99
100# These are the web pages that servers need to support for dynamic profiles
101my $HEAP_PAGE = "/pprof/heap";
102my $PROFILE_PAGE = "/pprof/profile";   # must support cgi-param "?seconds=#"
103my $PMUPROFILE_PAGE = "/pprof/pmuprofile(?:\\?.*)?"; # must support cgi-param
104                                                # ?seconds=#&event=x&period=n
105my $GROWTH_PAGE = "/pprof/growth";
106my $CONTENTION_PAGE = "/pprof/contention";
107my $WALL_PAGE = "/pprof/wall(?:\\?.*)?";  # accepts options like namefilter
108my $FILTEREDPROFILE_PAGE = "/pprof/filteredprofile(?:\\?.*)?";
109my $CENSUSPROFILE_PAGE = "/pprof/censusprofile(?:\\?.*)?"; # must support cgi-param
110                                                       # "?seconds=#",
111                                                       # "?tags_regexp=#" and
112                                                       # "?type=#".
113my $SYMBOL_PAGE = "/pprof/symbol";     # must support symbol lookup via POST
114my $PROGRAM_NAME_PAGE = "/pprof/cmdline";
115
116# These are the web pages that can be named on the command line.
117# All the alternatives must begin with /.
118my $PROFILES = "($HEAP_PAGE|$PROFILE_PAGE|$PMUPROFILE_PAGE|" .
119               "$GROWTH_PAGE|$CONTENTION_PAGE|$WALL_PAGE|" .
120               "$FILTEREDPROFILE_PAGE|$CENSUSPROFILE_PAGE)";
121
122# default binary name
123my $UNKNOWN_BINARY = "(unknown)";
124
125# There is a pervasive dependency on the length (in hex characters,
126# i.e., nibbles) of an address, distinguishing between 32-bit and
127# 64-bit profiles.  To err on the safe size, default to 64-bit here:
128my $address_length = 16;
129
130my $dev_null = "/dev/null";
131if (! -e $dev_null && $^O =~ /MSWin/) {    # $^O is the OS perl was built for
132  $dev_null = "nul";
133}
134
135# A list of paths to search for shared object files
136my @prefix_list = ();
137
138# Special routine name that should not have any symbols.
139# Used as separator to parse "addr2line -i" output.
140my $sep_symbol = '_fini';
141my $sep_address = undef;
142
143##### Argument parsing #####
144
145sub usage_string {
146  return <<EOF;
147Usage:
148jeprof [options] <program> <profiles>
149   <profiles> is a space separated list of profile names.
150jeprof [options] <symbolized-profiles>
151   <symbolized-profiles> is a list of profile files where each file contains
152   the necessary symbol mappings  as well as profile data (likely generated
153   with --raw).
154jeprof [options] <profile>
155   <profile> is a remote form.  Symbols are obtained from host:port$SYMBOL_PAGE
156
157   Each name can be:
158   /path/to/profile        - a path to a profile file
159   host:port[/<service>]   - a location of a service to get profile from
160
161   The /<service> can be $HEAP_PAGE, $PROFILE_PAGE, /pprof/pmuprofile,
162                         $GROWTH_PAGE, $CONTENTION_PAGE, /pprof/wall,
163                         $CENSUSPROFILE_PAGE, or /pprof/filteredprofile.
164   For instance:
165     jeprof http://myserver.com:80$HEAP_PAGE
166   If /<service> is omitted, the service defaults to $PROFILE_PAGE (cpu profiling).
167jeprof --symbols <program>
168   Maps addresses to symbol names.  In this mode, stdin should be a
169   list of library mappings, in the same format as is found in the heap-
170   and cpu-profile files (this loosely matches that of /proc/self/maps
171   on linux), followed by a list of hex addresses to map, one per line.
172
173   For more help with querying remote servers, including how to add the
174   necessary server-side support code, see this filename (or one like it):
175
176   /usr/doc/gperftools-$PPROF_VERSION/pprof_remote_servers.html
177
178Options:
179   --cum               Sort by cumulative data
180   --base=<base>       Subtract <base> from <profile> before display
181   --interactive       Run in interactive mode (interactive "help" gives help) [default]
182   --seconds=<n>       Length of time for dynamic profiles [default=30 secs]
183   --add_lib=<file>    Read additional symbols and line info from the given library
184   --lib_prefix=<dir>  Comma separated list of library path prefixes
185
186Reporting Granularity:
187   --addresses         Report at address level
188   --lines             Report at source line level
189   --functions         Report at function level [default]
190   --files             Report at source file level
191
192Output type:
193   --text              Generate text report
194   --callgrind         Generate callgrind format to stdout
195   --gv                Generate Postscript and display
196   --evince            Generate PDF and display
197   --web               Generate SVG and display
198   --list=<regexp>     Generate source listing of matching routines
199   --disasm=<regexp>   Generate disassembly of matching routines
200   --symbols           Print demangled symbol names found at given addresses
201   --dot               Generate DOT file to stdout
202   --ps                Generate Postcript to stdout
203   --pdf               Generate PDF to stdout
204   --svg               Generate SVG to stdout
205   --gif               Generate GIF to stdout
206   --raw               Generate symbolized jeprof data (useful with remote fetch)
207
208Heap-Profile Options:
209   --inuse_space       Display in-use (mega)bytes [default]
210   --inuse_objects     Display in-use objects
211   --alloc_space       Display allocated (mega)bytes
212   --alloc_objects     Display allocated objects
213   --show_bytes        Display space in bytes
214   --drop_negative     Ignore negative differences
215
216Contention-profile options:
217   --total_delay       Display total delay at each region [default]
218   --contentions       Display number of delays at each region
219   --mean_delay        Display mean delay at each region
220
221Call-graph Options:
222   --nodecount=<n>     Show at most so many nodes [default=80]
223   --nodefraction=<f>  Hide nodes below <f>*total [default=.005]
224   --edgefraction=<f>  Hide edges below <f>*total [default=.001]
225   --maxdegree=<n>     Max incoming/outgoing edges per node [default=8]
226   --focus=<regexp>    Focus on backtraces with nodes matching <regexp>
227   --thread=<n>        Show profile for thread <n>
228   --ignore=<regexp>   Ignore backtraces with nodes matching <regexp>
229   --scale=<n>         Set GV scaling [default=0]
230   --heapcheck         Make nodes with non-0 object counts
231                       (i.e. direct leak generators) more visible
232   --retain=<regexp>   Retain only nodes that match <regexp>
233   --exclude=<regexp>  Exclude all nodes that match <regexp>
234
235Miscellaneous:
236   --tools=<prefix or binary:fullpath>[,...]   \$PATH for object tool pathnames
237   --test              Run unit tests
238   --help              This message
239   --version           Version information
240
241Environment Variables:
242   JEPROF_TMPDIR        Profiles directory. Defaults to \$HOME/jeprof
243   JEPROF_TOOLS         Prefix for object tools pathnames
244
245Examples:
246
247jeprof /bin/ls ls.prof
248                       Enters "interactive" mode
249jeprof --text /bin/ls ls.prof
250                       Outputs one line per procedure
251jeprof --web /bin/ls ls.prof
252                       Displays annotated call-graph in web browser
253jeprof --gv /bin/ls ls.prof
254                       Displays annotated call-graph via 'gv'
255jeprof --gv --focus=Mutex /bin/ls ls.prof
256                       Restricts to code paths including a .*Mutex.* entry
257jeprof --gv --focus=Mutex --ignore=string /bin/ls ls.prof
258                       Code paths including Mutex but not string
259jeprof --list=getdir /bin/ls ls.prof
260                       (Per-line) annotated source listing for getdir()
261jeprof --disasm=getdir /bin/ls ls.prof
262                       (Per-PC) annotated disassembly for getdir()
263
264jeprof http://localhost:1234/
265                       Enters "interactive" mode
266jeprof --text localhost:1234
267                       Outputs one line per procedure for localhost:1234
268jeprof --raw localhost:1234 > ./local.raw
269jeprof --text ./local.raw
270                       Fetches a remote profile for later analysis and then
271                       analyzes it in text mode.
272EOF
273}
274
275sub version_string {
276  return <<EOF
277jeprof (part of jemalloc $JEPROF_VERSION)
278based on pprof (part of gperftools $PPROF_VERSION)
279
280Copyright 1998-2007 Google Inc.
281
282This is BSD licensed software; see the source for copying conditions
283and license information.
284There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
285PARTICULAR PURPOSE.
286EOF
287}
288
289sub usage {
290  my $msg = shift;
291  print STDERR "$msg\n\n";
292  print STDERR usage_string();
293  print STDERR "\nFATAL ERROR: $msg\n";    # just as a reminder
294  exit(1);
295}
296
297sub Init() {
298  # Setup tmp-file name and handler to clean it up.
299  # We do this in the very beginning so that we can use
300  # error() and cleanup() function anytime here after.
301  $main::tmpfile_sym = "/tmp/jeprof$$.sym";
302  $main::tmpfile_ps = "/tmp/jeprof$$";
303  $main::next_tmpfile = 0;
304  $SIG{'INT'} = \&sighandler;
305
306  # Cache from filename/linenumber to source code
307  $main::source_cache = ();
308
309  $main::opt_help = 0;
310  $main::opt_version = 0;
311
312  $main::opt_cum = 0;
313  $main::opt_base = '';
314  $main::opt_addresses = 0;
315  $main::opt_lines = 0;
316  $main::opt_functions = 0;
317  $main::opt_files = 0;
318  $main::opt_lib_prefix = "";
319
320  $main::opt_text = 0;
321  $main::opt_callgrind = 0;
322  $main::opt_list = "";
323  $main::opt_disasm = "";
324  $main::opt_symbols = 0;
325  $main::opt_gv = 0;
326  $main::opt_evince = 0;
327  $main::opt_web = 0;
328  $main::opt_dot = 0;
329  $main::opt_ps = 0;
330  $main::opt_pdf = 0;
331  $main::opt_gif = 0;
332  $main::opt_svg = 0;
333  $main::opt_raw = 0;
334
335  $main::opt_nodecount = 80;
336  $main::opt_nodefraction = 0.005;
337  $main::opt_edgefraction = 0.001;
338  $main::opt_maxdegree = 8;
339  $main::opt_focus = '';
340  $main::opt_thread = undef;
341  $main::opt_ignore = '';
342  $main::opt_scale = 0;
343  $main::opt_heapcheck = 0;
344  $main::opt_retain = '';
345  $main::opt_exclude = '';
346  $main::opt_seconds = 30;
347  $main::opt_lib = "";
348
349  $main::opt_inuse_space   = 0;
350  $main::opt_inuse_objects = 0;
351  $main::opt_alloc_space   = 0;
352  $main::opt_alloc_objects = 0;
353  $main::opt_show_bytes    = 0;
354  $main::opt_drop_negative = 0;
355  $main::opt_interactive   = 0;
356
357  $main::opt_total_delay = 0;
358  $main::opt_contentions = 0;
359  $main::opt_mean_delay = 0;
360
361  $main::opt_tools   = "";
362  $main::opt_debug   = 0;
363  $main::opt_test    = 0;
364
365  # These are undocumented flags used only by unittests.
366  $main::opt_test_stride = 0;
367
368  # Are we using $SYMBOL_PAGE?
369  $main::use_symbol_page = 0;
370
371  # Files returned by TempName.
372  %main::tempnames = ();
373
374  # Type of profile we are dealing with
375  # Supported types:
376  #     cpu
377  #     heap
378  #     growth
379  #     contention
380  $main::profile_type = '';     # Empty type means "unknown"
381
382  GetOptions("help!"          => \$main::opt_help,
383             "version!"       => \$main::opt_version,
384             "cum!"           => \$main::opt_cum,
385             "base=s"         => \$main::opt_base,
386             "seconds=i"      => \$main::opt_seconds,
387             "add_lib=s"      => \$main::opt_lib,
388             "lib_prefix=s"   => \$main::opt_lib_prefix,
389             "functions!"     => \$main::opt_functions,
390             "lines!"         => \$main::opt_lines,
391             "addresses!"     => \$main::opt_addresses,
392             "files!"         => \$main::opt_files,
393             "text!"          => \$main::opt_text,
394             "callgrind!"     => \$main::opt_callgrind,
395             "list=s"         => \$main::opt_list,
396             "disasm=s"       => \$main::opt_disasm,
397             "symbols!"       => \$main::opt_symbols,
398             "gv!"            => \$main::opt_gv,
399             "evince!"        => \$main::opt_evince,
400             "web!"           => \$main::opt_web,
401             "dot!"           => \$main::opt_dot,
402             "ps!"            => \$main::opt_ps,
403             "pdf!"           => \$main::opt_pdf,
404             "svg!"           => \$main::opt_svg,
405             "gif!"           => \$main::opt_gif,
406             "raw!"           => \$main::opt_raw,
407             "interactive!"   => \$main::opt_interactive,
408             "nodecount=i"    => \$main::opt_nodecount,
409             "nodefraction=f" => \$main::opt_nodefraction,
410             "edgefraction=f" => \$main::opt_edgefraction,
411             "maxdegree=i"    => \$main::opt_maxdegree,
412             "focus=s"        => \$main::opt_focus,
413             "thread=s"       => \$main::opt_thread,
414             "ignore=s"       => \$main::opt_ignore,
415             "scale=i"        => \$main::opt_scale,
416             "heapcheck"      => \$main::opt_heapcheck,
417             "retain=s"       => \$main::opt_retain,
418             "exclude=s"      => \$main::opt_exclude,
419             "inuse_space!"   => \$main::opt_inuse_space,
420             "inuse_objects!" => \$main::opt_inuse_objects,
421             "alloc_space!"   => \$main::opt_alloc_space,
422             "alloc_objects!" => \$main::opt_alloc_objects,
423             "show_bytes!"    => \$main::opt_show_bytes,
424             "drop_negative!" => \$main::opt_drop_negative,
425             "total_delay!"   => \$main::opt_total_delay,
426             "contentions!"   => \$main::opt_contentions,
427             "mean_delay!"    => \$main::opt_mean_delay,
428             "tools=s"        => \$main::opt_tools,
429             "test!"          => \$main::opt_test,
430             "debug!"         => \$main::opt_debug,
431             # Undocumented flags used only by unittests:
432             "test_stride=i"  => \$main::opt_test_stride,
433      ) || usage("Invalid option(s)");
434
435  # Deal with the standard --help and --version
436  if ($main::opt_help) {
437    print usage_string();
438    exit(0);
439  }
440
441  if ($main::opt_version) {
442    print version_string();
443    exit(0);
444  }
445
446  # Disassembly/listing/symbols mode requires address-level info
447  if ($main::opt_disasm || $main::opt_list || $main::opt_symbols) {
448    $main::opt_functions = 0;
449    $main::opt_lines = 0;
450    $main::opt_addresses = 1;
451    $main::opt_files = 0;
452  }
453
454  # Check heap-profiling flags
455  if ($main::opt_inuse_space +
456      $main::opt_inuse_objects +
457      $main::opt_alloc_space +
458      $main::opt_alloc_objects > 1) {
459    usage("Specify at most on of --inuse/--alloc options");
460  }
461
462  # Check output granularities
463  my $grains =
464      $main::opt_functions +
465      $main::opt_lines +
466      $main::opt_addresses +
467      $main::opt_files +
468      0;
469  if ($grains > 1) {
470    usage("Only specify one output granularity option");
471  }
472  if ($grains == 0) {
473    $main::opt_functions = 1;
474  }
475
476  # Check output modes
477  my $modes =
478      $main::opt_text +
479      $main::opt_callgrind +
480      ($main::opt_list eq '' ? 0 : 1) +
481      ($main::opt_disasm eq '' ? 0 : 1) +
482      ($main::opt_symbols == 0 ? 0 : 1) +
483      $main::opt_gv +
484      $main::opt_evince +
485      $main::opt_web +
486      $main::opt_dot +
487      $main::opt_ps +
488      $main::opt_pdf +
489      $main::opt_svg +
490      $main::opt_gif +
491      $main::opt_raw +
492      $main::opt_interactive +
493      0;
494  if ($modes > 1) {
495    usage("Only specify one output mode");
496  }
497  if ($modes == 0) {
498    if (-t STDOUT) {  # If STDOUT is a tty, activate interactive mode
499      $main::opt_interactive = 1;
500    } else {
501      $main::opt_text = 1;
502    }
503  }
504
505  if ($main::opt_test) {
506    RunUnitTests();
507    # Should not return
508    exit(1);
509  }
510
511  # Binary name and profile arguments list
512  $main::prog = "";
513  @main::pfile_args = ();
514
515  # Remote profiling without a binary (using $SYMBOL_PAGE instead)
516  if (@ARGV > 0) {
517    if (IsProfileURL($ARGV[0])) {
518      $main::use_symbol_page = 1;
519    } elsif (IsSymbolizedProfileFile($ARGV[0])) {
520      $main::use_symbolized_profile = 1;
521      $main::prog = $UNKNOWN_BINARY;  # will be set later from the profile file
522    }
523  }
524
525  if ($main::use_symbol_page || $main::use_symbolized_profile) {
526    # We don't need a binary!
527    my %disabled = ('--lines' => $main::opt_lines,
528                    '--disasm' => $main::opt_disasm);
529    for my $option (keys %disabled) {
530      usage("$option cannot be used without a binary") if $disabled{$option};
531    }
532    # Set $main::prog later...
533    scalar(@ARGV) || usage("Did not specify profile file");
534  } elsif ($main::opt_symbols) {
535    # --symbols needs a binary-name (to run nm on, etc) but not profiles
536    $main::prog = shift(@ARGV) || usage("Did not specify program");
537  } else {
538    $main::prog = shift(@ARGV) || usage("Did not specify program");
539    scalar(@ARGV) || usage("Did not specify profile file");
540  }
541
542  # Parse profile file/location arguments
543  foreach my $farg (@ARGV) {
544    if ($farg =~ m/(.*)\@([0-9]+)(|\/.*)$/ ) {
545      my $machine = $1;
546      my $num_machines = $2;
547      my $path = $3;
548      for (my $i = 0; $i < $num_machines; $i++) {
549        unshift(@main::pfile_args, "$i.$machine$path");
550      }
551    } else {
552      unshift(@main::pfile_args, $farg);
553    }
554  }
555
556  if ($main::use_symbol_page) {
557    unless (IsProfileURL($main::pfile_args[0])) {
558      error("The first profile should be a remote form to use $SYMBOL_PAGE\n");
559    }
560    CheckSymbolPage();
561    $main::prog = FetchProgramName();
562  } elsif (!$main::use_symbolized_profile) {  # may not need objtools!
563    ConfigureObjTools($main::prog)
564  }
565
566  # Break the opt_lib_prefix into the prefix_list array
567  @prefix_list = split (',', $main::opt_lib_prefix);
568
569  # Remove trailing / from the prefixes, in the list to prevent
570  # searching things like /my/path//lib/mylib.so
571  foreach (@prefix_list) {
572    s|/+$||;
573  }
574}
575
576sub FilterAndPrint {
577  my ($profile, $symbols, $libs, $thread) = @_;
578
579  # Get total data in profile
580  my $total = TotalProfile($profile);
581
582  # Remove uniniteresting stack items
583  $profile = RemoveUninterestingFrames($symbols, $profile);
584
585  # Focus?
586  if ($main::opt_focus ne '') {
587    $profile = FocusProfile($symbols, $profile, $main::opt_focus);
588  }
589
590  # Ignore?
591  if ($main::opt_ignore ne '') {
592    $profile = IgnoreProfile($symbols, $profile, $main::opt_ignore);
593  }
594
595  my $calls = ExtractCalls($symbols, $profile);
596
597  # Reduce profiles to required output granularity, and also clean
598  # each stack trace so a given entry exists at most once.
599  my $reduced = ReduceProfile($symbols, $profile);
600
601  # Get derived profiles
602  my $flat = FlatProfile($reduced);
603  my $cumulative = CumulativeProfile($reduced);
604
605  # Print
606  if (!$main::opt_interactive) {
607    if ($main::opt_disasm) {
608      PrintDisassembly($libs, $flat, $cumulative, $main::opt_disasm);
609    } elsif ($main::opt_list) {
610      PrintListing($total, $libs, $flat, $cumulative, $main::opt_list, 0);
611    } elsif ($main::opt_text) {
612      # Make sure the output is empty when have nothing to report
613      # (only matters when --heapcheck is given but we must be
614      # compatible with old branches that did not pass --heapcheck always):
615      if ($total != 0) {
616        printf("Total%s: %s %s\n",
617               (defined($thread) ? " (t$thread)" : ""),
618               Unparse($total), Units());
619      }
620      PrintText($symbols, $flat, $cumulative, -1);
621    } elsif ($main::opt_raw) {
622      PrintSymbolizedProfile($symbols, $profile, $main::prog);
623    } elsif ($main::opt_callgrind) {
624      PrintCallgrind($calls);
625    } else {
626      if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {
627        if ($main::opt_gv) {
628          RunGV(TempName($main::next_tmpfile, "ps"), "");
629        } elsif ($main::opt_evince) {
630          RunEvince(TempName($main::next_tmpfile, "pdf"), "");
631        } elsif ($main::opt_web) {
632          my $tmp = TempName($main::next_tmpfile, "svg");
633          RunWeb($tmp);
634          # The command we run might hand the file name off
635          # to an already running browser instance and then exit.
636          # Normally, we'd remove $tmp on exit (right now),
637          # but fork a child to remove $tmp a little later, so that the
638          # browser has time to load it first.
639          delete $main::tempnames{$tmp};
640          if (fork() == 0) {
641            sleep 5;
642            unlink($tmp);
643            exit(0);
644          }
645        }
646      } else {
647        cleanup();
648        exit(1);
649      }
650    }
651  } else {
652    InteractiveMode($profile, $symbols, $libs, $total);
653  }
654}
655
656sub Main() {
657  Init();
658  $main::collected_profile = undef;
659  @main::profile_files = ();
660  $main::op_time = time();
661
662  # Printing symbols is special and requires a lot less info that most.
663  if ($main::opt_symbols) {
664    PrintSymbols(*STDIN);   # Get /proc/maps and symbols output from stdin
665    return;
666  }
667
668  # Fetch all profile data
669  FetchDynamicProfiles();
670
671  # this will hold symbols that we read from the profile files
672  my $symbol_map = {};
673
674  # Read one profile, pick the last item on the list
675  my $data = ReadProfile($main::prog, pop(@main::profile_files));
676  my $profile = $data->{profile};
677  my $pcs = $data->{pcs};
678  my $libs = $data->{libs};   # Info about main program and shared libraries
679  $symbol_map = MergeSymbols($symbol_map, $data->{symbols});
680
681  # Add additional profiles, if available.
682  if (scalar(@main::profile_files) > 0) {
683    foreach my $pname (@main::profile_files) {
684      my $data2 = ReadProfile($main::prog, $pname);
685      $profile = AddProfile($profile, $data2->{profile});
686      $pcs = AddPcs($pcs, $data2->{pcs});
687      $symbol_map = MergeSymbols($symbol_map, $data2->{symbols});
688    }
689  }
690
691  # Subtract base from profile, if specified
692  if ($main::opt_base ne '') {
693    my $base = ReadProfile($main::prog, $main::opt_base);
694    $profile = SubtractProfile($profile, $base->{profile});
695    $pcs = AddPcs($pcs, $base->{pcs});
696    $symbol_map = MergeSymbols($symbol_map, $base->{symbols});
697  }
698
699  # Collect symbols
700  my $symbols;
701  if ($main::use_symbolized_profile) {
702    $symbols = FetchSymbols($pcs, $symbol_map);
703  } elsif ($main::use_symbol_page) {
704    $symbols = FetchSymbols($pcs);
705  } else {
706    # TODO(csilvers): $libs uses the /proc/self/maps data from profile1,
707    # which may differ from the data from subsequent profiles, especially
708    # if they were run on different machines.  Use appropriate libs for
709    # each pc somehow.
710    $symbols = ExtractSymbols($libs, $pcs);
711  }
712
713  if (!defined($main::opt_thread)) {
714    FilterAndPrint($profile, $symbols, $libs);
715  }
716  if (defined($data->{threads})) {
717    foreach my $thread (sort { $a <=> $b } keys(%{$data->{threads}})) {
718      if (defined($main::opt_thread) &&
719          ($main::opt_thread eq '*' || $main::opt_thread == $thread)) {
720        my $thread_profile = $data->{threads}{$thread};
721        FilterAndPrint($thread_profile, $symbols, $libs, $thread);
722      }
723    }
724  }
725
726  cleanup();
727  exit(0);
728}
729
730##### Entry Point #####
731
732Main();
733
734# Temporary code to detect if we're running on a Goobuntu system.
735# These systems don't have the right stuff installed for the special
736# Readline libraries to work, so as a temporary workaround, we default
737# to using the normal stdio code, rather than the fancier readline-based
738# code
739sub ReadlineMightFail {
740  if (-e '/lib/libtermcap.so.2') {
741    return 0;  # libtermcap exists, so readline should be okay
742  } else {
743    return 1;
744  }
745}
746
747sub RunGV {
748  my $fname = shift;
749  my $bg = shift;       # "" or " &" if we should run in background
750  if (!system(ShellEscape(@GV, "--version") . " >$dev_null 2>&1")) {
751    # Options using double dash are supported by this gv version.
752    # Also, turn on noantialias to better handle bug in gv for
753    # postscript files with large dimensions.
754    # TODO: Maybe we should not pass the --noantialias flag
755    # if the gv version is known to work properly without the flag.
756    system(ShellEscape(@GV, "--scale=$main::opt_scale", "--noantialias", $fname)
757           . $bg);
758  } else {
759    # Old gv version - only supports options that use single dash.
760    print STDERR ShellEscape(@GV, "-scale", $main::opt_scale) . "\n";
761    system(ShellEscape(@GV, "-scale", "$main::opt_scale", $fname) . $bg);
762  }
763}
764
765sub RunEvince {
766  my $fname = shift;
767  my $bg = shift;       # "" or " &" if we should run in background
768  system(ShellEscape(@EVINCE, $fname) . $bg);
769}
770
771sub RunWeb {
772  my $fname = shift;
773  print STDERR "Loading web page file:///$fname\n";
774
775  if (`uname` =~ /Darwin/) {
776    # OS X: open will use standard preference for SVG files.
777    system("/usr/bin/open", $fname);
778    return;
779  }
780
781  # Some kind of Unix; try generic symlinks, then specific browsers.
782  # (Stop once we find one.)
783  # Works best if the browser is already running.
784  my @alt = (
785    "/etc/alternatives/gnome-www-browser",
786    "/etc/alternatives/x-www-browser",
787    "google-chrome",
788    "firefox",
789  );
790  foreach my $b (@alt) {
791    if (system($b, $fname) == 0) {
792      return;
793    }
794  }
795
796  print STDERR "Could not load web browser.\n";
797}
798
799sub RunKcachegrind {
800  my $fname = shift;
801  my $bg = shift;       # "" or " &" if we should run in background
802  print STDERR "Starting '@KCACHEGRIND " . $fname . $bg . "'\n";
803  system(ShellEscape(@KCACHEGRIND, $fname) . $bg);
804}
805
806
807##### Interactive helper routines #####
808
809sub InteractiveMode {
810  $| = 1;  # Make output unbuffered for interactive mode
811  my ($orig_profile, $symbols, $libs, $total) = @_;
812
813  print STDERR "Welcome to jeprof!  For help, type 'help'.\n";
814
815  # Use ReadLine if it's installed and input comes from a console.
816  if ( -t STDIN &&
817       !ReadlineMightFail() &&
818       defined(eval {require Term::ReadLine}) ) {
819    my $term = new Term::ReadLine 'jeprof';
820    while ( defined ($_ = $term->readline('(jeprof) '))) {
821      $term->addhistory($_) if /\S/;
822      if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) {
823        last;    # exit when we get an interactive command to quit
824      }
825    }
826  } else {       # don't have readline
827    while (1) {
828      print STDERR "(jeprof) ";
829      $_ = <STDIN>;
830      last if ! defined $_ ;
831      s/\r//g;         # turn windows-looking lines into unix-looking lines
832
833      # Save some flags that might be reset by InteractiveCommand()
834      my $save_opt_lines = $main::opt_lines;
835
836      if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) {
837        last;    # exit when we get an interactive command to quit
838      }
839
840      # Restore flags
841      $main::opt_lines = $save_opt_lines;
842    }
843  }
844}
845
846# Takes two args: orig profile, and command to run.
847# Returns 1 if we should keep going, or 0 if we were asked to quit
848sub InteractiveCommand {
849  my($orig_profile, $symbols, $libs, $total, $command) = @_;
850  $_ = $command;                # just to make future m//'s easier
851  if (!defined($_)) {
852    print STDERR "\n";
853    return 0;
854  }
855  if (m/^\s*quit/) {
856    return 0;
857  }
858  if (m/^\s*help/) {
859    InteractiveHelpMessage();
860    return 1;
861  }
862  # Clear all the mode options -- mode is controlled by "$command"
863  $main::opt_text = 0;
864  $main::opt_callgrind = 0;
865  $main::opt_disasm = 0;
866  $main::opt_list = 0;
867  $main::opt_gv = 0;
868  $main::opt_evince = 0;
869  $main::opt_cum = 0;
870
871  if (m/^\s*(text|top)(\d*)\s*(.*)/) {
872    $main::opt_text = 1;
873
874    my $line_limit = ($2 ne "") ? int($2) : 10;
875
876    my $routine;
877    my $ignore;
878    ($routine, $ignore) = ParseInteractiveArgs($3);
879
880    my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
881    my $reduced = ReduceProfile($symbols, $profile);
882
883    # Get derived profiles
884    my $flat = FlatProfile($reduced);
885    my $cumulative = CumulativeProfile($reduced);
886
887    PrintText($symbols, $flat, $cumulative, $line_limit);
888    return 1;
889  }
890  if (m/^\s*callgrind\s*([^ \n]*)/) {
891    $main::opt_callgrind = 1;
892
893    # Get derived profiles
894    my $calls = ExtractCalls($symbols, $orig_profile);
895    my $filename = $1;
896    if ( $1 eq '' ) {
897      $filename = TempName($main::next_tmpfile, "callgrind");
898    }
899    PrintCallgrind($calls, $filename);
900    if ( $1 eq '' ) {
901      RunKcachegrind($filename, " & ");
902      $main::next_tmpfile++;
903    }
904
905    return 1;
906  }
907  if (m/^\s*(web)?list\s*(.+)/) {
908    my $html = (defined($1) && ($1 eq "web"));
909    $main::opt_list = 1;
910
911    my $routine;
912    my $ignore;
913    ($routine, $ignore) = ParseInteractiveArgs($2);
914
915    my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
916    my $reduced = ReduceProfile($symbols, $profile);
917
918    # Get derived profiles
919    my $flat = FlatProfile($reduced);
920    my $cumulative = CumulativeProfile($reduced);
921
922    PrintListing($total, $libs, $flat, $cumulative, $routine, $html);
923    return 1;
924  }
925  if (m/^\s*disasm\s*(.+)/) {
926    $main::opt_disasm = 1;
927
928    my $routine;
929    my $ignore;
930    ($routine, $ignore) = ParseInteractiveArgs($1);
931
932    # Process current profile to account for various settings
933    my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
934    my $reduced = ReduceProfile($symbols, $profile);
935
936    # Get derived profiles
937    my $flat = FlatProfile($reduced);
938    my $cumulative = CumulativeProfile($reduced);
939
940    PrintDisassembly($libs, $flat, $cumulative, $routine);
941    return 1;
942  }
943  if (m/^\s*(gv|web|evince)\s*(.*)/) {
944    $main::opt_gv = 0;
945    $main::opt_evince = 0;
946    $main::opt_web = 0;
947    if ($1 eq "gv") {
948      $main::opt_gv = 1;
949    } elsif ($1 eq "evince") {
950      $main::opt_evince = 1;
951    } elsif ($1 eq "web") {
952      $main::opt_web = 1;
953    }
954
955    my $focus;
956    my $ignore;
957    ($focus, $ignore) = ParseInteractiveArgs($2);
958
959    # Process current profile to account for various settings
960    my $profile = ProcessProfile($total, $orig_profile, $symbols,
961                                 $focus, $ignore);
962    my $reduced = ReduceProfile($symbols, $profile);
963
964    # Get derived profiles
965    my $flat = FlatProfile($reduced);
966    my $cumulative = CumulativeProfile($reduced);
967
968    if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {
969      if ($main::opt_gv) {
970        RunGV(TempName($main::next_tmpfile, "ps"), " &");
971      } elsif ($main::opt_evince) {
972        RunEvince(TempName($main::next_tmpfile, "pdf"), " &");
973      } elsif ($main::opt_web) {
974        RunWeb(TempName($main::next_tmpfile, "svg"));
975      }
976      $main::next_tmpfile++;
977    }
978    return 1;
979  }
980  if (m/^\s*$/) {
981    return 1;
982  }
983  print STDERR "Unknown command: try 'help'.\n";
984  return 1;
985}
986
987
988sub ProcessProfile {
989  my $total_count = shift;
990  my $orig_profile = shift;
991  my $symbols = shift;
992  my $focus = shift;
993  my $ignore = shift;
994
995  # Process current profile to account for various settings
996  my $profile = $orig_profile;
997  printf("Total: %s %s\n", Unparse($total_count), Units());
998  if ($focus ne '') {
999    $profile = FocusProfile($symbols, $profile, $focus);
1000    my $focus_count = TotalProfile($profile);
1001    printf("After focusing on '%s': %s %s of %s (%0.1f%%)\n",
1002           $focus,
1003           Unparse($focus_count), Units(),
1004           Unparse($total_count), ($focus_count*100.0) / $total_count);
1005  }
1006  if ($ignore ne '') {
1007    $profile = IgnoreProfile($symbols, $profile, $ignore);
1008    my $ignore_count = TotalProfile($profile);
1009    printf("After ignoring '%s': %s %s of %s (%0.1f%%)\n",
1010           $ignore,
1011           Unparse($ignore_count), Units(),
1012           Unparse($total_count),
1013           ($ignore_count*100.0) / $total_count);
1014  }
1015
1016  return $profile;
1017}
1018
1019sub InteractiveHelpMessage {
1020  print STDERR <<ENDOFHELP;
1021Interactive jeprof mode
1022
1023Commands:
1024  gv
1025  gv [focus] [-ignore1] [-ignore2]
1026      Show graphical hierarchical display of current profile.  Without
1027      any arguments, shows all samples in the profile.  With the optional
1028      "focus" argument, restricts the samples shown to just those where
1029      the "focus" regular expression matches a routine name on the stack
1030      trace.
1031
1032  web
1033  web [focus] [-ignore1] [-ignore2]
1034      Like GV, but displays profile in your web browser instead of using
1035      Ghostview. Works best if your web browser is already running.
1036      To change the browser that gets used:
1037      On Linux, set the /etc/alternatives/gnome-www-browser symlink.
1038      On OS X, change the Finder association for SVG files.
1039
1040  list [routine_regexp] [-ignore1] [-ignore2]
1041      Show source listing of routines whose names match "routine_regexp"
1042
1043  weblist [routine_regexp] [-ignore1] [-ignore2]
1044     Displays a source listing of routines whose names match "routine_regexp"
1045     in a web browser.  You can click on source lines to view the
1046     corresponding disassembly.
1047
1048  top [--cum] [-ignore1] [-ignore2]
1049  top20 [--cum] [-ignore1] [-ignore2]
1050  top37 [--cum] [-ignore1] [-ignore2]
1051      Show top lines ordered by flat profile count, or cumulative count
1052      if --cum is specified.  If a number is present after 'top', the
1053      top K routines will be shown (defaults to showing the top 10)
1054
1055  disasm [routine_regexp] [-ignore1] [-ignore2]
1056      Show disassembly of routines whose names match "routine_regexp",
1057      annotated with sample counts.
1058
1059  callgrind
1060  callgrind [filename]
1061      Generates callgrind file. If no filename is given, kcachegrind is called.
1062
1063  help - This listing
1064  quit or ^D - End jeprof
1065
1066For commands that accept optional -ignore tags, samples where any routine in
1067the stack trace matches the regular expression in any of the -ignore
1068parameters will be ignored.
1069
1070Further pprof details are available at this location (or one similar):
1071
1072 /usr/doc/gperftools-$PPROF_VERSION/cpu_profiler.html
1073 /usr/doc/gperftools-$PPROF_VERSION/heap_profiler.html
1074
1075ENDOFHELP
1076}
1077sub ParseInteractiveArgs {
1078  my $args = shift;
1079  my $focus = "";
1080  my $ignore = "";
1081  my @x = split(/ +/, $args);
1082  foreach $a (@x) {
1083    if ($a =~ m/^(--|-)lines$/) {
1084      $main::opt_lines = 1;
1085    } elsif ($a =~ m/^(--|-)cum$/) {
1086      $main::opt_cum = 1;
1087    } elsif ($a =~ m/^-(.*)/) {
1088      $ignore .= (($ignore ne "") ? "|" : "" ) . $1;
1089    } else {
1090      $focus .= (($focus ne "") ? "|" : "" ) . $a;
1091    }
1092  }
1093  if ($ignore ne "") {
1094    print STDERR "Ignoring samples in call stacks that match '$ignore'\n";
1095  }
1096  return ($focus, $ignore);
1097}
1098
1099##### Output code #####
1100
1101sub TempName {
1102  my $fnum = shift;
1103  my $ext = shift;
1104  my $file = "$main::tmpfile_ps.$fnum.$ext";
1105  $main::tempnames{$file} = 1;
1106  return $file;
1107}
1108
1109# Print profile data in packed binary format (64-bit) to standard out
1110sub PrintProfileData {
1111  my $profile = shift;
1112
1113  # print header (64-bit style)
1114  # (zero) (header-size) (version) (sample-period) (zero)
1115  print pack('L*', 0, 0, 3, 0, 0, 0, 1, 0, 0, 0);
1116
1117  foreach my $k (keys(%{$profile})) {
1118    my $count = $profile->{$k};
1119    my @addrs = split(/\n/, $k);
1120    if ($#addrs >= 0) {
1121      my $depth = $#addrs + 1;
1122      # int(foo / 2**32) is the only reliable way to get rid of bottom
1123      # 32 bits on both 32- and 64-bit systems.
1124      print pack('L*', $count & 0xFFFFFFFF, int($count / 2**32));
1125      print pack('L*', $depth & 0xFFFFFFFF, int($depth / 2**32));
1126
1127      foreach my $full_addr (@addrs) {
1128        my $addr = $full_addr;
1129        $addr =~ s/0x0*//;  # strip off leading 0x, zeroes
1130        if (length($addr) > 16) {
1131          print STDERR "Invalid address in profile: $full_addr\n";
1132          next;
1133        }
1134        my $low_addr = substr($addr, -8);       # get last 8 hex chars
1135        my $high_addr = substr($addr, -16, 8);  # get up to 8 more hex chars
1136        print pack('L*', hex('0x' . $low_addr), hex('0x' . $high_addr));
1137      }
1138    }
1139  }
1140}
1141
1142# Print symbols and profile data
1143sub PrintSymbolizedProfile {
1144  my $symbols = shift;
1145  my $profile = shift;
1146  my $prog = shift;
1147
1148  $SYMBOL_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
1149  my $symbol_marker = $&;
1150
1151  print '--- ', $symbol_marker, "\n";
1152  if (defined($prog)) {
1153    print 'binary=', $prog, "\n";
1154  }
1155  while (my ($pc, $name) = each(%{$symbols})) {
1156    my $sep = ' ';
1157    print '0x', $pc;
1158    # We have a list of function names, which include the inlined
1159    # calls.  They are separated (and terminated) by --, which is
1160    # illegal in function names.
1161    for (my $j = 2; $j <= $#{$name}; $j += 3) {
1162      print $sep, $name->[$j];
1163      $sep = '--';
1164    }
1165    print "\n";
1166  }
1167  print '---', "\n";
1168
1169  my $profile_marker;
1170  if ($main::profile_type eq 'heap') {
1171    $HEAP_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
1172    $profile_marker = $&;
1173  } elsif ($main::profile_type eq 'growth') {
1174    $GROWTH_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
1175    $profile_marker = $&;
1176  } elsif ($main::profile_type eq 'contention') {
1177    $CONTENTION_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
1178    $profile_marker = $&;
1179  } else { # elsif ($main::profile_type eq 'cpu')
1180    $PROFILE_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
1181    $profile_marker = $&;
1182  }
1183
1184  print '--- ', $profile_marker, "\n";
1185  if (defined($main::collected_profile)) {
1186    # if used with remote fetch, simply dump the collected profile to output.
1187    open(SRC, "<$main::collected_profile");
1188    while (<SRC>) {
1189      print $_;
1190    }
1191    close(SRC);
1192  } else {
1193    # --raw/http: For everything to work correctly for non-remote profiles, we
1194    # would need to extend PrintProfileData() to handle all possible profile
1195    # types, re-enable the code that is currently disabled in ReadCPUProfile()
1196    # and FixCallerAddresses(), and remove the remote profile dumping code in
1197    # the block above.
1198    die "--raw/http: jeprof can only dump remote profiles for --raw\n";
1199    # dump a cpu-format profile to standard out
1200    PrintProfileData($profile);
1201  }
1202}
1203
1204# Print text output
1205sub PrintText {
1206  my $symbols = shift;
1207  my $flat = shift;
1208  my $cumulative = shift;
1209  my $line_limit = shift;
1210
1211  my $total = TotalProfile($flat);
1212
1213  # Which profile to sort by?
1214  my $s = $main::opt_cum ? $cumulative : $flat;
1215
1216  my $running_sum = 0;
1217  my $lines = 0;
1218  foreach my $k (sort { GetEntry($s, $b) <=> GetEntry($s, $a) || $a cmp $b }
1219                 keys(%{$cumulative})) {
1220    my $f = GetEntry($flat, $k);
1221    my $c = GetEntry($cumulative, $k);
1222    $running_sum += $f;
1223
1224    my $sym = $k;
1225    if (exists($symbols->{$k})) {
1226      $sym = $symbols->{$k}->[0] . " " . $symbols->{$k}->[1];
1227      if ($main::opt_addresses) {
1228        $sym = $k . " " . $sym;
1229      }
1230    }
1231
1232    if ($f != 0 || $c != 0) {
1233      printf("%8s %6s %6s %8s %6s %s\n",
1234             Unparse($f),
1235             Percent($f, $total),
1236             Percent($running_sum, $total),
1237             Unparse($c),
1238             Percent($c, $total),
1239             $sym);
1240    }
1241    $lines++;
1242    last if ($line_limit >= 0 && $lines >= $line_limit);
1243  }
1244}
1245
1246# Callgrind format has a compression for repeated function and file
1247# names.  You show the name the first time, and just use its number
1248# subsequently.  This can cut down the file to about a third or a
1249# quarter of its uncompressed size.  $key and $val are the key/value
1250# pair that would normally be printed by callgrind; $map is a map from
1251# value to number.
1252sub CompressedCGName {
1253  my($key, $val, $map) = @_;
1254  my $idx = $map->{$val};
1255  # For very short keys, providing an index hurts rather than helps.
1256  if (length($val) <= 3) {
1257    return "$key=$val\n";
1258  } elsif (defined($idx)) {
1259    return "$key=($idx)\n";
1260  } else {
1261    # scalar(keys $map) gives the number of items in the map.
1262    $idx = scalar(keys(%{$map})) + 1;
1263    $map->{$val} = $idx;
1264    return "$key=($idx) $val\n";
1265  }
1266}
1267
1268# Print the call graph in a way that's suiteable for callgrind.
1269sub PrintCallgrind {
1270  my $calls = shift;
1271  my $filename;
1272  my %filename_to_index_map;
1273  my %fnname_to_index_map;
1274
1275  if ($main::opt_interactive) {
1276    $filename = shift;
1277    print STDERR "Writing callgrind file to '$filename'.\n"
1278  } else {
1279    $filename = "&STDOUT";
1280  }
1281  open(CG, ">$filename");
1282  printf CG ("events: Hits\n\n");
1283  foreach my $call ( map { $_->[0] }
1284                     sort { $a->[1] cmp $b ->[1] ||
1285                            $a->[2] <=> $b->[2] }
1286                     map { /([^:]+):(\d+):([^ ]+)( -> ([^:]+):(\d+):(.+))?/;
1287                           [$_, $1, $2] }
1288                     keys %$calls ) {
1289    my $count = int($calls->{$call});
1290    $call =~ /([^:]+):(\d+):([^ ]+)( -> ([^:]+):(\d+):(.+))?/;
1291    my ( $caller_file, $caller_line, $caller_function,
1292         $callee_file, $callee_line, $callee_function ) =
1293       ( $1, $2, $3, $5, $6, $7 );
1294
1295    # TODO(csilvers): for better compression, collect all the
1296    # caller/callee_files and functions first, before printing
1297    # anything, and only compress those referenced more than once.
1298    printf CG CompressedCGName("fl", $caller_file, \%filename_to_index_map);
1299    printf CG CompressedCGName("fn", $caller_function, \%fnname_to_index_map);
1300    if (defined $6) {
1301      printf CG CompressedCGName("cfl", $callee_file, \%filename_to_index_map);
1302      printf CG CompressedCGName("cfn", $callee_function, \%fnname_to_index_map);
1303      printf CG ("calls=$count $callee_line\n");
1304    }
1305    printf CG ("$caller_line $count\n\n");
1306  }
1307}
1308
1309# Print disassembly for all all routines that match $main::opt_disasm
1310sub PrintDisassembly {
1311  my $libs = shift;
1312  my $flat = shift;
1313  my $cumulative = shift;
1314  my $disasm_opts = shift;
1315
1316  my $total = TotalProfile($flat);
1317
1318  foreach my $lib (@{$libs}) {
1319    my $symbol_table = GetProcedureBoundaries($lib->[0], $disasm_opts);
1320    my $offset = AddressSub($lib->[1], $lib->[3]);
1321    foreach my $routine (sort ByName keys(%{$symbol_table})) {
1322      my $start_addr = $symbol_table->{$routine}->[0];
1323      my $end_addr = $symbol_table->{$routine}->[1];
1324      # See if there are any samples in this routine
1325      my $length = hex(AddressSub($end_addr, $start_addr));
1326      my $addr = AddressAdd($start_addr, $offset);
1327      for (my $i = 0; $i < $length; $i++) {
1328        if (defined($cumulative->{$addr})) {
1329          PrintDisassembledFunction($lib->[0], $offset,
1330                                    $routine, $flat, $cumulative,
1331                                    $start_addr, $end_addr, $total);
1332          last;
1333        }
1334        $addr = AddressInc($addr);
1335      }
1336    }
1337  }
1338}
1339
1340# Return reference to array of tuples of the form:
1341#       [start_address, filename, linenumber, instruction, limit_address]
1342# E.g.,
1343#       ["0x806c43d", "/foo/bar.cc", 131, "ret", "0x806c440"]
1344sub Disassemble {
1345  my $prog = shift;
1346  my $offset = shift;
1347  my $start_addr = shift;
1348  my $end_addr = shift;
1349
1350  my $objdump = $obj_tool_map{"objdump"};
1351  my $cmd = ShellEscape($objdump, "-C", "-d", "-l", "--no-show-raw-insn",
1352                        "--start-address=0x$start_addr",
1353                        "--stop-address=0x$end_addr", $prog);
1354  open(OBJDUMP, "$cmd |") || error("$cmd: $!\n");
1355  my @result = ();
1356  my $filename = "";
1357  my $linenumber = -1;
1358  my $last = ["", "", "", ""];
1359  while (<OBJDUMP>) {
1360    s/\r//g;         # turn windows-looking lines into unix-looking lines
1361    chop;
1362    if (m|\s*([^:\s]+):(\d+)\s*$|) {
1363      # Location line of the form:
1364      #   <filename>:<linenumber>
1365      $filename = $1;
1366      $linenumber = $2;
1367    } elsif (m/^ +([0-9a-f]+):\s*(.*)/) {
1368      # Disassembly line -- zero-extend address to full length
1369      my $addr = HexExtend($1);
1370      my $k = AddressAdd($addr, $offset);
1371      $last->[4] = $k;   # Store ending address for previous instruction
1372      $last = [$k, $filename, $linenumber, $2, $end_addr];
1373      push(@result, $last);
1374    }
1375  }
1376  close(OBJDUMP);
1377  return @result;
1378}
1379
1380# The input file should contain lines of the form /proc/maps-like
1381# output (same format as expected from the profiles) or that looks
1382# like hex addresses (like "0xDEADBEEF").  We will parse all
1383# /proc/maps output, and for all the hex addresses, we will output
1384# "short" symbol names, one per line, in the same order as the input.
1385sub PrintSymbols {
1386  my $maps_and_symbols_file = shift;
1387
1388  # ParseLibraries expects pcs to be in a set.  Fine by us...
1389  my @pclist = ();   # pcs in sorted order
1390  my $pcs = {};
1391  my $map = "";
1392  foreach my $line (<$maps_and_symbols_file>) {
1393    $line =~ s/\r//g;    # turn windows-looking lines into unix-looking lines
1394    if ($line =~ /\b(0x[0-9a-f]+)\b/i) {
1395      push(@pclist, HexExtend($1));
1396      $pcs->{$pclist[-1]} = 1;
1397    } else {
1398      $map .= $line;
1399    }
1400  }
1401
1402  my $libs = ParseLibraries($main::prog, $map, $pcs);
1403  my $symbols = ExtractSymbols($libs, $pcs);
1404
1405  foreach my $pc (@pclist) {
1406    # ->[0] is the shortname, ->[2] is the full name
1407    print(($symbols->{$pc}->[0] || "??") . "\n");
1408  }
1409}
1410
1411
1412# For sorting functions by name
1413sub ByName {
1414  return ShortFunctionName($a) cmp ShortFunctionName($b);
1415}
1416
1417# Print source-listing for all all routines that match $list_opts
1418sub PrintListing {
1419  my $total = shift;
1420  my $libs = shift;
1421  my $flat = shift;
1422  my $cumulative = shift;
1423  my $list_opts = shift;
1424  my $html = shift;
1425
1426  my $output = \*STDOUT;
1427  my $fname = "";
1428
1429  if ($html) {
1430    # Arrange to write the output to a temporary file
1431    $fname = TempName($main::next_tmpfile, "html");
1432    $main::next_tmpfile++;
1433    if (!open(TEMP, ">$fname")) {
1434      print STDERR "$fname: $!\n";
1435      return;
1436    }
1437    $output = \*TEMP;
1438    print $output HtmlListingHeader();
1439    printf $output ("<div class=\"legend\">%s<br>Total: %s %s</div>\n",
1440                    $main::prog, Unparse($total), Units());
1441  }
1442
1443  my $listed = 0;
1444  foreach my $lib (@{$libs}) {
1445    my $symbol_table = GetProcedureBoundaries($lib->[0], $list_opts);
1446    my $offset = AddressSub($lib->[1], $lib->[3]);
1447    foreach my $routine (sort ByName keys(%{$symbol_table})) {
1448      # Print if there are any samples in this routine
1449      my $start_addr = $symbol_table->{$routine}->[0];
1450      my $end_addr = $symbol_table->{$routine}->[1];
1451      my $length = hex(AddressSub($end_addr, $start_addr));
1452      my $addr = AddressAdd($start_addr, $offset);
1453      for (my $i = 0; $i < $length; $i++) {
1454        if (defined($cumulative->{$addr})) {
1455          $listed += PrintSource(
1456            $lib->[0], $offset,
1457            $routine, $flat, $cumulative,
1458            $start_addr, $end_addr,
1459            $html,
1460            $output);
1461          last;
1462        }
1463        $addr = AddressInc($addr);
1464      }
1465    }
1466  }
1467
1468  if ($html) {
1469    if ($listed > 0) {
1470      print $output HtmlListingFooter();
1471      close($output);
1472      RunWeb($fname);
1473    } else {
1474      close($output);
1475      unlink($fname);
1476    }
1477  }
1478}
1479
1480sub HtmlListingHeader {
1481  return <<'EOF';
1482<DOCTYPE html>
1483<html>
1484<head>
1485<title>Pprof listing</title>
1486<style type="text/css">
1487body {
1488  font-family: sans-serif;
1489}
1490h1 {
1491  font-size: 1.5em;
1492  margin-bottom: 4px;
1493}
1494.legend {
1495  font-size: 1.25em;
1496}
1497.line {
1498  color: #aaaaaa;
1499}
1500.nop {
1501  color: #aaaaaa;
1502}
1503.unimportant {
1504  color: #cccccc;
1505}
1506.disasmloc {
1507  color: #000000;
1508}
1509.deadsrc {
1510  cursor: pointer;
1511}
1512.deadsrc:hover {
1513  background-color: #eeeeee;
1514}
1515.livesrc {
1516  color: #0000ff;
1517  cursor: pointer;
1518}
1519.livesrc:hover {
1520  background-color: #eeeeee;
1521}
1522.asm {
1523  color: #008800;
1524  display: none;
1525}
1526</style>
1527<script type="text/javascript">
1528function jeprof_toggle_asm(e) {
1529  var target;
1530  if (!e) e = window.event;
1531  if (e.target) target = e.target;
1532  else if (e.srcElement) target = e.srcElement;
1533
1534  if (target) {
1535    var asm = target.nextSibling;
1536    if (asm && asm.className == "asm") {
1537      asm.style.display = (asm.style.display == "block" ? "" : "block");
1538      e.preventDefault();
1539      return false;
1540    }
1541  }
1542}
1543</script>
1544</head>
1545<body>
1546EOF
1547}
1548
1549sub HtmlListingFooter {
1550  return <<'EOF';
1551</body>
1552</html>
1553EOF
1554}
1555
1556sub HtmlEscape {
1557  my $text = shift;
1558  $text =~ s/&/&amp;/g;
1559  $text =~ s/</&lt;/g;
1560  $text =~ s/>/&gt;/g;
1561  return $text;
1562}
1563
1564# Returns the indentation of the line, if it has any non-whitespace
1565# characters.  Otherwise, returns -1.
1566sub Indentation {
1567  my $line = shift;
1568  if (m/^(\s*)\S/) {
1569    return length($1);
1570  } else {
1571    return -1;
1572  }
1573}
1574
1575# If the symbol table contains inlining info, Disassemble() may tag an
1576# instruction with a location inside an inlined function.  But for
1577# source listings, we prefer to use the location in the function we
1578# are listing.  So use MapToSymbols() to fetch full location
1579# information for each instruction and then pick out the first
1580# location from a location list (location list contains callers before
1581# callees in case of inlining).
1582#
1583# After this routine has run, each entry in $instructions contains:
1584#   [0] start address
1585#   [1] filename for function we are listing
1586#   [2] line number for function we are listing
1587#   [3] disassembly
1588#   [4] limit address
1589#   [5] most specific filename (may be different from [1] due to inlining)
1590#   [6] most specific line number (may be different from [2] due to inlining)
1591sub GetTopLevelLineNumbers {
1592  my ($lib, $offset, $instructions) = @_;
1593  my $pcs = [];
1594  for (my $i = 0; $i <= $#{$instructions}; $i++) {
1595    push(@{$pcs}, $instructions->[$i]->[0]);
1596  }
1597  my $symbols = {};
1598  MapToSymbols($lib, $offset, $pcs, $symbols);
1599  for (my $i = 0; $i <= $#{$instructions}; $i++) {
1600    my $e = $instructions->[$i];
1601    push(@{$e}, $e->[1]);
1602    push(@{$e}, $e->[2]);
1603    my $addr = $e->[0];
1604    my $sym = $symbols->{$addr};
1605    if (defined($sym)) {
1606      if ($#{$sym} >= 2 && $sym->[1] =~ m/^(.*):(\d+)$/) {
1607        $e->[1] = $1;  # File name
1608        $e->[2] = $2;  # Line number
1609      }
1610    }
1611  }
1612}
1613
1614# Print source-listing for one routine
1615sub PrintSource {
1616  my $prog = shift;
1617  my $offset = shift;
1618  my $routine = shift;
1619  my $flat = shift;
1620  my $cumulative = shift;
1621  my $start_addr = shift;
1622  my $end_addr = shift;
1623  my $html = shift;
1624  my $output = shift;
1625
1626  # Disassemble all instructions (just to get line numbers)
1627  my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);
1628  GetTopLevelLineNumbers($prog, $offset, \@instructions);
1629
1630  # Hack 1: assume that the first source file encountered in the
1631  # disassembly contains the routine
1632  my $filename = undef;
1633  for (my $i = 0; $i <= $#instructions; $i++) {
1634    if ($instructions[$i]->[2] >= 0) {
1635      $filename = $instructions[$i]->[1];
1636      last;
1637    }
1638  }
1639  if (!defined($filename)) {
1640    print STDERR "no filename found in $routine\n";
1641    return 0;
1642  }
1643
1644  # Hack 2: assume that the largest line number from $filename is the
1645  # end of the procedure.  This is typically safe since if P1 contains
1646  # an inlined call to P2, then P2 usually occurs earlier in the
1647  # source file.  If this does not work, we might have to compute a
1648  # density profile or just print all regions we find.
1649  my $lastline = 0;
1650  for (my $i = 0; $i <= $#instructions; $i++) {
1651    my $f = $instructions[$i]->[1];
1652    my $l = $instructions[$i]->[2];
1653    if (($f eq $filename) && ($l > $lastline)) {
1654      $lastline = $l;
1655    }
1656  }
1657
1658  # Hack 3: assume the first source location from "filename" is the start of
1659  # the source code.
1660  my $firstline = 1;
1661  for (my $i = 0; $i <= $#instructions; $i++) {
1662    if ($instructions[$i]->[1] eq $filename) {
1663      $firstline = $instructions[$i]->[2];
1664      last;
1665    }
1666  }
1667
1668  # Hack 4: Extend last line forward until its indentation is less than
1669  # the indentation we saw on $firstline
1670  my $oldlastline = $lastline;
1671  {
1672    if (!open(FILE, "<$filename")) {
1673      print STDERR "$filename: $!\n";
1674      return 0;
1675    }
1676    my $l = 0;
1677    my $first_indentation = -1;
1678    while (<FILE>) {
1679      s/\r//g;         # turn windows-looking lines into unix-looking lines
1680      $l++;
1681      my $indent = Indentation($_);
1682      if ($l >= $firstline) {
1683        if ($first_indentation < 0 && $indent >= 0) {
1684          $first_indentation = $indent;
1685          last if ($first_indentation == 0);
1686        }
1687      }
1688      if ($l >= $lastline && $indent >= 0) {
1689        if ($indent >= $first_indentation) {
1690          $lastline = $l+1;
1691        } else {
1692          last;
1693        }
1694      }
1695    }
1696    close(FILE);
1697  }
1698
1699  # Assign all samples to the range $firstline,$lastline,
1700  # Hack 4: If an instruction does not occur in the range, its samples
1701  # are moved to the next instruction that occurs in the range.
1702  my $samples1 = {};        # Map from line number to flat count
1703  my $samples2 = {};        # Map from line number to cumulative count
1704  my $running1 = 0;         # Unassigned flat counts
1705  my $running2 = 0;         # Unassigned cumulative counts
1706  my $total1 = 0;           # Total flat counts
1707  my $total2 = 0;           # Total cumulative counts
1708  my %disasm = ();          # Map from line number to disassembly
1709  my $running_disasm = "";  # Unassigned disassembly
1710  my $skip_marker = "---\n";
1711  if ($html) {
1712    $skip_marker = "";
1713    for (my $l = $firstline; $l <= $lastline; $l++) {
1714      $disasm{$l} = "";
1715    }
1716  }
1717  my $last_dis_filename = '';
1718  my $last_dis_linenum = -1;
1719  my $last_touched_line = -1;  # To detect gaps in disassembly for a line
1720  foreach my $e (@instructions) {
1721    # Add up counts for all address that fall inside this instruction
1722    my $c1 = 0;
1723    my $c2 = 0;
1724    for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {
1725      $c1 += GetEntry($flat, $a);
1726      $c2 += GetEntry($cumulative, $a);
1727    }
1728
1729    if ($html) {
1730      my $dis = sprintf("      %6s %6s \t\t%8s: %s ",
1731                        HtmlPrintNumber($c1),
1732                        HtmlPrintNumber($c2),
1733                        UnparseAddress($offset, $e->[0]),
1734                        CleanDisassembly($e->[3]));
1735
1736      # Append the most specific source line associated with this instruction
1737      if (length($dis) < 80) { $dis .= (' ' x (80 - length($dis))) };
1738      $dis = HtmlEscape($dis);
1739      my $f = $e->[5];
1740      my $l = $e->[6];
1741      if ($f ne $last_dis_filename) {
1742        $dis .= sprintf("<span class=disasmloc>%s:%d</span>",
1743                        HtmlEscape(CleanFileName($f)), $l);
1744      } elsif ($l ne $last_dis_linenum) {
1745        # De-emphasize the unchanged file name portion
1746        $dis .= sprintf("<span class=unimportant>%s</span>" .
1747                        "<span class=disasmloc>:%d</span>",
1748                        HtmlEscape(CleanFileName($f)), $l);
1749      } else {
1750        # De-emphasize the entire location
1751        $dis .= sprintf("<span class=unimportant>%s:%d</span>",
1752                        HtmlEscape(CleanFileName($f)), $l);
1753      }
1754      $last_dis_filename = $f;
1755      $last_dis_linenum = $l;
1756      $running_disasm .= $dis;
1757      $running_disasm .= "\n";
1758    }
1759
1760    $running1 += $c1;
1761    $running2 += $c2;
1762    $total1 += $c1;
1763    $total2 += $c2;
1764    my $file = $e->[1];
1765    my $line = $e->[2];
1766    if (($file eq $filename) &&
1767        ($line >= $firstline) &&
1768        ($line <= $lastline)) {
1769      # Assign all accumulated samples to this line
1770      AddEntry($samples1, $line, $running1);
1771      AddEntry($samples2, $line, $running2);
1772      $running1 = 0;
1773      $running2 = 0;
1774      if ($html) {
1775        if ($line != $last_touched_line && $disasm{$line} ne '') {
1776          $disasm{$line} .= "\n";
1777        }
1778        $disasm{$line} .= $running_disasm;
1779        $running_disasm = '';
1780        $last_touched_line = $line;
1781      }
1782    }
1783  }
1784
1785  # Assign any leftover samples to $lastline
1786  AddEntry($samples1, $lastline, $running1);
1787  AddEntry($samples2, $lastline, $running2);
1788  if ($html) {
1789    if ($lastline != $last_touched_line && $disasm{$lastline} ne '') {
1790      $disasm{$lastline} .= "\n";
1791    }
1792    $disasm{$lastline} .= $running_disasm;
1793  }
1794
1795  if ($html) {
1796    printf $output (
1797      "<h1>%s</h1>%s\n<pre onClick=\"jeprof_toggle_asm()\">\n" .
1798      "Total:%6s %6s (flat / cumulative %s)\n",
1799      HtmlEscape(ShortFunctionName($routine)),
1800      HtmlEscape(CleanFileName($filename)),
1801      Unparse($total1),
1802      Unparse($total2),
1803      Units());
1804  } else {
1805    printf $output (
1806      "ROUTINE ====================== %s in %s\n" .
1807      "%6s %6s Total %s (flat / cumulative)\n",
1808      ShortFunctionName($routine),
1809      CleanFileName($filename),
1810      Unparse($total1),
1811      Unparse($total2),
1812      Units());
1813  }
1814  if (!open(FILE, "<$filename")) {
1815    print STDERR "$filename: $!\n";
1816    return 0;
1817  }
1818  my $l = 0;
1819  while (<FILE>) {
1820    s/\r//g;         # turn windows-looking lines into unix-looking lines
1821    $l++;
1822    if ($l >= $firstline - 5 &&
1823        (($l <= $oldlastline + 5) || ($l <= $lastline))) {
1824      chop;
1825      my $text = $_;
1826      if ($l == $firstline) { print $output $skip_marker; }
1827      my $n1 = GetEntry($samples1, $l);
1828      my $n2 = GetEntry($samples2, $l);
1829      if ($html) {
1830        # Emit a span that has one of the following classes:
1831        #    livesrc -- has samples
1832        #    deadsrc -- has disassembly, but with no samples
1833        #    nop     -- has no matching disasembly
1834        # Also emit an optional span containing disassembly.
1835        my $dis = $disasm{$l};
1836        my $asm = "";
1837        if (defined($dis) && $dis ne '') {
1838          $asm = "<span class=\"asm\">" . $dis . "</span>";
1839        }
1840        my $source_class = (($n1 + $n2 > 0)
1841                            ? "livesrc"
1842                            : (($asm ne "") ? "deadsrc" : "nop"));
1843        printf $output (
1844          "<span class=\"line\">%5d</span> " .
1845          "<span class=\"%s\">%6s %6s %s</span>%s\n",
1846          $l, $source_class,
1847          HtmlPrintNumber($n1),
1848          HtmlPrintNumber($n2),
1849          HtmlEscape($text),
1850          $asm);
1851      } else {
1852        printf $output(
1853          "%6s %6s %4d: %s\n",
1854          UnparseAlt($n1),
1855          UnparseAlt($n2),
1856          $l,
1857          $text);
1858      }
1859      if ($l == $lastline)  { print $output $skip_marker; }
1860    };
1861  }
1862  close(FILE);
1863  if ($html) {
1864    print $output "</pre>\n";
1865  }
1866  return 1;
1867}
1868
1869# Return the source line for the specified file/linenumber.
1870# Returns undef if not found.
1871sub SourceLine {
1872  my $file = shift;
1873  my $line = shift;
1874
1875  # Look in cache
1876  if (!defined($main::source_cache{$file})) {
1877    if (100 < scalar keys(%main::source_cache)) {
1878      # Clear the cache when it gets too big
1879      $main::source_cache = ();
1880    }
1881
1882    # Read all lines from the file
1883    if (!open(FILE, "<$file")) {
1884      print STDERR "$file: $!\n";
1885      $main::source_cache{$file} = [];  # Cache the negative result
1886      return undef;
1887    }
1888    my $lines = [];
1889    push(@{$lines}, "");        # So we can use 1-based line numbers as indices
1890    while (<FILE>) {
1891      push(@{$lines}, $_);
1892    }
1893    close(FILE);
1894
1895    # Save the lines in the cache
1896    $main::source_cache{$file} = $lines;
1897  }
1898
1899  my $lines = $main::source_cache{$file};
1900  if (($line < 0) || ($line > $#{$lines})) {
1901    return undef;
1902  } else {
1903    return $lines->[$line];
1904  }
1905}
1906
1907# Print disassembly for one routine with interspersed source if available
1908sub PrintDisassembledFunction {
1909  my $prog = shift;
1910  my $offset = shift;
1911  my $routine = shift;
1912  my $flat = shift;
1913  my $cumulative = shift;
1914  my $start_addr = shift;
1915  my $end_addr = shift;
1916  my $total = shift;
1917
1918  # Disassemble all instructions
1919  my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);
1920
1921  # Make array of counts per instruction
1922  my @flat_count = ();
1923  my @cum_count = ();
1924  my $flat_total = 0;
1925  my $cum_total = 0;
1926  foreach my $e (@instructions) {
1927    # Add up counts for all address that fall inside this instruction
1928    my $c1 = 0;
1929    my $c2 = 0;
1930    for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {
1931      $c1 += GetEntry($flat, $a);
1932      $c2 += GetEntry($cumulative, $a);
1933    }
1934    push(@flat_count, $c1);
1935    push(@cum_count, $c2);
1936    $flat_total += $c1;
1937    $cum_total += $c2;
1938  }
1939
1940  # Print header with total counts
1941  printf("ROUTINE ====================== %s\n" .
1942         "%6s %6s %s (flat, cumulative) %.1f%% of total\n",
1943         ShortFunctionName($routine),
1944         Unparse($flat_total),
1945         Unparse($cum_total),
1946         Units(),
1947         ($cum_total * 100.0) / $total);
1948
1949  # Process instructions in order
1950  my $current_file = "";
1951  for (my $i = 0; $i <= $#instructions; ) {
1952    my $e = $instructions[$i];
1953
1954    # Print the new file name whenever we switch files
1955    if ($e->[1] ne $current_file) {
1956      $current_file = $e->[1];
1957      my $fname = $current_file;
1958      $fname =~ s|^\./||;   # Trim leading "./"
1959
1960      # Shorten long file names
1961      if (length($fname) >= 58) {
1962        $fname = "..." . substr($fname, -55);
1963      }
1964      printf("-------------------- %s\n", $fname);
1965    }
1966
1967    # TODO: Compute range of lines to print together to deal with
1968    # small reorderings.
1969    my $first_line = $e->[2];
1970    my $last_line = $first_line;
1971    my %flat_sum = ();
1972    my %cum_sum = ();
1973    for (my $l = $first_line; $l <= $last_line; $l++) {
1974      $flat_sum{$l} = 0;
1975      $cum_sum{$l} = 0;
1976    }
1977
1978    # Find run of instructions for this range of source lines
1979    my $first_inst = $i;
1980    while (($i <= $#instructions) &&
1981           ($instructions[$i]->[2] >= $first_line) &&
1982           ($instructions[$i]->[2] <= $last_line)) {
1983      $e = $instructions[$i];
1984      $flat_sum{$e->[2]} += $flat_count[$i];
1985      $cum_sum{$e->[2]} += $cum_count[$i];
1986      $i++;
1987    }
1988    my $last_inst = $i - 1;
1989
1990    # Print source lines
1991    for (my $l = $first_line; $l <= $last_line; $l++) {
1992      my $line = SourceLine($current_file, $l);
1993      if (!defined($line)) {
1994        $line = "?\n";
1995        next;
1996      } else {
1997        $line =~ s/^\s+//;
1998      }
1999      printf("%6s %6s %5d: %s",
2000             UnparseAlt($flat_sum{$l}),
2001             UnparseAlt($cum_sum{$l}),
2002             $l,
2003             $line);
2004    }
2005
2006    # Print disassembly
2007    for (my $x = $first_inst; $x <= $last_inst; $x++) {
2008      my $e = $instructions[$x];
2009      printf("%6s %6s    %8s: %6s\n",
2010             UnparseAlt($flat_count[$x]),
2011             UnparseAlt($cum_count[$x]),
2012             UnparseAddress($offset, $e->[0]),
2013             CleanDisassembly($e->[3]));
2014    }
2015  }
2016}
2017
2018# Print DOT graph
2019sub PrintDot {
2020  my $prog = shift;
2021  my $symbols = shift;
2022  my $raw = shift;
2023  my $flat = shift;
2024  my $cumulative = shift;
2025  my $overall_total = shift;
2026
2027  # Get total
2028  my $local_total = TotalProfile($flat);
2029  my $nodelimit = int($main::opt_nodefraction * $local_total);
2030  my $edgelimit = int($main::opt_edgefraction * $local_total);
2031  my $nodecount = $main::opt_nodecount;
2032
2033  # Find nodes to include
2034  my @list = (sort { abs(GetEntry($cumulative, $b)) <=>
2035                     abs(GetEntry($cumulative, $a))
2036                     || $a cmp $b }
2037              keys(%{$cumulative}));
2038  my $last = $nodecount - 1;
2039  if ($last > $#list) {
2040    $last = $#list;
2041  }
2042  while (($last >= 0) &&
2043         (abs(GetEntry($cumulative, $list[$last])) <= $nodelimit)) {
2044    $last--;
2045  }
2046  if ($last < 0) {
2047    print STDERR "No nodes to print\n";
2048    return 0;
2049  }
2050
2051  if ($nodelimit > 0 || $edgelimit > 0) {
2052    printf STDERR ("Dropping nodes with <= %s %s; edges with <= %s abs(%s)\n",
2053                   Unparse($nodelimit), Units(),
2054                   Unparse($edgelimit), Units());
2055  }
2056
2057  # Open DOT output file
2058  my $output;
2059  my $escaped_dot = ShellEscape(@DOT);
2060  my $escaped_ps2pdf = ShellEscape(@PS2PDF);
2061  if ($main::opt_gv) {
2062    my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "ps"));
2063    $output = "| $escaped_dot -Tps2 >$escaped_outfile";
2064  } elsif ($main::opt_evince) {
2065    my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "pdf"));
2066    $output = "| $escaped_dot -Tps2 | $escaped_ps2pdf - $escaped_outfile";
2067  } elsif ($main::opt_ps) {
2068    $output = "| $escaped_dot -Tps2";
2069  } elsif ($main::opt_pdf) {
2070    $output = "| $escaped_dot -Tps2 | $escaped_ps2pdf - -";
2071  } elsif ($main::opt_web || $main::opt_svg) {
2072    # We need to post-process the SVG, so write to a temporary file always.
2073    my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "svg"));
2074    $output = "| $escaped_dot -Tsvg >$escaped_outfile";
2075  } elsif ($main::opt_gif) {
2076    $output = "| $escaped_dot -Tgif";
2077  } else {
2078    $output = ">&STDOUT";
2079  }
2080  open(DOT, $output) || error("$output: $!\n");
2081
2082  # Title
2083  printf DOT ("digraph \"%s; %s %s\" {\n",
2084              $prog,
2085              Unparse($overall_total),
2086              Units());
2087  if ($main::opt_pdf) {
2088    # The output is more printable if we set the page size for dot.
2089    printf DOT ("size=\"8,11\"\n");
2090  }
2091  printf DOT ("node [width=0.375,height=0.25];\n");
2092
2093  # Print legend
2094  printf DOT ("Legend [shape=box,fontsize=24,shape=plaintext," .
2095              "label=\"%s\\l%s\\l%s\\l%s\\l%s\\l\"];\n",
2096              $prog,
2097              sprintf("Total %s: %s", Units(), Unparse($overall_total)),
2098              sprintf("Focusing on: %s", Unparse($local_total)),
2099              sprintf("Dropped nodes with <= %s abs(%s)",
2100                      Unparse($nodelimit), Units()),
2101              sprintf("Dropped edges with <= %s %s",
2102                      Unparse($edgelimit), Units())
2103              );
2104
2105  # Print nodes
2106  my %node = ();
2107  my $nextnode = 1;
2108  foreach my $a (@list[0..$last]) {
2109    # Pick font size
2110    my $f = GetEntry($flat, $a);
2111    my $c = GetEntry($cumulative, $a);
2112
2113    my $fs = 8;
2114    if ($local_total > 0) {
2115      $fs = 8 + (50.0 * sqrt(abs($f * 1.0 / $local_total)));
2116    }
2117
2118    $node{$a} = $nextnode++;
2119    my $sym = $a;
2120    $sym =~ s/\s+/\\n/g;
2121    $sym =~ s/::/\\n/g;
2122
2123    # Extra cumulative info to print for non-leaves
2124    my $extra = "";
2125    if ($f != $c) {
2126      $extra = sprintf("\\rof %s (%s)",
2127                       Unparse($c),
2128                       Percent($c, $local_total));
2129    }
2130    my $style = "";
2131    if ($main::opt_heapcheck) {
2132      if ($f > 0) {
2133        # make leak-causing nodes more visible (add a background)
2134        $style = ",style=filled,fillcolor=gray"
2135      } elsif ($f < 0) {
2136        # make anti-leak-causing nodes (which almost never occur)
2137        # stand out as well (triple border)
2138        $style = ",peripheries=3"
2139      }
2140    }
2141
2142    printf DOT ("N%d [label=\"%s\\n%s (%s)%s\\r" .
2143                "\",shape=box,fontsize=%.1f%s];\n",
2144                $node{$a},
2145                $sym,
2146                Unparse($f),
2147                Percent($f, $local_total),
2148                $extra,
2149                $fs,
2150                $style,
2151               );
2152  }
2153
2154  # Get edges and counts per edge
2155  my %edge = ();
2156  my $n;
2157  my $fullname_to_shortname_map = {};
2158  FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);
2159  foreach my $k (keys(%{$raw})) {
2160    # TODO: omit low %age edges
2161    $n = $raw->{$k};
2162    my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);
2163    for (my $i = 1; $i <= $#translated; $i++) {
2164      my $src = $translated[$i];
2165      my $dst = $translated[$i-1];
2166      #next if ($src eq $dst);  # Avoid self-edges?
2167      if (exists($node{$src}) && exists($node{$dst})) {
2168        my $edge_label = "$src\001$dst";
2169        if (!exists($edge{$edge_label})) {
2170          $edge{$edge_label} = 0;
2171        }
2172        $edge{$edge_label} += $n;
2173      }
2174    }
2175  }
2176
2177  # Print edges (process in order of decreasing counts)
2178  my %indegree = ();   # Number of incoming edges added per node so far
2179  my %outdegree = ();  # Number of outgoing edges added per node so far
2180  foreach my $e (sort { $edge{$b} <=> $edge{$a} } keys(%edge)) {
2181    my @x = split(/\001/, $e);
2182    $n = $edge{$e};
2183
2184    # Initialize degree of kept incoming and outgoing edges if necessary
2185    my $src = $x[0];
2186    my $dst = $x[1];
2187    if (!exists($outdegree{$src})) { $outdegree{$src} = 0; }
2188    if (!exists($indegree{$dst})) { $indegree{$dst} = 0; }
2189
2190    my $keep;
2191    if ($indegree{$dst} == 0) {
2192      # Keep edge if needed for reachability
2193      $keep = 1;
2194    } elsif (abs($n) <= $edgelimit) {
2195      # Drop if we are below --edgefraction
2196      $keep = 0;
2197    } elsif ($outdegree{$src} >= $main::opt_maxdegree ||
2198             $indegree{$dst} >= $main::opt_maxdegree) {
2199      # Keep limited number of in/out edges per node
2200      $keep = 0;
2201    } else {
2202      $keep = 1;
2203    }
2204
2205    if ($keep) {
2206      $outdegree{$src}++;
2207      $indegree{$dst}++;
2208
2209      # Compute line width based on edge count
2210      my $fraction = abs($local_total ? (3 * ($n / $local_total)) : 0);
2211      if ($fraction > 1) { $fraction = 1; }
2212      my $w = $fraction * 2;
2213      if ($w < 1 && ($main::opt_web || $main::opt_svg)) {
2214        # SVG output treats line widths < 1 poorly.
2215        $w = 1;
2216      }
2217
2218      # Dot sometimes segfaults if given edge weights that are too large, so
2219      # we cap the weights at a large value
2220      my $edgeweight = abs($n) ** 0.7;
2221      if ($edgeweight > 100000) { $edgeweight = 100000; }
2222      $edgeweight = int($edgeweight);
2223
2224      my $style = sprintf("setlinewidth(%f)", $w);
2225      if ($x[1] =~ m/\(inline\)/) {
2226        $style .= ",dashed";
2227      }
2228
2229      # Use a slightly squashed function of the edge count as the weight
2230      printf DOT ("N%s -> N%s [label=%s, weight=%d, style=\"%s\"];\n",
2231                  $node{$x[0]},
2232                  $node{$x[1]},
2233                  Unparse($n),
2234                  $edgeweight,
2235                  $style);
2236    }
2237  }
2238
2239  print DOT ("}\n");
2240  close(DOT);
2241
2242  if ($main::opt_web || $main::opt_svg) {
2243    # Rewrite SVG to be more usable inside web browser.
2244    RewriteSvg(TempName($main::next_tmpfile, "svg"));
2245  }
2246
2247  return 1;
2248}
2249
2250sub RewriteSvg {
2251  my $svgfile = shift;
2252
2253  open(SVG, $svgfile) || die "open temp svg: $!";
2254  my @svg = <SVG>;
2255  close(SVG);
2256  unlink $svgfile;
2257  my $svg = join('', @svg);
2258
2259  # Dot's SVG output is
2260  #
2261  #    <svg width="___" height="___"
2262  #     viewBox="___" xmlns=...>
2263  #    <g id="graph0" transform="...">
2264  #    ...
2265  #    </g>
2266  #    </svg>
2267  #
2268  # Change it to
2269  #
2270  #    <svg width="100%" height="100%"
2271  #     xmlns=...>
2272  #    $svg_javascript
2273  #    <g id="viewport" transform="translate(0,0)">
2274  #    <g id="graph0" transform="...">
2275  #    ...
2276  #    </g>
2277  #    </g>
2278  #    </svg>
2279
2280  # Fix width, height; drop viewBox.
2281  $svg =~ s/(?s)<svg width="[^"]+" height="[^"]+"(.*?)viewBox="[^"]+"/<svg width="100%" height="100%"$1/;
2282
2283  # Insert script, viewport <g> above first <g>
2284  my $svg_javascript = SvgJavascript();
2285  my $viewport = "<g id=\"viewport\" transform=\"translate(0,0)\">\n";
2286  $svg =~ s/<g id="graph\d"/$svg_javascript$viewport$&/;
2287
2288  # Insert final </g> above </svg>.
2289  $svg =~ s/(.*)(<\/svg>)/$1<\/g>$2/;
2290  $svg =~ s/<g id="graph\d"(.*?)/<g id="viewport"$1/;
2291
2292  if ($main::opt_svg) {
2293    # --svg: write to standard output.
2294    print $svg;
2295  } else {
2296    # Write back to temporary file.
2297    open(SVG, ">$svgfile") || die "open $svgfile: $!";
2298    print SVG $svg;
2299    close(SVG);
2300  }
2301}
2302
2303sub SvgJavascript {
2304  return <<'EOF';
2305<script type="text/ecmascript"><![CDATA[
2306// SVGPan
2307// http://www.cyberz.org/blog/2009/12/08/svgpan-a-javascript-svg-panzoomdrag-library/
2308// Local modification: if(true || ...) below to force panning, never moving.
2309
2310/**
2311 *  SVGPan library 1.2
2312 * ====================
2313 *
2314 * Given an unique existing element with id "viewport", including the
2315 * the library into any SVG adds the following capabilities:
2316 *
2317 *  - Mouse panning
2318 *  - Mouse zooming (using the wheel)
2319 *  - Object dargging
2320 *
2321 * Known issues:
2322 *
2323 *  - Zooming (while panning) on Safari has still some issues
2324 *
2325 * Releases:
2326 *
2327 * 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui
2328 *	Fixed a bug with browser mouse handler interaction
2329 *
2330 * 1.1, Wed Feb  3 17:39:33 GMT 2010, Zeng Xiaohui
2331 *	Updated the zoom code to support the mouse wheel on Safari/Chrome
2332 *
2333 * 1.0, Andrea Leofreddi
2334 *	First release
2335 *
2336 * This code is licensed under the following BSD license:
2337 *
2338 * Copyright 2009-2010 Andrea Leofreddi <a.leofreddi@itcharm.com>. All rights reserved.
2339 *
2340 * Redistribution and use in source and binary forms, with or without modification, are
2341 * permitted provided that the following conditions are met:
2342 *
2343 *    1. Redistributions of source code must retain the above copyright notice, this list of
2344 *       conditions and the following disclaimer.
2345 *
2346 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
2347 *       of conditions and the following disclaimer in the documentation and/or other materials
2348 *       provided with the distribution.
2349 *
2350 * THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ``AS IS'' AND ANY EXPRESS OR IMPLIED
2351 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
2352 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR
2353 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2354 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2355 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
2356 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
2357 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
2358 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2359 *
2360 * The views and conclusions contained in the software and documentation are those of the
2361 * authors and should not be interpreted as representing official policies, either expressed
2362 * or implied, of Andrea Leofreddi.
2363 */
2364
2365var root = document.documentElement;
2366
2367var state = 'none', stateTarget, stateOrigin, stateTf;
2368
2369setupHandlers(root);
2370
2371/**
2372 * Register handlers
2373 */
2374function setupHandlers(root){
2375	setAttributes(root, {
2376		"onmouseup" : "add(evt)",
2377		"onmousedown" : "handleMouseDown(evt)",
2378		"onmousemove" : "handleMouseMove(evt)",
2379		"onmouseup" : "handleMouseUp(evt)",
2380		//"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element
2381	});
2382
2383	if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0)
2384		window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari
2385	else
2386		window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others
2387
2388	var g = svgDoc.getElementById("svg");
2389	g.width = "100%";
2390	g.height = "100%";
2391}
2392
2393/**
2394 * Instance an SVGPoint object with given event coordinates.
2395 */
2396function getEventPoint(evt) {
2397	var p = root.createSVGPoint();
2398
2399	p.x = evt.clientX;
2400	p.y = evt.clientY;
2401
2402	return p;
2403}
2404
2405/**
2406 * Sets the current transform matrix of an element.
2407 */
2408function setCTM(element, matrix) {
2409	var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
2410
2411	element.setAttribute("transform", s);
2412}
2413
2414/**
2415 * Dumps a matrix to a string (useful for debug).
2416 */
2417function dumpMatrix(matrix) {
2418	var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n  " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n  0, 0, 1 ]";
2419
2420	return s;
2421}
2422
2423/**
2424 * Sets attributes of an element.
2425 */
2426function setAttributes(element, attributes){
2427	for (i in attributes)
2428		element.setAttributeNS(null, i, attributes[i]);
2429}
2430
2431/**
2432 * Handle mouse move event.
2433 */
2434function handleMouseWheel(evt) {
2435	if(evt.preventDefault)
2436		evt.preventDefault();
2437
2438	evt.returnValue = false;
2439
2440	var svgDoc = evt.target.ownerDocument;
2441
2442	var delta;
2443
2444	if(evt.wheelDelta)
2445		delta = evt.wheelDelta / 3600; // Chrome/Safari
2446	else
2447		delta = evt.detail / -90; // Mozilla
2448
2449	var z = 1 + delta; // Zoom factor: 0.9/1.1
2450
2451	var g = svgDoc.getElementById("viewport");
2452
2453	var p = getEventPoint(evt);
2454
2455	p = p.matrixTransform(g.getCTM().inverse());
2456
2457	// Compute new scale matrix in current mouse position
2458	var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
2459
2460        setCTM(g, g.getCTM().multiply(k));
2461
2462	stateTf = stateTf.multiply(k.inverse());
2463}
2464
2465/**
2466 * Handle mouse move event.
2467 */
2468function handleMouseMove(evt) {
2469	if(evt.preventDefault)
2470		evt.preventDefault();
2471
2472	evt.returnValue = false;
2473
2474	var svgDoc = evt.target.ownerDocument;
2475
2476	var g = svgDoc.getElementById("viewport");
2477
2478	if(state == 'pan') {
2479		// Pan mode
2480		var p = getEventPoint(evt).matrixTransform(stateTf);
2481
2482		setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y));
2483	} else if(state == 'move') {
2484		// Move mode
2485		var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse());
2486
2487		setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM()));
2488
2489		stateOrigin = p;
2490	}
2491}
2492
2493/**
2494 * Handle click event.
2495 */
2496function handleMouseDown(evt) {
2497	if(evt.preventDefault)
2498		evt.preventDefault();
2499
2500	evt.returnValue = false;
2501
2502	var svgDoc = evt.target.ownerDocument;
2503
2504	var g = svgDoc.getElementById("viewport");
2505
2506	if(true || evt.target.tagName == "svg") {
2507		// Pan mode
2508		state = 'pan';
2509
2510		stateTf = g.getCTM().inverse();
2511
2512		stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
2513	} else {
2514		// Move mode
2515		state = 'move';
2516
2517		stateTarget = evt.target;
2518
2519		stateTf = g.getCTM().inverse();
2520
2521		stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
2522	}
2523}
2524
2525/**
2526 * Handle mouse button release event.
2527 */
2528function handleMouseUp(evt) {
2529	if(evt.preventDefault)
2530		evt.preventDefault();
2531
2532	evt.returnValue = false;
2533
2534	var svgDoc = evt.target.ownerDocument;
2535
2536	if(state == 'pan' || state == 'move') {
2537		// Quit pan mode
2538		state = '';
2539	}
2540}
2541
2542]]></script>
2543EOF
2544}
2545
2546# Provides a map from fullname to shortname for cases where the
2547# shortname is ambiguous.  The symlist has both the fullname and
2548# shortname for all symbols, which is usually fine, but sometimes --
2549# such as overloaded functions -- two different fullnames can map to
2550# the same shortname.  In that case, we use the address of the
2551# function to disambiguate the two.  This function fills in a map that
2552# maps fullnames to modified shortnames in such cases.  If a fullname
2553# is not present in the map, the 'normal' shortname provided by the
2554# symlist is the appropriate one to use.
2555sub FillFullnameToShortnameMap {
2556  my $symbols = shift;
2557  my $fullname_to_shortname_map = shift;
2558  my $shortnames_seen_once = {};
2559  my $shortnames_seen_more_than_once = {};
2560
2561  foreach my $symlist (values(%{$symbols})) {
2562    # TODO(csilvers): deal with inlined symbols too.
2563    my $shortname = $symlist->[0];
2564    my $fullname = $symlist->[2];
2565    if ($fullname !~ /<[0-9a-fA-F]+>$/) {  # fullname doesn't end in an address
2566      next;       # the only collisions we care about are when addresses differ
2567    }
2568    if (defined($shortnames_seen_once->{$shortname}) &&
2569        $shortnames_seen_once->{$shortname} ne $fullname) {
2570      $shortnames_seen_more_than_once->{$shortname} = 1;
2571    } else {
2572      $shortnames_seen_once->{$shortname} = $fullname;
2573    }
2574  }
2575
2576  foreach my $symlist (values(%{$symbols})) {
2577    my $shortname = $symlist->[0];
2578    my $fullname = $symlist->[2];
2579    # TODO(csilvers): take in a list of addresses we care about, and only
2580    # store in the map if $symlist->[1] is in that list.  Saves space.
2581    next if defined($fullname_to_shortname_map->{$fullname});
2582    if (defined($shortnames_seen_more_than_once->{$shortname})) {
2583      if ($fullname =~ /<0*([^>]*)>$/) {   # fullname has address at end of it
2584        $fullname_to_shortname_map->{$fullname} = "$shortname\@$1";
2585      }
2586    }
2587  }
2588}
2589
2590# Return a small number that identifies the argument.
2591# Multiple calls with the same argument will return the same number.
2592# Calls with different arguments will return different numbers.
2593sub ShortIdFor {
2594  my $key = shift;
2595  my $id = $main::uniqueid{$key};
2596  if (!defined($id)) {
2597    $id = keys(%main::uniqueid) + 1;
2598    $main::uniqueid{$key} = $id;
2599  }
2600  return $id;
2601}
2602
2603# Translate a stack of addresses into a stack of symbols
2604sub TranslateStack {
2605  my $symbols = shift;
2606  my $fullname_to_shortname_map = shift;
2607  my $k = shift;
2608
2609  my @addrs = split(/\n/, $k);
2610  my @result = ();
2611  for (my $i = 0; $i <= $#addrs; $i++) {
2612    my $a = $addrs[$i];
2613
2614    # Skip large addresses since they sometimes show up as fake entries on RH9
2615    if (length($a) > 8 && $a gt "7fffffffffffffff") {
2616      next;
2617    }
2618
2619    if ($main::opt_disasm || $main::opt_list) {
2620      # We want just the address for the key
2621      push(@result, $a);
2622      next;
2623    }
2624
2625    my $symlist = $symbols->{$a};
2626    if (!defined($symlist)) {
2627      $symlist = [$a, "", $a];
2628    }
2629
2630    # We can have a sequence of symbols for a particular entry
2631    # (more than one symbol in the case of inlining).  Callers
2632    # come before callees in symlist, so walk backwards since
2633    # the translated stack should contain callees before callers.
2634    for (my $j = $#{$symlist}; $j >= 2; $j -= 3) {
2635      my $func = $symlist->[$j-2];
2636      my $fileline = $symlist->[$j-1];
2637      my $fullfunc = $symlist->[$j];
2638      if (defined($fullname_to_shortname_map->{$fullfunc})) {
2639        $func = $fullname_to_shortname_map->{$fullfunc};
2640      }
2641      if ($j > 2) {
2642        $func = "$func (inline)";
2643      }
2644
2645      # Do not merge nodes corresponding to Callback::Run since that
2646      # causes confusing cycles in dot display.  Instead, we synthesize
2647      # a unique name for this frame per caller.
2648      if ($func =~ m/Callback.*::Run$/) {
2649        my $caller = ($i > 0) ? $addrs[$i-1] : 0;
2650        $func = "Run#" . ShortIdFor($caller);
2651      }
2652
2653      if ($main::opt_addresses) {
2654        push(@result, "$a $func $fileline");
2655      } elsif ($main::opt_lines) {
2656        if ($func eq '??' && $fileline eq '??:0') {
2657          push(@result, "$a");
2658        } else {
2659          push(@result, "$func $fileline");
2660        }
2661      } elsif ($main::opt_functions) {
2662        if ($func eq '??') {
2663          push(@result, "$a");
2664        } else {
2665          push(@result, $func);
2666        }
2667      } elsif ($main::opt_files) {
2668        if ($fileline eq '??:0' || $fileline eq '') {
2669          push(@result, "$a");
2670        } else {
2671          my $f = $fileline;
2672          $f =~ s/:\d+$//;
2673          push(@result, $f);
2674        }
2675      } else {
2676        push(@result, $a);
2677        last;  # Do not print inlined info
2678      }
2679    }
2680  }
2681
2682  # print join(",", @addrs), " => ", join(",", @result), "\n";
2683  return @result;
2684}
2685
2686# Generate percent string for a number and a total
2687sub Percent {
2688  my $num = shift;
2689  my $tot = shift;
2690  if ($tot != 0) {
2691    return sprintf("%.1f%%", $num * 100.0 / $tot);
2692  } else {
2693    return ($num == 0) ? "nan" : (($num > 0) ? "+inf" : "-inf");
2694  }
2695}
2696
2697# Generate pretty-printed form of number
2698sub Unparse {
2699  my $num = shift;
2700  if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
2701    if ($main::opt_inuse_objects || $main::opt_alloc_objects) {
2702      return sprintf("%d", $num);
2703    } else {
2704      if ($main::opt_show_bytes) {
2705        return sprintf("%d", $num);
2706      } else {
2707        return sprintf("%.1f", $num / 1048576.0);
2708      }
2709    }
2710  } elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) {
2711    return sprintf("%.3f", $num / 1e9); # Convert nanoseconds to seconds
2712  } else {
2713    return sprintf("%d", $num);
2714  }
2715}
2716
2717# Alternate pretty-printed form: 0 maps to "."
2718sub UnparseAlt {
2719  my $num = shift;
2720  if ($num == 0) {
2721    return ".";
2722  } else {
2723    return Unparse($num);
2724  }
2725}
2726
2727# Alternate pretty-printed form: 0 maps to ""
2728sub HtmlPrintNumber {
2729  my $num = shift;
2730  if ($num == 0) {
2731    return "";
2732  } else {
2733    return Unparse($num);
2734  }
2735}
2736
2737# Return output units
2738sub Units {
2739  if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
2740    if ($main::opt_inuse_objects || $main::opt_alloc_objects) {
2741      return "objects";
2742    } else {
2743      if ($main::opt_show_bytes) {
2744        return "B";
2745      } else {
2746        return "MB";
2747      }
2748    }
2749  } elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) {
2750    return "seconds";
2751  } else {
2752    return "samples";
2753  }
2754}
2755
2756##### Profile manipulation code #####
2757
2758# Generate flattened profile:
2759# If count is charged to stack [a,b,c,d], in generated profile,
2760# it will be charged to [a]
2761sub FlatProfile {
2762  my $profile = shift;
2763  my $result = {};
2764  foreach my $k (keys(%{$profile})) {
2765    my $count = $profile->{$k};
2766    my @addrs = split(/\n/, $k);
2767    if ($#addrs >= 0) {
2768      AddEntry($result, $addrs[0], $count);
2769    }
2770  }
2771  return $result;
2772}
2773
2774# Generate cumulative profile:
2775# If count is charged to stack [a,b,c,d], in generated profile,
2776# it will be charged to [a], [b], [c], [d]
2777sub CumulativeProfile {
2778  my $profile = shift;
2779  my $result = {};
2780  foreach my $k (keys(%{$profile})) {
2781    my $count = $profile->{$k};
2782    my @addrs = split(/\n/, $k);
2783    foreach my $a (@addrs) {
2784      AddEntry($result, $a, $count);
2785    }
2786  }
2787  return $result;
2788}
2789
2790# If the second-youngest PC on the stack is always the same, returns
2791# that pc.  Otherwise, returns undef.
2792sub IsSecondPcAlwaysTheSame {
2793  my $profile = shift;
2794
2795  my $second_pc = undef;
2796  foreach my $k (keys(%{$profile})) {
2797    my @addrs = split(/\n/, $k);
2798    if ($#addrs < 1) {
2799      return undef;
2800    }
2801    if (not defined $second_pc) {
2802      $second_pc = $addrs[1];
2803    } else {
2804      if ($second_pc ne $addrs[1]) {
2805        return undef;
2806      }
2807    }
2808  }
2809  return $second_pc;
2810}
2811
2812sub ExtractSymbolLocation {
2813  my $symbols = shift;
2814  my $address = shift;
2815  # 'addr2line' outputs "??:0" for unknown locations; we do the
2816  # same to be consistent.
2817  my $location = "??:0:unknown";
2818  if (exists $symbols->{$address}) {
2819    my $file = $symbols->{$address}->[1];
2820    if ($file eq "?") {
2821      $file = "??:0"
2822    }
2823    $location = $file . ":" . $symbols->{$address}->[0];
2824  }
2825  return $location;
2826}
2827
2828# Extracts a graph of calls.
2829sub ExtractCalls {
2830  my $symbols = shift;
2831  my $profile = shift;
2832
2833  my $calls = {};
2834  while( my ($stack_trace, $count) = each %$profile ) {
2835    my @address = split(/\n/, $stack_trace);
2836    my $destination = ExtractSymbolLocation($symbols, $address[0]);
2837    AddEntry($calls, $destination, $count);
2838    for (my $i = 1; $i <= $#address; $i++) {
2839      my $source = ExtractSymbolLocation($symbols, $address[$i]);
2840      my $call = "$source -> $destination";
2841      AddEntry($calls, $call, $count);
2842      $destination = $source;
2843    }
2844  }
2845
2846  return $calls;
2847}
2848
2849sub FilterFrames {
2850  my $symbols = shift;
2851  my $profile = shift;
2852
2853  if ($main::opt_retain eq '' && $main::opt_exclude eq '') {
2854    return $profile;
2855  }
2856
2857  my $result = {};
2858  foreach my $k (keys(%{$profile})) {
2859    my $count = $profile->{$k};
2860    my @addrs = split(/\n/, $k);
2861    my @path = ();
2862    foreach my $a (@addrs) {
2863      my $sym;
2864      if (exists($symbols->{$a})) {
2865        $sym = $symbols->{$a}->[0];
2866      } else {
2867        $sym = $a;
2868      }
2869      if ($main::opt_retain ne '' && $sym !~ m/$main::opt_retain/) {
2870        next;
2871      }
2872      if ($main::opt_exclude ne '' && $sym =~ m/$main::opt_exclude/) {
2873        next;
2874      }
2875      push(@path, $a);
2876    }
2877    if (scalar(@path) > 0) {
2878      my $reduced_path = join("\n", @path);
2879      AddEntry($result, $reduced_path, $count);
2880    }
2881  }
2882
2883  return $result;
2884}
2885
2886sub RemoveUninterestingFrames {
2887  my $symbols = shift;
2888  my $profile = shift;
2889
2890  # List of function names to skip
2891  my %skip = ();
2892  my $skip_regexp = 'NOMATCH';
2893  if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
2894    foreach my $name ('calloc',
2895                      'cfree',
2896                      'malloc',
2897                      'free',
2898                      'memalign',
2899                      'posix_memalign',
2900                      'aligned_alloc',
2901                      'pvalloc',
2902                      'valloc',
2903                      'realloc',
2904                      'mallocx', # jemalloc
2905                      'rallocx', # jemalloc
2906                      'xallocx', # jemalloc
2907                      'dallocx', # jemalloc
2908                      'sdallocx', # jemalloc
2909                      'tc_calloc',
2910                      'tc_cfree',
2911                      'tc_malloc',
2912                      'tc_free',
2913                      'tc_memalign',
2914                      'tc_posix_memalign',
2915                      'tc_pvalloc',
2916                      'tc_valloc',
2917                      'tc_realloc',
2918                      'tc_new',
2919                      'tc_delete',
2920                      'tc_newarray',
2921                      'tc_deletearray',
2922                      'tc_new_nothrow',
2923                      'tc_newarray_nothrow',
2924                      'do_malloc',
2925                      '::do_malloc',   # new name -- got moved to an unnamed ns
2926                      '::do_malloc_or_cpp_alloc',
2927                      'DoSampledAllocation',
2928                      'simple_alloc::allocate',
2929                      '__malloc_alloc_template::allocate',
2930                      '__builtin_delete',
2931                      '__builtin_new',
2932                      '__builtin_vec_delete',
2933                      '__builtin_vec_new',
2934                      'operator new',
2935                      'operator new[]',
2936                      # The entry to our memory-allocation routines on OS X
2937                      'malloc_zone_malloc',
2938                      'malloc_zone_calloc',
2939                      'malloc_zone_valloc',
2940                      'malloc_zone_realloc',
2941                      'malloc_zone_memalign',
2942                      'malloc_zone_free',
2943                      # These mark the beginning/end of our custom sections
2944                      '__start_google_malloc',
2945                      '__stop_google_malloc',
2946                      '__start_malloc_hook',
2947                      '__stop_malloc_hook') {
2948      $skip{$name} = 1;
2949      $skip{"_" . $name} = 1;   # Mach (OS X) adds a _ prefix to everything
2950    }
2951    # TODO: Remove TCMalloc once everything has been
2952    # moved into the tcmalloc:: namespace and we have flushed
2953    # old code out of the system.
2954    $skip_regexp = "TCMalloc|^tcmalloc::";
2955  } elsif ($main::profile_type eq 'contention') {
2956    foreach my $vname ('base::RecordLockProfileData',
2957                       'base::SubmitMutexProfileData',
2958                       'base::SubmitSpinLockProfileData',
2959                       'Mutex::Unlock',
2960                       'Mutex::UnlockSlow',
2961                       'Mutex::ReaderUnlock',
2962                       'MutexLock::~MutexLock',
2963                       'SpinLock::Unlock',
2964                       'SpinLock::SlowUnlock',
2965                       'SpinLockHolder::~SpinLockHolder') {
2966      $skip{$vname} = 1;
2967    }
2968  } elsif ($main::profile_type eq 'cpu') {
2969    # Drop signal handlers used for CPU profile collection
2970    # TODO(dpeng): this should not be necessary; it's taken
2971    # care of by the general 2nd-pc mechanism below.
2972    foreach my $name ('ProfileData::Add',           # historical
2973                      'ProfileData::prof_handler',  # historical
2974                      'CpuProfiler::prof_handler',
2975                      '__FRAME_END__',
2976                      '__pthread_sighandler',
2977                      '__restore') {
2978      $skip{$name} = 1;
2979    }
2980  } else {
2981    # Nothing skipped for unknown types
2982  }
2983
2984  if ($main::profile_type eq 'cpu') {
2985    # If all the second-youngest program counters are the same,
2986    # this STRONGLY suggests that it is an artifact of measurement,
2987    # i.e., stack frames pushed by the CPU profiler signal handler.
2988    # Hence, we delete them.
2989    # (The topmost PC is read from the signal structure, not from
2990    # the stack, so it does not get involved.)
2991    while (my $second_pc = IsSecondPcAlwaysTheSame($profile)) {
2992      my $result = {};
2993      my $func = '';
2994      if (exists($symbols->{$second_pc})) {
2995        $second_pc = $symbols->{$second_pc}->[0];
2996      }
2997      print STDERR "Removing $second_pc from all stack traces.\n";
2998      foreach my $k (keys(%{$profile})) {
2999        my $count = $profile->{$k};
3000        my @addrs = split(/\n/, $k);
3001        splice @addrs, 1, 1;
3002        my $reduced_path = join("\n", @addrs);
3003        AddEntry($result, $reduced_path, $count);
3004      }
3005      $profile = $result;
3006    }
3007  }
3008
3009  my $result = {};
3010  foreach my $k (keys(%{$profile})) {
3011    my $count = $profile->{$k};
3012    my @addrs = split(/\n/, $k);
3013    my @path = ();
3014    foreach my $a (@addrs) {
3015      if (exists($symbols->{$a})) {
3016        my $func = $symbols->{$a}->[0];
3017        if ($skip{$func} || ($func =~ m/$skip_regexp/)) {
3018          # Throw away the portion of the backtrace seen so far, under the
3019          # assumption that previous frames were for functions internal to the
3020          # allocator.
3021          @path = ();
3022          next;
3023        }
3024      }
3025      push(@path, $a);
3026    }
3027    my $reduced_path = join("\n", @path);
3028    AddEntry($result, $reduced_path, $count);
3029  }
3030
3031  $result = FilterFrames($symbols, $result);
3032
3033  return $result;
3034}
3035
3036# Reduce profile to granularity given by user
3037sub ReduceProfile {
3038  my $symbols = shift;
3039  my $profile = shift;
3040  my $result = {};
3041  my $fullname_to_shortname_map = {};
3042  FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);
3043  foreach my $k (keys(%{$profile})) {
3044    my $count = $profile->{$k};
3045    my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);
3046    my @path = ();
3047    my %seen = ();
3048    $seen{''} = 1;      # So that empty keys are skipped
3049    foreach my $e (@translated) {
3050      # To avoid double-counting due to recursion, skip a stack-trace
3051      # entry if it has already been seen
3052      if (!$seen{$e}) {
3053        $seen{$e} = 1;
3054        push(@path, $e);
3055      }
3056    }
3057    my $reduced_path = join("\n", @path);
3058    AddEntry($result, $reduced_path, $count);
3059  }
3060  return $result;
3061}
3062
3063# Does the specified symbol array match the regexp?
3064sub SymbolMatches {
3065  my $sym = shift;
3066  my $re = shift;
3067  if (defined($sym)) {
3068    for (my $i = 0; $i < $#{$sym}; $i += 3) {
3069      if ($sym->[$i] =~ m/$re/ || $sym->[$i+1] =~ m/$re/) {
3070        return 1;
3071      }
3072    }
3073  }
3074  return 0;
3075}
3076
3077# Focus only on paths involving specified regexps
3078sub FocusProfile {
3079  my $symbols = shift;
3080  my $profile = shift;
3081  my $focus = shift;
3082  my $result = {};
3083  foreach my $k (keys(%{$profile})) {
3084    my $count = $profile->{$k};
3085    my @addrs = split(/\n/, $k);
3086    foreach my $a (@addrs) {
3087      # Reply if it matches either the address/shortname/fileline
3088      if (($a =~ m/$focus/) || SymbolMatches($symbols->{$a}, $focus)) {
3089        AddEntry($result, $k, $count);
3090        last;
3091      }
3092    }
3093  }
3094  return $result;
3095}
3096
3097# Focus only on paths not involving specified regexps
3098sub IgnoreProfile {
3099  my $symbols = shift;
3100  my $profile = shift;
3101  my $ignore = shift;
3102  my $result = {};
3103  foreach my $k (keys(%{$profile})) {
3104    my $count = $profile->{$k};
3105    my @addrs = split(/\n/, $k);
3106    my $matched = 0;
3107    foreach my $a (@addrs) {
3108      # Reply if it matches either the address/shortname/fileline
3109      if (($a =~ m/$ignore/) || SymbolMatches($symbols->{$a}, $ignore)) {
3110        $matched = 1;
3111        last;
3112      }
3113    }
3114    if (!$matched) {
3115      AddEntry($result, $k, $count);
3116    }
3117  }
3118  return $result;
3119}
3120
3121# Get total count in profile
3122sub TotalProfile {
3123  my $profile = shift;
3124  my $result = 0;
3125  foreach my $k (keys(%{$profile})) {
3126    $result += $profile->{$k};
3127  }
3128  return $result;
3129}
3130
3131# Add A to B
3132sub AddProfile {
3133  my $A = shift;
3134  my $B = shift;
3135
3136  my $R = {};
3137  # add all keys in A
3138  foreach my $k (keys(%{$A})) {
3139    my $v = $A->{$k};
3140    AddEntry($R, $k, $v);
3141  }
3142  # add all keys in B
3143  foreach my $k (keys(%{$B})) {
3144    my $v = $B->{$k};
3145    AddEntry($R, $k, $v);
3146  }
3147  return $R;
3148}
3149
3150# Merges symbol maps
3151sub MergeSymbols {
3152  my $A = shift;
3153  my $B = shift;
3154
3155  my $R = {};
3156  foreach my $k (keys(%{$A})) {
3157    $R->{$k} = $A->{$k};
3158  }
3159  if (defined($B)) {
3160    foreach my $k (keys(%{$B})) {
3161      $R->{$k} = $B->{$k};
3162    }
3163  }
3164  return $R;
3165}
3166
3167
3168# Add A to B
3169sub AddPcs {
3170  my $A = shift;
3171  my $B = shift;
3172
3173  my $R = {};
3174  # add all keys in A
3175  foreach my $k (keys(%{$A})) {
3176    $R->{$k} = 1
3177  }
3178  # add all keys in B
3179  foreach my $k (keys(%{$B})) {
3180    $R->{$k} = 1
3181  }
3182  return $R;
3183}
3184
3185# Subtract B from A
3186sub SubtractProfile {
3187  my $A = shift;
3188  my $B = shift;
3189
3190  my $R = {};
3191  foreach my $k (keys(%{$A})) {
3192    my $v = $A->{$k} - GetEntry($B, $k);
3193    if ($v < 0 && $main::opt_drop_negative) {
3194      $v = 0;
3195    }
3196    AddEntry($R, $k, $v);
3197  }
3198  if (!$main::opt_drop_negative) {
3199    # Take care of when subtracted profile has more entries
3200    foreach my $k (keys(%{$B})) {
3201      if (!exists($A->{$k})) {
3202        AddEntry($R, $k, 0 - $B->{$k});
3203      }
3204    }
3205  }
3206  return $R;
3207}
3208
3209# Get entry from profile; zero if not present
3210sub GetEntry {
3211  my $profile = shift;
3212  my $k = shift;
3213  if (exists($profile->{$k})) {
3214    return $profile->{$k};
3215  } else {
3216    return 0;
3217  }
3218}
3219
3220# Add entry to specified profile
3221sub AddEntry {
3222  my $profile = shift;
3223  my $k = shift;
3224  my $n = shift;
3225  if (!exists($profile->{$k})) {
3226    $profile->{$k} = 0;
3227  }
3228  $profile->{$k} += $n;
3229}
3230
3231# Add a stack of entries to specified profile, and add them to the $pcs
3232# list.
3233sub AddEntries {
3234  my $profile = shift;
3235  my $pcs = shift;
3236  my $stack = shift;
3237  my $count = shift;
3238  my @k = ();
3239
3240  foreach my $e (split(/\s+/, $stack)) {
3241    my $pc = HexExtend($e);
3242    $pcs->{$pc} = 1;
3243    push @k, $pc;
3244  }
3245  AddEntry($profile, (join "\n", @k), $count);
3246}
3247
3248##### Code to profile a server dynamically #####
3249
3250sub CheckSymbolPage {
3251  my $url = SymbolPageURL();
3252  my $command = ShellEscape(@URL_FETCHER, $url);
3253  open(SYMBOL, "$command |") or error($command);
3254  my $line = <SYMBOL>;
3255  $line =~ s/\r//g;         # turn windows-looking lines into unix-looking lines
3256  close(SYMBOL);
3257  unless (defined($line)) {
3258    error("$url doesn't exist\n");
3259  }
3260
3261  if ($line =~ /^num_symbols:\s+(\d+)$/) {
3262    if ($1 == 0) {
3263      error("Stripped binary. No symbols available.\n");
3264    }
3265  } else {
3266    error("Failed to get the number of symbols from $url\n");
3267  }
3268}
3269
3270sub IsProfileURL {
3271  my $profile_name = shift;
3272  if (-f $profile_name) {
3273    printf STDERR "Using local file $profile_name.\n";
3274    return 0;
3275  }
3276  return 1;
3277}
3278
3279sub ParseProfileURL {
3280  my $profile_name = shift;
3281
3282  if (!defined($profile_name) || $profile_name eq "") {
3283    return ();
3284  }
3285
3286  # Split profile URL - matches all non-empty strings, so no test.
3287  $profile_name =~ m,^(https?://)?([^/]+)(.*?)(/|$PROFILES)?$,;
3288
3289  my $proto = $1 || "http://";
3290  my $hostport = $2;
3291  my $prefix = $3;
3292  my $profile = $4 || "/";
3293
3294  my $host = $hostport;
3295  $host =~ s/:.*//;
3296
3297  my $baseurl = "$proto$hostport$prefix";
3298  return ($host, $baseurl, $profile);
3299}
3300
3301# We fetch symbols from the first profile argument.
3302sub SymbolPageURL {
3303  my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);
3304  return "$baseURL$SYMBOL_PAGE";
3305}
3306
3307sub FetchProgramName() {
3308  my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);
3309  my $url = "$baseURL$PROGRAM_NAME_PAGE";
3310  my $command_line = ShellEscape(@URL_FETCHER, $url);
3311  open(CMDLINE, "$command_line |") or error($command_line);
3312  my $cmdline = <CMDLINE>;
3313  $cmdline =~ s/\r//g;   # turn windows-looking lines into unix-looking lines
3314  close(CMDLINE);
3315  error("Failed to get program name from $url\n") unless defined($cmdline);
3316  $cmdline =~ s/\x00.+//;  # Remove argv[1] and latters.
3317  $cmdline =~ s!\n!!g;  # Remove LFs.
3318  return $cmdline;
3319}
3320
3321# Gee, curl's -L (--location) option isn't reliable at least
3322# with its 7.12.3 version.  Curl will forget to post data if
3323# there is a redirection.  This function is a workaround for
3324# curl.  Redirection happens on borg hosts.
3325sub ResolveRedirectionForCurl {
3326  my $url = shift;
3327  my $command_line = ShellEscape(@URL_FETCHER, "--head", $url);
3328  open(CMDLINE, "$command_line |") or error($command_line);
3329  while (<CMDLINE>) {
3330    s/\r//g;         # turn windows-looking lines into unix-looking lines
3331    if (/^Location: (.*)/) {
3332      $url = $1;
3333    }
3334  }
3335  close(CMDLINE);
3336  return $url;
3337}
3338
3339# Add a timeout flat to URL_FETCHER.  Returns a new list.
3340sub AddFetchTimeout {
3341  my $timeout = shift;
3342  my @fetcher = @_;
3343  if (defined($timeout)) {
3344    if (join(" ", @fetcher) =~ m/\bcurl -s/) {
3345      push(@fetcher, "--max-time", sprintf("%d", $timeout));
3346    } elsif (join(" ", @fetcher) =~ m/\brpcget\b/) {
3347      push(@fetcher, sprintf("--deadline=%d", $timeout));
3348    }
3349  }
3350  return @fetcher;
3351}
3352
3353# Reads a symbol map from the file handle name given as $1, returning
3354# the resulting symbol map.  Also processes variables relating to symbols.
3355# Currently, the only variable processed is 'binary=<value>' which updates
3356# $main::prog to have the correct program name.
3357sub ReadSymbols {
3358  my $in = shift;
3359  my $map = {};
3360  while (<$in>) {
3361    s/\r//g;         # turn windows-looking lines into unix-looking lines
3362    # Removes all the leading zeroes from the symbols, see comment below.
3363    if (m/^0x0*([0-9a-f]+)\s+(.+)/) {
3364      $map->{$1} = $2;
3365    } elsif (m/^---/) {
3366      last;
3367    } elsif (m/^([a-z][^=]*)=(.*)$/ ) {
3368      my ($variable, $value) = ($1, $2);
3369      for ($variable, $value) {
3370        s/^\s+//;
3371        s/\s+$//;
3372      }
3373      if ($variable eq "binary") {
3374        if ($main::prog ne $UNKNOWN_BINARY && $main::prog ne $value) {
3375          printf STDERR ("Warning: Mismatched binary name '%s', using '%s'.\n",
3376                         $main::prog, $value);
3377        }
3378        $main::prog = $value;
3379      } else {
3380        printf STDERR ("Ignoring unknown variable in symbols list: " .
3381            "'%s' = '%s'\n", $variable, $value);
3382      }
3383    }
3384  }
3385  return $map;
3386}
3387
3388sub URLEncode {
3389  my $str = shift;
3390  $str =~ s/([^A-Za-z0-9\-_.!~*'()])/ sprintf "%%%02x", ord $1 /eg;
3391  return $str;
3392}
3393
3394sub AppendSymbolFilterParams {
3395  my $url = shift;
3396  my @params = ();
3397  if ($main::opt_retain ne '') {
3398    push(@params, sprintf("retain=%s", URLEncode($main::opt_retain)));
3399  }
3400  if ($main::opt_exclude ne '') {
3401    push(@params, sprintf("exclude=%s", URLEncode($main::opt_exclude)));
3402  }
3403  if (scalar @params > 0) {
3404    $url = sprintf("%s?%s", $url, join("&", @params));
3405  }
3406  return $url;
3407}
3408
3409# Fetches and processes symbols to prepare them for use in the profile output
3410# code.  If the optional 'symbol_map' arg is not given, fetches symbols from
3411# $SYMBOL_PAGE for all PC values found in profile.  Otherwise, the raw symbols
3412# are assumed to have already been fetched into 'symbol_map' and are simply
3413# extracted and processed.
3414sub FetchSymbols {
3415  my $pcset = shift;
3416  my $symbol_map = shift;
3417
3418  my %seen = ();
3419  my @pcs = grep { !$seen{$_}++ } keys(%$pcset);  # uniq
3420
3421  if (!defined($symbol_map)) {
3422    my $post_data = join("+", sort((map {"0x" . "$_"} @pcs)));
3423
3424    open(POSTFILE, ">$main::tmpfile_sym");
3425    print POSTFILE $post_data;
3426    close(POSTFILE);
3427
3428    my $url = SymbolPageURL();
3429
3430    my $command_line;
3431    if (join(" ", @URL_FETCHER) =~ m/\bcurl -s/) {
3432      $url = ResolveRedirectionForCurl($url);
3433      $url = AppendSymbolFilterParams($url);
3434      $command_line = ShellEscape(@URL_FETCHER, "-d", "\@$main::tmpfile_sym",
3435                                  $url);
3436    } else {
3437      $url = AppendSymbolFilterParams($url);
3438      $command_line = (ShellEscape(@URL_FETCHER, "--post", $url)
3439                       . " < " . ShellEscape($main::tmpfile_sym));
3440    }
3441    # We use c++filt in case $SYMBOL_PAGE gives us mangled symbols.
3442    my $escaped_cppfilt = ShellEscape($obj_tool_map{"c++filt"});
3443    open(SYMBOL, "$command_line | $escaped_cppfilt |") or error($command_line);
3444    $symbol_map = ReadSymbols(*SYMBOL{IO});
3445    close(SYMBOL);
3446  }
3447
3448  my $symbols = {};
3449  foreach my $pc (@pcs) {
3450    my $fullname;
3451    # For 64 bits binaries, symbols are extracted with 8 leading zeroes.
3452    # Then /symbol reads the long symbols in as uint64, and outputs
3453    # the result with a "0x%08llx" format which get rid of the zeroes.
3454    # By removing all the leading zeroes in both $pc and the symbols from
3455    # /symbol, the symbols match and are retrievable from the map.
3456    my $shortpc = $pc;
3457    $shortpc =~ s/^0*//;
3458    # Each line may have a list of names, which includes the function
3459    # and also other functions it has inlined.  They are separated (in
3460    # PrintSymbolizedProfile), by --, which is illegal in function names.
3461    my $fullnames;
3462    if (defined($symbol_map->{$shortpc})) {
3463      $fullnames = $symbol_map->{$shortpc};
3464    } else {
3465      $fullnames = "0x" . $pc;  # Just use addresses
3466    }
3467    my $sym = [];
3468    $symbols->{$pc} = $sym;
3469    foreach my $fullname (split("--", $fullnames)) {
3470      my $name = ShortFunctionName($fullname);
3471      push(@{$sym}, $name, "?", $fullname);
3472    }
3473  }
3474  return $symbols;
3475}
3476
3477sub BaseName {
3478  my $file_name = shift;
3479  $file_name =~ s!^.*/!!;  # Remove directory name
3480  return $file_name;
3481}
3482
3483sub MakeProfileBaseName {
3484  my ($binary_name, $profile_name) = @_;
3485  my ($host, $baseURL, $path) = ParseProfileURL($profile_name);
3486  my $binary_shortname = BaseName($binary_name);
3487  return sprintf("%s.%s.%s",
3488                 $binary_shortname, $main::op_time, $host);
3489}
3490
3491sub FetchDynamicProfile {
3492  my $binary_name = shift;
3493  my $profile_name = shift;
3494  my $fetch_name_only = shift;
3495  my $encourage_patience = shift;
3496
3497  if (!IsProfileURL($profile_name)) {
3498    return $profile_name;
3499  } else {
3500    my ($host, $baseURL, $path) = ParseProfileURL($profile_name);
3501    if ($path eq "" || $path eq "/") {
3502      # Missing type specifier defaults to cpu-profile
3503      $path = $PROFILE_PAGE;
3504    }
3505
3506    my $profile_file = MakeProfileBaseName($binary_name, $profile_name);
3507
3508    my $url = "$baseURL$path";
3509    my $fetch_timeout = undef;
3510    if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE/) {
3511      if ($path =~ m/[?]/) {
3512        $url .= "&";
3513      } else {
3514        $url .= "?";
3515      }
3516      $url .= sprintf("seconds=%d", $main::opt_seconds);
3517      $fetch_timeout = $main::opt_seconds * 1.01 + 60;
3518      # Set $profile_type for consumption by PrintSymbolizedProfile.
3519      $main::profile_type = 'cpu';
3520    } else {
3521      # For non-CPU profiles, we add a type-extension to
3522      # the target profile file name.
3523      my $suffix = $path;
3524      $suffix =~ s,/,.,g;
3525      $profile_file .= $suffix;
3526      # Set $profile_type for consumption by PrintSymbolizedProfile.
3527      if ($path =~ m/$HEAP_PAGE/) {
3528        $main::profile_type = 'heap';
3529      } elsif ($path =~ m/$GROWTH_PAGE/) {
3530        $main::profile_type = 'growth';
3531      } elsif ($path =~ m/$CONTENTION_PAGE/) {
3532        $main::profile_type = 'contention';
3533      }
3534    }
3535
3536    my $profile_dir = $ENV{"JEPROF_TMPDIR"} || ($ENV{HOME} . "/jeprof");
3537    if (! -d $profile_dir) {
3538      mkdir($profile_dir)
3539          || die("Unable to create profile directory $profile_dir: $!\n");
3540    }
3541    my $tmp_profile = "$profile_dir/.tmp.$profile_file";
3542    my $real_profile = "$profile_dir/$profile_file";
3543
3544    if ($fetch_name_only > 0) {
3545      return $real_profile;
3546    }
3547
3548    my @fetcher = AddFetchTimeout($fetch_timeout, @URL_FETCHER);
3549    my $cmd = ShellEscape(@fetcher, $url) . " > " . ShellEscape($tmp_profile);
3550    if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE|$CENSUSPROFILE_PAGE/){
3551      print STDERR "Gathering CPU profile from $url for $main::opt_seconds seconds to\n  ${real_profile}\n";
3552      if ($encourage_patience) {
3553        print STDERR "Be patient...\n";
3554      }
3555    } else {
3556      print STDERR "Fetching $path profile from $url to\n  ${real_profile}\n";
3557    }
3558
3559    (system($cmd) == 0) || error("Failed to get profile: $cmd: $!\n");
3560    (system("mv", $tmp_profile, $real_profile) == 0) || error("Unable to rename profile\n");
3561    print STDERR "Wrote profile to $real_profile\n";
3562    $main::collected_profile = $real_profile;
3563    return $main::collected_profile;
3564  }
3565}
3566
3567# Collect profiles in parallel
3568sub FetchDynamicProfiles {
3569  my $items = scalar(@main::pfile_args);
3570  my $levels = log($items) / log(2);
3571
3572  if ($items == 1) {
3573    $main::profile_files[0] = FetchDynamicProfile($main::prog, $main::pfile_args[0], 0, 1);
3574  } else {
3575    # math rounding issues
3576    if ((2 ** $levels) < $items) {
3577     $levels++;
3578    }
3579    my $count = scalar(@main::pfile_args);
3580    for (my $i = 0; $i < $count; $i++) {
3581      $main::profile_files[$i] = FetchDynamicProfile($main::prog, $main::pfile_args[$i], 1, 0);
3582    }
3583    print STDERR "Fetching $count profiles, Be patient...\n";
3584    FetchDynamicProfilesRecurse($levels, 0, 0);
3585    $main::collected_profile = join(" \\\n    ", @main::profile_files);
3586  }
3587}
3588
3589# Recursively fork a process to get enough processes
3590# collecting profiles
3591sub FetchDynamicProfilesRecurse {
3592  my $maxlevel = shift;
3593  my $level = shift;
3594  my $position = shift;
3595
3596  if (my $pid = fork()) {
3597    $position = 0 | ($position << 1);
3598    TryCollectProfile($maxlevel, $level, $position);
3599    wait;
3600  } else {
3601    $position = 1 | ($position << 1);
3602    TryCollectProfile($maxlevel, $level, $position);
3603    cleanup();
3604    exit(0);
3605  }
3606}
3607
3608# Collect a single profile
3609sub TryCollectProfile {
3610  my $maxlevel = shift;
3611  my $level = shift;
3612  my $position = shift;
3613
3614  if ($level >= ($maxlevel - 1)) {
3615    if ($position < scalar(@main::pfile_args)) {
3616      FetchDynamicProfile($main::prog, $main::pfile_args[$position], 0, 0);
3617    }
3618  } else {
3619    FetchDynamicProfilesRecurse($maxlevel, $level+1, $position);
3620  }
3621}
3622
3623##### Parsing code #####
3624
3625# Provide a small streaming-read module to handle very large
3626# cpu-profile files.  Stream in chunks along a sliding window.
3627# Provides an interface to get one 'slot', correctly handling
3628# endian-ness differences.  A slot is one 32-bit or 64-bit word
3629# (depending on the input profile).  We tell endianness and bit-size
3630# for the profile by looking at the first 8 bytes: in cpu profiles,
3631# the second slot is always 3 (we'll accept anything that's not 0).
3632BEGIN {
3633  package CpuProfileStream;
3634
3635  sub new {
3636    my ($class, $file, $fname) = @_;
3637    my $self = { file        => $file,
3638                 base        => 0,
3639                 stride      => 512 * 1024,   # must be a multiple of bitsize/8
3640                 slots       => [],
3641                 unpack_code => "",           # N for big-endian, V for little
3642                 perl_is_64bit => 1,          # matters if profile is 64-bit
3643    };
3644    bless $self, $class;
3645    # Let unittests adjust the stride
3646    if ($main::opt_test_stride > 0) {
3647      $self->{stride} = $main::opt_test_stride;
3648    }
3649    # Read the first two slots to figure out bitsize and endianness.
3650    my $slots = $self->{slots};
3651    my $str;
3652    read($self->{file}, $str, 8);
3653    # Set the global $address_length based on what we see here.
3654    # 8 is 32-bit (8 hexadecimal chars); 16 is 64-bit (16 hexadecimal chars).
3655    $address_length = ($str eq (chr(0)x8)) ? 16 : 8;
3656    if ($address_length == 8) {
3657      if (substr($str, 6, 2) eq chr(0)x2) {
3658        $self->{unpack_code} = 'V';  # Little-endian.
3659      } elsif (substr($str, 4, 2) eq chr(0)x2) {
3660        $self->{unpack_code} = 'N';  # Big-endian
3661      } else {
3662        ::error("$fname: header size >= 2**16\n");
3663      }
3664      @$slots = unpack($self->{unpack_code} . "*", $str);
3665    } else {
3666      # If we're a 64-bit profile, check if we're a 64-bit-capable
3667      # perl.  Otherwise, each slot will be represented as a float
3668      # instead of an int64, losing precision and making all the
3669      # 64-bit addresses wrong.  We won't complain yet, but will
3670      # later if we ever see a value that doesn't fit in 32 bits.
3671      my $has_q = 0;
3672      eval { $has_q = pack("Q", "1") ? 1 : 1; };
3673      if (!$has_q) {
3674        $self->{perl_is_64bit} = 0;
3675      }
3676      read($self->{file}, $str, 8);
3677      if (substr($str, 4, 4) eq chr(0)x4) {
3678        # We'd love to use 'Q', but it's a) not universal, b) not endian-proof.
3679        $self->{unpack_code} = 'V';  # Little-endian.
3680      } elsif (substr($str, 0, 4) eq chr(0)x4) {
3681        $self->{unpack_code} = 'N';  # Big-endian
3682      } else {
3683        ::error("$fname: header size >= 2**32\n");
3684      }
3685      my @pair = unpack($self->{unpack_code} . "*", $str);
3686      # Since we know one of the pair is 0, it's fine to just add them.
3687      @$slots = (0, $pair[0] + $pair[1]);
3688    }
3689    return $self;
3690  }
3691
3692  # Load more data when we access slots->get(X) which is not yet in memory.
3693  sub overflow {
3694    my ($self) = @_;
3695    my $slots = $self->{slots};
3696    $self->{base} += $#$slots + 1;   # skip over data we're replacing
3697    my $str;
3698    read($self->{file}, $str, $self->{stride});
3699    if ($address_length == 8) {      # the 32-bit case
3700      # This is the easy case: unpack provides 32-bit unpacking primitives.
3701      @$slots = unpack($self->{unpack_code} . "*", $str);
3702    } else {
3703      # We need to unpack 32 bits at a time and combine.
3704      my @b32_values = unpack($self->{unpack_code} . "*", $str);
3705      my @b64_values = ();
3706      for (my $i = 0; $i < $#b32_values; $i += 2) {
3707        # TODO(csilvers): if this is a 32-bit perl, the math below
3708        #    could end up in a too-large int, which perl will promote
3709        #    to a double, losing necessary precision.  Deal with that.
3710        #    Right now, we just die.
3711        my ($lo, $hi) = ($b32_values[$i], $b32_values[$i+1]);
3712        if ($self->{unpack_code} eq 'N') {    # big-endian
3713          ($lo, $hi) = ($hi, $lo);
3714        }
3715        my $value = $lo + $hi * (2**32);
3716        if (!$self->{perl_is_64bit} &&   # check value is exactly represented
3717            (($value % (2**32)) != $lo || int($value / (2**32)) != $hi)) {
3718          ::error("Need a 64-bit perl to process this 64-bit profile.\n");
3719        }
3720        push(@b64_values, $value);
3721      }
3722      @$slots = @b64_values;
3723    }
3724  }
3725
3726  # Access the i-th long in the file (logically), or -1 at EOF.
3727  sub get {
3728    my ($self, $idx) = @_;
3729    my $slots = $self->{slots};
3730    while ($#$slots >= 0) {
3731      if ($idx < $self->{base}) {
3732        # The only time we expect a reference to $slots[$i - something]
3733        # after referencing $slots[$i] is reading the very first header.
3734        # Since $stride > |header|, that shouldn't cause any lookback
3735        # errors.  And everything after the header is sequential.
3736        print STDERR "Unexpected look-back reading CPU profile";
3737        return -1;   # shrug, don't know what better to return
3738      } elsif ($idx > $self->{base} + $#$slots) {
3739        $self->overflow();
3740      } else {
3741        return $slots->[$idx - $self->{base}];
3742      }
3743    }
3744    # If we get here, $slots is [], which means we've reached EOF
3745    return -1;  # unique since slots is supposed to hold unsigned numbers
3746  }
3747}
3748
3749# Reads the top, 'header' section of a profile, and returns the last
3750# line of the header, commonly called a 'header line'.  The header
3751# section of a profile consists of zero or more 'command' lines that
3752# are instructions to jeprof, which jeprof executes when reading the
3753# header.  All 'command' lines start with a %.  After the command
3754# lines is the 'header line', which is a profile-specific line that
3755# indicates what type of profile it is, and perhaps other global
3756# information about the profile.  For instance, here's a header line
3757# for a heap profile:
3758#   heap profile:     53:    38236 [  5525:  1284029] @ heapprofile
3759# For historical reasons, the CPU profile does not contain a text-
3760# readable header line.  If the profile looks like a CPU profile,
3761# this function returns "".  If no header line could be found, this
3762# function returns undef.
3763#
3764# The following commands are recognized:
3765#   %warn -- emit the rest of this line to stderr, prefixed by 'WARNING:'
3766#
3767# The input file should be in binmode.
3768sub ReadProfileHeader {
3769  local *PROFILE = shift;
3770  my $firstchar = "";
3771  my $line = "";
3772  read(PROFILE, $firstchar, 1);
3773  seek(PROFILE, -1, 1);                    # unread the firstchar
3774  if ($firstchar !~ /[[:print:]]/) {       # is not a text character
3775    return "";
3776  }
3777  while (defined($line = <PROFILE>)) {
3778    $line =~ s/\r//g;   # turn windows-looking lines into unix-looking lines
3779    if ($line =~ /^%warn\s+(.*)/) {        # 'warn' command
3780      # Note this matches both '%warn blah\n' and '%warn\n'.
3781      print STDERR "WARNING: $1\n";        # print the rest of the line
3782    } elsif ($line =~ /^%/) {
3783      print STDERR "Ignoring unknown command from profile header: $line";
3784    } else {
3785      # End of commands, must be the header line.
3786      return $line;
3787    }
3788  }
3789  return undef;     # got to EOF without seeing a header line
3790}
3791
3792sub IsSymbolizedProfileFile {
3793  my $file_name = shift;
3794  if (!(-e $file_name) || !(-r $file_name)) {
3795    return 0;
3796  }
3797  # Check if the file contains a symbol-section marker.
3798  open(TFILE, "<$file_name");
3799  binmode TFILE;
3800  my $firstline = ReadProfileHeader(*TFILE);
3801  close(TFILE);
3802  if (!$firstline) {
3803    return 0;
3804  }
3805  $SYMBOL_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
3806  my $symbol_marker = $&;
3807  return $firstline =~ /^--- *$symbol_marker/;
3808}
3809
3810# Parse profile generated by common/profiler.cc and return a reference
3811# to a map:
3812#      $result->{version}     Version number of profile file
3813#      $result->{period}      Sampling period (in microseconds)
3814#      $result->{profile}     Profile object
3815#      $result->{threads}     Map of thread IDs to profile objects
3816#      $result->{map}         Memory map info from profile
3817#      $result->{pcs}         Hash of all PC values seen, key is hex address
3818sub ReadProfile {
3819  my $prog = shift;
3820  my $fname = shift;
3821  my $result;            # return value
3822
3823  $CONTENTION_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
3824  my $contention_marker = $&;
3825  $GROWTH_PAGE  =~ m,[^/]+$,;    # matches everything after the last slash
3826  my $growth_marker = $&;
3827  $SYMBOL_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
3828  my $symbol_marker = $&;
3829  $PROFILE_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
3830  my $profile_marker = $&;
3831  $HEAP_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
3832  my $heap_marker = $&;
3833
3834  # Look at first line to see if it is a heap or a CPU profile.
3835  # CPU profile may start with no header at all, and just binary data
3836  # (starting with \0\0\0\0) -- in that case, don't try to read the
3837  # whole firstline, since it may be gigabytes(!) of data.
3838  open(PROFILE, "<$fname") || error("$fname: $!\n");
3839  binmode PROFILE;      # New perls do UTF-8 processing
3840  my $header = ReadProfileHeader(*PROFILE);
3841  if (!defined($header)) {   # means "at EOF"
3842    error("Profile is empty.\n");
3843  }
3844
3845  my $symbols;
3846  if ($header =~ m/^--- *$symbol_marker/o) {
3847    # Verify that the user asked for a symbolized profile
3848    if (!$main::use_symbolized_profile) {
3849      # we have both a binary and symbolized profiles, abort
3850      error("FATAL ERROR: Symbolized profile\n   $fname\ncannot be used with " .
3851            "a binary arg. Try again without passing\n   $prog\n");
3852    }
3853    # Read the symbol section of the symbolized profile file.
3854    $symbols = ReadSymbols(*PROFILE{IO});
3855    # Read the next line to get the header for the remaining profile.
3856    $header = ReadProfileHeader(*PROFILE) || "";
3857  }
3858
3859  if ($header =~ m/^--- *($heap_marker|$growth_marker)/o) {
3860    # Skip "--- ..." line for profile types that have their own headers.
3861    $header = ReadProfileHeader(*PROFILE) || "";
3862  }
3863
3864  $main::profile_type = '';
3865
3866  if ($header =~ m/^heap profile:.*$growth_marker/o) {
3867    $main::profile_type = 'growth';
3868    $result =  ReadHeapProfile($prog, *PROFILE, $header);
3869  } elsif ($header =~ m/^heap profile:/) {
3870    $main::profile_type = 'heap';
3871    $result =  ReadHeapProfile($prog, *PROFILE, $header);
3872  } elsif ($header =~ m/^heap/) {
3873    $main::profile_type = 'heap';
3874    $result = ReadThreadedHeapProfile($prog, $fname, $header);
3875  } elsif ($header =~ m/^--- *$contention_marker/o) {
3876    $main::profile_type = 'contention';
3877    $result = ReadSynchProfile($prog, *PROFILE);
3878  } elsif ($header =~ m/^--- *Stacks:/) {
3879    print STDERR
3880      "Old format contention profile: mistakenly reports " .
3881      "condition variable signals as lock contentions.\n";
3882    $main::profile_type = 'contention';
3883    $result = ReadSynchProfile($prog, *PROFILE);
3884  } elsif ($header =~ m/^--- *$profile_marker/) {
3885    # the binary cpu profile data starts immediately after this line
3886    $main::profile_type = 'cpu';
3887    $result = ReadCPUProfile($prog, $fname, *PROFILE);
3888  } else {
3889    if (defined($symbols)) {
3890      # a symbolized profile contains a format we don't recognize, bail out
3891      error("$fname: Cannot recognize profile section after symbols.\n");
3892    }
3893    # no ascii header present -- must be a CPU profile
3894    $main::profile_type = 'cpu';
3895    $result = ReadCPUProfile($prog, $fname, *PROFILE);
3896  }
3897
3898  close(PROFILE);
3899
3900  # if we got symbols along with the profile, return those as well
3901  if (defined($symbols)) {
3902    $result->{symbols} = $symbols;
3903  }
3904
3905  return $result;
3906}
3907
3908# Subtract one from caller pc so we map back to call instr.
3909# However, don't do this if we're reading a symbolized profile
3910# file, in which case the subtract-one was done when the file
3911# was written.
3912#
3913# We apply the same logic to all readers, though ReadCPUProfile uses an
3914# independent implementation.
3915sub FixCallerAddresses {
3916  my $stack = shift;
3917  # --raw/http: Always subtract one from pc's, because PrintSymbolizedProfile()
3918  # dumps unadjusted profiles.
3919  {
3920    $stack =~ /(\s)/;
3921    my $delimiter = $1;
3922    my @addrs = split(' ', $stack);
3923    my @fixedaddrs;
3924    $#fixedaddrs = $#addrs;
3925    if ($#addrs >= 0) {
3926      $fixedaddrs[0] = $addrs[0];
3927    }
3928    for (my $i = 1; $i <= $#addrs; $i++) {
3929      $fixedaddrs[$i] = AddressSub($addrs[$i], "0x1");
3930    }
3931    return join $delimiter, @fixedaddrs;
3932  }
3933}
3934
3935# CPU profile reader
3936sub ReadCPUProfile {
3937  my $prog = shift;
3938  my $fname = shift;       # just used for logging
3939  local *PROFILE = shift;
3940  my $version;
3941  my $period;
3942  my $i;
3943  my $profile = {};
3944  my $pcs = {};
3945
3946  # Parse string into array of slots.
3947  my $slots = CpuProfileStream->new(*PROFILE, $fname);
3948
3949  # Read header.  The current header version is a 5-element structure
3950  # containing:
3951  #   0: header count (always 0)
3952  #   1: header "words" (after this one: 3)
3953  #   2: format version (0)
3954  #   3: sampling period (usec)
3955  #   4: unused padding (always 0)
3956  if ($slots->get(0) != 0 ) {
3957    error("$fname: not a profile file, or old format profile file\n");
3958  }
3959  $i = 2 + $slots->get(1);
3960  $version = $slots->get(2);
3961  $period = $slots->get(3);
3962  # Do some sanity checking on these header values.
3963  if ($version > (2**32) || $period > (2**32) || $i > (2**32) || $i < 5) {
3964    error("$fname: not a profile file, or corrupted profile file\n");
3965  }
3966
3967  # Parse profile
3968  while ($slots->get($i) != -1) {
3969    my $n = $slots->get($i++);
3970    my $d = $slots->get($i++);
3971    if ($d > (2**16)) {  # TODO(csilvers): what's a reasonable max-stack-depth?
3972      my $addr = sprintf("0%o", $i * ($address_length == 8 ? 4 : 8));
3973      print STDERR "At index $i (address $addr):\n";
3974      error("$fname: stack trace depth >= 2**32\n");
3975    }
3976    if ($slots->get($i) == 0) {
3977      # End of profile data marker
3978      $i += $d;
3979      last;
3980    }
3981
3982    # Make key out of the stack entries
3983    my @k = ();
3984    for (my $j = 0; $j < $d; $j++) {
3985      my $pc = $slots->get($i+$j);
3986      # Subtract one from caller pc so we map back to call instr.
3987      $pc--;
3988      $pc = sprintf("%0*x", $address_length, $pc);
3989      $pcs->{$pc} = 1;
3990      push @k, $pc;
3991    }
3992
3993    AddEntry($profile, (join "\n", @k), $n);
3994    $i += $d;
3995  }
3996
3997  # Parse map
3998  my $map = '';
3999  seek(PROFILE, $i * 4, 0);
4000  read(PROFILE, $map, (stat PROFILE)[7]);
4001
4002  my $r = {};
4003  $r->{version} = $version;
4004  $r->{period} = $period;
4005  $r->{profile} = $profile;
4006  $r->{libs} = ParseLibraries($prog, $map, $pcs);
4007  $r->{pcs} = $pcs;
4008
4009  return $r;
4010}
4011
4012sub HeapProfileIndex {
4013  my $index = 1;
4014  if ($main::opt_inuse_space) {
4015    $index = 1;
4016  } elsif ($main::opt_inuse_objects) {
4017    $index = 0;
4018  } elsif ($main::opt_alloc_space) {
4019    $index = 3;
4020  } elsif ($main::opt_alloc_objects) {
4021    $index = 2;
4022  }
4023  return $index;
4024}
4025
4026sub ReadMappedLibraries {
4027  my $fh = shift;
4028  my $map = "";
4029  # Read the /proc/self/maps data
4030  while (<$fh>) {
4031    s/\r//g;         # turn windows-looking lines into unix-looking lines
4032    $map .= $_;
4033  }
4034  return $map;
4035}
4036
4037sub ReadMemoryMap {
4038  my $fh = shift;
4039  my $map = "";
4040  # Read /proc/self/maps data as formatted by DumpAddressMap()
4041  my $buildvar = "";
4042  while (<PROFILE>) {
4043    s/\r//g;         # turn windows-looking lines into unix-looking lines
4044    # Parse "build=<dir>" specification if supplied
4045    if (m/^\s*build=(.*)\n/) {
4046      $buildvar = $1;
4047    }
4048
4049    # Expand "$build" variable if available
4050    $_ =~ s/\$build\b/$buildvar/g;
4051
4052    $map .= $_;
4053  }
4054  return $map;
4055}
4056
4057sub AdjustSamples {
4058  my ($sample_adjustment, $sampling_algorithm, $n1, $s1, $n2, $s2) = @_;
4059  if ($sample_adjustment) {
4060    if ($sampling_algorithm == 2) {
4061      # Remote-heap version 2
4062      # The sampling frequency is the rate of a Poisson process.
4063      # This means that the probability of sampling an allocation of
4064      # size X with sampling rate Y is 1 - exp(-X/Y)
4065      if ($n1 != 0) {
4066        my $ratio = (($s1*1.0)/$n1)/($sample_adjustment);
4067        my $scale_factor = 1/(1 - exp(-$ratio));
4068        $n1 *= $scale_factor;
4069        $s1 *= $scale_factor;
4070      }
4071      if ($n2 != 0) {
4072        my $ratio = (($s2*1.0)/$n2)/($sample_adjustment);
4073        my $scale_factor = 1/(1 - exp(-$ratio));
4074        $n2 *= $scale_factor;
4075        $s2 *= $scale_factor;
4076      }
4077    } else {
4078      # Remote-heap version 1
4079      my $ratio;
4080      $ratio = (($s1*1.0)/$n1)/($sample_adjustment);
4081      if ($ratio < 1) {
4082        $n1 /= $ratio;
4083        $s1 /= $ratio;
4084      }
4085      $ratio = (($s2*1.0)/$n2)/($sample_adjustment);
4086      if ($ratio < 1) {
4087        $n2 /= $ratio;
4088        $s2 /= $ratio;
4089      }
4090    }
4091  }
4092  return ($n1, $s1, $n2, $s2);
4093}
4094
4095sub ReadHeapProfile {
4096  my $prog = shift;
4097  local *PROFILE = shift;
4098  my $header = shift;
4099
4100  my $index = HeapProfileIndex();
4101
4102  # Find the type of this profile.  The header line looks like:
4103  #    heap profile:   1246:  8800744 [  1246:  8800744] @ <heap-url>/266053
4104  # There are two pairs <count: size>, the first inuse objects/space, and the
4105  # second allocated objects/space.  This is followed optionally by a profile
4106  # type, and if that is present, optionally by a sampling frequency.
4107  # For remote heap profiles (v1):
4108  # The interpretation of the sampling frequency is that the profiler, for
4109  # each sample, calculates a uniformly distributed random integer less than
4110  # the given value, and records the next sample after that many bytes have
4111  # been allocated.  Therefore, the expected sample interval is half of the
4112  # given frequency.  By default, if not specified, the expected sample
4113  # interval is 128KB.  Only remote-heap-page profiles are adjusted for
4114  # sample size.
4115  # For remote heap profiles (v2):
4116  # The sampling frequency is the rate of a Poisson process. This means that
4117  # the probability of sampling an allocation of size X with sampling rate Y
4118  # is 1 - exp(-X/Y)
4119  # For version 2, a typical header line might look like this:
4120  # heap profile:   1922: 127792360 [  1922: 127792360] @ <heap-url>_v2/524288
4121  # the trailing number (524288) is the sampling rate. (Version 1 showed
4122  # double the 'rate' here)
4123  my $sampling_algorithm = 0;
4124  my $sample_adjustment = 0;
4125  chomp($header);
4126  my $type = "unknown";
4127  if ($header =~ m"^heap profile:\s*(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\](\s*@\s*([^/]*)(/(\d+))?)?") {
4128    if (defined($6) && ($6 ne '')) {
4129      $type = $6;
4130      my $sample_period = $8;
4131      # $type is "heapprofile" for profiles generated by the
4132      # heap-profiler, and either "heap" or "heap_v2" for profiles
4133      # generated by sampling directly within tcmalloc.  It can also
4134      # be "growth" for heap-growth profiles.  The first is typically
4135      # found for profiles generated locally, and the others for
4136      # remote profiles.
4137      if (($type eq "heapprofile") || ($type !~ /heap/) ) {
4138        # No need to adjust for the sampling rate with heap-profiler-derived data
4139        $sampling_algorithm = 0;
4140      } elsif ($type =~ /_v2/) {
4141        $sampling_algorithm = 2;     # version 2 sampling
4142        if (defined($sample_period) && ($sample_period ne '')) {
4143          $sample_adjustment = int($sample_period);
4144        }
4145      } else {
4146        $sampling_algorithm = 1;     # version 1 sampling
4147        if (defined($sample_period) && ($sample_period ne '')) {
4148          $sample_adjustment = int($sample_period)/2;
4149        }
4150      }
4151    } else {
4152      # We detect whether or not this is a remote-heap profile by checking
4153      # that the total-allocated stats ($n2,$s2) are exactly the
4154      # same as the in-use stats ($n1,$s1).  It is remotely conceivable
4155      # that a non-remote-heap profile may pass this check, but it is hard
4156      # to imagine how that could happen.
4157      # In this case it's so old it's guaranteed to be remote-heap version 1.
4158      my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4);
4159      if (($n1 == $n2) && ($s1 == $s2)) {
4160        # This is likely to be a remote-heap based sample profile
4161        $sampling_algorithm = 1;
4162      }
4163    }
4164  }
4165
4166  if ($sampling_algorithm > 0) {
4167    # For remote-heap generated profiles, adjust the counts and sizes to
4168    # account for the sample rate (we sample once every 128KB by default).
4169    if ($sample_adjustment == 0) {
4170      # Turn on profile adjustment.
4171      $sample_adjustment = 128*1024;
4172      print STDERR "Adjusting heap profiles for 1-in-128KB sampling rate\n";
4173    } else {
4174      printf STDERR ("Adjusting heap profiles for 1-in-%d sampling rate\n",
4175                     $sample_adjustment);
4176    }
4177    if ($sampling_algorithm > 1) {
4178      # We don't bother printing anything for the original version (version 1)
4179      printf STDERR "Heap version $sampling_algorithm\n";
4180    }
4181  }
4182
4183  my $profile = {};
4184  my $pcs = {};
4185  my $map = "";
4186
4187  while (<PROFILE>) {
4188    s/\r//g;         # turn windows-looking lines into unix-looking lines
4189    if (/^MAPPED_LIBRARIES:/) {
4190      $map .= ReadMappedLibraries(*PROFILE);
4191      last;
4192    }
4193
4194    if (/^--- Memory map:/) {
4195      $map .= ReadMemoryMap(*PROFILE);
4196      last;
4197    }
4198
4199    # Read entry of the form:
4200    #  <count1>: <bytes1> [<count2>: <bytes2>] @ a1 a2 a3 ... an
4201    s/^\s*//;
4202    s/\s*$//;
4203    if (m/^\s*(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\]\s+@\s+(.*)$/) {
4204      my $stack = $5;
4205      my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4);
4206      my @counts = AdjustSamples($sample_adjustment, $sampling_algorithm,
4207                                 $n1, $s1, $n2, $s2);
4208      AddEntries($profile, $pcs, FixCallerAddresses($stack), $counts[$index]);
4209    }
4210  }
4211
4212  my $r = {};
4213  $r->{version} = "heap";
4214  $r->{period} = 1;
4215  $r->{profile} = $profile;
4216  $r->{libs} = ParseLibraries($prog, $map, $pcs);
4217  $r->{pcs} = $pcs;
4218  return $r;
4219}
4220
4221sub ReadThreadedHeapProfile {
4222  my ($prog, $fname, $header) = @_;
4223
4224  my $index = HeapProfileIndex();
4225  my $sampling_algorithm = 0;
4226  my $sample_adjustment = 0;
4227  chomp($header);
4228  my $type = "unknown";
4229  # Assuming a very specific type of header for now.
4230  if ($header =~ m"^heap_v2/(\d+)") {
4231    $type = "_v2";
4232    $sampling_algorithm = 2;
4233    $sample_adjustment = int($1);
4234  }
4235  if ($type ne "_v2" || !defined($sample_adjustment)) {
4236    die "Threaded heap profiles require v2 sampling with a sample rate\n";
4237  }
4238
4239  my $profile = {};
4240  my $thread_profiles = {};
4241  my $pcs = {};
4242  my $map = "";
4243  my $stack = "";
4244
4245  while (<PROFILE>) {
4246    s/\r//g;
4247    if (/^MAPPED_LIBRARIES:/) {
4248      $map .= ReadMappedLibraries(*PROFILE);
4249      last;
4250    }
4251
4252    if (/^--- Memory map:/) {
4253      $map .= ReadMemoryMap(*PROFILE);
4254      last;
4255    }
4256
4257    # Read entry of the form:
4258    # @ a1 a2 ... an
4259    #   t*: <count1>: <bytes1> [<count2>: <bytes2>]
4260    #   t1: <count1>: <bytes1> [<count2>: <bytes2>]
4261    #     ...
4262    #   tn: <count1>: <bytes1> [<count2>: <bytes2>]
4263    s/^\s*//;
4264    s/\s*$//;
4265    if (m/^@\s+(.*)$/) {
4266      $stack = $1;
4267    } elsif (m/^\s*(t(\*|\d+)):\s+(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\]$/) {
4268      if ($stack eq "") {
4269        # Still in the header, so this is just a per-thread summary.
4270        next;
4271      }
4272      my $thread = $2;
4273      my ($n1, $s1, $n2, $s2) = ($3, $4, $5, $6);
4274      my @counts = AdjustSamples($sample_adjustment, $sampling_algorithm,
4275                                 $n1, $s1, $n2, $s2);
4276      if ($thread eq "*") {
4277        AddEntries($profile, $pcs, FixCallerAddresses($stack), $counts[$index]);
4278      } else {
4279        if (!exists($thread_profiles->{$thread})) {
4280          $thread_profiles->{$thread} = {};
4281        }
4282        AddEntries($thread_profiles->{$thread}, $pcs,
4283                   FixCallerAddresses($stack), $counts[$index]);
4284      }
4285    }
4286  }
4287
4288  my $r = {};
4289  $r->{version} = "heap";
4290  $r->{period} = 1;
4291  $r->{profile} = $profile;
4292  $r->{threads} = $thread_profiles;
4293  $r->{libs} = ParseLibraries($prog, $map, $pcs);
4294  $r->{pcs} = $pcs;
4295  return $r;
4296}
4297
4298sub ReadSynchProfile {
4299  my $prog = shift;
4300  local *PROFILE = shift;
4301  my $header = shift;
4302
4303  my $map = '';
4304  my $profile = {};
4305  my $pcs = {};
4306  my $sampling_period = 1;
4307  my $cyclespernanosec = 2.8;   # Default assumption for old binaries
4308  my $seen_clockrate = 0;
4309  my $line;
4310
4311  my $index = 0;
4312  if ($main::opt_total_delay) {
4313    $index = 0;
4314  } elsif ($main::opt_contentions) {
4315    $index = 1;
4316  } elsif ($main::opt_mean_delay) {
4317    $index = 2;
4318  }
4319
4320  while ( $line = <PROFILE> ) {
4321    $line =~ s/\r//g;      # turn windows-looking lines into unix-looking lines
4322    if ( $line =~ /^\s*(\d+)\s+(\d+) \@\s*(.*?)\s*$/ ) {
4323      my ($cycles, $count, $stack) = ($1, $2, $3);
4324
4325      # Convert cycles to nanoseconds
4326      $cycles /= $cyclespernanosec;
4327
4328      # Adjust for sampling done by application
4329      $cycles *= $sampling_period;
4330      $count *= $sampling_period;
4331
4332      my @values = ($cycles, $count, $cycles / $count);
4333      AddEntries($profile, $pcs, FixCallerAddresses($stack), $values[$index]);
4334
4335    } elsif ( $line =~ /^(slow release).*thread \d+  \@\s*(.*?)\s*$/ ||
4336              $line =~ /^\s*(\d+) \@\s*(.*?)\s*$/ ) {
4337      my ($cycles, $stack) = ($1, $2);
4338      if ($cycles !~ /^\d+$/) {
4339        next;
4340      }
4341
4342      # Convert cycles to nanoseconds
4343      $cycles /= $cyclespernanosec;
4344
4345      # Adjust for sampling done by application
4346      $cycles *= $sampling_period;
4347
4348      AddEntries($profile, $pcs, FixCallerAddresses($stack), $cycles);
4349
4350    } elsif ( $line =~ m/^([a-z][^=]*)=(.*)$/ ) {
4351      my ($variable, $value) = ($1,$2);
4352      for ($variable, $value) {
4353        s/^\s+//;
4354        s/\s+$//;
4355      }
4356      if ($variable eq "cycles/second") {
4357        $cyclespernanosec = $value / 1e9;
4358        $seen_clockrate = 1;
4359      } elsif ($variable eq "sampling period") {
4360        $sampling_period = $value;
4361      } elsif ($variable eq "ms since reset") {
4362        # Currently nothing is done with this value in jeprof
4363        # So we just silently ignore it for now
4364      } elsif ($variable eq "discarded samples") {
4365        # Currently nothing is done with this value in jeprof
4366        # So we just silently ignore it for now
4367      } else {
4368        printf STDERR ("Ignoring unnknown variable in /contention output: " .
4369                       "'%s' = '%s'\n",$variable,$value);
4370      }
4371    } else {
4372      # Memory map entry
4373      $map .= $line;
4374    }
4375  }
4376
4377  if (!$seen_clockrate) {
4378    printf STDERR ("No cycles/second entry in profile; Guessing %.1f GHz\n",
4379                   $cyclespernanosec);
4380  }
4381
4382  my $r = {};
4383  $r->{version} = 0;
4384  $r->{period} = $sampling_period;
4385  $r->{profile} = $profile;
4386  $r->{libs} = ParseLibraries($prog, $map, $pcs);
4387  $r->{pcs} = $pcs;
4388  return $r;
4389}
4390
4391# Given a hex value in the form "0x1abcd" or "1abcd", return either
4392# "0001abcd" or "000000000001abcd", depending on the current (global)
4393# address length.
4394sub HexExtend {
4395  my $addr = shift;
4396
4397  $addr =~ s/^(0x)?0*//;
4398  my $zeros_needed = $address_length - length($addr);
4399  if ($zeros_needed < 0) {
4400    printf STDERR "Warning: address $addr is longer than address length $address_length\n";
4401    return $addr;
4402  }
4403  return ("0" x $zeros_needed) . $addr;
4404}
4405
4406##### Symbol extraction #####
4407
4408# Aggressively search the lib_prefix values for the given library
4409# If all else fails, just return the name of the library unmodified.
4410# If the lib_prefix is "/my/path,/other/path" and $file is "/lib/dir/mylib.so"
4411# it will search the following locations in this order, until it finds a file:
4412#   /my/path/lib/dir/mylib.so
4413#   /other/path/lib/dir/mylib.so
4414#   /my/path/dir/mylib.so
4415#   /other/path/dir/mylib.so
4416#   /my/path/mylib.so
4417#   /other/path/mylib.so
4418#   /lib/dir/mylib.so              (returned as last resort)
4419sub FindLibrary {
4420  my $file = shift;
4421  my $suffix = $file;
4422
4423  # Search for the library as described above
4424  do {
4425    foreach my $prefix (@prefix_list) {
4426      my $fullpath = $prefix . $suffix;
4427      if (-e $fullpath) {
4428        return $fullpath;
4429      }
4430    }
4431  } while ($suffix =~ s|^/[^/]+/|/|);
4432  return $file;
4433}
4434
4435# Return path to library with debugging symbols.
4436# For libc libraries, the copy in /usr/lib/debug contains debugging symbols
4437sub DebuggingLibrary {
4438  my $file = shift;
4439  if ($file =~ m|^/|) {
4440      if (-f "/usr/lib/debug$file") {
4441        return "/usr/lib/debug$file";
4442      } elsif (-f "/usr/lib/debug$file.debug") {
4443        return "/usr/lib/debug$file.debug";
4444      }
4445  }
4446  return undef;
4447}
4448
4449# Parse text section header of a library using objdump
4450sub ParseTextSectionHeaderFromObjdump {
4451  my $lib = shift;
4452
4453  my $size = undef;
4454  my $vma;
4455  my $file_offset;
4456  # Get objdump output from the library file to figure out how to
4457  # map between mapped addresses and addresses in the library.
4458  my $cmd = ShellEscape($obj_tool_map{"objdump"}, "-h", $lib);
4459  open(OBJDUMP, "$cmd |") || error("$cmd: $!\n");
4460  while (<OBJDUMP>) {
4461    s/\r//g;         # turn windows-looking lines into unix-looking lines
4462    # Idx Name          Size      VMA       LMA       File off  Algn
4463    #  10 .text         00104b2c  420156f0  420156f0  000156f0  2**4
4464    # For 64-bit objects, VMA and LMA will be 16 hex digits, size and file
4465    # offset may still be 8.  But AddressSub below will still handle that.
4466    my @x = split;
4467    if (($#x >= 6) && ($x[1] eq '.text')) {
4468      $size = $x[2];
4469      $vma = $x[3];
4470      $file_offset = $x[5];
4471      last;
4472    }
4473  }
4474  close(OBJDUMP);
4475
4476  if (!defined($size)) {
4477    return undef;
4478  }
4479
4480  my $r = {};
4481  $r->{size} = $size;
4482  $r->{vma} = $vma;
4483  $r->{file_offset} = $file_offset;
4484
4485  return $r;
4486}
4487
4488# Parse text section header of a library using otool (on OS X)
4489sub ParseTextSectionHeaderFromOtool {
4490  my $lib = shift;
4491
4492  my $size = undef;
4493  my $vma = undef;
4494  my $file_offset = undef;
4495  # Get otool output from the library file to figure out how to
4496  # map between mapped addresses and addresses in the library.
4497  my $command = ShellEscape($obj_tool_map{"otool"}, "-l", $lib);
4498  open(OTOOL, "$command |") || error("$command: $!\n");
4499  my $cmd = "";
4500  my $sectname = "";
4501  my $segname = "";
4502  foreach my $line (<OTOOL>) {
4503    $line =~ s/\r//g;      # turn windows-looking lines into unix-looking lines
4504    # Load command <#>
4505    #       cmd LC_SEGMENT
4506    # [...]
4507    # Section
4508    #   sectname __text
4509    #    segname __TEXT
4510    #       addr 0x000009f8
4511    #       size 0x00018b9e
4512    #     offset 2552
4513    #      align 2^2 (4)
4514    # We will need to strip off the leading 0x from the hex addresses,
4515    # and convert the offset into hex.
4516    if ($line =~ /Load command/) {
4517      $cmd = "";
4518      $sectname = "";
4519      $segname = "";
4520    } elsif ($line =~ /Section/) {
4521      $sectname = "";
4522      $segname = "";
4523    } elsif ($line =~ /cmd (\w+)/) {
4524      $cmd = $1;
4525    } elsif ($line =~ /sectname (\w+)/) {
4526      $sectname = $1;
4527    } elsif ($line =~ /segname (\w+)/) {
4528      $segname = $1;
4529    } elsif (!(($cmd eq "LC_SEGMENT" || $cmd eq "LC_SEGMENT_64") &&
4530               $sectname eq "__text" &&
4531               $segname eq "__TEXT")) {
4532      next;
4533    } elsif ($line =~ /\baddr 0x([0-9a-fA-F]+)/) {
4534      $vma = $1;
4535    } elsif ($line =~ /\bsize 0x([0-9a-fA-F]+)/) {
4536      $size = $1;
4537    } elsif ($line =~ /\boffset ([0-9]+)/) {
4538      $file_offset = sprintf("%016x", $1);
4539    }
4540    if (defined($vma) && defined($size) && defined($file_offset)) {
4541      last;
4542    }
4543  }
4544  close(OTOOL);
4545
4546  if (!defined($vma) || !defined($size) || !defined($file_offset)) {
4547     return undef;
4548  }
4549
4550  my $r = {};
4551  $r->{size} = $size;
4552  $r->{vma} = $vma;
4553  $r->{file_offset} = $file_offset;
4554
4555  return $r;
4556}
4557
4558sub ParseTextSectionHeader {
4559  # obj_tool_map("otool") is only defined if we're in a Mach-O environment
4560  if (defined($obj_tool_map{"otool"})) {
4561    my $r = ParseTextSectionHeaderFromOtool(@_);
4562    if (defined($r)){
4563      return $r;
4564    }
4565  }
4566  # If otool doesn't work, or we don't have it, fall back to objdump
4567  return ParseTextSectionHeaderFromObjdump(@_);
4568}
4569
4570# Split /proc/pid/maps dump into a list of libraries
4571sub ParseLibraries {
4572  return if $main::use_symbol_page;  # We don't need libraries info.
4573  my $prog = shift;
4574  my $map = shift;
4575  my $pcs = shift;
4576
4577  my $result = [];
4578  my $h = "[a-f0-9]+";
4579  my $zero_offset = HexExtend("0");
4580
4581  my $buildvar = "";
4582  foreach my $l (split("\n", $map)) {
4583    if ($l =~ m/^\s*build=(.*)$/) {
4584      $buildvar = $1;
4585    }
4586
4587    my $start;
4588    my $finish;
4589    my $offset;
4590    my $lib;
4591    if ($l =~ /^($h)-($h)\s+..x.\s+($h)\s+\S+:\S+\s+\d+\s+(\S+\.(so|dll|dylib|bundle)((\.\d+)+\w*(\.\d+){0,3})?)$/i) {
4592      # Full line from /proc/self/maps.  Example:
4593      #   40000000-40015000 r-xp 00000000 03:01 12845071   /lib/ld-2.3.2.so
4594      $start = HexExtend($1);
4595      $finish = HexExtend($2);
4596      $offset = HexExtend($3);
4597      $lib = $4;
4598      $lib =~ s|\\|/|g;     # turn windows-style paths into unix-style paths
4599    } elsif ($l =~ /^\s*($h)-($h):\s*(\S+\.so(\.\d+)*)/) {
4600      # Cooked line from DumpAddressMap.  Example:
4601      #   40000000-40015000: /lib/ld-2.3.2.so
4602      $start = HexExtend($1);
4603      $finish = HexExtend($2);
4604      $offset = $zero_offset;
4605      $lib = $3;
4606    }
4607    # FreeBSD 10.0 virtual memory map /proc/curproc/map as defined in
4608    # function procfs_doprocmap (sys/fs/procfs/procfs_map.c)
4609    #
4610    # Example:
4611    # 0x800600000 0x80061a000 26 0 0xfffff800035a0000 r-x 75 33 0x1004 COW NC vnode /libexec/ld-elf.s
4612    # o.1 NCH -1
4613    elsif ($l =~ /^(0x$h)\s(0x$h)\s\d+\s\d+\s0x$h\sr-x\s\d+\s\d+\s0x\d+\s(COW|NCO)\s(NC|NNC)\svnode\s(\S+\.so(\.\d+)*)/) {
4614      $start = HexExtend($1);
4615      $finish = HexExtend($2);
4616      $offset = $zero_offset;
4617      $lib = FindLibrary($5);
4618
4619    } else {
4620      next;
4621    }
4622
4623    # Expand "$build" variable if available
4624    $lib =~ s/\$build\b/$buildvar/g;
4625
4626    $lib = FindLibrary($lib);
4627
4628    # Check for pre-relocated libraries, which use pre-relocated symbol tables
4629    # and thus require adjusting the offset that we'll use to translate
4630    # VM addresses into symbol table addresses.
4631    # Only do this if we're not going to fetch the symbol table from a
4632    # debugging copy of the library.
4633    if (!DebuggingLibrary($lib)) {
4634      my $text = ParseTextSectionHeader($lib);
4635      if (defined($text)) {
4636         my $vma_offset = AddressSub($text->{vma}, $text->{file_offset});
4637         $offset = AddressAdd($offset, $vma_offset);
4638      }
4639    }
4640
4641    if($main::opt_debug) { printf STDERR "$start:$finish ($offset) $lib\n"; }
4642    push(@{$result}, [$lib, $start, $finish, $offset]);
4643  }
4644
4645  # Append special entry for additional library (not relocated)
4646  if ($main::opt_lib ne "") {
4647    my $text = ParseTextSectionHeader($main::opt_lib);
4648    if (defined($text)) {
4649       my $start = $text->{vma};
4650       my $finish = AddressAdd($start, $text->{size});
4651
4652       push(@{$result}, [$main::opt_lib, $start, $finish, $start]);
4653    }
4654  }
4655
4656  # Append special entry for the main program.  This covers
4657  # 0..max_pc_value_seen, so that we assume pc values not found in one
4658  # of the library ranges will be treated as coming from the main
4659  # program binary.
4660  my $min_pc = HexExtend("0");
4661  my $max_pc = $min_pc;          # find the maximal PC value in any sample
4662  foreach my $pc (keys(%{$pcs})) {
4663    if (HexExtend($pc) gt $max_pc) { $max_pc = HexExtend($pc); }
4664  }
4665  push(@{$result}, [$prog, $min_pc, $max_pc, $zero_offset]);
4666
4667  return $result;
4668}
4669
4670# Add two hex addresses of length $address_length.
4671# Run jeprof --test for unit test if this is changed.
4672sub AddressAdd {
4673  my $addr1 = shift;
4674  my $addr2 = shift;
4675  my $sum;
4676
4677  if ($address_length == 8) {
4678    # Perl doesn't cope with wraparound arithmetic, so do it explicitly:
4679    $sum = (hex($addr1)+hex($addr2)) % (0x10000000 * 16);
4680    return sprintf("%08x", $sum);
4681
4682  } else {
4683    # Do the addition in 7-nibble chunks to trivialize carry handling.
4684
4685    if ($main::opt_debug and $main::opt_test) {
4686      print STDERR "AddressAdd $addr1 + $addr2 = ";
4687    }
4688
4689    my $a1 = substr($addr1,-7);
4690    $addr1 = substr($addr1,0,-7);
4691    my $a2 = substr($addr2,-7);
4692    $addr2 = substr($addr2,0,-7);
4693    $sum = hex($a1) + hex($a2);
4694    my $c = 0;
4695    if ($sum > 0xfffffff) {
4696      $c = 1;
4697      $sum -= 0x10000000;
4698    }
4699    my $r = sprintf("%07x", $sum);
4700
4701    $a1 = substr($addr1,-7);
4702    $addr1 = substr($addr1,0,-7);
4703    $a2 = substr($addr2,-7);
4704    $addr2 = substr($addr2,0,-7);
4705    $sum = hex($a1) + hex($a2) + $c;
4706    $c = 0;
4707    if ($sum > 0xfffffff) {
4708      $c = 1;
4709      $sum -= 0x10000000;
4710    }
4711    $r = sprintf("%07x", $sum) . $r;
4712
4713    $sum = hex($addr1) + hex($addr2) + $c;
4714    if ($sum > 0xff) { $sum -= 0x100; }
4715    $r = sprintf("%02x", $sum) . $r;
4716
4717    if ($main::opt_debug and $main::opt_test) { print STDERR "$r\n"; }
4718
4719    return $r;
4720  }
4721}
4722
4723
4724# Subtract two hex addresses of length $address_length.
4725# Run jeprof --test for unit test if this is changed.
4726sub AddressSub {
4727  my $addr1 = shift;
4728  my $addr2 = shift;
4729  my $diff;
4730
4731  if ($address_length == 8) {
4732    # Perl doesn't cope with wraparound arithmetic, so do it explicitly:
4733    $diff = (hex($addr1)-hex($addr2)) % (0x10000000 * 16);
4734    return sprintf("%08x", $diff);
4735
4736  } else {
4737    # Do the addition in 7-nibble chunks to trivialize borrow handling.
4738    # if ($main::opt_debug) { print STDERR "AddressSub $addr1 - $addr2 = "; }
4739
4740    my $a1 = hex(substr($addr1,-7));
4741    $addr1 = substr($addr1,0,-7);
4742    my $a2 = hex(substr($addr2,-7));
4743    $addr2 = substr($addr2,0,-7);
4744    my $b = 0;
4745    if ($a2 > $a1) {
4746      $b = 1;
4747      $a1 += 0x10000000;
4748    }
4749    $diff = $a1 - $a2;
4750    my $r = sprintf("%07x", $diff);
4751
4752    $a1 = hex(substr($addr1,-7));
4753    $addr1 = substr($addr1,0,-7);
4754    $a2 = hex(substr($addr2,-7)) + $b;
4755    $addr2 = substr($addr2,0,-7);
4756    $b = 0;
4757    if ($a2 > $a1) {
4758      $b = 1;
4759      $a1 += 0x10000000;
4760    }
4761    $diff = $a1 - $a2;
4762    $r = sprintf("%07x", $diff) . $r;
4763
4764    $a1 = hex($addr1);
4765    $a2 = hex($addr2) + $b;
4766    if ($a2 > $a1) { $a1 += 0x100; }
4767    $diff = $a1 - $a2;
4768    $r = sprintf("%02x", $diff) . $r;
4769
4770    # if ($main::opt_debug) { print STDERR "$r\n"; }
4771
4772    return $r;
4773  }
4774}
4775
4776# Increment a hex addresses of length $address_length.
4777# Run jeprof --test for unit test if this is changed.
4778sub AddressInc {
4779  my $addr = shift;
4780  my $sum;
4781
4782  if ($address_length == 8) {
4783    # Perl doesn't cope with wraparound arithmetic, so do it explicitly:
4784    $sum = (hex($addr)+1) % (0x10000000 * 16);
4785    return sprintf("%08x", $sum);
4786
4787  } else {
4788    # Do the addition in 7-nibble chunks to trivialize carry handling.
4789    # We are always doing this to step through the addresses in a function,
4790    # and will almost never overflow the first chunk, so we check for this
4791    # case and exit early.
4792
4793    # if ($main::opt_debug) { print STDERR "AddressInc $addr1 = "; }
4794
4795    my $a1 = substr($addr,-7);
4796    $addr = substr($addr,0,-7);
4797    $sum = hex($a1) + 1;
4798    my $r = sprintf("%07x", $sum);
4799    if ($sum <= 0xfffffff) {
4800      $r = $addr . $r;
4801      # if ($main::opt_debug) { print STDERR "$r\n"; }
4802      return HexExtend($r);
4803    } else {
4804      $r = "0000000";
4805    }
4806
4807    $a1 = substr($addr,-7);
4808    $addr = substr($addr,0,-7);
4809    $sum = hex($a1) + 1;
4810    $r = sprintf("%07x", $sum) . $r;
4811    if ($sum <= 0xfffffff) {
4812      $r = $addr . $r;
4813      # if ($main::opt_debug) { print STDERR "$r\n"; }
4814      return HexExtend($r);
4815    } else {
4816      $r = "00000000000000";
4817    }
4818
4819    $sum = hex($addr) + 1;
4820    if ($sum > 0xff) { $sum -= 0x100; }
4821    $r = sprintf("%02x", $sum) . $r;
4822
4823    # if ($main::opt_debug) { print STDERR "$r\n"; }
4824    return $r;
4825  }
4826}
4827
4828# Extract symbols for all PC values found in profile
4829sub ExtractSymbols {
4830  my $libs = shift;
4831  my $pcset = shift;
4832
4833  my $symbols = {};
4834
4835  # Map each PC value to the containing library.  To make this faster,
4836  # we sort libraries by their starting pc value (highest first), and
4837  # advance through the libraries as we advance the pc.  Sometimes the
4838  # addresses of libraries may overlap with the addresses of the main
4839  # binary, so to make sure the libraries 'win', we iterate over the
4840  # libraries in reverse order (which assumes the binary doesn't start
4841  # in the middle of a library, which seems a fair assumption).
4842  my @pcs = (sort { $a cmp $b } keys(%{$pcset}));  # pcset is 0-extended strings
4843  foreach my $lib (sort {$b->[1] cmp $a->[1]} @{$libs}) {
4844    my $libname = $lib->[0];
4845    my $start = $lib->[1];
4846    my $finish = $lib->[2];
4847    my $offset = $lib->[3];
4848
4849    # Use debug library if it exists
4850    my $debug_libname = DebuggingLibrary($libname);
4851    if ($debug_libname) {
4852        $libname = $debug_libname;
4853    }
4854
4855    # Get list of pcs that belong in this library.
4856    my $contained = [];
4857    my ($start_pc_index, $finish_pc_index);
4858    # Find smallest finish_pc_index such that $finish < $pc[$finish_pc_index].
4859    for ($finish_pc_index = $#pcs + 1; $finish_pc_index > 0;
4860         $finish_pc_index--) {
4861      last if $pcs[$finish_pc_index - 1] le $finish;
4862    }
4863    # Find smallest start_pc_index such that $start <= $pc[$start_pc_index].
4864    for ($start_pc_index = $finish_pc_index; $start_pc_index > 0;
4865         $start_pc_index--) {
4866      last if $pcs[$start_pc_index - 1] lt $start;
4867    }
4868    # This keeps PC values higher than $pc[$finish_pc_index] in @pcs,
4869    # in case there are overlaps in libraries and the main binary.
4870    @{$contained} = splice(@pcs, $start_pc_index,
4871                           $finish_pc_index - $start_pc_index);
4872    # Map to symbols
4873    MapToSymbols($libname, AddressSub($start, $offset), $contained, $symbols);
4874  }
4875
4876  return $symbols;
4877}
4878
4879# Map list of PC values to symbols for a given image
4880sub MapToSymbols {
4881  my $image = shift;
4882  my $offset = shift;
4883  my $pclist = shift;
4884  my $symbols = shift;
4885
4886  my $debug = 0;
4887
4888  # Ignore empty binaries
4889  if ($#{$pclist} < 0) { return; }
4890
4891  # Figure out the addr2line command to use
4892  my $addr2line = $obj_tool_map{"addr2line"};
4893  my $cmd = ShellEscape($addr2line, "-f", "-C", "-e", $image);
4894  if (exists $obj_tool_map{"addr2line_pdb"}) {
4895    $addr2line = $obj_tool_map{"addr2line_pdb"};
4896    $cmd = ShellEscape($addr2line, "--demangle", "-f", "-C", "-e", $image);
4897  }
4898
4899  # If "addr2line" isn't installed on the system at all, just use
4900  # nm to get what info we can (function names, but not line numbers).
4901  if (system(ShellEscape($addr2line, "--help") . " >$dev_null 2>&1") != 0) {
4902    MapSymbolsWithNM($image, $offset, $pclist, $symbols);
4903    return;
4904  }
4905
4906  # "addr2line -i" can produce a variable number of lines per input
4907  # address, with no separator that allows us to tell when data for
4908  # the next address starts.  So we find the address for a special
4909  # symbol (_fini) and interleave this address between all real
4910  # addresses passed to addr2line.  The name of this special symbol
4911  # can then be used as a separator.
4912  $sep_address = undef;  # May be filled in by MapSymbolsWithNM()
4913  my $nm_symbols = {};
4914  MapSymbolsWithNM($image, $offset, $pclist, $nm_symbols);
4915  if (defined($sep_address)) {
4916    # Only add " -i" to addr2line if the binary supports it.
4917    # addr2line --help returns 0, but not if it sees an unknown flag first.
4918    if (system("$cmd -i --help >$dev_null 2>&1") == 0) {
4919      $cmd .= " -i";
4920    } else {
4921      $sep_address = undef;   # no need for sep_address if we don't support -i
4922    }
4923  }
4924
4925  # Make file with all PC values with intervening 'sep_address' so
4926  # that we can reliably detect the end of inlined function list
4927  open(ADDRESSES, ">$main::tmpfile_sym") || error("$main::tmpfile_sym: $!\n");
4928  if ($debug) { print("---- $image ---\n"); }
4929  for (my $i = 0; $i <= $#{$pclist}; $i++) {
4930    # addr2line always reads hex addresses, and does not need '0x' prefix.
4931    if ($debug) { printf STDERR ("%s\n", $pclist->[$i]); }
4932    printf ADDRESSES ("%s\n", AddressSub($pclist->[$i], $offset));
4933    if (defined($sep_address)) {
4934      printf ADDRESSES ("%s\n", $sep_address);
4935    }
4936  }
4937  close(ADDRESSES);
4938  if ($debug) {
4939    print("----\n");
4940    system("cat", $main::tmpfile_sym);
4941    print("----\n");
4942    system("$cmd < " . ShellEscape($main::tmpfile_sym));
4943    print("----\n");
4944  }
4945
4946  open(SYMBOLS, "$cmd <" . ShellEscape($main::tmpfile_sym) . " |")
4947      || error("$cmd: $!\n");
4948  my $count = 0;   # Index in pclist
4949  while (<SYMBOLS>) {
4950    # Read fullfunction and filelineinfo from next pair of lines
4951    s/\r?\n$//g;
4952    my $fullfunction = $_;
4953    $_ = <SYMBOLS>;
4954    s/\r?\n$//g;
4955    my $filelinenum = $_;
4956
4957    if (defined($sep_address) && $fullfunction eq $sep_symbol) {
4958      # Terminating marker for data for this address
4959      $count++;
4960      next;
4961    }
4962
4963    $filelinenum =~ s|\\|/|g; # turn windows-style paths into unix-style paths
4964
4965    my $pcstr = $pclist->[$count];
4966    my $function = ShortFunctionName($fullfunction);
4967    my $nms = $nm_symbols->{$pcstr};
4968    if (defined($nms)) {
4969      if ($fullfunction eq '??') {
4970        # nm found a symbol for us.
4971        $function = $nms->[0];
4972        $fullfunction = $nms->[2];
4973      } else {
4974	# MapSymbolsWithNM tags each routine with its starting address,
4975	# useful in case the image has multiple occurrences of this
4976	# routine.  (It uses a syntax that resembles template paramters,
4977	# that are automatically stripped out by ShortFunctionName().)
4978	# addr2line does not provide the same information.  So we check
4979	# if nm disambiguated our symbol, and if so take the annotated
4980	# (nm) version of the routine-name.  TODO(csilvers): this won't
4981	# catch overloaded, inlined symbols, which nm doesn't see.
4982	# Better would be to do a check similar to nm's, in this fn.
4983	if ($nms->[2] =~ m/^\Q$function\E/) {  # sanity check it's the right fn
4984	  $function = $nms->[0];
4985	  $fullfunction = $nms->[2];
4986	}
4987      }
4988    }
4989
4990    # Prepend to accumulated symbols for pcstr
4991    # (so that caller comes before callee)
4992    my $sym = $symbols->{$pcstr};
4993    if (!defined($sym)) {
4994      $sym = [];
4995      $symbols->{$pcstr} = $sym;
4996    }
4997    unshift(@{$sym}, $function, $filelinenum, $fullfunction);
4998    if ($debug) { printf STDERR ("%s => [%s]\n", $pcstr, join(" ", @{$sym})); }
4999    if (!defined($sep_address)) {
5000      # Inlining is off, so this entry ends immediately
5001      $count++;
5002    }
5003  }
5004  close(SYMBOLS);
5005}
5006
5007# Use nm to map the list of referenced PCs to symbols.  Return true iff we
5008# are able to read procedure information via nm.
5009sub MapSymbolsWithNM {
5010  my $image = shift;
5011  my $offset = shift;
5012  my $pclist = shift;
5013  my $symbols = shift;
5014
5015  # Get nm output sorted by increasing address
5016  my $symbol_table = GetProcedureBoundaries($image, ".");
5017  if (!%{$symbol_table}) {
5018    return 0;
5019  }
5020  # Start addresses are already the right length (8 or 16 hex digits).
5021  my @names = sort { $symbol_table->{$a}->[0] cmp $symbol_table->{$b}->[0] }
5022    keys(%{$symbol_table});
5023
5024  if ($#names < 0) {
5025    # No symbols: just use addresses
5026    foreach my $pc (@{$pclist}) {
5027      my $pcstr = "0x" . $pc;
5028      $symbols->{$pc} = [$pcstr, "?", $pcstr];
5029    }
5030    return 0;
5031  }
5032
5033  # Sort addresses so we can do a join against nm output
5034  my $index = 0;
5035  my $fullname = $names[0];
5036  my $name = ShortFunctionName($fullname);
5037  foreach my $pc (sort { $a cmp $b } @{$pclist}) {
5038    # Adjust for mapped offset
5039    my $mpc = AddressSub($pc, $offset);
5040    while (($index < $#names) && ($mpc ge $symbol_table->{$fullname}->[1])){
5041      $index++;
5042      $fullname = $names[$index];
5043      $name = ShortFunctionName($fullname);
5044    }
5045    if ($mpc lt $symbol_table->{$fullname}->[1]) {
5046      $symbols->{$pc} = [$name, "?", $fullname];
5047    } else {
5048      my $pcstr = "0x" . $pc;
5049      $symbols->{$pc} = [$pcstr, "?", $pcstr];
5050    }
5051  }
5052  return 1;
5053}
5054
5055sub ShortFunctionName {
5056  my $function = shift;
5057  while ($function =~ s/\([^()]*\)(\s*const)?//g) { }   # Argument types
5058  while ($function =~ s/<[^<>]*>//g)  { }    # Remove template arguments
5059  $function =~ s/^.*\s+(\w+::)/$1/;          # Remove leading type
5060  return $function;
5061}
5062
5063# Trim overly long symbols found in disassembler output
5064sub CleanDisassembly {
5065  my $d = shift;
5066  while ($d =~ s/\([^()%]*\)(\s*const)?//g) { } # Argument types, not (%rax)
5067  while ($d =~ s/(\w+)<[^<>]*>/$1/g)  { }       # Remove template arguments
5068  return $d;
5069}
5070
5071# Clean file name for display
5072sub CleanFileName {
5073  my ($f) = @_;
5074  $f =~ s|^/proc/self/cwd/||;
5075  $f =~ s|^\./||;
5076  return $f;
5077}
5078
5079# Make address relative to section and clean up for display
5080sub UnparseAddress {
5081  my ($offset, $address) = @_;
5082  $address = AddressSub($address, $offset);
5083  $address =~ s/^0x//;
5084  $address =~ s/^0*//;
5085  return $address;
5086}
5087
5088##### Miscellaneous #####
5089
5090# Find the right versions of the above object tools to use.  The
5091# argument is the program file being analyzed, and should be an ELF
5092# 32-bit or ELF 64-bit executable file.  The location of the tools
5093# is determined by considering the following options in this order:
5094#   1) --tools option, if set
5095#   2) JEPROF_TOOLS environment variable, if set
5096#   3) the environment
5097sub ConfigureObjTools {
5098  my $prog_file = shift;
5099
5100  # Check for the existence of $prog_file because /usr/bin/file does not
5101  # predictably return error status in prod.
5102  (-e $prog_file)  || error("$prog_file does not exist.\n");
5103
5104  my $file_type = undef;
5105  if (-e "/usr/bin/file") {
5106    # Follow symlinks (at least for systems where "file" supports that).
5107    my $escaped_prog_file = ShellEscape($prog_file);
5108    $file_type = `/usr/bin/file -L $escaped_prog_file 2>$dev_null ||
5109                  /usr/bin/file $escaped_prog_file`;
5110  } elsif ($^O == "MSWin32") {
5111    $file_type = "MS Windows";
5112  } else {
5113    print STDERR "WARNING: Can't determine the file type of $prog_file";
5114  }
5115
5116  if ($file_type =~ /64-bit/) {
5117    # Change $address_length to 16 if the program file is ELF 64-bit.
5118    # We can't detect this from many (most?) heap or lock contention
5119    # profiles, since the actual addresses referenced are generally in low
5120    # memory even for 64-bit programs.
5121    $address_length = 16;
5122  }
5123
5124  if ($file_type =~ /MS Windows/) {
5125    # For windows, we provide a version of nm and addr2line as part of
5126    # the opensource release, which is capable of parsing
5127    # Windows-style PDB executables.  It should live in the path, or
5128    # in the same directory as jeprof.
5129    $obj_tool_map{"nm_pdb"} = "nm-pdb";
5130    $obj_tool_map{"addr2line_pdb"} = "addr2line-pdb";
5131  }
5132
5133  if ($file_type =~ /Mach-O/) {
5134    # OS X uses otool to examine Mach-O files, rather than objdump.
5135    $obj_tool_map{"otool"} = "otool";
5136    $obj_tool_map{"addr2line"} = "false";  # no addr2line
5137    $obj_tool_map{"objdump"} = "false";  # no objdump
5138  }
5139
5140  # Go fill in %obj_tool_map with the pathnames to use:
5141  foreach my $tool (keys %obj_tool_map) {
5142    $obj_tool_map{$tool} = ConfigureTool($obj_tool_map{$tool});
5143  }
5144}
5145
5146# Returns the path of a caller-specified object tool.  If --tools or
5147# JEPROF_TOOLS are specified, then returns the full path to the tool
5148# with that prefix.  Otherwise, returns the path unmodified (which
5149# means we will look for it on PATH).
5150sub ConfigureTool {
5151  my $tool = shift;
5152  my $path;
5153
5154  # --tools (or $JEPROF_TOOLS) is a comma separated list, where each
5155  # item is either a) a pathname prefix, or b) a map of the form
5156  # <tool>:<path>.  First we look for an entry of type (b) for our
5157  # tool.  If one is found, we use it.  Otherwise, we consider all the
5158  # pathname prefixes in turn, until one yields an existing file.  If
5159  # none does, we use a default path.
5160  my $tools = $main::opt_tools || $ENV{"JEPROF_TOOLS"} || "";
5161  if ($tools =~ m/(,|^)\Q$tool\E:([^,]*)/) {
5162    $path = $2;
5163    # TODO(csilvers): sanity-check that $path exists?  Hard if it's relative.
5164  } elsif ($tools ne '') {
5165    foreach my $prefix (split(',', $tools)) {
5166      next if ($prefix =~ /:/);    # ignore "tool:fullpath" entries in the list
5167      if (-x $prefix . $tool) {
5168        $path = $prefix . $tool;
5169        last;
5170      }
5171    }
5172    if (!$path) {
5173      error("No '$tool' found with prefix specified by " .
5174            "--tools (or \$JEPROF_TOOLS) '$tools'\n");
5175    }
5176  } else {
5177    # ... otherwise use the version that exists in the same directory as
5178    # jeprof.  If there's nothing there, use $PATH.
5179    $0 =~ m,[^/]*$,;     # this is everything after the last slash
5180    my $dirname = $`;    # this is everything up to and including the last slash
5181    if (-x "$dirname$tool") {
5182      $path = "$dirname$tool";
5183    } else {
5184      $path = $tool;
5185    }
5186  }
5187  if ($main::opt_debug) { print STDERR "Using '$path' for '$tool'.\n"; }
5188  return $path;
5189}
5190
5191sub ShellEscape {
5192  my @escaped_words = ();
5193  foreach my $word (@_) {
5194    my $escaped_word = $word;
5195    if ($word =~ m![^a-zA-Z0-9/.,_=-]!) {  # check for anything not in whitelist
5196      $escaped_word =~ s/'/'\\''/;
5197      $escaped_word = "'$escaped_word'";
5198    }
5199    push(@escaped_words, $escaped_word);
5200  }
5201  return join(" ", @escaped_words);
5202}
5203
5204sub cleanup {
5205  unlink($main::tmpfile_sym);
5206  unlink(keys %main::tempnames);
5207
5208  # We leave any collected profiles in $HOME/jeprof in case the user wants
5209  # to look at them later.  We print a message informing them of this.
5210  if ((scalar(@main::profile_files) > 0) &&
5211      defined($main::collected_profile)) {
5212    if (scalar(@main::profile_files) == 1) {
5213      print STDERR "Dynamically gathered profile is in $main::collected_profile\n";
5214    }
5215    print STDERR "If you want to investigate this profile further, you can do:\n";
5216    print STDERR "\n";
5217    print STDERR "  jeprof \\\n";
5218    print STDERR "    $main::prog \\\n";
5219    print STDERR "    $main::collected_profile\n";
5220    print STDERR "\n";
5221  }
5222}
5223
5224sub sighandler {
5225  cleanup();
5226  exit(1);
5227}
5228
5229sub error {
5230  my $msg = shift;
5231  print STDERR $msg;
5232  cleanup();
5233  exit(1);
5234}
5235
5236
5237# Run $nm_command and get all the resulting procedure boundaries whose
5238# names match "$regexp" and returns them in a hashtable mapping from
5239# procedure name to a two-element vector of [start address, end address]
5240sub GetProcedureBoundariesViaNm {
5241  my $escaped_nm_command = shift;    # shell-escaped
5242  my $regexp = shift;
5243
5244  my $symbol_table = {};
5245  open(NM, "$escaped_nm_command |") || error("$escaped_nm_command: $!\n");
5246  my $last_start = "0";
5247  my $routine = "";
5248  while (<NM>) {
5249    s/\r//g;         # turn windows-looking lines into unix-looking lines
5250    if (m/^\s*([0-9a-f]+) (.) (..*)/) {
5251      my $start_val = $1;
5252      my $type = $2;
5253      my $this_routine = $3;
5254
5255      # It's possible for two symbols to share the same address, if
5256      # one is a zero-length variable (like __start_google_malloc) or
5257      # one symbol is a weak alias to another (like __libc_malloc).
5258      # In such cases, we want to ignore all values except for the
5259      # actual symbol, which in nm-speak has type "T".  The logic
5260      # below does this, though it's a bit tricky: what happens when
5261      # we have a series of lines with the same address, is the first
5262      # one gets queued up to be processed.  However, it won't
5263      # *actually* be processed until later, when we read a line with
5264      # a different address.  That means that as long as we're reading
5265      # lines with the same address, we have a chance to replace that
5266      # item in the queue, which we do whenever we see a 'T' entry --
5267      # that is, a line with type 'T'.  If we never see a 'T' entry,
5268      # we'll just go ahead and process the first entry (which never
5269      # got touched in the queue), and ignore the others.
5270      if ($start_val eq $last_start && $type =~ /t/i) {
5271        # We are the 'T' symbol at this address, replace previous symbol.
5272        $routine = $this_routine;
5273        next;
5274      } elsif ($start_val eq $last_start) {
5275        # We're not the 'T' symbol at this address, so ignore us.
5276        next;
5277      }
5278
5279      if ($this_routine eq $sep_symbol) {
5280        $sep_address = HexExtend($start_val);
5281      }
5282
5283      # Tag this routine with the starting address in case the image
5284      # has multiple occurrences of this routine.  We use a syntax
5285      # that resembles template parameters that are automatically
5286      # stripped out by ShortFunctionName()
5287      $this_routine .= "<$start_val>";
5288
5289      if (defined($routine) && $routine =~ m/$regexp/) {
5290        $symbol_table->{$routine} = [HexExtend($last_start),
5291                                     HexExtend($start_val)];
5292      }
5293      $last_start = $start_val;
5294      $routine = $this_routine;
5295    } elsif (m/^Loaded image name: (.+)/) {
5296      # The win32 nm workalike emits information about the binary it is using.
5297      if ($main::opt_debug) { print STDERR "Using Image $1\n"; }
5298    } elsif (m/^PDB file name: (.+)/) {
5299      # The win32 nm workalike emits information about the pdb it is using.
5300      if ($main::opt_debug) { print STDERR "Using PDB $1\n"; }
5301    }
5302  }
5303  close(NM);
5304  # Handle the last line in the nm output.  Unfortunately, we don't know
5305  # how big this last symbol is, because we don't know how big the file
5306  # is.  For now, we just give it a size of 0.
5307  # TODO(csilvers): do better here.
5308  if (defined($routine) && $routine =~ m/$regexp/) {
5309    $symbol_table->{$routine} = [HexExtend($last_start),
5310                                 HexExtend($last_start)];
5311  }
5312  return $symbol_table;
5313}
5314
5315# Gets the procedure boundaries for all routines in "$image" whose names
5316# match "$regexp" and returns them in a hashtable mapping from procedure
5317# name to a two-element vector of [start address, end address].
5318# Will return an empty map if nm is not installed or not working properly.
5319sub GetProcedureBoundaries {
5320  my $image = shift;
5321  my $regexp = shift;
5322
5323  # If $image doesn't start with /, then put ./ in front of it.  This works
5324  # around an obnoxious bug in our probing of nm -f behavior.
5325  # "nm -f $image" is supposed to fail on GNU nm, but if:
5326  #
5327  # a. $image starts with [BbSsPp] (for example, bin/foo/bar), AND
5328  # b. you have a.out in your current directory (a not uncommon occurence)
5329  #
5330  # then "nm -f $image" succeeds because -f only looks at the first letter of
5331  # the argument, which looks valid because it's [BbSsPp], and then since
5332  # there's no image provided, it looks for a.out and finds it.
5333  #
5334  # This regex makes sure that $image starts with . or /, forcing the -f
5335  # parsing to fail since . and / are not valid formats.
5336  $image =~ s#^[^/]#./$&#;
5337
5338  # For libc libraries, the copy in /usr/lib/debug contains debugging symbols
5339  my $debugging = DebuggingLibrary($image);
5340  if ($debugging) {
5341    $image = $debugging;
5342  }
5343
5344  my $nm = $obj_tool_map{"nm"};
5345  my $cppfilt = $obj_tool_map{"c++filt"};
5346
5347  # nm can fail for two reasons: 1) $image isn't a debug library; 2) nm
5348  # binary doesn't support --demangle.  In addition, for OS X we need
5349  # to use the -f flag to get 'flat' nm output (otherwise we don't sort
5350  # properly and get incorrect results).  Unfortunately, GNU nm uses -f
5351  # in an incompatible way.  So first we test whether our nm supports
5352  # --demangle and -f.
5353  my $demangle_flag = "";
5354  my $cppfilt_flag = "";
5355  my $to_devnull = ">$dev_null 2>&1";
5356  if (system(ShellEscape($nm, "--demangle", "image") . $to_devnull) == 0) {
5357    # In this mode, we do "nm --demangle <foo>"
5358    $demangle_flag = "--demangle";
5359    $cppfilt_flag = "";
5360  } elsif (system(ShellEscape($cppfilt, $image) . $to_devnull) == 0) {
5361    # In this mode, we do "nm <foo> | c++filt"
5362    $cppfilt_flag = " | " . ShellEscape($cppfilt);
5363  };
5364  my $flatten_flag = "";
5365  if (system(ShellEscape($nm, "-f", $image) . $to_devnull) == 0) {
5366    $flatten_flag = "-f";
5367  }
5368
5369  # Finally, in the case $imagie isn't a debug library, we try again with
5370  # -D to at least get *exported* symbols.  If we can't use --demangle,
5371  # we use c++filt instead, if it exists on this system.
5372  my @nm_commands = (ShellEscape($nm, "-n", $flatten_flag, $demangle_flag,
5373                                 $image) . " 2>$dev_null $cppfilt_flag",
5374                     ShellEscape($nm, "-D", "-n", $flatten_flag, $demangle_flag,
5375                                 $image) . " 2>$dev_null $cppfilt_flag",
5376                     # 6nm is for Go binaries
5377                     ShellEscape("6nm", "$image") . " 2>$dev_null | sort",
5378                     );
5379
5380  # If the executable is an MS Windows PDB-format executable, we'll
5381  # have set up obj_tool_map("nm_pdb").  In this case, we actually
5382  # want to use both unix nm and windows-specific nm_pdb, since
5383  # PDB-format executables can apparently include dwarf .o files.
5384  if (exists $obj_tool_map{"nm_pdb"}) {
5385    push(@nm_commands,
5386         ShellEscape($obj_tool_map{"nm_pdb"}, "--demangle", $image)
5387         . " 2>$dev_null");
5388  }
5389
5390  foreach my $nm_command (@nm_commands) {
5391    my $symbol_table = GetProcedureBoundariesViaNm($nm_command, $regexp);
5392    return $symbol_table if (%{$symbol_table});
5393  }
5394  my $symbol_table = {};
5395  return $symbol_table;
5396}
5397
5398
5399# The test vectors for AddressAdd/Sub/Inc are 8-16-nibble hex strings.
5400# To make them more readable, we add underscores at interesting places.
5401# This routine removes the underscores, producing the canonical representation
5402# used by jeprof to represent addresses, particularly in the tested routines.
5403sub CanonicalHex {
5404  my $arg = shift;
5405  return join '', (split '_',$arg);
5406}
5407
5408
5409# Unit test for AddressAdd:
5410sub AddressAddUnitTest {
5411  my $test_data_8 = shift;
5412  my $test_data_16 = shift;
5413  my $error_count = 0;
5414  my $fail_count = 0;
5415  my $pass_count = 0;
5416  # print STDERR "AddressAddUnitTest: ", 1+$#{$test_data_8}, " tests\n";
5417
5418  # First a few 8-nibble addresses.  Note that this implementation uses
5419  # plain old arithmetic, so a quick sanity check along with verifying what
5420  # happens to overflow (we want it to wrap):
5421  $address_length = 8;
5422  foreach my $row (@{$test_data_8}) {
5423    if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
5424    my $sum = AddressAdd ($row->[0], $row->[1]);
5425    if ($sum ne $row->[2]) {
5426      printf STDERR "ERROR: %s != %s + %s = %s\n", $sum,
5427             $row->[0], $row->[1], $row->[2];
5428      ++$fail_count;
5429    } else {
5430      ++$pass_count;
5431    }
5432  }
5433  printf STDERR "AddressAdd 32-bit tests: %d passes, %d failures\n",
5434         $pass_count, $fail_count;
5435  $error_count = $fail_count;
5436  $fail_count = 0;
5437  $pass_count = 0;
5438
5439  # Now 16-nibble addresses.
5440  $address_length = 16;
5441  foreach my $row (@{$test_data_16}) {
5442    if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
5443    my $sum = AddressAdd (CanonicalHex($row->[0]), CanonicalHex($row->[1]));
5444    my $expected = join '', (split '_',$row->[2]);
5445    if ($sum ne CanonicalHex($row->[2])) {
5446      printf STDERR "ERROR: %s != %s + %s = %s\n", $sum,
5447             $row->[0], $row->[1], $row->[2];
5448      ++$fail_count;
5449    } else {
5450      ++$pass_count;
5451    }
5452  }
5453  printf STDERR "AddressAdd 64-bit tests: %d passes, %d failures\n",
5454         $pass_count, $fail_count;
5455  $error_count += $fail_count;
5456
5457  return $error_count;
5458}
5459
5460
5461# Unit test for AddressSub:
5462sub AddressSubUnitTest {
5463  my $test_data_8 = shift;
5464  my $test_data_16 = shift;
5465  my $error_count = 0;
5466  my $fail_count = 0;
5467  my $pass_count = 0;
5468  # print STDERR "AddressSubUnitTest: ", 1+$#{$test_data_8}, " tests\n";
5469
5470  # First a few 8-nibble addresses.  Note that this implementation uses
5471  # plain old arithmetic, so a quick sanity check along with verifying what
5472  # happens to overflow (we want it to wrap):
5473  $address_length = 8;
5474  foreach my $row (@{$test_data_8}) {
5475    if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
5476    my $sum = AddressSub ($row->[0], $row->[1]);
5477    if ($sum ne $row->[3]) {
5478      printf STDERR "ERROR: %s != %s - %s = %s\n", $sum,
5479             $row->[0], $row->[1], $row->[3];
5480      ++$fail_count;
5481    } else {
5482      ++$pass_count;
5483    }
5484  }
5485  printf STDERR "AddressSub 32-bit tests: %d passes, %d failures\n",
5486         $pass_count, $fail_count;
5487  $error_count = $fail_count;
5488  $fail_count = 0;
5489  $pass_count = 0;
5490
5491  # Now 16-nibble addresses.
5492  $address_length = 16;
5493  foreach my $row (@{$test_data_16}) {
5494    if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
5495    my $sum = AddressSub (CanonicalHex($row->[0]), CanonicalHex($row->[1]));
5496    if ($sum ne CanonicalHex($row->[3])) {
5497      printf STDERR "ERROR: %s != %s - %s = %s\n", $sum,
5498             $row->[0], $row->[1], $row->[3];
5499      ++$fail_count;
5500    } else {
5501      ++$pass_count;
5502    }
5503  }
5504  printf STDERR "AddressSub 64-bit tests: %d passes, %d failures\n",
5505         $pass_count, $fail_count;
5506  $error_count += $fail_count;
5507
5508  return $error_count;
5509}
5510
5511
5512# Unit test for AddressInc:
5513sub AddressIncUnitTest {
5514  my $test_data_8 = shift;
5515  my $test_data_16 = shift;
5516  my $error_count = 0;
5517  my $fail_count = 0;
5518  my $pass_count = 0;
5519  # print STDERR "AddressIncUnitTest: ", 1+$#{$test_data_8}, " tests\n";
5520
5521  # First a few 8-nibble addresses.  Note that this implementation uses
5522  # plain old arithmetic, so a quick sanity check along with verifying what
5523  # happens to overflow (we want it to wrap):
5524  $address_length = 8;
5525  foreach my $row (@{$test_data_8}) {
5526    if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
5527    my $sum = AddressInc ($row->[0]);
5528    if ($sum ne $row->[4]) {
5529      printf STDERR "ERROR: %s != %s + 1 = %s\n", $sum,
5530             $row->[0], $row->[4];
5531      ++$fail_count;
5532    } else {
5533      ++$pass_count;
5534    }
5535  }
5536  printf STDERR "AddressInc 32-bit tests: %d passes, %d failures\n",
5537         $pass_count, $fail_count;
5538  $error_count = $fail_count;
5539  $fail_count = 0;
5540  $pass_count = 0;
5541
5542  # Now 16-nibble addresses.
5543  $address_length = 16;
5544  foreach my $row (@{$test_data_16}) {
5545    if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
5546    my $sum = AddressInc (CanonicalHex($row->[0]));
5547    if ($sum ne CanonicalHex($row->[4])) {
5548      printf STDERR "ERROR: %s != %s + 1 = %s\n", $sum,
5549             $row->[0], $row->[4];
5550      ++$fail_count;
5551    } else {
5552      ++$pass_count;
5553    }
5554  }
5555  printf STDERR "AddressInc 64-bit tests: %d passes, %d failures\n",
5556         $pass_count, $fail_count;
5557  $error_count += $fail_count;
5558
5559  return $error_count;
5560}
5561
5562
5563# Driver for unit tests.
5564# Currently just the address add/subtract/increment routines for 64-bit.
5565sub RunUnitTests {
5566  my $error_count = 0;
5567
5568  # This is a list of tuples [a, b, a+b, a-b, a+1]
5569  my $unit_test_data_8 = [
5570    [qw(aaaaaaaa 50505050 fafafafa 5a5a5a5a aaaaaaab)],
5571    [qw(50505050 aaaaaaaa fafafafa a5a5a5a6 50505051)],
5572    [qw(ffffffff aaaaaaaa aaaaaaa9 55555555 00000000)],
5573    [qw(00000001 ffffffff 00000000 00000002 00000002)],
5574    [qw(00000001 fffffff0 fffffff1 00000011 00000002)],
5575  ];
5576  my $unit_test_data_16 = [
5577    # The implementation handles data in 7-nibble chunks, so those are the
5578    # interesting boundaries.
5579    [qw(aaaaaaaa 50505050
5580        00_000000f_afafafa 00_0000005_a5a5a5a 00_000000a_aaaaaab)],
5581    [qw(50505050 aaaaaaaa
5582        00_000000f_afafafa ff_ffffffa_5a5a5a6 00_0000005_0505051)],
5583    [qw(ffffffff aaaaaaaa
5584        00_000001a_aaaaaa9 00_0000005_5555555 00_0000010_0000000)],
5585    [qw(00000001 ffffffff
5586        00_0000010_0000000 ff_ffffff0_0000002 00_0000000_0000002)],
5587    [qw(00000001 fffffff0
5588        00_000000f_ffffff1 ff_ffffff0_0000011 00_0000000_0000002)],
5589
5590    [qw(00_a00000a_aaaaaaa 50505050
5591        00_a00000f_afafafa 00_a000005_a5a5a5a 00_a00000a_aaaaaab)],
5592    [qw(0f_fff0005_0505050 aaaaaaaa
5593        0f_fff000f_afafafa 0f_ffefffa_5a5a5a6 0f_fff0005_0505051)],
5594    [qw(00_000000f_fffffff 01_800000a_aaaaaaa
5595        01_800001a_aaaaaa9 fe_8000005_5555555 00_0000010_0000000)],
5596    [qw(00_0000000_0000001 ff_fffffff_fffffff
5597        00_0000000_0000000 00_0000000_0000002 00_0000000_0000002)],
5598    [qw(00_0000000_0000001 ff_fffffff_ffffff0
5599        ff_fffffff_ffffff1 00_0000000_0000011 00_0000000_0000002)],
5600  ];
5601
5602  $error_count += AddressAddUnitTest($unit_test_data_8, $unit_test_data_16);
5603  $error_count += AddressSubUnitTest($unit_test_data_8, $unit_test_data_16);
5604  $error_count += AddressIncUnitTest($unit_test_data_8, $unit_test_data_16);
5605  if ($error_count > 0) {
5606    print STDERR $error_count, " errors: FAILED\n";
5607  } else {
5608    print STDERR "PASS\n";
5609  }
5610  exit ($error_count);
5611}
5612