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