/*
 * Copyright (c) 1993-1998 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)readelf.c	1.20	98/04/09 SMI"

#include	"gprof.h"
#include	<stdlib.h>
#include	<sys/file.h>
#include	<fcntl.h>
#include	<unistd.h>
#include	<string.h>
#include	<sysexits.h>
#include	<libelf.h>
#include 	"gelf.h"

#ifdef DEBUG
static void	debug_dup_del(nltype *, nltype *);

#define	DPRINTF(msg, file)	if (debug & ELFDEBUG) \
					printf(msg, file);

#define	PRINTF(msg)		if (debug & ELFDEBUG) \
					printf(msg);

#define	DEBUG_DUP_DEL(keeper, louser)	if (debug & ELFDEBUG) \
						debug_dup_del(keeper, louser);

#else
#define	DPRINTF(msg, file)
#define	PRINTF(msg)
#define	DEBUG_DUP_DEL(keeper, louser)
#endif



#define	VOID_P		void *

size_t	textbegin, textsize;

/* Prototype definitions first */

static void	process(char *filename, int fd);
static void	get_symtab(Elf *elf, char *filename, mod_info_t *module);
static void	get_textseg(Elf *elf, int fd, char *filename);
static void	save_aout_info(char *);
static int	compare(nltype *a, nltype *b);

static void
fatal_error(char *error)
{
	fprintf(stderr, "Fatal ELF error: %s (%s)\n", error, elf_errmsg(-1));
	exit(EX_SOFTWARE);
}

bool
is_shared_obj(char *name)
{
	int		fd;
	Elf		*elf;
	GElf_Ehdr	ehdr;

	if ((fd = open(name, O_RDONLY)) == -1) {
		fprintf(stderr, "%s: can't open `%s'\n", whoami, name);
		exit(EX_NOINPUT);
	}

	if (elf_version(EV_CURRENT) == EV_NONE)
		fatal_error("libelf is out of date");

	if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL)
		fatal_error("can't read as ELF file");

	if (gelf_getehdr(elf, &ehdr) == NULL)
		fatal_error("can't read ehdr");

	elf_end(elf);
	close(fd);

	if (ehdr.e_type == ET_DYN)
		return (TRUE);
	else
		return (FALSE);
}

static void
save_aout_info(char *aoutname)
{
	struct stat		buf;
	extern fl_info_t	aout_info;

	if (stat(aoutname, &buf) == -1) {
		fprintf(stderr, "%s: can't get info on `%s'\n",
							whoami, aoutname);
		exit(EX_NOINPUT);
	}

	aout_info.dev = buf.st_dev;
	aout_info.ino = buf.st_ino;
	aout_info.mtime = buf.st_mtime;
	aout_info.size = buf.st_size;
}

void
getnfile(char *aoutname)
{
	int	fd;

	DPRINTF(" Attempting to open %s  \n", aoutname);
	if ((fd = open((aoutname), O_RDONLY)) == -1) {
		(void) fprintf(stderr, "%s: can't open `%s'\n",
							whoami, aoutname);
		exit(EX_NOINPUT);
	}
	process(aoutname, fd);
	save_aout_info(aoutname);

	(void) close(fd);
}

static GElf_Addr
get_txtorigin(Elf *elf)
{
	GElf_Ehdr	ehdr;
	GElf_Phdr	phdr;
	GElf_Half	ndx;
	GElf_Addr	txt_origin = 0;
	bool		first_load_seg = TRUE;

	if (gelf_getehdr(elf, &ehdr) == NULL)
		fatal_error("can't read ehdr");

	for (ndx = 0; ndx < ehdr.e_phnum; ndx++) {
		if (gelf_getphdr(elf, ndx, &phdr) == NULL)
			continue;

		if ((phdr.p_type == PT_LOAD) && !(phdr.p_flags & PF_W)) {
			if (first_load_seg || phdr.p_vaddr < txt_origin)
				txt_origin = phdr.p_vaddr;

			if (first_load_seg)
				first_load_seg = FALSE;
		}
	}

	return (txt_origin);
}

void
process_namelist(mod_info_t *module)
{
	int		fd;
	Elf		*elf;

	if ((fd = open(module->name, O_RDONLY)) == -1) {
		(void) fprintf(stderr, "%s: can't read %s\n",
							whoami, module->name);
		fprintf(stderr, "Exiting due to error(s)...\n");
		exit(EX_NOINPUT);
	}

	/*
	 * libelf's version already verified in processing a.out,
	 * so directly do elf_begin()
	 */
	if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL)
		fatal_error("can't read as ELF file");

	module->next = NULL;
	module->txt_origin = get_txtorigin(elf);
	get_symtab(elf, module->name, module);
	module->active = TRUE;
}

/*
 * Get the ELF header and,  if it exists, call get_symtab()
 * to begin processing of the file; otherwise, return from
 * processing the file with a warning.
 */
static void
process(char *filename, int fd)
{
	Elf			*elf;
	extern bool		cflag;
	extern bool		Bflag;

	if (elf_version(EV_CURRENT) == EV_NONE)
		fatal_error("libelf is out of date");

	if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL)
		fatal_error("can't read as ELF file");

	if (gelf_getclass(elf) == ELFCLASS64)
		Bflag = TRUE;

	/*
	 * Initialize active modules list. Note that we set the end
	 * address while reading the symbol table, in get_symtab
	 */
	modules.id = 1;
	modules.next = NULL;
	modules.txt_origin = get_txtorigin(elf);
	modules.load_base = modules.txt_origin;
	if ((modules.name = (char *) malloc(strlen(filename) + 1)) == NULL) {
		fprintf(stderr, "%s: can't malloc %d bytes",
					    whoami, strlen(filename) + 1);
		exit(EX_UNAVAILABLE);
	}
	strcpy(modules.name, filename);

	get_symtab(elf, filename, &modules);

	modules.load_end = modules.data_end;
	modules.active = TRUE;
	n_modules = 1;

	if (cflag)
		get_textseg(elf, fd, filename);
}

static void
get_textseg(Elf *elf, int fd, char *filename)
{
	GElf_Ehdr ehdr;
	GElf_Phdr phdr;
	GElf_Half i;

	if (gelf_getehdr(elf, &ehdr) == NULL)
		fatal_error("can't read ehdr");

	for (i = 0; i < ehdr.e_phnum; i++) {

		if (gelf_getphdr(elf, i, &phdr) == NULL)
			continue;

		if (!(phdr.p_flags & PF_W) && (phdr.p_filesz > textsize)) {
			size_t chk;

			/*
			 * We could have multiple loadable text segments;
			 * keep the largest we find.
			 */
			if (textspace)
				free(textspace);

			/*
			 * gprof is a 32-bit program;  if this text segment
			 * has a > 32-bit offset or length, it's too big.
			 */
			chk = (size_t)phdr.p_vaddr + (size_t)phdr.p_filesz;
			if (phdr.p_vaddr + phdr.p_filesz != (GElf_Xword)chk)
				fatal_error("text segment too large for -c");

			textbegin = (size_t)phdr.p_vaddr;
			textsize = (size_t)phdr.p_filesz;

			textspace = malloc(textsize);

			if (lseek(fd, (off_t)phdr.p_offset, SEEK_SET) !=
			    (off_t)phdr.p_offset)
				fatal_error("cannot seek to text section");

			if (read(fd, textspace, textsize) != textsize)
				fatal_error("cannot read text");
		}
	}

	if (textsize == 0)
		fatal_error("can't find text segment");
}

#ifdef DEBUG
static void
debug_dup_del(nltype * keeper, nltype * louser)
{
	printf("remove_dup_syms: discarding sym %s over sym %s\n",
		louser->name, keeper->name);
}
#endif DEBUG

static void
remove_dup_syms(nltype *nl, sztype *sym_count)
{
	int	i;
	int	index;
	int	nextsym;

	nltype *	orig_list;
	if ((orig_list = malloc(sizeof (nltype) * *sym_count)) == NULL) {
		fprintf(stderr, "gprof: remove_dup_syms: malloc failed\n");
		fprintf(stderr, "Exiting due to error(s)...\n");
		exit(EX_UNAVAILABLE);
	}
	memcpy(orig_list, nl, sizeof (nltype) * *sym_count);

	for (i = 0, index = 0, nextsym = 1; nextsym < *sym_count; nextsym++) {
		int	i_type;
		int	n_bind;
		int	n_type;

		/*
		 * If orig_list[nextsym] points to a new symvalue, then we
		 * will copy our keeper and move on to the next symbol.
		 */
		if ((orig_list + i)->value < (orig_list + nextsym)->value) {
			*(nl + index++) = *(orig_list +i);
			i = nextsym;
			continue;
		}

		/*
		 * If these two symbols have the same info, then we
		 * keep the first and keep checking for dups.
		 */
		if ((orig_list + i)->syminfo ==
		    (orig_list + nextsym)->syminfo) {
			DEBUG_DUP_DEL(orig_list + i, orig_list + nextsym);
			continue;
		}
		n_bind = ELF32_ST_BIND((orig_list + nextsym)->syminfo);
		i_type = ELF32_ST_TYPE((orig_list + i)->syminfo);
		n_type = ELF32_ST_TYPE((orig_list + nextsym)->syminfo);

		/*
		 * If they have the same type we take the stronger
		 * bound function.
		 */
		if (i_type == n_type) {
			if (n_bind == STB_WEAK) {
				DEBUG_DUP_DEL((orig_list + i),
				    (orig_list + nextsym));
				continue;
			}
			DEBUG_DUP_DEL((orig_list + nextsym),
			    (orig_list + i));
			i = nextsym;
			continue;
		}

		/*
		 * If the first symbol isn't of type NOTYPE then it must
		 * be the keeper.
		 */
		if (i_type != STT_NOTYPE) {
			DEBUG_DUP_DEL((orig_list + i),
			    (orig_list + nextsym));
			continue;
		}

		/*
		 * Throw away the first one and take the new
		 * symbol
		 */
		DEBUG_DUP_DEL((orig_list + nextsym), (orig_list + i));
		i = nextsym;
	}

	if ((orig_list + i)->value > (nl + index - 1)->value)
		*(nl + index++) = *(orig_list +i);

	*sym_count = index;
}

/*
 * compare either by name or by value for sorting.
 * This is the comparison function called by qsort to
 * sort the symbols either by name or value when requested.
 */
static int
compare(nltype *a, nltype *b)
{
	if (a->value > b->value)
		return (1);
	else
		return ((a->value == b->value) - 1);
}

static int
is_function(Elf *elf, GElf_Sym *sym)
{
	Elf_Scn *scn;
	GElf_Shdr shdr;

	/*
	 * With shared objects, it is possible we come across a function
	 * that's global, but is undefined. The definition is probably
	 * elsewhere, so we'll have to skip it as far as this object is
	 * concerned.
	 */
	if (sym->st_shndx == SHN_UNDEF)
		return (0);

	if (GELF_ST_TYPE(sym->st_info) == STT_FUNC) {
		if (GELF_ST_BIND(sym->st_info) == STB_GLOBAL)
			return (1);

		if (GELF_ST_BIND(sym->st_info) == STB_WEAK)
			return (1);

		if (!aflag && GELF_ST_BIND(sym->st_info) == STB_LOCAL)
			return (1);
	}

	/*
	 * It's not a function; determine if it's in an executable section.
	 */
	if (GELF_ST_TYPE(sym->st_info) != STT_NOTYPE)
		return (0);

	/*
	 * If it isn't global, and it isn't weak, and it either isn't
	 * local or the "all flag" isn't set, then get out.
	 */
	if (GELF_ST_BIND(sym->st_info) != STB_GLOBAL &&
	    GELF_ST_BIND(sym->st_info) != STB_WEAK &&
	    (GELF_ST_BIND(sym->st_info) != STB_LOCAL || aflag))
		return (0);

	if (sym->st_shndx >= SHN_LORESERVE)
		return (0);

	scn = elf_getscn(elf, sym->st_shndx);
	gelf_getshdr(scn, &shdr);

	if (!(shdr.sh_flags & SHF_EXECINSTR))
		return (0);

	return (1);
}

static void
get_symtab(Elf *elf, char *filename, mod_info_t *module)
{
	Elf_Scn		*scn = NULL, *sym = NULL;
	GElf_Word	strndx = 0;
	sztype		nsyms, i;
	Elf_Data	*symdata;
	nltype		*etext = NULL;

	nltype			*l_nl, *l_npe;
	sztype			l_nname;
	extern sztype		total_names;

	while ((scn = elf_nextscn(elf, scn)) != NULL) {
		GElf_Shdr shdr;

		if (gelf_getshdr(scn, &shdr) == NULL)
			continue;

		if (shdr.sh_type == SHT_SYMTAB || shdr.sh_type == SHT_DYNSYM) {
			GElf_Xword chk = shdr.sh_size / shdr.sh_entsize;

			nsyms = (sztype)(shdr.sh_size / shdr.sh_entsize);

			if (chk != (GElf_Xword)nsyms)
				fatal_error("32-bit gprof cannot handle"
				    "more than 2^32 symbols");

			strndx = shdr.sh_link;
			sym = scn;
		}

		/*
		 * If we've found a real symbol table, we're done.
		 */
		if (shdr.sh_type == SHT_SYMTAB)
			break;
	}

	if (sym == NULL || strndx == 0)
		fatal_error("can't find symbol table.\n");

	if ((symdata = elf_getdata(scn, NULL)) == NULL)
		fatal_error("can't read symbol data.\n");

	if ((l_nl = l_npe = (nltype *)calloc(nsyms + PRF_SYMCNT,
	    sizeof (nltype))) == NULL)
		fatal_error("cannot allocate symbol data.\n");

	/*
	 * Now we need to cruise through the symbol table eliminating
	 * all non-functions from consideration, and making strings
	 * real.
	 */
	l_nname = 0;

	for (i = 1; i < nsyms; i++) {
		GElf_Sym gsym;
		char *name;

		gelf_getsym(symdata, i, &gsym);

		name = elf_strptr(elf, strndx, gsym.st_name);

		/*
		 * We're interested in this symbol if it's a function or
		 * if it's the symbol "_etext"
		 */
		if (is_function(elf, &gsym) || strcmp(name, PRF_ETEXT) == 0) {

			l_npe->name = name;
			l_npe->value = gsym.st_value;
			l_npe->sz = gsym.st_size;
			l_npe->syminfo = gsym.st_info;
			l_npe->module = module;

			if (strcmp(name, PRF_ETEXT) == 0)
				etext = l_npe;

			if (lflag == TRUE &&
			    GELF_ST_BIND(gsym.st_info) == STB_LOCAL) {
				/*
				 * If the "locals only" flag is on, then
				 * we add the local symbols to the
				 * exclusion lists.
				 */
				addlist(Elist, name);
				addlist(elist, name);
			}
			DPRINTF("Index %lld:", l_nname);
			DPRINTF("\tValue: 0x%llx\t", l_npe->value);
			DPRINTF("Name: %s \n", l_npe->name);
			l_npe++;
			l_nname++;
		}

		if (strcmp(name, PRF_END) == 0)
			module->data_end = gsym.st_value;
	}

	if (l_npe == l_nl)
		fatal_error("no valid functions found");

	/*
	 * Finally, we need to construct some dummy entries.
	 */
	if (etext) {
		l_npe->name = PRF_EXTSYM;
		l_npe->value = etext->value + 1;
		l_npe->syminfo = GELF_ST_INFO(STB_GLOBAL, STT_FUNC);
		l_npe->module = module;
		l_npe++;
		l_nname++;
	}

	l_npe->name = PRF_MEMTERM;
	l_npe->value = (pctype)-1;
	l_npe->syminfo = GELF_ST_INFO(STB_GLOBAL, STT_FUNC);
	l_npe->module = module;
	l_npe++;
	l_nname++;

	/*
	 * We're almost done;  all we need to do is sort the symbols
	 * and then remove the duplicates.
	 */
	qsort(l_nl, (size_t)l_nname, sizeof (nltype),
	    (int(*)(const void *, const void *)) compare);
	remove_dup_syms(l_nl, &l_nname);

	module->nl = l_nl;
	module->npe = l_npe;
	module->nname = l_nname;

	total_names += l_nname;
}
