#!/usr/bin/env perl

# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# SPDX-License-Identifier: MPL-2.0
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0.  If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.

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

my $rev = 'Id';
$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 ($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, "");
	# *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 "") {
		next if ($symmap{$addr});

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

sub lhex {
	my $la = substr($a, -8);
	my $lb = substr($b, -8);
	my $ha = substr($a, 0, length($a) - length($la));
	my $hb = substr($b, 0, length($b) - length($lb));
	$ha = "0" if ($ha eq "");
	$ha = "0" if ($hb eq "");
	if (hex($ha) != hex($hb)) {
		$la = $ha;
		$lb = $hb;
	}
	hex($la) <=> hex($lb)
}

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 lhex 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);
