| #!/usr/bin/perl |
| |
| # ********************************************************** |
| # Copyright (c) 2004-2006 VMware, Inc. All rights reserved. |
| # ********************************************************** |
| |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are met: |
| # |
| # * Redistributions of source code must retain the above copyright notice, |
| # this list of conditions and the following disclaimer. |
| # |
| # * Redistributions in binary form must reproduce the above copyright notice, |
| # this list of conditions and the following disclaimer in the documentation |
| # and/or other materials provided with the distribution. |
| # |
| # * Neither the name of VMware, Inc. nor the names of its contributors may be |
| # used to endorse or promote products derived from this software without |
| # specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| # ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE |
| # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| # DAMAGE. |
| |
| ### tracestats.pl |
| ### formerly called Frags, with fewer options |
| ### author: Derek Bruening February 2001 |
| ### |
| ### Analyzes DynamoRIO trace output: sorts traces based |
| ### on counts or time spent in them (from DynamoRIO profiling info) |
| ### |
| ### -brief: lists summary table instead of full info for each trace |
| ### -stats: lists completion stats instead of full info |
| ### -prefix_stats: lists counts of prefixes that need to restore eflags |
| ### -links: will print % of time each exit from a trace was used |
| ### (only with Dynamo's -prof_counts) |
| ### -groups: attempts to group traces based on their links |
| ### -exe exename: will look up line numbers in original |
| ### code that traces came from |
| ### (only with Dynamo's -dump_trace_origins flag) |
| ### |
| ### Several scalars below could overflow a 32-bit int, I assume |
| ### perl always uses floats internally and won't overflow |
| |
| # FIXME: some of these are mutually exclusive, indicate which |
| $usage = "Usage: $0 [-brief] [-stats] [-prefix_stats] [-links] [-groups] |
| \t[-sideline] [-exe exename] [-bbpcs] [-pcs pcfile] <tracefile> |
| |
| -stats and -bbpcs: work best with -tracedump_origins output in the tracefile |
| -links and -groups: only work with -prof_counts output in the tracefile |
| "; |
| |
| # defaults |
| $have_exe = 0; |
| $src = 0; |
| $brief = 0; |
| $links = 0; |
| $groups = 0; |
| $pcs = 0; |
| $linux_pcs = 0; |
| |
| # win32 pcs only |
| $win32_pcs_step = 0; |
| $overcount_instrs = 0; |
| $overcount_samples = 0; |
| |
| $stats = 0; |
| $prefix_stats = 0; |
| $infile = ""; |
| |
| # get optional params |
| while ($#ARGV >= 0) { |
| if ($ARGV[0] eq '-exe') { |
| if ($#ARGV <= 0) { print $usage; exit; } |
| shift; |
| $exe = $ARGV[0]; |
| unless (-f "$exe" && -x "$exe" ) { |
| die "$err No executable $exe\n"; |
| } |
| $have_exe = 1; |
| $src = 1; # src is not a separate option anymore |
| } elsif ($ARGV[0] eq '-in') { |
| if ($#ARGV <= 0) { print $usage; exit; } |
| shift; |
| $infile = $ARGV[0]; |
| } elsif ($ARGV[0] eq '-pcs') { |
| if ($#ARGV <= 0) { print $usage; exit; } |
| shift; |
| $pcs = 1; |
| $pcfile = $ARGV[0]; |
| } elsif ($ARGV[0] eq '-stats') { |
| $stats = 1; |
| } elsif ($ARGV[0] eq '-prefix_stats') { |
| # study of static and dynamic count of prefixes that need to restore eflags |
| $prefix_stats = 1; |
| $prefix_noeflags = 0; |
| $prefix_eflags = 0; |
| $prefix_noeflags_count = 0; |
| $prefix_eflags_count = 0; |
| } elsif ($ARGV[0] eq '-brief') { |
| $brief = 1; |
| } elsif ($ARGV[0] eq '-sideline') { |
| $brief = 1; |
| $sideline = 1; |
| } elsif ($ARGV[0] eq '-links') { |
| $brief = 1; |
| $links = 1; |
| } elsif ($ARGV[0] eq '-groups') { |
| $groups = 1; |
| $brief = 1; |
| $links = 1; |
| } elsif ($ARGV[0] eq '-bbpcs') { |
| $pcs_by_orig_bb = 1; |
| } elsif ($ARGV[0] =~ /^-/) { |
| print $usage; |
| exit; |
| } else { |
| $infile = $ARGV[0]; |
| last; |
| } |
| shift; |
| } |
| |
| die $usage if ($infile eq ""); |
| |
| if ($src && !$have_exe) { |
| print "Error: must specify executable name with -exe when using -src\n"; |
| exit 0; |
| } |
| |
| if ($pcs) { |
| # read in all pcs into a table |
| open(PCS, "< $pcfile") || die "Error: Couldn't open $pcfile for input\n"; |
| # Linux format: |
| # ITIMER distribution (74): |
| # 74.3% of time in INTERPRETER (55) |
| # ... |
| # |
| # PC PROFILING RESULTS |
| # pc=0xf6e8c129 #=1 in DynamoRIO dispatch |
| # pc=0x179d102c #=1 in trace # 251 @0x08048470 w/ offs 0x00000028 |
| # pc=0x002c1192 #=1 in DynamoRIO interpreter |
| # ... |
| # Windows format: |
| # ... |
| # Dumping dynamorio.dll profile |
| # 127 hits out of 596, 21.30% |
| # ... |
| # Dumping global profile |
| # 596 hits |
| # ... |
| # Dumping fcache trace unit profile (Shared) |
| # 134 hits |
| # Profile Dump |
| # Range 0x182f1000-0x182fefff |
| # Step 0x00000008 (0-7167) |
| # 0x182f1c78 1 |
| # 0x182f1d60 1 |
| # 0x182f1d78 1 |
| # ... |
| # Finished Profile Dump |
| my $in_fcache_pcs = 0; |
| while (<PCS>) { |
| chop; |
| # handle DOS end-of-line: |
| if ($_ =~ / |
| $/) { chop; }; |
| if ($_ =~ /ITIMER distribution/) { |
| $linux_pcs = 1; |
| } |
| if ($_ =~ /ITIMER distribution \(([0-9]+)\)/ || |
| $_ =~ /hits out of ([0-9]+)/) { |
| $total_samples = $1; |
| } |
| $in_fcache_pcs = 1 if (/trace unit/); |
| next unless ((!$linux_pcs && $in_fcache_pcs) || ($linux_pcs && $_ =~ /^pc/)); |
| $l = $_; |
| if ($linux_pcs) { |
| # only care about traces and fragments |
| # can be multiple entries for a single pc: must key by pc AND tag |
| if ($l =~ /trace/ || $l =~ /fragment/) { |
| if ($l =~ /trace \#/ || $l =~ /fragment \#/) { |
| $l =~ /^pc=([x0-9a-f]+)[^\#]+\#=([0-9]+)[^\#]+\# *([0-9]+) @([x0-9a-f]+)/; |
| # $1 = pc, $2 = count, $3 = id, $4 = tag |
| $pc_samples{$4, $1} = $2; |
| } else { |
| # release run: no trace id! |
| $l =~ /^pc=([x0-9a-f]+)[^\#]+\#=([0-9]+)[^@]+@([x0-9a-f]+)/; |
| # $1 = pc, $2 = count, $3 = tag |
| $pc_samples{$3, $1} = $2; |
| } |
| } |
| } else { |
| if (/Finished Profile Dump/) { |
| $in_fcache_pcs = 0; |
| } elsif (/Step 0x0*([1-9]+)/) { |
| # bucket size |
| $win32_pcs_step = hex($1); |
| } else { |
| next unless ($l =~ /^0x/); |
| $l =~ /([x0-9a-f]+)\s+([0-9]+)/; |
| # FIXME: no tag info available so will double-count for traces |
| # that re-use same fcache slots |
| $pc_samples{$1} = $2; |
| $pc_sample_taken{$1} = 0; |
| } |
| } |
| } |
| close(PCS); |
| } |
| |
| if ($src) { |
| # get info on shared libraries |
| $shared_num = 0; |
| open(LDD, "ldd $exe |") || die "Error: Couldn't run ldd $exe\n"; |
| while (<LDD>) { |
| chop; |
| $l = $_; |
| if ($l =~ /^\s*([\/a-zA-Z_\.0-9-]+) => .* \((0x[0-9A-Fa-f]+)\)/) { |
| $shared_name[$shared_num] = $1; |
| $shared_start[$shared_num] = $2; |
| $shared_sort[$shared_num] = $shared_num; |
| $shared_num++; |
| } |
| } |
| close(LDD); |
| |
| # now sort by starting address |
| @shared_sort = sort({hex($shared_start[$b]) <=> hex($shared_start[$a])} |
| @shared_sort); |
| print "Shared library assumptions:\n"; |
| for ($i=$shared_num-1; $i>=0; $i--) { |
| $addr1 = $shared_start[$shared_sort[$i]]; |
| if ($i == 0) { |
| $addr2 = "above"; |
| } else { |
| $addr2 = sprintf("0x%08x", |
| hex($shared_start[$shared_sort[$i-1]]) - 1); |
| } |
| print "\t$addr1 .. $addr2 == $shared_name[$shared_sort[$i]]\n"; |
| } |
| print "\n"; |
| } |
| |
| # read in and store all lines from file |
| $in_trace = 0; |
| $in_stub_summary = 0; |
| $line = 0; |
| $tnum = 0; |
| $linked_to = 0; |
| $have_times = 0; |
| $max_stub = 0; |
| $stub_64bit = 0; |
| $body_start = 0; |
| $direct_total = 0; |
| $indirect_total = 0; |
| $exitstub_boundary = -1; |
| $flushed = 0; |
| $stub_num = 0; |
| $in_original = 0; |
| $unseen_count = 0; |
| |
| # for pc samples |
| $stubs_start = 0; |
| $stub_samples = 0; |
| $ind_samples = 0; |
| $in_cmp = 0; |
| $samples = 0; |
| $dcontext_ecx = 0; |
| $ret_samples = 0; |
| $indjc_samples = 0; # indirect jumps & calls |
| $branch_samples = 0; |
| |
| open(IN, "< $infile") || die "Error: Couldn't open $infile for input\n"; |
| while (<IN>) { |
| chop; |
| # handle DOS end-of-line: |
| if ($_ =~ / |
| $/) { chop; }; |
| $l = $_; |
| if ($l =~ /^TRACE \# ([0-9]+)/) { |
| # start of trace |
| $in_trace = 1; |
| # in case no Fragment# data |
| $id[$tnum] = $1; |
| if ($pcs && $pcs_by_orig_bb) { |
| # only keep src bb tag info for the current trace |
| $cur_bbnum = 0; |
| $cur_stubnum = 0; |
| } |
| if ($prefix_stats) { |
| $trace_total_exec = 0; |
| } |
| } |
| if ($in_trace) { |
| if ($l =~ /^Fragment \# ([0-9]+)/) { |
| # get fragment id |
| $id[$tnum] = $1; |
| # now initialize everything using fragment id: |
| # for reverse-lookup |
| $tnum_from_id{$1} = $tnum; |
| $tag[$tnum] = -1; |
| $size[$tnum] = -1; |
| if ($pcs) { |
| $time[$tnum] = 0; |
| } else { |
| $time[$tnum] = -1; |
| } |
| $count[$tnum] = -1; |
| $sideline_pre[$tnum] = -1; |
| $sideline_during[$tnum] = -1; |
| $sideline_post[$tnum] = -1; |
| # trace completion stats |
| $bbs[$tnum] = 0; |
| $bbs_noelide[$tnum] = 0; |
| $ind_branches[$tnum] = 0; |
| $bb_instrs[$tnum] = 0; |
| $bb_bytes[$tnum] = 0; |
| $end_in_cbr[$tnum] = 0; |
| $completion_all[$tnum] = 0; |
| $completion_half[$tnum] = 0; |
| $last_instr = "<not init>"; |
| } |
| if (!$in_original && $l =~ /^ORIGINAL CODE/) { |
| $in_original = 1; |
| } |
| if ($in_original) { |
| if ($l =~ /^END ORIGINAL CODE/) { |
| $in_original = 0; |
| if ($pcs && $pcs_by_orig_bb) { |
| # now count over again via exit jmps from trace |
| $cur_bbnum = 0; |
| } |
| if ($stats || ($pcs && $pcs_by_orig_bb)) { |
| # does trace end with conditional branch? |
| if ($last_instr =~ / 0x[0-9a-fA-F]+ 7/ || # jcc short |
| $last_instr =~ / 0x[0-9a-fA-F]+ 0[fF] 8/ || # jcc |
| $last_instr =~ / 0x[0-9a-fA-F]+ e[0123]/) { # loop*, jecxz |
| $end_in_cbr[$tnum] = 1; |
| } |
| #printf("Frag \# %2d: ends in %s => %d cbr\n", |
| # $id[$tnum], $last_instr, $end_in_cbr[$tnum]); |
| } |
| } |
| if ($pcs && $pcs_by_orig_bb) { |
| if ($l =~ /^basic block.*start_pc = (0x[0-9a-fA-F]+)/ || |
| # tracedump.exe output: arbitrarily different, should fix |
| $l =~ /^Basic block.*tag (0x[0-9a-fA-F]+)/) { |
| $bbtag[$cur_bbnum++] = $1; |
| } |
| # handle elision |
| # easy for tracedump_text: |
| # if ($l =~ /continuing in .* at (0x[0-9a-fA-F]+)/) { |
| # but rather than doing that and having to distinguish text |
| # and binary we calculate ourselves from call and jmp instrs: |
| if ($l =~ / 0x([0-9a-fA-F]+)\s+(e8|e9) (([0-9a-fA-F]+ ){4})\s*(call|jmp)/) { |
| my $instraddr = $1; |
| my $disp = $3; |
| $disp =~ /(\w\w)\s+(\w\w)\s+(\w\w)\s+(\w\w)/; |
| my $first = $4; |
| $disp = hex("$4$3$2$1"); |
| # perl doesn't consider leading 1 to be negative |
| if ($first =~ /^[89a-fA-F]/) { |
| $disp -= 0xffffffff; |
| } |
| my $target = hex($instraddr) + 5 # len of instr |
| + $disp; |
| $target = sprintf("0x%08x", $target); |
| # no exit for it so instead of making a new bb |
| # just list it with the original tag |
| $bbtag[$cur_bbnum-1] .= ",$target"; |
| } |
| if ($l =~ /^ 0x[0-9a-fA-F]+/) { |
| $last_instr = $l; |
| } |
| } |
| if ($stats) { |
| if ($l =~ /^basic block \# ([0-9]+)/ || |
| # tracedump.exe output: arbitrarily different, should fix |
| $l =~ /^Basic block ([0-9]+):/) { |
| $bbs[$tnum] = $1 + 1; |
| $bbs_noelide[$tnum]++; |
| } elsif ($l =~ /continuing/) { |
| $bbs_noelide[$tnum]++; |
| } elsif ($l =~ /^ 0x[0-9a-fA-F]+ (([0-9a-fA-F][0-9a-fA-F] )+)/) { |
| $bb_instrs[$tnum]++; |
| $bb_bytes[$tnum] += (length($1)/3); |
| $last_instr = $l; |
| } elsif ($l =~ /^ (( [0-9a-fA-F][0-9a-fA-F])+)/) { |
| $bb_bytes[$tnum] += (length($1)/3); |
| } |
| } |
| } |
| # even if brief, if pcs, we need to traverse |
| if (!$brief || $pcs) { |
| if ($prefix_stats && !$in_original && $l =~ /prefix entry/) { |
| # was prefix simply restore of eax? |
| if ($trace{$tnum,$line-2} =~ /indirect branch target entry/) { |
| $prefix_noeflags++; |
| $prefix_noeflags_count += $trace_total_exec; |
| } else { |
| $prefix_eflags++; |
| $prefix_eflags_count += $trace_total_exec; |
| } |
| } |
| if (!$in_original && ($pcs || $stats) && $dcontext_ecx == 0 && |
| $l =~ /normal entry/) { |
| # get dcontext addr by examining prefix |
| # final instruction is always a restore to ecx |
| $trace{$tnum,$line-1} =~ /mov\s+(0x[0-9a-fA-F]*) -> \%ecx/; |
| $dcontext_ecx = hex($1); # 4 = ecx_offs - ebx_offs |
| } |
| if (!$in_original && ($pcs || $stats) && $l =~ /^ (0x[0-9a-fA-F]+)/) { |
| $addr = $1; # keep in string form |
| if ($linux_pcs) { |
| $samples = $pc_samples{$tag[$tnum], $addr}; |
| } else { |
| $addrnum = $addr; |
| $addrnum =~ s/0x//; |
| $addrnum = hex($addrnum); |
| # Round it down to bucket size |
| $addrnum = $addrnum & (~($win32_pcs_step - 1)); |
| # Now back to hex |
| $addrnum = sprintf("0x%08x", $addrnum); |
| # FIMXE: no tag so will dup in every trace that occupied that spot! |
| $samples = $pc_samples{$addrnum}; |
| $pc_sample_taken{$addrnum}++; |
| if ($pc_sample_taken{$addrnum} > 1) { |
| $overcount_instrs++; |
| $overcount_samples += $samples; |
| # FIXME: rather than attributing samples to EVERY instr |
| # that rounds into this bucket (and thus have summary stats |
| # massively over-count), we instead assign samples to the |
| # first instr in the bucket only, potentially missing |
| # samples that actually happened on DR-overhead instrs. |
| $samples = 0; |
| } |
| $time[$tnum] += $samples; |
| if ($pcs && $pcs_by_orig_bb) { |
| if ($stubs_start) { |
| $stubsamples[$cur_stubnum] += $samples; |
| } else { |
| $bbsamples[$cur_bbnum] += $samples; |
| } |
| } |
| } |
| if ($l =~ / j[a-z]+\s*\$0x/ && !$stubs_start && !$in_cmp) { |
| # direct branch |
| $branch_samples += $samples; |
| } |
| # look for ind br overhead by watching for "save ecx" |
| if (is_save_ecx($l)) { |
| $in_cmp = 1; |
| } |
| if ($in_cmp) { |
| # assume ind br is a return if "save ecx" followed by a pop |
| if ($in_cmp == 2) { |
| if ($stats) { |
| $ind_branches[$tnum]++; |
| } |
| if ($pcs && $pcs_by_orig_bb) { |
| $bbib[$cur_bbnum] = 1; |
| } |
| if ($l =~ /pop /) { |
| $ret_samples += $samples; |
| } else { |
| $indjc_samples += $samples; |
| } |
| } |
| $in_cmp++; |
| # we count from save ecx through either restore ecx |
| # (inlined) or jmp to exit stub, we do include jmp |
| $ind_samples += $samples; |
| if ($l =~ /jmp.*exit stub/) { |
| $in_cmp = 0; |
| } |
| if (is_restore_ecx($l)) { |
| $in_cmp = 0; |
| } |
| } |
| if ($stubs_start) { |
| $stub_samples += $samples; |
| } |
| if (!$brief) { |
| $trace{$tnum,$line++} = sprintf("%3d%s", $samples, $l); |
| } |
| if ($pcs && $pcs_by_orig_bb) { |
| if (!$stubs_start && is_exit_cti($l)) { |
| $cur_bbnum++; |
| } |
| # $stubs_start case is handled below where we look for "exit stub |
| # n", as looking at jmps is not enough for -prof_counts |
| } |
| } else { |
| if (!$brief) { |
| $trace{$tnum,$line++} = $l; |
| } |
| } |
| if (!$in_original && $pcs && $pcs_by_orig_bb && $stubs_start && |
| $l =~ /-- exit stub /) { |
| # $stubs_start is set below, which is what we want: don't inc until #1 |
| $cur_stubnum++; |
| } |
| } |
| if ($in_original) { |
| # no more processing |
| next; |
| } |
| if ($in_stub_summary) { |
| if ($l =~ /^\s*\#/) { |
| if ($l =~ /\#([0-9]+): target = 0x([0-9a-fA-F]+) \(F\#([0-9-]+)\), count = ([0-9]+)(.*)/) { |
| $stub_target{$tnum,$num_stubs[$tnum]} = $3; |
| $cnt = $4; |
| } elsif ($l =~ /\#([0-9]+): target = 0x([0-9a-fA-F]+).*count = ([0-9]+)(.*)/) { |
| $stub_target{$tnum,$num_stubs[$tnum]} = 0; |
| $cnt = $3; |
| } |
| # count total direct & indirect |
| if ($prefix_stats) { |
| $trace_total_exec += $cnt; |
| } |
| if ($2 eq "0") { |
| $indirect_total += $cnt; |
| } else { |
| $direct_total += $cnt; |
| } |
| $linked = $5; |
| if ($linked ne "") { |
| if (!($linked =~ /linked/)) { |
| die "Weird exit stub string: $linked\n"; |
| } |
| $stub_linked{$tnum,$num_stubs[$tnum]} = 1; |
| } else { |
| $stub_linked{$tnum,$num_stubs[$tnum]} = 0; |
| } |
| # old-style (before %U): 0 prior to real number |
| if ($cnt =~ /^0([0-9]+)/) { |
| $cnt = $1; |
| } |
| if ($cnt > hex("0xffffffff")) { |
| $stub_64bit = 1; |
| } |
| |
| if ($cnt > $max_stub) { |
| $max_stub = $cnt; |
| } |
| $stub_count{$tnum,$num_stubs[$tnum]} = $cnt; |
| # set to -1 so can tell if gets set later |
| $stub_eflags{$tnum,$num_stubs[$tnum]} = -1; |
| $num_stubs[$tnum]++; |
| } else { |
| $in_stub_summary = 0; |
| } |
| } |
| if ($have_stubs) { |
| # find out if direct linked exit stub saves eflags or not |
| if ($exitstub_boundary > -1) { |
| if ($stub_linked{$tnum,$exitstub_boundary} && |
| $stub_target{$tnum,$exitstub_boundary} > -1) { |
| # this is first instr of stub |
| if ($l =~ /mov \%eax -> /) { |
| # saves eflags |
| $stub_eflags{$tnum,$exitstub_boundary} = 1; |
| } else { |
| # does not save eflags |
| $stub_eflags{$tnum,$exitstub_boundary} = 0; |
| } |
| } |
| $exitstub_boundary = -1; |
| } elsif ($l =~ /-- exit stub ([0-9]+)/) { |
| $exitstub_boundary = $1; |
| } |
| } |
| if ($linked_to) { |
| if ($l =~ /^\s/) { |
| $l =~ /^\sFragment \# ([0-9]+)/; |
| if ($1 eq $id[$tnum]) { |
| $trace{$tnum,$line++} = "\t ==> Self-loop"; |
| } |
| } else { |
| $linked_to = 0; |
| } |
| } |
| if ($body_start && $l =~ /^ (0x[0-9a-fA-F]+)/) { |
| $start_addr = hex($1); |
| $body_start = 0; |
| } |
| if ($l =~ /^Tag = (0x[0-9a-fA-F]+)/) { |
| $tag[$tnum] = $1; |
| } elsif ($l =~ /normal entry/) { |
| $body_start = 1; |
| } elsif ($l =~ /Flushed/) { |
| $flushed++; |
| } elsif ($l =~ /^Size = ([0-9]+)/) { |
| $size[$tnum] = $1; |
| } elsif ($l =~ /pre-opt count = ([0-9]+)/) { |
| $sideline_pre[$tnum] = $1; |
| } elsif ($l =~ /post-opt count = ([0-9]+)/) { |
| $sideline_during[$tnum] = $1; |
| } elsif ($l =~ /new trace count = ([0-9]+)/) { |
| $sideline_post[$tnum] = $1; |
| } elsif ($l =~ /^\stime/) { |
| $have_times = 1; |
| $l =~ /[^0-9]([0-9.]+)/; |
| $time[$tnum] = $1; |
| if ($count[$tnum] == 0) { |
| $per = 0.; |
| } else { |
| $per = 1000.0 * ($time[$tnum] / $count[$tnum]); |
| } |
| $trace{$tnum,$line++} = "\t => us per execution: $per"; |
| } elsif ($l =~ /^\scount/) { |
| $l =~ /[^0-9]([0-9]+)/; |
| $count[$tnum] = $1; |
| } elsif ($l =~/^Exit stubs/) { |
| $have_stubs = 1; |
| $in_stub_summary = 1; |
| $num_stubs[$tnum] = 0; |
| } elsif ($l =~/^Linked to/) { |
| $linked_to = 1; |
| } elsif ($l =~ /^ (0x[0-9a-fA-F]+)\s.+</ || |
| # workaround for bug where <exit stub N> is not printed: |
| (!$stubs_start && $l =~ /^ (0x[0-9a-fA-F]+)\s.+j.+/)) { |
| # measure # of instr bytes from top until this exit from trace |
| # we assume all entires were to normal entry, not ibt entry |
| # we also do not count the # bytes of the exit instr itself |
| # neither of these assumptions is that egregious -- we're only |
| # using this estimate as a relative weight among different exits! |
| $addr = hex($1); |
| $stub_size{$tnum,$stub_num} = ($addr - $start_addr); |
| $stub_num++; |
| } elsif ($l =~ /-- exit stub 0/) { |
| $stubs_start = 1; |
| } elsif ($l =~ /^END TRACE/) { |
| # end of trace |
| $trace{$tnum,$line++} = "Total samples: $time[$tnum]"; |
| if ($pcs && $pcs_by_orig_bb) { |
| $cur_stubnum++; |
| $trace{$tnum,$line++} = "BB samples breakdown:"; |
| die "Error: trace $tnum tag $tag[$tnum]: $cur_stubnum stubs != $cur_bbnum bbs in trace $tnum" |
| if ($cur_stubnum != $cur_bbnum); |
| if ($end_in_cbr[$tnum]) { |
| # last jmp is fall-through, count as part of penultimate |
| $cur_bbnum--; |
| $bbsamples[$cur_bbnum-1] += $bbsamples[$cur_bbnum]; |
| $stubsamples[$cur_bbnum-1] += $stubsamples[$cur_bbnum]; |
| } |
| for ($i=0; $i<$cur_bbnum; $i++) { |
| $trace{$tnum,$line++} = |
| sprintf("\tbb# %2d %-20s = %4d samples, %4d in %8s exit => %4d total", |
| $i, $bbtag[$i], $bbsamples[$i], $stubsamples[$i], |
| $bbib[$i] ? "indirect" : "direct", |
| $bbsamples[$i] + $stubsamples[$i]); |
| $bbtag[$i] = ""; |
| $bbib[$i] = 0; |
| $bbsamples[$i] = 0; |
| $stubsamples[$i] = 0; |
| } |
| } |
| $sortme[$tnum] = $tnum; |
| $length[$tnum++] = $line; |
| $in_trace = 0; |
| $line = 0; |
| $stub_num = 0; |
| $stubs_start = 0; |
| } |
| } else { # !in_trace |
| if ($l =~ /^Traces below dump threshold of (\d+): (\d+)/) { |
| $unseen_threshold = $1; |
| $unseen_num = $2; |
| } elsif ($l =~ /^Total count below dump threshold: (\d+)/) { |
| $unseen_count = $1; |
| } |
| } |
| } |
| close(IN); |
| |
| if ($prefix_stats) { |
| printf("Prefixes that restore eflags vs. those that do not:\n"); |
| printf("Static restore: %12d, do not: %12d, => %5.2f%% restore\n", |
| $prefix_eflags, $prefix_noeflags, |
| $prefix_eflags*100/($prefix_eflags+$prefix_noeflags)); |
| printf("Dynamic restore: %12.0f, do not: %12.0f, => %5.2f%% restore\n", |
| $prefix_eflags_count, $prefix_noeflags_count, |
| $prefix_eflags_count*100/($prefix_eflags_count+$prefix_noeflags_count)); |
| exit 0; |
| } |
| |
| print "Total traces dumped: $tnum\n"; |
| print "\tTraces prematurely kicked out of cache: $flushed\n"; |
| print "Total traces not dumped: $unseen_num\n"; |
| $total_traces = $tnum + $unseen_num; |
| print "=> Total traces: $total_traces\n\n"; |
| |
| if ($pcs) { |
| print "Total PC samples: $total_samples\n"; |
| if (!$linux_pcs) { |
| print "\tOverlapping instrs: $overcount_instrs\n"; |
| print "\tOverlapping samples: $overcount_samples\n"; |
| } |
| print "PC samples inside exit stubs: $stub_samples\n"; |
| print "PC samples in indirect branch overhead: $ind_samples\n"; |
| $tot = $stub_samples + $ind_samples; |
| $tot_frac = $tot / $total_samples * 100.; |
| printf("Total of previous 2 lines = %d = %3.1f%% of total samples\n", |
| $tot, $tot_frac); |
| print "PC samples on return instructions: $ret_samples\n"; |
| print "PC samples on other indirect branches: $indjc_samples\n"; |
| $tot = $ret_samples + $indjc_samples; |
| if ($tot == 0) { |
| $tot_frac = 0; |
| } else { |
| $tot_frac = $ret_samples / $tot * 100.; |
| } |
| printf("=> %% indirect branches that are returns: %3.1f%%\n", $tot_frac); |
| print "PC samples on direct branches: $branch_samples\n"; |
| if ($branch_samples + $tot == 0) { |
| $tot_frac = 0; |
| } else { |
| $tot_frac = $tot / ($branch_samples + $tot) * 100.; |
| } |
| printf("=> %% branches that are indirect: %3.1f%%\n", $tot_frac); |
| print "\n"; |
| } |
| |
| # if have both stubs and times, sort by stubs |
| if ($have_stubs) { |
| if (!$groups) { |
| print "Max stub count is $max_stub\n"; |
| if ($stub_64bit) { |
| print "Needed 64-bit stub counters!\n"; |
| } |
| $grand_total = $direct_total + $indirect_total; |
| print "Indirect=$indirect_total, direct=$direct_total, total=$grand_total\n"; |
| $direct_frac = 100. * ($direct_total / $grand_total); |
| $indirect_frac = 100. * ($indirect_total / $grand_total); |
| printf("\t=> %4.1f%% direct, %4.1f%% indirect\n", |
| $direct_frac, $indirect_frac); |
| |
| if ($unseen_count > 0) { |
| $grand_grand_total = $grand_total + $unseen_count; |
| print "\nUnseen traces below $unseen_threshold threshold: $unseen_num traces\n"; |
| $unseen_cnt_frac = 100. * $unseen_count / $grand_grand_total; |
| printf("\ttotal count=$unseen_count (%4.1f%%) => grand total=$grand_grand_total\n\n", |
| $unseen_cnt_frac); |
| } |
| |
| # eflags saving stats |
| $eflags_save_count = 0; |
| $eflags_save_stubs = 0; |
| $eflags_nosave_count = 0; |
| $eflags_nosave_stubs = 0; |
| for ($t=0; $t<$tnum; $t++) { |
| for ($e=0; $e<$num_stubs[$t]; $e++) { |
| # only look at linked direct branches |
| if ($stub_linked{$t,$e} && $stub_target{$t,$e} > -1) { |
| if ($stub_eflags{$t,$e} == -1) { |
| print "Error: eflags not set for trace $id[$t] exit $e\n"; |
| } elsif ($stub_eflags{$t,$e}) { |
| $eflags_save_stubs++; |
| $eflags_save_count += $stub_count{$t,$e}; |
| } else { |
| $eflags_nosave_stubs++; |
| $eflags_nosave_count += $stub_count{$t,$e}; |
| } |
| } |
| } |
| } |
| if ($eflags_save_stubs + $eflags_nosave_stubs > 0) { |
| $eflags_stub_frac = 100. * $eflags_save_stubs / |
| ($eflags_save_stubs + $eflags_nosave_stubs); |
| print "Eflags stubs: save=$eflags_save_stubs"; |
| printf(" (%4.1f%%), nosave=$eflags_nosave_stubs\n", |
| $eflags_stub_frac); |
| $eflags_count_frac = 100. * $eflags_save_count / |
| ($eflags_save_count + $eflags_nosave_count); |
| print "Eflags counts: save=$eflags_save_count"; |
| printf(" (%4.1f%%), nosave=$eflags_nosave_count\n", |
| $eflags_count_frac); |
| } else { |
| print "Error obtaining eflags stubs stats\n"; |
| } |
| |
| print "\n"; |
| } |
| print "----------------------------------------------\n\n"; |
| # estimate times |
| $total_estimate = 0; |
| if ($unseen_count > 0) { |
| # FIXME: can we do better than this average number of instrs |
| # in a trace, *3/4 since half exit at halfway point? |
| # ave # instrs across spec & win32 is 29 |
| $unseen_size = 29 * 0.75; |
| $unseen_estimate = $unseen_count / 1000000. * $unseen_size; |
| $total_estimate += $unseen_estimate; |
| } |
| for ($t=0; $t<$tnum; $t++) { |
| # estimate time spent inside |
| $estimate[$t] = 0.; |
| $entry_count[$t] = 0.; |
| for ($e=0; $e<$num_stubs[$t]; $e++) { |
| # scale down to avoid overflow and for easier visual comparisons |
| $cnt = $stub_count{$t,$e} / 1000000.; |
| if (! defined($stub_size{$t,$e})) { |
| die "Error: size of trace $id[$t]'s stub $e not defined\n"; |
| } |
| $entry_count[$t] += $cnt; |
| $estimate[$t] += $cnt * $stub_size{$t,$e}; |
| } |
| $total_estimate += $estimate[$t]; |
| } |
| for ($t=0; $t<$tnum; $t++) { |
| $estimate_frac[$t] = 100. * $estimate[$t] / $total_estimate; |
| } |
| if ($unseen_count > 0) { |
| $unseen_frac = 100. * $unseen_estimate / $total_estimate; |
| print "Unseen traces, time based only on general averages:\n"; |
| printf("%6d below %8d threshold traces: count %7.2f M, time %9.2f (%4.1f%%)\n\n", |
| $unseen_num, $unseen_threshold, |
| $unseen_count/1000000., $unseen_estimate, $unseen_frac); |
| print "Dumped traces:\n"; |
| } |
| @sortme = sort({$estimate[$a] <=> $estimate[$b]} @sortme); |
| } |
| elsif ($have_times) { |
| @sortme = sort({$time[$a] <=> $time[$b]} @sortme); |
| |
| # get total time |
| $total_time = 0; |
| for ($t=0; $t<$tnum; $t++) { |
| $total_time += $time[$t]; |
| } |
| } |
| elsif ($pcs) { |
| @sortme = sort({$time[$a] <=> $time[$b]} @sortme); |
| } |
| else { |
| # leave in original order == reverse order |
| @sortme = sort({$b <=> $a} @sortme); |
| } |
| |
| # now output in sorted order |
| if ($stats) { |
| $max_bbs = 0; |
| $ave_bbs = 0; |
| $max_bbs_noelide = 0; |
| $ave_bbs_noelide = 0; |
| $max_ibs = 0; |
| $ave_ibs = 0; |
| $max_ins = 0; |
| $ave_ins = 0; |
| $max_bytes = 0; |
| $ave_bytes = 0; |
| $ave_comp_all = 0; |
| $ave_comp_half = 0; |
| $max10_bbs = 0; |
| $ave10_bbs = 0; |
| $max10_bbs_noelide = 0; |
| $ave10_bbs_noelide = 0; |
| $max10_ibs = 0; |
| $ave10_ibs = 0; |
| $max10_ins = 0; |
| $ave10_ins = 0; |
| $max10_bytes = 0; |
| $ave10_bytes = 0; |
| $ave10_comp_all = 0; |
| $ave10_comp_half = 0; |
| $coverage5 = 0; |
| $coverage10 = 0; |
| $coverage50 = 0; |
| } |
| for ($t=$tnum-1; $t>=0; $t--) { |
| $n = $sortme[$t]; |
| |
| if (($brief && !$pcs_by_orig_bb) || $stats) { |
| if (!$groups && !$stats) { |
| # if have both stubs and times, just use stubs |
| if ($have_stubs) { |
| $s = sprintf(", count %7.2f M, time %9.2f (%4.1f%%)", |
| $entry_count[$n], $estimate[$n], $estimate_frac[$n]); |
| $frac = $estimate_frac[$n]; |
| } elsif ($have_times) { |
| $frac = 100 * $time[$n] / $total_time; |
| my $cnt = $count[$n] / 1000000; |
| $s = sprintf(", count %7.2f M, time %9.2f ms (%4.1f%%)", |
| $cnt, $time[$n], $frac); |
| } elsif ($pcs) { |
| $frac = 100 * $time[$n] / $total_samples; |
| $s = sprintf(", samples %8d (%4.1f%%)", |
| $time[$n], $frac); |
| } else { |
| $s = ""; |
| } |
| if ($sideline) { |
| if ($sideline_pre[$n] == -1) { |
| printf("Frag \# %5d ($tag[$n]) (%4.1f%%) = not sideline-optimized\n", |
| $id[$n], $frac); |
| } else { |
| $post_frac = 100. * $sideline_post[$n] / |
| ($sideline_pre[$n] + $sideline_during[$n] + |
| $sideline_post[$n]); |
| printf("Frag \# %5d ($tag[$n]) (%4.1f%%) = pre %6d, dur %6d, post %8d (%5.1f%%)\n", |
| $id[$n], $frac, $sideline_pre[$n], $sideline_during[$n], |
| $sideline_post[$n], $post_frac); |
| } |
| } else { |
| printf("Frag \# %5d ($tag[$n]) = size %5d$s\n", |
| $id[$n], $size[$n]); |
| } |
| } |
| |
| if (($links || $stats) && $have_stubs) { |
| $link_total = 0; |
| for ($e=0; $e<$num_stubs[$n]; $e++) { |
| $link_total += $stub_count{$n,$e}; |
| } |
| if ($link_total == 0) { |
| for ($e=0; $e<$num_stubs[$n]; $e++) { |
| $stub_percent{$n,$e} = 0; |
| } |
| next; |
| } |
| if ($stats) { |
| # trying to get % of time completed trace (went all the way to |
| # end bb) and % of time got halfway through trace |
| if ($end_in_cbr[$n]) { |
| # for purposes of completion, final 2 exits are identical |
| # (just different directions of final cbr) |
| $end_stub = $num_stubs[$n] - 2; # index of penultimate |
| } else { |
| $end_stub = $num_stubs[$n] - 1; # index of final |
| } |
| } |
| for ($e=0; $e<$num_stubs[$n]; $e++) { |
| $stub_frac = 100. * ($stub_count{$n,$e} / $link_total); |
| if (!$groups && !$stats) { |
| if ($stub_frac > 1.0) { |
| printf("\tStub \#%2d: %4.1f%% => %s\n", |
| $e, $stub_frac, $stub_target{$n,$e}); |
| } |
| } |
| # store for use in finding groups |
| $stub_percent{$n,$e} = $stub_frac; |
| if ($stats && $e < $end_stub/2) { |
| # add up through halfway point |
| $completion_half[$n] += $stub_frac; |
| } |
| } |
| # store for use in finding groups |
| $stub_total[$n] = $link_total; |
| if ($stats) { |
| $completion_all[$n] = $stub_percent{$n,$num_stubs[$n]-1}; |
| $completion_half[$n] = 100. - $completion_half[$n]; |
| } |
| } |
| next if (!$stats); |
| } |
| |
| if ($stats) { |
| printf("Frag \# %5d = %2d/%2d bbs, %3d ibs, %5d ins, %5d bytes; %5.1f%% all, %5.1f%% half\n", |
| |
| $id[$n], $bbs[$n], $bbs_noelide[$n], $ind_branches[$n], $bb_instrs[$n], $bb_bytes[$n], |
| $completion_all[$n], $completion_half[$n]); |
| if ($stats) { |
| $max_bbs = $bbs[$n] if ($bbs[$n] > $max_bbs); |
| $ave_bbs += $bbs[$n]; |
| $max_bbs_noelide = $bbs_noelide[$n] if ($bbs_noelide[$n] > $max_bbs_noelide); |
| $ave_bbs_noelide += $bbs_noelide[$n]; |
| $max_ibs = $ind_branches[$n] if ($ind_branches[$n] > $max_ibs); |
| $ave_ibs += $ind_branches[$n]; |
| $max_ins = $bb_instrs[$n] if ($bb_instrs[$n] > $max_ins); |
| $ave_ins += $bb_instrs[$n]; |
| $max_bytes = $bb_bytes[$n] if ($bb_bytes[$n] > $max_bytes); |
| $ave_bytes += $bb_bytes[$n]; |
| $ave_comp_all += $completion_all[$n]; |
| $ave_comp_half += $completion_half[$n]; |
| if ($t >= $tnum-10) { |
| # stats for top 10 traces |
| $max10_bbs = $bbs[$n] if ($bbs[$n] > $max10_bbs); |
| $ave10_bbs += $bbs[$n]; |
| $max10_bbs_noelide = $bbs_noelide[$n] if ($bbs_noelide[$n] > $max10_bbs_noelide); |
| $ave10_bbs_noelide += $bbs_noelide[$n]; |
| $max10_ibs = $ind_branches[$n] if ($ind_branches[$n] > $max10_ibs); |
| $ave10_ibs += $ind_branches[$n]; |
| $max10_ins = $bb_instrs[$n] if ($bb_instrs[$n] > $max10_ins); |
| $ave10_ins += $bb_instrs[$n]; |
| $max10_bytes = $bb_bytes[$n] if ($bb_bytes[$n] > $max10_bytes); |
| $ave10_bytes += $bb_bytes[$n]; |
| $ave10_comp_all += $completion_all[$n]; |
| $ave10_comp_half += $completion_half[$n]; |
| $coverage10 += $estimate_frac[$n]; |
| } |
| if ($t >= $tnum-5) { |
| # stats for top 5 traces |
| $coverage5 += $estimate_frac[$n]; |
| } |
| if ($t >= $tnum-50) { |
| # stats for top 50 traces |
| $coverage50 += $estimate_frac[$n]; |
| } |
| } |
| next; |
| } |
| |
| print "===========================================================================\n\n"; |
| $in_origins = 0; |
| $last_srcline = ""; |
| for ($o=0; $o<$length[$n]; $o++) { |
| $l = $trace{$n,$o}; |
| |
| if ($in_origins) { |
| if ($src && $l =~ /^\s*(0x[0-9A-Fa-f]+)/) { |
| $addr = $1; |
| $instr = substr($l, 36); |
| $srcline = lookup_line($exe, $addr); |
| if ($srcline =~ /\?\?\(\) @ \?\?:0/) { |
| $i = 0; |
| while ($i < $shared_num) { |
| if (hex($addr) >= $shared_start[$i] && |
| ($i == $shared_num-1 || |
| hex($addr) < $shared_start[$i+1])) { |
| $srcline = "[somewhere in $shared_name[$i]]"; |
| } |
| $i++; |
| } |
| if ($srcline eq $last_srcline) { |
| $srcline = ""; |
| } else { |
| $last_srcline = $srcline; |
| } |
| } |
| ## get actual code |
| if ($srcline =~ /@ ([\w.]+):(\d+)/) { |
| $file = $1; |
| $lineno = $2; |
| if ($srcline eq $last_srcline) { |
| $srcline = ""; |
| } else { |
| $last_srcline = $srcline; |
| $actual = `sed -n '$lineno p' $file`; |
| chomp $actual; |
| ## strip leading WS: ($actual) = ($actual =~ /\s*(.*)$/); |
| $srcline = sprintf("[%s]%s", $srcline, $actual); |
| } |
| } |
| ## print sprintf("%-35s %s\n", $instr, $srcline); |
| if ($srcline ne "") { |
| print "$srcline\n"; |
| } |
| } |
| if ($l =~ /^END ORIGINAL CODE/) { |
| $in_origins = 0; |
| } |
| } |
| print "$l\n"; |
| if ($have_times && $o==1) { |
| $frac = 100 * $time[$n] / $total_time; |
| printf("Time Recorded: %9.2f ms (%4.1f%%)\n", $time[$n], $frac); |
| } |
| if ($have_stubs && $o==1) { |
| printf("Time Estimate: %9.2f (%4.1f%%)\n", |
| $estimate[$n], $estimate_frac[$n]); |
| for ($e=0; $e<$num_stubs[$n]; $e++) { |
| print "\tStub \#$e: size $stub_size{$n,$e}\n"; |
| } |
| } |
| if ($l =~ /^ORIGINAL CODE/ && $have_exe) { |
| $in_origins = 1; |
| } |
| } |
| print "\n"; |
| } |
| |
| if ($stats) { |
| printf("Max all = %3d bbs, %3d ibs, %5d ins, %5d bytes\n", |
| $max_bbs, $max_ibs, $max_ins, $max_bytes); |
| printf("Max top 10 = %3d bbs, %3d ibs, %5d ins, %5d bytes\n", |
| $max10_bbs, $max10_ibs, $max10_ins, $max10_bytes); |
| printf("Ave all = %4.1f bbs, %4.1f ibs, %5d ins, %5d bytes; %5.1f%% all, %5.1f%% half\n", |
| $ave_bbs/$tnum, $ave_ibs/$tnum, $ave_ins/$tnum, $ave_bytes/$tnum, |
| $ave_comp_all/$tnum, $ave_comp_half/$tnum); |
| printf("Ave top 10 = %4.1f bbs, %4.1f ibs, %5d ins, %5d bytes; %5.1f%% all, %5.1f%% half\n", |
| $ave10_bbs/10, $ave10_ibs/10, $ave10_ins/10, $ave10_bytes/10, |
| $ave10_comp_all/10, $ave10_comp_half/10); |
| printf("No elide bbs: all: %3d max, %4.1f ave; top10: %3d max, %4.1f ave\n", |
| $max_bbs_noelide, $ave_bbs_noelide/$tnum, $max10_bbs_noelide, $ave10_bbs_noelide/10); |
| printf("Coverage: top 5 = %5.1f%%, top 10 = %5.1f%%, top 50 = %5.1f%%\n", |
| $coverage5, $coverage10, $coverage50); |
| exit; |
| } |
| |
| # Find groups |
| # My method: go through traces in sorted order |
| # For each trace: recursively walk exits w/ exit freq >= 10% |
| # If hit an already-examined trace, stop |
| # Add est time % to "group %" unless trace is already part of another group |
| # (this way total group %'s will add to 100, and by going through in |
| # sorted order this shouldn't cause us to miss larger groups in |
| # favor of smaller) |
| if ($groups && $have_stubs) { |
| @todo = {}; |
| @tabs = {}; |
| @frac = {}; |
| for ($t=$0; $t<$tnum; $t++) { |
| $n = $sortme[$t]; |
| push(@todo, $n); |
| push(@tabs, 0); |
| push(@frac, -1); |
| $group_done[$t] = 0; |
| } |
| $gnum = 0; |
| $group_percent[0] = 0; |
| $group_out[0] = ""; |
| $in_group = 0; |
| while ($#todo > 0) { |
| $t = pop(@todo); |
| $indent = pop(@tabs); |
| $wt = pop(@frac); |
| if ($indent == 0) { |
| if ($in_group) { |
| $group_out[$gnum] .= sprintf("\tTotal for group = %4.1f%%\n\n", |
| $group_percent[$gnum]); |
| $sort_group[$gnum] = $gnum; |
| $gnum++; |
| $group_percent[$gnum] = 0; |
| for ($n=$0; $n<$tnum; $n++) { |
| $group_done[$n] = 0; |
| } |
| } |
| if ($done[$t]) { |
| $in_group = 0; |
| next; |
| } else { |
| $in_group = 1; |
| $group_top[$gnum] = $t; |
| } |
| } |
| # yes group_done is not needed, I'm leaving it in so I can go |
| # back to "double counting" that may find bigger/better groups |
| # (but I doubt it) |
| if ($in_group && $t != -1 && !$done[$t] && !$group_done[$t]) { |
| $group_percent[$gnum] += $estimate_frac[$t]; |
| $group_done[$t] = 1; |
| } |
| for ($i=0; $i<$indent; $i++) { |
| $group_out[$gnum] .= "\t"; |
| } |
| if ($t == -1) { |
| $name = sprintf("%-6s", "*"); |
| } else { |
| $name = sprintf("%-6d", $id[$t]); |
| } |
| $group_out[$gnum] .= "$name"; |
| if ($indent > 0) { |
| $group_out[$gnum] .= sprintf("(%4.1f%%)", $wt); |
| } |
| if ($t == -1) { |
| $group_out[$gnum] .= "\n"; |
| next; |
| } |
| if ($done[$t]) { |
| $out = sprintf(" [%9.2f (%4.1f%%)] +", |
| $estimate[$t], $estimate_frac[$t]); |
| if ($group_num[$t] != $gnum) { |
| if ($t == $group_top[$group_num[$t]]) { |
| $out .= " ==> top of group $group_num[$t]"; |
| } else { |
| $out .= " ==> middle of group $group_num[$t]"; |
| } |
| } else { |
| $out .= " ==> loop"; |
| } |
| } else { |
| $out = sprintf(" [%9.2f (%4.1f%%)]", |
| $estimate[$t], $estimate_frac[$t]); |
| if ($t > -1) { |
| # gather exits above the cutoff to be sorted |
| $num_sort_stub = 0; |
| undef @sort_stub; |
| for ($e=0; $e<$num_stubs[$t]; $e++) { |
| # cutoff is 10%! |
| if ($stub_percent{$t,$e} > 9.999) { |
| $sort_stub[$num_sort_stub++] = $e; |
| } |
| } |
| @sort_stub = sort( { $stub_percent{$t,$a} <=> $stub_percent{$t,$b} } |
| @sort_stub ); |
| # now push exits, in reverse sorted order so do highest first |
| for ($i=0; $i<$num_sort_stub; $i++) { |
| $e = $sort_stub[$i]; |
| if ($stub_target{$t,$e} == -1) { |
| $n = -1; |
| } else { |
| $n = $tnum_from_id{$stub_target{$t,$e}}; |
| } |
| push(@todo, $n); |
| push(@tabs, ($indent+1)); |
| push(@frac, $stub_percent{$t,$e}); |
| } |
| } |
| $done[$t] = 1; |
| $group_num[$t] = $gnum; |
| } |
| $group_out[$gnum] .= "$out\n"; |
| } |
| @sort_group = sort({$group_percent[$a] <=> $group_percent[$b]} @sort_group); |
| for ($g=$gnum-1; $g>=0; $g--) { |
| $gp = $sort_group[$g]; |
| print "Group $gp:\n$group_out[$gp]"; |
| } |
| } |
| |
| |
| sub compare_numbers() { |
| $a <=> $b; |
| # $a <=> $b is equivalent to: |
| # if ($a<$b){return -1;} elsif ($a>$b){return 1;} else{return 0;} |
| } |
| |
| sub lookup_line($, $) { |
| my ($exe, $addr) = @_; |
| my ($i, $func, $loc); |
| open(FOO, "addr2line -e $exe -f $addr |") || |
| die "Error running addr2line\n"; |
| $i = 0; |
| while (<FOO>) { |
| chop; |
| if ($i == 0) { |
| $func = $_; |
| } else { |
| $loc = $_; |
| } |
| $i++; |
| } |
| close(FOO); |
| $loc =~ s/\/.+\///; |
| $res = "$func() @ $loc"; |
| return $res; |
| } |
| |
| sub is_save_ecx($) { |
| my ($l) = @_; |
| # save ecx can either be a move to dcontext as absolute |
| # address, or a move to a tls slot in fs: |
| return (($l =~ /mov\s+\%ecx -> (0x[0-9a-fA-F]*)/ && |
| hex($1) == $dcontext_ecx) || |
| ($l =~ /mov\s+\%ecx -> \%fs:0x[0-9a-fA-F]*/)); |
| } |
| |
| sub is_restore_ecx($) { |
| my ($l) = @_; |
| # save ecx can either be a move to dcontext as absolute |
| # address, or a move to a tls slot in fs: |
| return (($l =~ /mov\s+(0x[0-9a-fA-F]*) -> \%ecx/ && |
| hex($1) == $dcontext_ecx) || |
| ($l =~ /mov\s+\%fs:0x[0-9a-fA-F]* -> \%ecx/)); |
| } |
| |
| sub is_exit_cti($) { |
| my ($l) = @_; |
| # 32-bit jmps only to exclude jmp part of jecxz mangle |
| return ($l =~ /(e9|(0f 8[0-9a-fA-F])) ([0-9a-fA-F]+ ){4}\s*j/); |
| } |
| |
| ### OBSOLETE CODE |
| ### This code ran objdump, read in resulting assembly code, and then |
| ### used it to print out disassembly of trace: |
| ### |
| ### if ($have_exe && $dis) { |
| ### # disassemble exe, read in assembly code |
| ### system("objdump -d $exe > $exe.dis"); |
| ### system("echo \"\" >> $exe.dis"); # blank line to get end of last func! |
| ### $i = 0; |
| ### $in_func = 0; |
| ### open(OBJ, "< $exe.dis") || die "Error: Couldn't open $exe.dis for input\n"; |
| ### while (<OBJ>) { |
| ### chop; |
| ### $l = $_; |
| ### if ($in_func) { |
| ### if ($l =~ /^\s*([0-9A-Fa-f]+)/) { |
| ### $addr{$faddr,$i} = hex($1); |
| ### $instr{$faddr,$i} = $l; |
| ### $num_instrs{$faddr}++; |
| ### $i++; |
| ### } else { |
| ### $last{$faddr} = $addr{$faddr,($i-1)}; |
| ### $in_func = 0; |
| ### $i = 0; |
| ### # print "Function $func{$faddr} = $first{$faddr}...$last{$faddr}\n"; |
| ### } |
| ### } |
| ### if ($l =~ /^([0-9A-Fa-f]+) <([^>]+)>/) { |
| ### # can have multiple func names (if static) so key off of |
| ### # function address, not func name! |
| ### $faddr = hex($1); |
| ### $func{$faddr} = $2; |
| ### $first{$faddr} = $faddr; |
| ### $in_func = 1; |
| ### } |
| ### } |
| ### close(OBJ); |
| ### system("rm -f $exe.dis"); |
| ### } |
| ### |
| ### $l =~ /^\s(0x[0-9A-Fa-f]+)\.\.\.(0x[0-9A-Fa-f]+)/; |
| ### $start = $1; |
| ### $end = $2; |
| ### if ($src && 0) { |
| ### $start_line = lookup_line($exe, $start); |
| ### $end_line = lookup_line($exe, $end); |
| ### print "\t == $start_line ... $end_line\n"; |
| ### } |
| ### if ($dis) { |
| ### $start =~ s/0x//; |
| ### $end =~ s/0x//; |
| ### $found = lookup_dis($start, $end); |
| ### if (!$found) { |
| ### print "Couldn't find $start...$end in $exe\n"; |
| ### } |
| ### print "\n"; |
| ### } |
| ### |
| ### sub lookup_dis($, $) { |
| ### my ($start, $end) = @_; |
| ### my ($f, $i); |
| ### $num_start = hex($start); |
| ### $num_end = hex($end); |
| ### # start & end guaranteed to be in same function |
| ### # just do linear search, performance not an issue |
| ### foreach $f (keys %first) { |
| ### if ($num_start >= $first{$f} && |
| ### $num_end <= $last{$f}) { |
| ### print "$start...$end is in $func{$f}:\n"; |
| ### $between = 0; |
| ### for ($i=0; $i<$num_instrs{$f}; $i++) { |
| ### if ($num_start == $addr{$f,$i}) { |
| ### $between = 1; |
| ### } |
| ### # end addr is past the last addr |
| ### if ($num_end == $addr{$f,$i}) { |
| ### $between = 0; |
| ### } |
| ### if ($between) { |
| ### print "$instr{$f,$i}\n"; |
| ### } |
| ### } |
| ### return 1; |
| ### } |
| ### } |
| ### return 0; |
| ### } |
| ### |
| ### END OBSOLETE CODE |
| |