#!/usr/bin/perl
## sym-trace --                      Really, its -*-perl-*-.
##
## Copyright (c) 1997, 1998 University of Utah and the Flux Group.
## All rights reserved.
## 
## This file is part of the Flux OSKit.  The OSKit is free software, also known
## as "open source;" you can redistribute it and/or modify it under the terms
## of the GNU General Public License (GPL), version 2, as published by the Free
## Software Foundation (FSF).  To explore alternate licensing terms, contact
## the University of Utah at csl-dist@cs.utah.edu or +1-801-585-3271.
## 
## The OSKit is distributed in the hope that it will be useful, but WITHOUT ANY
## WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
## FOR A PARTICULAR PURPOSE.  See the GPL for more details.  You should have
## received a copy of the GPL along with the OSKit; see the file COPYING.  If
## not, write to the FSF, 59 Temple Place #330, Boston, MA 02111-1307, USA.
##
## 
## Turn a stack trace into a list of function names.
## 
## Expects a list in the format that backtrace() dumps it.
## By default looks at obj/kernel/fluke.  You may want
## something different and should use the "-o <obj>" option
## to specifiy a different object/executable.  Or, set the
## env var FLSYM_TARGET to the name of the object file.
##
## This script is tweaked and customized for the envronment here
## at the University of Utah.  It is not a supported part of
## of the OSKit release (Tech Support just won't know what you're
## talking about.)  You will have to customize it somewhat for
## your environment.
##

open(HOSTNAME_HACK, "hostname|") || die "Can't exec 'hostname'\n";
$hostname = <HOSTNAME_HACK>;
chop $hostname;
close(HOSTNAME_HACK);

#default object file
$objectFile = $ENV{'FLSYM_TARGET'};
$objectFile = "obj/kernel/fluke" if ( "$objectFile" eq "" ) ;

$nm = "nm";
if ($hostname eq "marker.cs.utah.edu") {
    $nm = "/usr/local/fluke/bin/i486-linux-nm";
} elsif ($hostname eq "fast.cs.utah.edu") {
    $nm = "/n/fast/usr/lsrc/mach/tools/i486-linux/bin/nm";
}

## Parse the command line arguments
$inputFormat = 1;  # default format

while (@ARGV) {
    ## -o <objectfile> to use a different object file
    if ($ARGV[0] eq "-o") {
	if ($#ARGV >= 1) {
	    $objectFile = $ARGV[1];
	    shift @ARGV;
	} else {
	    &usage;
	}
    ##
    } elsif ($ARGV[0] eq "-oneline") {
	$inputFormat = 1;
    } elsif ($ARGV[0] eq "-perline") {
	$inputFormat = 0;
    ## -h for usage
    } elsif ($ARGV[0] eq "-h") {
	&usage;
    ## Unknown args are fatal
    } else {
	print "Unknown option $ARGV[0]\n";
	&usage;
    }
    shift @ARGV;
}

-x $objectFile || die "$objectFile not found.\n";
## yes this is bad.. just stick the whole output into
## the OBLIST array.  Note that we make nm output in
## decimal, sorted by address
open (NMOUT, "$nm --numeric-sort --radix=d $objectFile|");
@OBLIST = <NMOUT>;

@BACKTRACE = <STDIN>;

@BACKTRACE || die "No stdin provided??\n";
$ct = 0;

print "Generating backtrace for object file $objectFile\n";

while (@BACKTRACE) {
    $traceLine = shift @BACKTRACE;
    chop $traceLine;
    
    next if ($traceLine eq "");

    if ($inputFormat == 0) {
	# If old, eip-per-line format	
	($bs, $eipStr) = split(/=/, $traceLine, 2);

	$eip = oct($eipStr);	# Cvt from 0xNNN notation to _decimal_
	&findFunction($eip);
    } else {
	# Else, the one line of eips format 
	@eips = split(' ', $traceLine);
	foreach $eip (@eips) {
	    $eipDec = oct("0x" . "$eip");# Cvt from hex NNN notation to _decimal_
	    &findFunction($eipDec);
	}
    }
}

#
# Given an EIP, find the name of the corresponding function
# (EIP should be in decimal)
sub findFunction {
    local($eip) = @_;
    local($more, $index, $bestAddr, $bestFunc, $addr, $type, $proc);

    $more = 1;
    $index = 0;
    
    $bestAddr = -1;
    $bestFunc = "NOT FOUND";
    
    while ($more && $eip && ($index <= $#OBLIST)) {
	$_ = $OBLIST[$index];
	chop;
	
	($addr, $type, $proc) = split(/ /, $_, 3);
	
	# List is sorted, so if we go beyond, then prev is answer.
	if ($addr > $eip) {
	    $more = 0;
	}
	else		
	{
	    $bestFunc = $proc;
	    $bestAddr = $addr;
	    $index   += 1;
	}
    }			
    
    # If we didn't find a useful address...
    if ($index > $#OBLIST) {
	$bestFunc = "<Not Found>";
	$bestAddr = 0;
    }

    if ($eip) {
	printf "0x%07x in (0x%07x) $bestFunc()\n", $eip, $bestAddr;
    }
}

sub usage
{
    print "sym-trace [-o <objectfile>] [-perline] [-oneline] [-h]\n";
    exit 0;
}
