/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * ELFCLASS specific code for elfedit, built once for each class
 */
#include	<stdlib.h>
#include	<stdio.h>
#include	<unistd.h>
#include	<machdep.h>
#include	<libelf.h>
#include	<strings.h>
#include	<sgs.h>
#include	"msg.h"
#include	"_elfedit.h"



/*
 * Look up the elfedit_symtab_t that corresponds to the symbol table
 * referenced by the sh_link field of the given auxiliary section.
 *
 * entry:
 *	obj_state - Partially constructed object state from
 *		elfedit_init_obj_state().
 *	auxsec - Section that is associated with the symbol table section
 *
 * exit:
 *	Returns the pointer to the elfedit_symtab_t entry that is
 *	referenced by the auxiliary section. If not found,
 *	outputs a debug message, and returns NULL.
 */
static elfedit_symtab_t *
get_symtab(elfedit_obj_state_t *obj_state, elfedit_section_t *auxsec)
{
	elfedit_symtab_t *symtab = obj_state->os_symtab;
	Word	sh_link = auxsec->sec_shdr->sh_link;
	Word	i;

	for (i = 0; i < obj_state->os_symtabnum; i++, symtab++)
		if (symtab->symt_shndx == sh_link)
			return (symtab);

	/*
	 * If we don't return above, it doesn't reference a valid
	 * symbol table. Issue warning.
	 */
	elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_AUX_LINK),
	    EC_WORD(auxsec->sec_shndx), auxsec->sec_name,
	    EC_WORD(sh_link));

	return (NULL);
}


/*
 * Fill in state.elf.obj_state with a a dynamically allocated
 * elfedit_obj_state_t struct of the appropriate ELFCLASS.
 * This pre-chewed form is fed to each command, reducing the amount
 * of ELF boilerplate code each command needs to contain.
 *
 * entry:
 *	file - Name of file to process
 *	fd - Descriptor of open file which has been successfully
 *		processed by elf_begin().
 *	elf - Elf handle returned by elf_begin
 *
 * exit:
 *	An elfedit_obj_state_t struct of the appropriate ELFCLASS has
 *	been dynamically allocated, and state.elf.obj_state references it.
 *	On failure, this routine does not return to the caller.
 *
 * note: The resulting elfedit_obj_state_t is allocated from a single
 *	piece of memory, such that a single call to free() suffices
 *	to release it as well as any memory it references.
 */
#ifdef _ELF64
void
elfedit64_init_obj_state(const char *file, int fd, Elf *elf)
#else
void
elfedit32_init_obj_state(const char *file, int fd, Elf *elf)
#endif
{
#define	INITIAL_SYMTABNDX_ALLOC	5

	/*
	 * This macro is used to call functions from libelf, all of which
	 * return NULL for failure and something else for success. On error,
	 * libelf_fail_name is set and execution jumps to the libelf_failure
	 * label for handling. Otherwise, the results of the call are ready
	 * for use by the caller.
	 */
#define	LIBELF(_libelf_expr, _name) \
	if ((_libelf_expr) == NULL) { \
		libelf_fail_name = _name; \
		goto libelf_failure; \
	}

	const char *libelf_fail_name;	/* Used for LIBELF errors */

	Elf_Scn			*scn;
	Elf_Data		*data;
	uint_t			ndx;
	size_t			len, os_size, secarr_size;
	char			*names = 0;
	size_t			names_len;
	elfedit_section_t	*_cache;
	elfedit_obj_state_t	tstate;
	elfedit_obj_state_t	*obj_state = NULL;
	Word			*symtabndx = NULL;
	Word			symtabndx_size = 0;
	elfedit_symtab_t	*symtab;

	tstate.os_file = file;
	tstate.os_fd = fd;
	tstate.os_elf = elf;
	tstate.os_dynndx = SHN_UNDEF;
	tstate.os_symtabnum = 0;

	LIBELF(tstate.os_ehdr = elf_getehdr(tstate.os_elf),
	    MSG_ORIG(MSG_ELF_GETEHDR))

	/* Program header array count and address */
	LIBELF(elf_getphnum(tstate.os_elf, &tstate.os_phnum),
	    MSG_ORIG(MSG_ELF_GETPHNUM))
	if (tstate.os_phnum > 0) {
		LIBELF((tstate.os_phdr = elf_getphdr(tstate.os_elf)),
		    MSG_ORIG(MSG_ELF_GETPHDR))
	} else {
		tstate.os_phdr = NULL;
	}


	LIBELF(elf_getshnum(tstate.os_elf, &tstate.os_shnum),
	    MSG_ORIG(MSG_ELF_GETSHNUM))


	/*
	 * Obtain the .shstrtab data buffer to provide the required section
	 * name strings.
	 */
	LIBELF(elf_getshstrndx(tstate.os_elf, &tstate.os_shstrndx),
	    MSG_ORIG(MSG_ELF_GETSHSTRNDX))
	LIBELF((scn = elf_getscn(tstate.os_elf, tstate.os_shstrndx)),
	    MSG_ORIG(MSG_ELF_GETSCN))
	LIBELF((data = elf_getdata(scn, NULL)), MSG_ORIG(MSG_ELF_GETDATA))
	names = data->d_buf;
	names_len = (names == NULL) ? 0 : data->d_size;

	/*
	 * Count the number of symbol tables and capture their indexes.
	 * Find the dynamic section.
	 */
	for (ndx = 1, scn = NULL; scn = elf_nextscn(tstate.os_elf, scn);
	    ndx++) {
		Shdr *shdr;

		LIBELF(shdr = elf_getshdr(scn), MSG_ORIG(MSG_ELF_GETSHDR));

		switch (shdr->sh_type) {
		case SHT_DYNAMIC:
			/* Save index of dynamic section for use below */
			tstate.os_dynndx = ndx;
			break;

		case SHT_SYMTAB:
		case SHT_DYNSYM:
		case SHT_SUNW_LDYNSYM:
			if (symtabndx_size <= tstate.os_symtabnum) {
				symtabndx_size = (symtabndx_size == 0) ?
				    INITIAL_SYMTABNDX_ALLOC :
				    (symtabndx_size * 2);
				symtabndx = elfedit_realloc(
				    MSG_INTL(MSG_ALLOC_SYMTABOS), symtabndx,
				    symtabndx_size * sizeof (symtabndx[0]));
			}
			symtabndx[tstate.os_symtabnum++] = ndx;
			break;
		}
	}

	/*
	 * Allocate space to hold the state. We allocate space for everything
	 * in one chunk to make releasing it easy:
	 *	(1) elfedit_obj_state_t struct
	 *	(2) The array of elfedit_section_t items referenced from
	 *		the elfedit_obj_state_t struct.
	 *	(3) The array of elfedit_symtab_t items referenced from
	 *		the elfedit_obj_state_t struct.
	 *	(4) The file name.
	 *
	 * Note that we round up the size of (1) and (2) to a double boundary
	 * to ensure proper alignment of (2) and (3). (4) can align on any
	 * boundary.
	 */
	os_size = S_DROUND(sizeof (tstate));
	secarr_size = (tstate.os_shnum * sizeof (elfedit_section_t));
	secarr_size = S_DROUND(secarr_size);
	len = strlen(tstate.os_file) + 1;
	obj_state = elfedit_malloc(MSG_INTL(MSG_ALLOC_OBJSTATE),
	    os_size + secarr_size +
	    (tstate.os_symtabnum * sizeof (elfedit_symtab_t)) + len);
	*obj_state = tstate;

	/*LINTED E_BAD_PTR_CAST_ALIGN*/
	obj_state->os_secarr = (elfedit_section_t *)
	    ((char *)obj_state + os_size);
	if (obj_state->os_symtabnum == 0)
		obj_state->os_symtab = NULL;
	else
		/*LINTED E_BAD_PTR_CAST_ALIGN*/
		obj_state->os_symtab = (elfedit_symtab_t *)
		    ((char *)obj_state->os_secarr + secarr_size);
	obj_state->os_file =
	    (char *)(obj_state->os_symtab + tstate.os_symtabnum);
	(void) strncpy((char *)obj_state->os_file, tstate.os_file, len);

	/*
	 * Fill in obj_state->os_secarr with information for each section.
	 * At the same time, fill in obj_state->os_symtab with the symbol
	 * table related data.
	 */
	bzero(obj_state->os_secarr, sizeof (obj_state->os_secarr[0]));
	_cache = obj_state->os_secarr;
	LIBELF(scn = elf_getscn(tstate.os_elf, 0),
	    MSG_ORIG(MSG_ELF_GETSCN));
	_cache->sec_scn = scn;
	LIBELF(_cache->sec_shdr = elf_getshdr(scn), MSG_ORIG(MSG_ELF_GETSHDR));
	_cache->sec_name = (_cache->sec_shdr->sh_name < names_len) ?
	    (names + _cache->sec_shdr->sh_name) : MSG_INTL(MSG_UNKNOWNSECNAM);
	_cache++;

	if (obj_state->os_symtab != NULL) {
		bzero(obj_state->os_symtab,
		    sizeof (obj_state->os_symtab[0]) * obj_state->os_symtabnum);
		for (ndx = 0; ndx < obj_state->os_symtabnum; ndx++)
			obj_state->os_symtab[ndx].symt_shndx = symtabndx[ndx];
		free(symtabndx);
	}

	for (ndx = 1, scn = NULL; scn = elf_nextscn(tstate.os_elf, scn);
	    ndx++, _cache++) {
		_cache->sec_shndx = ndx;
		_cache->sec_scn = scn;
		LIBELF(_cache->sec_shdr = elf_getshdr(scn),
		    MSG_ORIG(MSG_ELF_GETSHDR))
		_cache->sec_data = elf_getdata(scn, NULL);
		_cache->sec_name = (_cache->sec_shdr->sh_name < names_len) ?
		    (names + _cache->sec_shdr->sh_name) :
		    MSG_INTL(MSG_UNKNOWNSECNAM);

		switch (_cache->sec_shdr->sh_type) {
		case SHT_SYMTAB_SHNDX:
			symtab = get_symtab(obj_state, _cache);
			symtab->symt_xshndx = ndx;
			break;

		case SHT_SUNW_syminfo:
			symtab = get_symtab(obj_state, _cache);
			symtab->symt_syminfo = ndx;
			break;

		case SHT_SUNW_versym:
			symtab = get_symtab(obj_state, _cache);
			symtab->symt_versym = ndx;
			break;
		}
	}

	/*
	 * Sanity check the symbol tables, and discard any auxiliary
	 * sections without enough elements.
	 */
	symtab = obj_state->os_symtab;
	for (ndx = 0; ndx < obj_state->os_symtabnum; ndx++, symtab++) {
		elfedit_section_t	*symsec;
		Word			symsec_cnt, aux_cnt;

		symsec = &obj_state->os_secarr[symtab->symt_shndx];
		symsec_cnt = symsec->sec_shdr->sh_size / sizeof (Sym);

		/* Extended section indexes */
		if (symtab->symt_xshndx != SHN_UNDEF) {
			_cache = &obj_state->os_secarr[symtab->symt_xshndx];
			aux_cnt = _cache->sec_shdr->sh_size / sizeof (Word);
			if (symsec_cnt > aux_cnt)
				elfedit_msg(ELFEDIT_MSG_DEBUG,
				    MSG_INTL(MSG_DEBUG_AUX_SIZE),
				    EC_WORD(ndx), _cache->sec_name,
				    EC_WORD(aux_cnt),
				    EC_WORD(symsec->sec_shndx),
				    symsec->sec_name, EC_WORD(aux_cnt));
		}

		/* Syminfo */
		if (symtab->symt_syminfo != SHN_UNDEF) {
			_cache = &obj_state->os_secarr[symtab->symt_syminfo];
			aux_cnt = _cache->sec_shdr->sh_size / sizeof (Syminfo);
			if (symsec_cnt > aux_cnt)
				elfedit_msg(ELFEDIT_MSG_DEBUG,
				    MSG_INTL(MSG_DEBUG_AUX_SIZE),
				    EC_WORD(ndx), _cache->sec_name,
				    EC_WORD(aux_cnt),
				    EC_WORD(symsec->sec_shndx),
				    symsec->sec_name, EC_WORD(aux_cnt));
		}

		/* Versym */
		if (symtab->symt_versym != SHN_UNDEF) {
			_cache = &obj_state->os_secarr[symtab->symt_versym];
			aux_cnt = _cache->sec_shdr->sh_size / sizeof (Versym);
			if (symsec_cnt > aux_cnt)
				elfedit_msg(ELFEDIT_MSG_DEBUG,
				    MSG_INTL(MSG_DEBUG_AUX_SIZE),
				    EC_WORD(ndx), _cache->sec_name,
				    EC_WORD(aux_cnt),
				    EC_WORD(symsec->sec_shndx),
				    symsec->sec_name, EC_WORD(aux_cnt));
		}
	}

	/*
	 * If this object has a dynsym section with a FLAGS_1 field,
	 * then set the DF_1_EDITED bit. elfedit allows changes that
	 * can break the resulting program, so knowing that a file was
	 * edited can be helpful when encountering a core file or other
	 * unexpected failure in the field. A single bit can't tell you
	 * what was changed, but it will alert you to the possibility that
	 * some additional questions might be in order.
	 */
	if (obj_state->os_dynndx != SHN_UNDEF) {
		Word			i;
		Word			numdyn;
		elfedit_section_t	*dynsec;
		elfedit_dyn_elt_t	flags_1_elt;
		elfedit_dyn_elt_t	null_elt;
		Dyn			*dyn;

		dynsec = &obj_state->os_secarr[obj_state->os_dynndx];
		dyn = (Dyn *) dynsec->sec_data->d_buf;
		numdyn = dynsec->sec_shdr->sh_size /
		    dynsec->sec_shdr->sh_entsize;
		elfedit_dyn_elt_init(&flags_1_elt);
		elfedit_dyn_elt_init(&null_elt);
		for (i = 0; i < numdyn; i++) {

			switch (dyn[i].d_tag) {
			case DT_NULL:
				/*
				 * Remember state of the first DT_NULL. If there
				 * are more than one (i.e. the first one is not
				 * in the final spot), and there is no flags1,
				 * then we will turn the first one into a
				 * DT_FLAGS_1.
				 */
				if (!null_elt.dn_seen)
					elfedit_dyn_elt_save(&null_elt, i,
					    &dyn[i]);
				break;

			case DT_FLAGS_1:
				elfedit_dyn_elt_save(&flags_1_elt, i, &dyn[i]);
				break;
			}
		}
		/* If don't have a flags1 field, can we make one from a NULL? */
		if (!flags_1_elt.dn_seen && null_elt.dn_seen &&
		    (null_elt.dn_ndx < (numdyn - 1))) {
			elfedit_msg(ELFEDIT_MSG_DEBUG,
			    MSG_INTL(MSG_DEBUG_NULL2DYNFL1),
			    EC_WORD(obj_state->os_dynndx),
			    dynsec->sec_name, EC_WORD(null_elt.dn_ndx));
			flags_1_elt.dn_seen = 1;
			flags_1_elt.dn_ndx = null_elt.dn_ndx;
			flags_1_elt.dn_dyn.d_tag = DT_FLAGS_1;
			flags_1_elt.dn_dyn.d_un.d_val = 0;
		}
		/*
		 * If there is a flags 1 field, add the edit flag if
		 * it is not present, and report it's presence otherwise.
		 */
		if (flags_1_elt.dn_seen) {
			if (flags_1_elt.dn_dyn.d_un.d_val & DF_1_EDITED) {
				elfedit_msg(ELFEDIT_MSG_DEBUG,
				    MSG_INTL(MSG_DEBUG_SEEDYNFLG),
				    EC_WORD(obj_state->os_dynndx),
				    dynsec->sec_name,
				    EC_WORD(flags_1_elt.dn_ndx));
			} else {
				elfedit_msg(ELFEDIT_MSG_DEBUG,
				    MSG_INTL(MSG_DEBUG_ADDDYNFLG),
				    EC_WORD(obj_state->os_dynndx),
				    dynsec->sec_name,
				    EC_WORD(flags_1_elt.dn_ndx));
				flags_1_elt.dn_dyn.d_un.d_val |= DF_1_EDITED;
				dyn[flags_1_elt.dn_ndx] = flags_1_elt.dn_dyn;
				elfedit_modified_data(dynsec);
			}
		}
	}

#ifdef _ELF64
	state.elf.obj_state.s64 = obj_state;
#else
	state.elf.obj_state.s32 = obj_state;
#endif
	return;

libelf_failure:
	/*
	 * Control comes here if there is an error with LIBELF.
	 *
	 * entry:
	 *	libelf_fail_name - Name of failing libelf function
	 *	tstate.os_file - Name of ELF file being processed
	 *	tstate.os_fd - Descriptor of open ELF file
	 *
	 * exit:
	 *	- dynamic memory is released if necessary
	 *	- The error issued
	 */
	if (obj_state != NULL)
		free(obj_state);
	(void) close(tstate.os_fd);
	elfedit_elferr(tstate.os_file, libelf_fail_name);
#undef LIBELF_FAILURE
#undef INITIAL_SYMTABNDX_ALLOC
}