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