#!/usr/bin/env perl
#
# Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.

# $Id: mksymtbl.pl,v 1.4 2009/10/05 22:39:09 jinmei Exp $

use strict;
use diagnostics;
$^W = 1;

my $rev = '$Id: mksymtbl.pl,v 1.4 2009/10/05 22:39:09 jinmei Exp $';
$rev =~ s/\$//g;
$rev =~ s/,v//g;
$rev =~ s/Id: //;

use Getopt::Std;
my %options;
getopts('i:o:', \%options);

my ($binname, $need_uscorefix, $outputfile, $nsyms, $ostype, $nm_prog);
my %symmap;

$binname = $ARGV[0];
$need_uscorefix = 0;
if ($options{'o'}) {
	$outputfile = $options{'o'};
} else {
	$outputfile = "symtbl.c";
}

# OS-depending configuration
$nm_prog = "nm";
$ostype = `uname -s`;
chop($ostype);
if ($ostype eq "SunOS" || $ostype eq "HP-UX") {
	$nm_prog = "/usr/ccs/bin/nm -x"
}

if ($options{'i'}) {
	open(SYMBOLS, $options{'i'}) || die "failed to open $options{'i'}";
} else {
	open(SYMBOLS, "$nm_prog $binname |") ||
	    die "failed to invoke utility to get symbols";
}
open(TBLFILE, ">$outputfile") || die "failed to open output file: $outputfile";

$nsyms = 0;
while (<SYMBOLS>) {
	my ($addr, $symbol) = (0, "");
	if ($ostype eq "SunOS") {
		if (/\[\d*\]\s*\|\s*0x([0-9a-f]*)\|\s*0x[0-9a-f]*\|FUNC\s*(.*)\|([^|]+)$/) {
			next if ($2 =~ /UNDEF/); # skip undefined symbols
			$addr = $1;
			$symbol = $3;
			chop($symbol);
		}
	} elsif ($ostype eq "HP-UX") {
		if (/(\S*)\s*\|0x([0-9a-f]*)\|([^|]*\|entry|extern\|code)/) {
			$addr = $2;
			$symbol = $1;
			# this filter catches a massive number of awkward
			# symbols such as "$START$".  we are not interested in
			# those and ignore them.
			next if ($symbol =~ /\$/);
		}
	} else {
		# *BSDs, Linux, etc.
		if (/([0-9a-f]*)\s[tT]\s(.*)/) {
			($addr, $symbol) = ($1, $2);
			# heuristics: some compilers add a "_" to all program
			# defined symbols.  Detect and fix it for a well known
			# symbol of "main".
			$need_uscorefix = 1 if ($symbol eq "_main");
		}
	}
	if ($symbol ne "") {
		# XXX: HP-UX's nm can produce a duplicate entry for the same
		# address.  Ignore duplicate entries except the first one.
		next if ($symmap{$addr});

		$symmap{$addr} = $symbol;
		$nsyms++;
	}
}

print TBLFILE "/*\n * Generated by $rev \n */\n";
print TBLFILE "#include <isc/backtrace.h>\n";
print TBLFILE "const int isc__backtrace_nsymbols = $nsyms;\n";
print TBLFILE "const isc_backtrace_symmap_t isc__backtrace_symtable[] = {\n";
foreach (sort {hex($a) <=> hex($b)} keys(%symmap)) {
	my ($addr, $symbol) = ($_, $symmap{$_});
	if ($need_uscorefix && $symbol =~ /^_(.*)/) {
		$symbol = $1;
	}
	print TBLFILE "\t{ (void *)0x$addr, \"$symbol\" },\n";
}
print TBLFILE "\t{ (void *)0x0, \"\" },\n";
print TBLFILE "};\n";

close(TBLFILE);
close(SYMBOLS);
