Home | History | Annotate | Line # | Download | only in bin
jeprof.in revision 1.1.1.1
      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 
     71 use strict;
     72 use warnings;
     73 use Getopt::Long;
     74 use Cwd;
     75 
     76 my $JEPROF_VERSION = "@jemalloc_version@";
     77 my $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.
     82 my %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.
     93 my @DOT = ("dot");          # leave non-absolute, since it may be in /usr/local
     94 my @GV = ("gv");
     95 my @EVINCE = ("evince");    # could also be xpdf or perhaps acroread
     96 my @KCACHEGRIND = ("kcachegrind");
     97 my @PS2PDF = ("ps2pdf");
     98 # These are used for dynamic profiles
     99 my @URL_FETCHER = ("curl", "-s", "--fail");
    100 
    101 # These are the web pages that servers need to support for dynamic profiles
    102 my $HEAP_PAGE = "/pprof/heap";
    103 my $PROFILE_PAGE = "/pprof/profile";   # must support cgi-param "?seconds=#"
    104 my $PMUPROFILE_PAGE = "/pprof/pmuprofile(?:\\?.*)?"; # must support cgi-param
    105                                                 # ?seconds=#&event=x&period=n
    106 my $GROWTH_PAGE = "/pprof/growth";
    107 my $CONTENTION_PAGE = "/pprof/contention";
    108 my $WALL_PAGE = "/pprof/wall(?:\\?.*)?";  # accepts options like namefilter
    109 my $FILTEREDPROFILE_PAGE = "/pprof/filteredprofile(?:\\?.*)?";
    110 my $CENSUSPROFILE_PAGE = "/pprof/censusprofile(?:\\?.*)?"; # must support cgi-param
    111                                                        # "?seconds=#",
    112                                                        # "?tags_regexp=#" and
    113                                                        # "?type=#".
    114 my $SYMBOL_PAGE = "/pprof/symbol";     # must support symbol lookup via POST
    115 my $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 /.
    119 my $PROFILES = "($HEAP_PAGE|$PROFILE_PAGE|$PMUPROFILE_PAGE|" .
    120                "$GROWTH_PAGE|$CONTENTION_PAGE|$WALL_PAGE|" .
    121                "$FILTEREDPROFILE_PAGE|$CENSUSPROFILE_PAGE)";
    122 
    123 # default binary name
    124 my $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:
    129 my $address_length = 16;
    130 
    131 my $dev_null = "/dev/null";
    132 if (! -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
    137 my @prefix_list = ();
    138 
    139 # Special routine name that should not have any symbols.
    140 # Used as separator to parse "addr2line -i" output.
    141 my $sep_symbol = '_fini';
    142 my $sep_address = undef;
    143 
    144 ##### Argument parsing #####
    145 
    146 sub usage_string {
    147   return <<EOF;
    148 Usage:
    149 jeprof [options] <program> <profiles>
    150    <profiles> is a space separated list of profile names.
    151 jeprof [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).
    155 jeprof [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).
    168 jeprof --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 
    179 Options:
    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 
    187 Reporting 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 
    193 Output 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 
    209 Heap-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 
    217 Contention-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 
    222 Call-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 
    236 Miscellaneous:
    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 
    242 Environment Variables:
    243    JEPROF_TMPDIR        Profiles directory. Defaults to \$HOME/jeprof
    244    JEPROF_TOOLS         Prefix for object tools pathnames
    245 
    246 Examples:
    247 
    248 jeprof /bin/ls ls.prof
    249                        Enters "interactive" mode
    250 jeprof --text /bin/ls ls.prof
    251                        Outputs one line per procedure
    252 jeprof --web /bin/ls ls.prof
    253                        Displays annotated call-graph in web browser
    254 jeprof --gv /bin/ls ls.prof
    255                        Displays annotated call-graph via 'gv'
    256 jeprof --gv --focus=Mutex /bin/ls ls.prof
    257                        Restricts to code paths including a .*Mutex.* entry
    258 jeprof --gv --focus=Mutex --ignore=string /bin/ls ls.prof
    259                        Code paths including Mutex but not string
    260 jeprof --list=getdir /bin/ls ls.prof
    261                        (Per-line) annotated source listing for getdir()
    262 jeprof --disasm=getdir /bin/ls ls.prof
    263                        (Per-PC) annotated disassembly for getdir()
    264 
    265 jeprof http://localhost:1234/
    266                        Enters "interactive" mode
    267 jeprof --text localhost:1234
    268                        Outputs one line per procedure for localhost:1234
    269 jeprof --raw localhost:1234 > ./local.raw
    270 jeprof --text ./local.raw
    271                        Fetches a remote profile for later analysis and then
    272                        analyzes it in text mode.
    273 EOF
    274 }
    275 
    276 sub version_string {
    277   return <<EOF
    278 jeprof (part of jemalloc $JEPROF_VERSION)
    279 based on pprof (part of gperftools $PPROF_VERSION)
    280 
    281 Copyright 1998-2007 Google Inc.
    282 
    283 This is BSD licensed software; see the source for copying conditions
    284 and license information.
    285 There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
    286 PARTICULAR PURPOSE.
    287 EOF
    288 }
    289 
    290 sub 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 
    298 sub 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 
    577 sub 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 
    657 sub 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 
    733 Main();
    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
    740 sub 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 
    748 sub 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 
    766 sub RunEvince {
    767   my $fname = shift;
    768   my $bg = shift;       # "" or " &" if we should run in background
    769   system(ShellEscape(@EVINCE, $fname) . $bg);
    770 }
    771 
    772 sub 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 
    800 sub 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 
    810 sub 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
    849 sub 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 
    989 sub 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 
   1020 sub InteractiveHelpMessage {
   1021   print STDERR <<ENDOFHELP;
   1022 Interactive jeprof mode
   1023 
   1024 Commands:
   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 
   1067 For commands that accept optional -ignore tags, samples where any routine in
   1068 the stack trace matches the regular expression in any of the -ignore
   1069 parameters will be ignored.
   1070 
   1071 Further 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 
   1076 ENDOFHELP
   1077 }
   1078 sub 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 
   1102 sub 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
   1111 sub 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
   1144 sub 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
   1206 sub 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.
   1253 sub 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.
   1270 sub 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
   1311 sub 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"]
   1345 sub 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.
   1386 sub 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
   1414 sub ByName {
   1415   return ShortFunctionName($a) cmp ShortFunctionName($b);
   1416 }
   1417 
   1418 # Print source-listing for all all routines that match $list_opts
   1419 sub 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 
   1481 sub HtmlListingHeader {
   1482   return <<'EOF';
   1483 <DOCTYPE html>
   1484 <html>
   1485 <head>
   1486 <title>Pprof listing</title>
   1487 <style type="text/css">
   1488 body {
   1489   font-family: sans-serif;
   1490 }
   1491 h1 {
   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">
   1529 function 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>
   1547 EOF
   1548 }
   1549 
   1550 sub HtmlListingFooter {
   1551   return <<'EOF';
   1552 </body>
   1553 </html>
   1554 EOF
   1555 }
   1556 
   1557 sub 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.
   1567 sub 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)
   1592 sub 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
   1616 sub 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.
   1872 sub 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
   1909 sub 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
   2020 sub 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 
   2251 sub 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 
   2304 sub 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 
   2366 var root = document.documentElement;
   2367 
   2368 var state = 'none', stateTarget, stateOrigin, stateTf;
   2369 
   2370 setupHandlers(root);
   2371 
   2372 /**
   2373  * Register handlers
   2374  */
   2375 function 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  */
   2397 function 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  */
   2409 function 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  */
   2418 function 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  */
   2427 function setAttributes(element, attributes){
   2428 	for (i in attributes)
   2429 		element.setAttributeNS(null, i, attributes[i]);
   2430 }
   2431 
   2432 /**
   2433  * Handle mouse move event.
   2434  */
   2435 function 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  */
   2469 function 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  */
   2497 function 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  */
   2529 function 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>
   2544 EOF
   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.
   2556 sub 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.
   2594 sub 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
   2605 sub 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
   2688 sub 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
   2699 sub 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 "."
   2719 sub 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 ""
   2729 sub HtmlPrintNumber {
   2730   my $num = shift;
   2731   if ($num == 0) {
   2732     return "";
   2733   } else {
   2734     return Unparse($num);
   2735   }
   2736 }
   2737 
   2738 # Return output units
   2739 sub 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]
   2762 sub 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]
   2778 sub 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.
   2793 sub 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 
   2813 sub 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.
   2830 sub 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 
   2850 sub 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 
   2887 sub 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 ('@JEMALLOC_PREFIX@calloc',
   2896                       'cfree',
   2897                       '@JEMALLOC_PREFIX@malloc',
   2898                       'newImpl',
   2899                       'void* newImpl',
   2900                       '@JEMALLOC_PREFIX@free',
   2901                       '@JEMALLOC_PREFIX@memalign',
   2902                       '@JEMALLOC_PREFIX@posix_memalign',
   2903                       '@JEMALLOC_PREFIX@aligned_alloc',
   2904                       'pvalloc',
   2905                       '@JEMALLOC_PREFIX@valloc',
   2906                       '@JEMALLOC_PREFIX@realloc',
   2907                       '@JEMALLOC_PREFIX@mallocx',
   2908                       '@JEMALLOC_PREFIX@rallocx',
   2909                       '@JEMALLOC_PREFIX@xallocx',
   2910                       '@JEMALLOC_PREFIX@dallocx',
   2911                       '@JEMALLOC_PREFIX@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
   3040 sub 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?
   3067 sub 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
   3081 sub 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
   3101 sub 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
   3125 sub 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
   3135 sub 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
   3154 sub 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
   3172 sub 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
   3189 sub 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
   3213 sub 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
   3224 sub 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.
   3236 sub 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 
   3253 sub 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 
   3273 sub 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 
   3282 sub 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.
   3305 sub SymbolPageURL {
   3306   my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);
   3307   return "$baseURL$SYMBOL_PAGE";
   3308 }
   3309 
   3310 sub 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.
   3328 sub 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.
   3343 sub 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.
   3360 sub 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 
   3391 sub URLEncode {
   3392   my $str = shift;
   3393   $str =~ s/([^A-Za-z0-9\-_.!~*'()])/ sprintf "%%%02x", ord $1 /eg;
   3394   return $str;
   3395 }
   3396 
   3397 sub 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.
   3417 sub 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 
   3480 sub BaseName {
   3481   my $file_name = shift;
   3482   $file_name =~ s!^.*/!!;  # Remove directory name
   3483   return $file_name;
   3484 }
   3485 
   3486 sub 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 
   3494 sub 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
   3571 sub 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
   3594 sub 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
   3612 sub 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).
   3635 BEGIN {
   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.
   3771 sub 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 
   3795 sub 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
   3821 sub 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.
   3918 sub 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
   3939 sub 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 
   4015 sub 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 
   4029 sub 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 
   4040 sub 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 
   4060 sub 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 
   4098 sub 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 
   4224 sub 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 
   4301 sub 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.
   4397 sub 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)
   4422 sub 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
   4440 sub 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
   4453 sub 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)
   4492 sub 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 
   4561 sub 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
   4574 sub 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.
   4685 sub 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.
   4739 sub 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.
   4791 sub 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
   4842 sub 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
   4893 sub 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.
   5022 sub 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 
   5068 sub 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
   5077 sub 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
   5085 sub 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
   5093 sub 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
   5110 sub 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).
   5163 sub 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 
   5204 sub 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 
   5217 sub 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 
   5237 sub sighandler {
   5238   cleanup();
   5239   exit(1);
   5240 }
   5241 
   5242 sub 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]
   5253 sub 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.
   5332 sub 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.
   5416 sub CanonicalHex {
   5417   my $arg = shift;
   5418   return join '', (split '_',$arg);
   5419 }
   5420 
   5421 
   5422 # Unit test for AddressAdd:
   5423 sub 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:
   5475 sub 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:
   5526 sub 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.
   5578 sub 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