blob: 9cfbba17c5e9504b5e2f7891ac5913c60a5365ad [file] [log] [blame]
#!/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