#!/pkg/gnu/bin/perl -w
#
# Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
# ident	"@(#)coverage.pl	1.1	06/03/30 SMI"
#
# Purpose of this script is to take the coverage output from legion
# and map it to the dis-assembly of a binary (in this case, hypervisor)
# so that we can map out which code paths in Hypervisor have been
# executed.
#
# Input:
#    q.dis        - output from dis-assembly of q binary 
#    coverage.log - output from code coverage_dump tool in legion
#
# Processing:
#    - read the coverage data a build up an array of addresses and ref_cnts
#    - read the dis-assembly file and figure out the address offsets for
#      each instruction.
#    - foreach line in the dis-assembly, get the ref_cnt from the coverage
#      file and write out to new file.
# 
# Output:
#    coverage.txt - A text file containing the dis output for the binary
#		    under examination with a ref_cnt per line showing how
#		    many times each instn was executed.
#    coverage.html- A html file version of the coverage.txt file with
#		    un-executed code paths highlighted in red text.
#

use strict;
use Switch;

# input files
my $disfile = "q.dis";
my $covfile="coverage.log";

# output files
my $outfile_txt="coverage.txt";
my $outfile_html="coverage.html";

# to be parse from command line
my $hypervisor="";

# init all variables
my $cov_idx = 0;
my $cov_addr = 0;
my $cov_cnt = 0;
my $cov_instn = 0;
my $dis_sym = 0;
my $dis_instn = 0;
my $num_addrs_executed = 0;
my $font_head = 0;
my $font_tail = 0;
my %cov_cnt;
my %cov_instn;
my $hv_base_addr_str = "";
my $ignore0 = 0;
my $ignore1 = 0;
my $percentage = 0;
my $summary = 0;
my $ref_cnt = 0;
my $ref_str = '';
my $col_idx = 0;
my $col_ref_str = "";
my $col_sym = "";
my $col_instn = "";

sub main
{
	#
	# Figure out from args whether this is an N1, N2 or Rock Hypervisor
	# we are analysing. The output from dis needs to have it's addresses
	# aligned to the location in memory where Hypervisor runs from.
	#
	$hypervisor = "$ARGV[0]";
	switch ($hypervisor) {
		case "n1"		{ print "Analysing Niagara1 hypervisor\n" }
		case "n2"		{ print "Analysing Niagara2 hypervisor\n" }
		case "rock"		{ print "Analysing Rock hypervisor\n" }
		case ""			{ die "Usage: coverage.pl [processor]\nValid processors, [n1|n2|rock]\n" }
		else			{ die "Fatal: Unknown processor $hypervisor\n" }
	}

	#
	# Load dis and coverage files
	#
	open(dis_f, $disfile)
		|| die "Cannot open $disfile: $!\n";

	open(cov_f, $covfile)
		|| die "Cannot open $covfile: $!\n";

	#
	# open output files
	#
	open(outf_txt, ">$outfile_txt")
		|| die "Cannot open $$outfile_txt: $!\n";

	open(outf_html, ">$outfile_html")
		|| die "Cannot open $$outfile_html: $!\n";

	# Print header of html and text files
	print_header();

	#
	# build up an array of the coverage log file output so that
	# we can search it using the address as an index later
	#
	while (<cov_f>) {
		($cov_idx,$cov_addr,$cov_cnt,$cov_instn) = split(/:/);
		$cov_cnt{$cov_addr} = hex($cov_cnt);
		$cov_instn{$cov_addr} = $cov_instn;
	}
	
	# Accounting for coverage data
	my $num_addrs = 0;		# total number of PC samples
	my $num_addrs_excuted = 0;	# only incr when ref_cnt > 0

	my $hv_base_addr = get_hv_base_addr();
	printf ("Hypervisor base address is 0x%llx\n", $hv_base_addr);
	my $hv_addr_offset = 0x0;

	#
	# For each line of the dis output, check the coverage array
	# for a ref_cnt. Then output the data into a new file using
 	# the text output and the html output.
	#
	while (<dis_f>) {

		chomp;
		next if (!/\s*([^:]+):\s*(.*)$/);

		$dis_sym = $1;
		$dis_instn = $2;

		# Calculate the %pc address for this instruction
		my $dis_addr +=  ($hv_base_addr + $hv_addr_offset);
		$hv_addr_offset += 0x4;

		#
		# Map dis-assembly address to a legion address (offset in memory where HV
		# runs from) so that it can match the coverage data.
		#
		switch ($hypervisor) {
			case "n1"       { no warnings; $dis_addr = (($dis_addr - hex('fff0000000')) + hex('400000')); }
			case "n2"       { no warnings; $dis_addr = (($dis_addr - hex('fff0000000')) + hex('100000')); }
			case "rock"     { $dis_addr += (hex('100000')); }
			else		{ die "Fatal: need to figure out addr conversion for $hypervisor" }
		}

		#
		# convert the address to a string with "0x" and the hex number
		# so that we can use it as an index into the coverage data array
		#
		my $index_addr = sprintf("0x%llx", $dis_addr);	
		
		$ref_cnt = 0x0;
		$ref_cnt = $cov_cnt{$index_addr} if $cov_cnt{$index_addr};

		if ($ref_cnt == 0) {
		    # %pc not executed
		    $ref_str = 'NIL';
		} else {
		    # %pc executed
		    $ref_str = sprintf("%d", $ref_cnt);
		    $num_addrs_executed += 1;
		}

		#
		# Count number of %pc samples
		# Ignore illtrap %pcs for coverage purposes
		#
		if (($dis_instn =~ /illtrap/) && (!$ref_cnt)) {
			# Don't count or print illtrap %pcs that have no ref_cnt
		} else {
			# count number of %pcs whether they are executed or not
			$num_addrs += 1;
			print_row($index_addr, $ref_str, $dis_sym, $dis_instn);
		}
	}

	#
	# Calculate coverage accounting totals
	#
	$percentage = (($num_addrs_executed  / ($num_addrs)) * 100);
	$summary = sprintf ("Summary: Number of valid %%pc values in binary is %llu, coverage is [%.2f%%]\n",
                $num_addrs, $percentage);
	printf outf_txt ("%s", $summary);
	printf outf_html ("<br>%s<br>\n", $summary);
	printf ("%s", $summary);

	print_footer();

} #end main



sub print_header
{
	print outf_txt "Addr, Ref_Cnt, Label, Instruction (raw, decoded)\n";

	print outf_html << "EOF";
<html>
<head>
<title>Hypervisor Coverage data for $hypervisor</title>
</head>
<body>
<table WIDTH="100%" NOSAVE >
EOF
} # end print_header

sub print_footer
{
	print outf_html << "EOF";
</table>
</verbatim>
</body>
</html>
EOF

} # end print_footer


#
# print a row of the table using args passed in
#
sub print_row
{
	$col_idx 	= $_[0];
	$col_ref_str 	= $_[1];
	$col_sym 	= $_[2];
	$col_instn	= $_[3];

#	chomp($col_instn);

	$font_head="";
	$font_tail="";

	if ($col_ref_str eq 'NIL') {
		# Mark all NIL lines in red font
		$font_head= "<font color=red>";
		$font_tail= "</font>";
		print outf_txt "   ";

	} elsif ($col_ref_str eq 'N/A') {
		# Print this for each line whose ref_cnt is N/A 
		print outf_txt "   ";
	} else {
		# Print this for each line whose ref_cnt is non-zero
		print outf_txt "-->";
	}
	
	#
	# output to html file
	#
	print outf_html "<tr>\n";
	print outf_html "\t<td>$font_head $col_idx $font_tail</td>\n";
	print outf_html "\t<td>$font_head $col_ref_str $font_tail</td>\n";
	print outf_html "\t<td>$font_head $col_sym $font_tail</td>\n";
	print outf_html "\t<td>$font_head $col_instn $font_tail</td>\n";
	print outf_html "</tr>";
	print outf_html "\n";

	#
	# output to text file
	#
	print outf_txt "$col_idx, $col_ref_str, $col_sym, $col_instn\n";

} # end print_row

#
# Get the base address of the hypervisor using /usr/ccs/bin/nm
# to lookup the address of the _START_ symbol 
#
sub get_hv_base_addr
{
	my $returncode = `/usr/ccs/bin/nm -xp q | grep _START_`; 
	($hv_base_addr_str,$ignore0,$ignore1) = split(/ /, $returncode);
	no warnings;
	return  hex($hv_base_addr_str);

} # end get_hv_base_addr


main();
