/* $NetBSD: crunchide.c,v 1.5 2006/10/02 19:11:13 joerg Exp $ */
/* NetBSD: crunchide.c,v 1.9 1999/01/11 22:40:00 kleink Exp 	 */

/*
 * Copyright (c) 1997 Christopher G. Demetriou.  All rights reserved.
 * Copyright (c) 1994 University of Maryland
 * All Rights Reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of U.M. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  U.M. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
 * BE LIABLE FOR ANY SPECIAL, 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.
 *
 * Author: James da Silva, Systems Design and Analysis Group
 *			   Computer Science Department
 *			   University of Maryland at College Park
 */
/*
 * crunchide.c - tiptoes through an a.out symbol table, hiding all defined
 *	global symbols.  Allows the user to supply a "keep list" of symbols
 *	that are not to be hidden.  This program relies on the use of the
 * 	linker's -dc flag to actually put global bss data into the file's
 * 	bss segment (rather than leaving it as undefined "common" data).
 *
 * 	The point of all this is to allow multiple programs to be linked
 *	together without getting multiple-defined errors.
 *
 *	For example, consider a program "foo.c".  It can be linked with a
 *	small stub routine, called "foostub.c", eg:
 *	    int foo_main(int argc, char **argv){ return main(argc, argv); }
 *      like so:
 *	    cc -c foo.c foostub.c
 *	    ld -dc -r foo.o foostub.o -o foo.combined.o
 *	    crunchide -k _foo_main foo.combined.o
 *	at this point, foo.combined.o can be linked with another program
 * 	and invoked with "foo_main(argc, argv)".  foo's main() and any
 * 	other globals are hidden and will not conflict with other symbols.
 *
 * TODO:
 *	- resolve the theoretical hanging reloc problem (see check_reloc()
 *	  below). I have yet to see this problem actually occur in any real
 *	  program. In what cases will gcc/gas generate code that needs a
 *	  relative reloc from a global symbol, other than PIC?  The
 *	  solution is to not hide the symbol from the linker in this case,
 *	  but to generate some random name for it so that it doesn't link
 *	  with anything but holds the place for the reloc.
 *      - arrange that all the BSS segments start at the same address, so
 *	  that the final crunched binary BSS size is the max of all the
 *	  component programs' BSS sizes, rather than their sum.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: crunchide.c,v 1.5 2006/10/02 19:11:13 joerg Exp $");
#endif

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
#include <a.out.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <bfd.h>

extern const char *__progname;

void usage      __P((void));
void add_to_keep_list __P((char *));
void add_file_to_keep_list __P((char *));
int hide_syms   __P((const char *, char *));
static void setup_section __P((bfd *, sec_ptr, PTR));
static void copy_section __P((bfd *, sec_ptr, PTR));

int verbose;

struct keep {
	struct keep *next;
	char *sym;
} *keep_list;

struct bfd_cookie {
	bfd *bfd;
	asymbol **symtable;
};

int
main(argc, argv)
	int argc;
	char **argv;
{
	int ch, errors;
	char *bfdname = NULL;

	while ((ch = getopt(argc, argv, "b:k:f:v")) != -1)
		switch (ch) {
		case 'b':
			bfdname = optarg;
			break;
		case 'k':
			add_to_keep_list(optarg);
			break;
		case 'f':
			add_file_to_keep_list(optarg);
			break;
		case 'v':
			verbose = 1;
			break;
		default:
			usage();
		}

	argc -= optind;
	argv += optind;

	if (argc == 0)
		usage();

	errors = 0;
	while (argc) {
		if (hide_syms(*argv, bfdname))
			errors = 1;
		argc--, argv++;
	}

	return errors;
}

void 
usage()
{
	const char **list;

	fprintf(stderr,
"Usage: %s [-b bfdname] [-k <symbol-name>] [-f <keep-list-file>] <files> ...\n",
		__progname);
	fprintf(stderr, "supported targets:");
	for (list = bfd_target_list(); *list != NULL; list++)
		fprintf(stderr, " %s", *list);
	fprintf(stderr, "\n");
	exit(1);
}

void 
add_to_keep_list(symbol)
	char *symbol;
{
	struct keep *newp, *prevp, *curp;
	int cmp;

	cmp = 0;

	for (curp = keep_list, prevp = NULL; curp;
	    prevp = curp, curp = curp->next)
		if ((cmp = strcmp(symbol, curp->sym)) <= 0)
			break;

	if (curp && cmp == 0)
		return;		/* already in table */

	newp = (struct keep *)malloc(sizeof (struct keep));
	if (newp)
		newp->sym = strdup(symbol);
	if (newp == NULL || newp->sym == NULL) {
		fprintf(stderr,
			"%s: out of memory for keep list\n", __progname);
		exit(1);
	}
	newp->next = curp;
	if (prevp)
		prevp->next = newp;
	else
		keep_list = newp;
}

int 
in_keep_list(symbol)
	const char *symbol;
{
	struct keep *curp;
	int cmp;

	cmp = 0;

	for (curp = keep_list; curp; curp = curp->next)
		if ((cmp = strcmp(symbol, curp->sym)) <= 0)
			break;

	return curp && cmp == 0;
}

void 
add_file_to_keep_list(filename)
	char *filename;
{
	FILE *keepf;
	char symbol[1024];
	int len;

	if ((keepf = fopen(filename, "r")) == NULL) {
		perror(filename);
		usage();
	}
	while (fgets(symbol, 1024, keepf)) {
		len = strlen(symbol);
		if (len && symbol[len - 1] == '\n')
			symbol[len - 1] = '\0';

		add_to_keep_list(symbol);
	}
	fclose(keepf);
}

int 
hide_syms(filename, bfdname)
	const char *filename;
	char *bfdname;
{
	int i, n, rv = 0;
	bfd *org_bfd = NULL, *new_bfd = NULL;
	char tempname[16];
	char **name;
	long storage_needed, number_of_symbols;
	size_t fn_size;
	asymbol **org_symtable, **new_symtable;

	fn_size = strlen(filename);

	bfd_init();
	if ((org_bfd = bfd_openr(filename, bfdname)) == NULL) {
		bfd_perror(filename);
		return 1;
	}
	if (!bfd_check_format(org_bfd, bfd_object)) {
		bfd_perror(filename);
		goto err;
	}

	bfdname = bfd_get_target(org_bfd);
	strcpy(tempname, "tmp.XXXXXXX");
	mktemp(tempname);
	if ((new_bfd = bfd_openw(tempname, bfdname)) == NULL) {
		bfd_perror(tempname);
		goto err;
	}

	if (!bfd_set_format(new_bfd, bfd_get_format(org_bfd)) ||
	    !bfd_set_start_address(new_bfd, bfd_get_start_address(org_bfd)) ||
	    !bfd_set_file_flags(new_bfd, (bfd_get_file_flags(org_bfd) &
	      bfd_applicable_file_flags(new_bfd))) ||
	    !bfd_set_arch_mach (new_bfd, bfd_get_arch (org_bfd),
	      bfd_get_mach (org_bfd))) {
		bfd_perror(tempname);
		goto err;
	}
	bfd_map_over_sections(org_bfd, setup_section, (void *)new_bfd);

	storage_needed = bfd_get_symtab_upper_bound(org_bfd);
	if (storage_needed < 0)
		goto err;
	if (storage_needed == 0)
		goto out;

	org_symtable = (asymbol **)malloc(storage_needed);
	number_of_symbols = bfd_canonicalize_symtab(org_bfd, org_symtable);
	if (number_of_symbols < 0)
		goto err;

	new_symtable = (asymbol **)malloc(storage_needed);
	if (new_symtable == NULL)
		goto err;
	name = (char **)calloc(sizeof (char *), number_of_symbols);
	if (name == NULL)
		goto err;

	n = 0;
	for (i = 0; i < number_of_symbols; i++) {
		const char *symname;

		new_symtable[i] = org_symtable[i];
		if (!(new_symtable[i]->flags & BSF_GLOBAL))
			continue;

		symname = bfd_asymbol_name(new_symtable[i]);
		if (in_keep_list(symname))
			continue;

		n++;

		/*
		 * make sure there's size for the next entry, even if it's
		 * as large as it can be.
		 *
		 * "_$$hide$$ <filename> <symname><NUL>" ->
		 *    9 + 3 + sizes of fn and sym name 
		 */
		name[i] = (char *)malloc(12 + fn_size + strlen(symname));
		sprintf(name[i], "_$$hide$$ %s %s", filename, symname);
		new_symtable[i]->name = name[i];
	}

	if (n > 0) {
		struct bfd_cookie cookie;

		new_symtable[number_of_symbols] = NULL;
		bfd_set_symtab(new_bfd, new_symtable, number_of_symbols);

		cookie.bfd = new_bfd;
		cookie.symtable = org_symtable;
		bfd_map_over_sections(org_bfd, copy_section, (void *)&cookie);

		if (!bfd_copy_private_bfd_data(org_bfd, new_bfd)) {
			bfd_perror("bfd_copy_private_bfd_data");
			goto err;
		}
	}

	bfd_close(new_bfd);
	bfd_close(org_bfd);

	if (rename(tempname, filename) < 0)
		perror("rename");
	unlink(tempname);

	for (i = 0; i < number_of_symbols; i++)
		if (name[i])
			free(name[i]);
	free(new_symtable);
	free(name);

out:
	return (rv);

err:
	rv = 1;
	if (org_bfd)
		bfd_close(org_bfd);
	if (new_bfd) {
		unlink(tempname);
		bfd_close(new_bfd);
	}
	goto out;
}

static void
setup_section(ibfd, isection, arg)
	bfd *ibfd;
	sec_ptr isection;
	PTR arg;
{
	sec_ptr osection;
	bfd *obfd = (bfd *)arg;

	if ((osection = bfd_make_section_anyway(obfd,
	      bfd_section_name(ibfd, isection))) == NULL ||
	    !bfd_set_section_size(obfd, osection,
	      bfd_section_size(ibfd, isection)) ||
	    !bfd_set_section_vma(obfd, osection,
	      bfd_section_vma(ibfd, isection)) ||
	    !bfd_set_section_alignment(obfd, osection,
	      bfd_section_alignment(ibfd, isection)) ||
	    !bfd_set_section_flags(obfd, osection,
	      bfd_get_section_flags(ibfd, isection))) {
		bfd_perror("setup_section");
		return;
	}

	isection->output_section = osection;
	isection->output_offset = 0;
	if (!bfd_copy_private_section_data(ibfd, isection, obfd, osection)) {
		bfd_perror("setup_section");
		return;
	}
}

static void
copy_section(ibfd, isection, arg)
	bfd *ibfd;
	sec_ptr isection;
	PTR arg;
{
	struct bfd_cookie *bc = (struct bfd_cookie *)arg;
	bfd *obfd = bc->bfd;
	asymbol **isym = bc->symtable;
	arelent **relpp;
	sec_ptr osection;
	PTR memhunk;
	bfd_size_type size;
	long relcount, relsize;

	osection = isection->output_section;
	size = isection->rawsize;
	if (size == 0 || osection == 0)
		return;

	relsize = bfd_get_reloc_upper_bound(ibfd, isection);
	if (relsize < 0) {
		bfd_perror(bfd_get_filename(ibfd));
	}
	if (relsize == 0) {
		bfd_set_reloc(obfd, osection, NULL, 0);
	} else {
		relpp = (arelent **)malloc(relsize);
		relcount = bfd_canonicalize_reloc(ibfd, isection, relpp, isym);
		if (relcount < 0)
			bfd_perror(bfd_get_filename(ibfd));
		bfd_set_reloc(obfd, osection, relpp, relcount);
	}

	isection->size = isection->rawsize;
	isection->reloc_done = 1;

	if (bfd_get_section_flags(ibfd, isection) & SEC_HAS_CONTENTS) {
		memhunk = (PTR)malloc((unsigned int)size);
		if (!bfd_get_section_contents(ibfd, isection, memhunk, 0, size))
			bfd_perror(bfd_get_filename(ibfd));
		if (!bfd_set_section_contents(obfd, osection, memhunk, 0, size))
			bfd_perror(bfd_get_filename(obfd));
		free(memhunk);
	} else if (bfd_get_section_flags(ibfd, isection) & SEC_LOAD) {
		memhunk = (PTR)malloc((unsigned int)size);
		memset(memhunk, 0, size);
		if (!bfd_set_section_contents(obfd, osection, memhunk, 0, size))
			bfd_perror(bfd_get_filename(obfd));
		free(memhunk);
	}
}
