/*
 * readelf.c  -  Read an ELF executable file and apply any relocations
 *
 * Copyright (C) 2005-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **************************************************************************
 *
 * This file is based on a program called readelf which can by found in
 * the GNU binutils package.  However, it has been extensively modified.
 * The original sources carry the following copyright:
 *
 *   readelf.c -- display contents of an ELF format file
 *   Copyright 1998, 1999, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
 *
 *   Originally developed by Eric Youngdale <eric@andante.jic.com>
 *   Modifications by Nick Clifton <nickc@redhat.com>
 *
 **************************************************************************
 *
 * This code does not read every ELF file. It only reads executable 32-bit
 * ELF files for the x86 architecture (e.g. after final linking), which
 * optionally have relocation sections. This relocation information can be
 * included into the executable file using the -q command line option to
 * the GNU linker. It does not support loading of any dynamic section.
 *
 **************************************************************************
 *
 * $Id: readelf.c,v 1.8 2007/01/06 18:31:39 gkminix Exp $
 */

#define NEED_BINARY
#include <common.h>
#include <nblib.h>
#include <system/elf.h>


/*
 * Misc. internal definitions
 */
#define MAX_ERR_LEN	127		/* max length of error message */




/*
 **************************************************************************
 *
 *		Internal representation of ELF structures
 *
 **************************************************************************
 */

/* ELF file header */
typedef struct elf_internal_ehdr {
  unsigned char   e_ident[EI_NIDENT];	/* ELF "magic number" */
  unsigned long   e_entry;		/* Entry point virtual address */
  unsigned long   e_phoff;		/* Program header table file offset */
  unsigned long   e_shoff;		/* Section header table file offset */
  unsigned long   e_version;		/* Identifies object file version */
  unsigned long   e_flags;		/* Processor-specific flags */
  unsigned int    e_type;		/* Identifies object file type */
  unsigned int    e_machine;		/* Specifies required architecture */
  unsigned int    e_ehsize;		/* ELF header size in bytes */
  unsigned int    e_phentsize;		/* Program header table entry size */
  unsigned int    e_phnum;		/* Program header table entry count */
  unsigned int    e_shentsize;		/* Section header table entry size */
  unsigned int    e_shnum;		/* Section header table entry count */
  unsigned int    e_shstrndx;		/* Section header string table index */
} Elf_Internal_Ehdr;


/* Program header */
typedef struct elf_internal_phdr {
  unsigned long   p_type;		/* Identifies program segment type */
  unsigned long   p_flags;		/* Segment flags */
  unsigned long   p_offset;		/* Segment file offset */
  unsigned long   p_vaddr;		/* Segment virtual address */
  unsigned long   p_paddr;		/* Segment physical address */
  unsigned long   p_filesz;		/* Segment size in file */
  unsigned long   p_memsz;		/* Segment size in memory */
  unsigned long   p_align;		/* Segment alignment, file & memory */
} Elf_Internal_Phdr;


/* Section header */
typedef struct elf_internal_shdr {
  unsigned int       sh_name;		/* Section name, index in string tbl */
  unsigned int       sh_type;		/* Type of section */
  unsigned long      sh_flags;		/* Miscellaneous section attributes */
  unsigned long      sh_addr;		/* Section virtual addr at execution */
  unsigned long      sh_size;		/* Size of section in bytes */
  unsigned long      sh_entsize;	/* Entry size if section holds table */
  unsigned long      sh_link;		/* Index of another section */
  unsigned long      sh_info;		/* Additional section information */
  unsigned long      sh_offset;		/* Section file offset */
  unsigned int       sh_addralign;	/* Section alignment */
  unsigned int       sh_phdridx;	/* Program header index */
} Elf_Internal_Shdr;


/* Relocation Entries */
typedef struct elf_internal_rela {
  unsigned long   r_offset;	/* Location at which to apply the action */
  unsigned long   r_info;	/* Index and type of relocation */
  unsigned long   r_addend;	/* Constant addend used to compute value */
} Elf_Internal_Rela;


/* Symbol table entry */
typedef struct elf_internal_sym {
  unsigned long   st_name;	/* Symbol name, index in string tbl */
  unsigned long   st_value;	/* Value of the symbol */
  unsigned long   st_size;	/* Associated symbol size */
  unsigned char   st_info;	/* Type and binding attributes */
  unsigned char   st_other;	/* Visibiity, and target specific */
  unsigned int    st_shndx;	/* Associated section index */
} Elf_Internal_Sym;




/*
 **************************************************************************
 *
 *		Local variables
 *
 **************************************************************************
 */

/* Current file position */
static unsigned long cur_sect_offset;
static unsigned long cur_seg_offset;
static unsigned long cur_size;
static unsigned int cur_segment;
static unsigned int cur_section;
static unsigned int cur_mode = ELF_MODE_NONE;
static __u8 *cur_contents = NULL;


/* ELF input file headers */
static Elf_Internal_Ehdr elf_header;
static Elf_Internal_Shdr *section_headers = NULL;
static Elf_Internal_Phdr *program_headers = NULL;
static Elf_Internal_Rela *reloc_headers = NULL;
static Elf_Internal_Shdr *symtab_shndx = NULL;
static Elf_Internal_Sym  *symtab = NULL;
static unsigned int reloc_header_num;
static unsigned int symtab_num;
static unsigned int symtab_sectnum;
static unsigned int text_segment;
static unsigned int data_segment;


/* Misc. local variables */
static char elf_error[MAX_ERR_LEN + 1];
static int infile = -1;




/*
 **************************************************************************
 *
 *		Internal routines
 *
 **************************************************************************
 */

/*
 * Set error message
 */
static void seterror __F((msg, arg), const char *msg AND const char *arg)
{
  size_t arglen;
  char *argmsg;

  /* Check if we have anything to do */
  if (msg == NULL) {
	elf_error[0] = '\0';
	return;
  }

  /* Check if error message already set */
  if (elf_error[0] != '\0')
	return;

  /* Without an argument, just copy the error message into global storage */
  if (arg == NULL) {
	strncpy(elf_error, msg, MAX_ERR_LEN);
	return;
  }


  /* Otherwise create new message within global storage */
  arglen = strlen(arg);
  if ((arglen + strlen(msg)) >= MAX_ERR_LEN) {
	arglen = MAX_ERR_LEN - strlen(msg);
	argmsg = (char *)nbmalloc(arglen + 1);
	strncpy(argmsg, arg, arglen);
	sprintf(elf_error, msg, argmsg);
	free(argmsg);
  } else
	sprintf(elf_error, msg, arg);
}



/*
 * Read a buffer from input file
 */
static voidstar get_data __F((offset, size),
				unsigned long offset AND
				unsigned long size)
{
  voidstar buf;

  /* Seek to region of interest */
  if (offset > LONG_MAX || lseek(infile, offset, SEEK_SET) == (off_t)(-1)) {
	seterror("Unable to seek in ELF file", NULL);
	return(NULL);
  }

  /* Check size value */
  if (size > INT_MAX) {
	seterror("Read size too large", NULL);
	return(NULL);
  }

  /* Now read the file into the buffer */
  buf = nbmalloc(size);
  if (size > 0 && (unsigned long)read(infile, buf, size) != size) {
	seterror("Unable to read in ELF file", NULL);
	free(buf);
	return(NULL);
  }
  return(buf);
}



/*
 * Apply any relocations to the data in the buffer
 */
static int apply_relocs __F((sectnum), unsigned int sectnum)
{
  Elf_Internal_Rela *prel;
  Elf_Internal_Sym *psym;
  Elf_Internal_Shdr *sect, *symsect;
  Elf_Internal_Phdr *phdr, *symphdr;
  unsigned long reloc, reloffset, relval, lastrel, symval, sectaddr;
  unsigned int i, j, relsize, relsym, reltype, ispcrel;

  /* Check if we have anything to do */
  if (reloc_headers == NULL || reloc_header_num == 0)
	return (TRUE);

  /* Determine current section and program header */
  assert(sectnum < (unsigned int)elf_header.e_shnum);
  sect = &(section_headers[sectnum]);
  assert(sect->sh_phdridx != (unsigned int)(-1));
  phdr = &(program_headers[sect->sh_phdridx]);

  /* Determine section relocation and start address */
  reloc = phdr->p_paddr - phdr->p_vaddr;
  sectaddr = sect->sh_addr + reloc;

  /* Scan through all relocations and check if they match */
  lastrel = cur_size;
  for (i = 0, prel = reloc_headers; i < reloc_header_num; i++, prel++) {
	/* Check relocation type */
	ispcrel = FALSE;
	reltype = ELF_R_TYPE(prel->r_info);
	switch (reltype) {
		case R_386_PC32:
				ispcrel = TRUE;
				/* Fall through */
		case R_386_32:
				relsize = 4;
				break;
		case R_386_PC16:
				ispcrel = TRUE;
				/* Fall through */
		case R_386_16:
				relsize = 2;
				break;
		case R_386_PC8:
				ispcrel = TRUE;
				/* Fall through */
		case R_386_8:
				relsize = 1;
				break;
		default:
				relsize = 0;
				break;
	}

	/* Relative relocations are only handled with relocatable modules */
	if (elf_header.e_type != ET_REL && ispcrel)
		continue;

	/* Check if relocation is within buffer range */
	reloffset = prel->r_offset;
	if (relsize == 0 || relsize > cur_size ||
	    reloffset > (cur_size - relsize))
		continue;

	/* Determine relocation symbol and it's value */
	psym = NULL;
	relsym = ELF_R_SYM(prel->r_info);
	if (elf_header.e_type == ET_REL && relsym > 0) {
		if (relsym >= symtab_num) {
			seterror("Invalid symbol index in relocation table",
									NULL);
			return(FALSE);
		}
		psym = &(symtab[relsym]);
		if (ELF_ST_TYPE(psym->st_info) == STT_FILE ||
		    ELF_ST_TYPE(psym->st_info) == STT_TLS) {
			seterror("Invalid relocation symbol type", NULL);
			return(FALSE);
		}
		symval = psym->st_value;
		symsect = (psym->st_shndx < (unsigned int)elf_header.e_shnum ?
				&(section_headers[psym->st_shndx]) : NULL);
		if (reloffset != lastrel && symsect != NULL) {
			assert(symsect->sh_phdridx != (unsigned int)(-1));
			symphdr = &(program_headers[symsect->sh_phdridx]);
			symval += symsect->sh_addr -
					symphdr->p_vaddr + symphdr->p_paddr;
		}
	} else
		symval = (reloffset == lastrel ? 0 : reloc);

	/* Apply relocation */
	relval = 0;
	for (j = ((unsigned int)reloffset + relsize - 1);
	     j >= (unsigned int)reloffset; j--) {
		relval <<= 8;
		relval += cur_contents[j] & 0x00FF;
	}
	if (ispcrel)
		relval += symval - (reloffset + sectaddr);
	else
		relval += symval;
	for (j = (unsigned int)reloffset;
	     j < ((unsigned int)reloffset + relsize); j++) {
		cur_contents[j] = (__u8)(relval & 0xFF);
		relval >>= 8;
	}
	lastrel = reloffset;
  }
  return(TRUE);
}



/*
 * Read symbols from ELF input file
 */
static int slurp_symbols __F((sect, sectnum),
				Elf_Internal_Shdr *sect AND
				unsigned int sectnum)
{
  Elf_Sym *esyms;
  Elf_Sym_Shndx *shndx;
  Elf_Internal_Sym *psym;
  unsigned int i, external, invalid, common;

  /* Read symbol section from ELF input file */
  if ((esyms = get_data(sect->sh_offset, sect->sh_size)) == NULL)
	return(FALSE);

  /* Read symbol index table from ELF input file */
  shndx = NULL;
  if (symtab_shndx != NULL && symtab_shndx->sh_link == sectnum) {
	shndx = get_data(symtab_shndx->sh_offset, symtab_shndx->sh_size);
	if (shndx == NULL) {
		free(esyms);
		return(FALSE);
	}
  }

  /* Assign memory space for symbol table */
  symtab_sectnum = sectnum;
  symtab_num = (unsigned int)(sect->sh_size / sect->sh_entsize);
  symtab = (Elf_Internal_Sym *)nbcalloc(symtab_num, sizeof(Elf_Internal_Sym));

  /* Convert symbols into internal form */
  external = 0;
  common = 0;
  invalid = 0;
  for (i = 0, psym = symtab; i < symtab_num; i++, psym++) {
	psym->st_name  = get_long(esyms[i].st_name);
	psym->st_value = get_long(esyms[i].st_value);
	psym->st_size  = get_long(esyms[i].st_size);
	psym->st_shndx = get_word(esyms[i].st_shndx);
	psym->st_info  = (unsigned char)(esyms[i].st_info);
	psym->st_other = (unsigned char)(esyms[i].st_other);
	if (psym->st_shndx == SHN_XINDEX && shndx != NULL)
		psym->st_shndx = get_long(shndx[i].est_shndx);
	if (psym->st_shndx == SHN_COMMON)
		common++;
	else if (psym->st_shndx != SHN_ABS &&
	         psym->st_shndx >= elf_header.e_shnum)
		invalid++;
	else if (psym->st_shndx == SHN_UNDEF && i > 0)
		external++;
  }
  if (shndx != NULL)
	free(shndx);
  free(esyms);
  if (common > 0) {
	seterror("ELF input file contains common references", NULL);
	return(FALSE);
  }
  if (external > 0) {
	seterror("ELF input file contains external references", NULL);
	return(FALSE);
  }
  if (invalid > 0) {
	seterror("Invalid section index in symbol table", NULL);
	return(FALSE);
  }
  return(TRUE);
}



/*
 * Read relocations from ELF input file (RELA type)
 */
static int slurp_rela_relocs __F((sect), Elf_Internal_Shdr *sect)
{
  Elf_Rela *erels;
  Elf_Internal_Rela *prel;
  unsigned int i;

  /* Read relocation section from ELF input file */
  if ((erels = get_data(sect->sh_offset, sect->sh_size)) == NULL)
	return(FALSE);

  /* Assign memory space for relocation info */
  reloc_header_num = (unsigned int)(sect->sh_size / sect->sh_entsize);
  reloc_headers = (Elf_Internal_Rela *)nbcalloc(reloc_header_num,
						sizeof(Elf_Internal_Rela));

  /* Convert relocations into internal form */
  for (i = 0, prel = reloc_headers; i < reloc_header_num; i++, prel++) {
	prel->r_offset = get_long(erels[i].r_offset);
	prel->r_info   = get_long(erels[i].r_info);
	prel->r_addend = get_long(erels[i].r_addend);
  }
  free(erels);
  return(TRUE);
}



/*
 * Read relocations from ELF input file (REL type)
 */
static int slurp_rel_relocs __F((sect), Elf_Internal_Shdr *sect)
{
  Elf_Rel *erels;
  Elf_Internal_Rela *prel;
  unsigned int i;

  /* Read relocation section from ELF input file */
  if ((erels = get_data(sect->sh_offset, sect->sh_size)) == NULL)
	return(FALSE);

  /* Assign memory space for relocation info */
  reloc_header_num = (unsigned int)(sect->sh_size / sect->sh_entsize);
  reloc_headers = (Elf_Internal_Rela *)nbcalloc(reloc_header_num,
						sizeof(Elf_Internal_Rela));

  /* Convert relocations into internal form */
  for (i = 0, prel = reloc_headers; i < reloc_header_num; i++, prel++) {
	prel->r_offset = get_long(erels[i].r_offset);
	prel->r_info   = get_long(erels[i].r_info);
	prel->r_addend = 0;
  }
  free(erels);
  return(TRUE);
}



/*
 * Read all relocations for a section
 */
static int get_section_relocs __F((sectnum), unsigned int sectnum)
{
  Elf_Internal_Shdr *sect;
  unsigned int i;

  /* Remove any old relocations */
  if (reloc_headers != NULL) {
	free(reloc_headers);
	reloc_headers = NULL;
	reloc_header_num = 0;
  }

  /* We don't need relocations anything but PROGBITS sections */
  if (section_headers[sectnum].sh_type != SHT_PROGBITS)
	return(TRUE);

  /* Find relocation section for current section */
  for (i = 0, sect = section_headers;
       i < (unsigned int)elf_header.e_shnum;
       i++, sect++)
	if (sect->sh_info == sectnum && sect->sh_size > 0 &&
	    (sect->sh_type == SHT_RELA || sect->sh_type == SHT_REL))
		break;
  if (i == (unsigned int)elf_header.e_shnum)
	sect = NULL;

  /*
   * Preloaded FreeBSD modules are special: they include the relocation
   * section into the text segment. Even though they are dynamic modules,
   * no relocation should be done. The FreeBSD kernel will do the linking
   * and relocation at runtime.
   */
  if (elf_header.e_type == ET_DYN &&
      sect != NULL && sect->sh_phdridx != (unsigned int)(-1))
	return(TRUE);

  /* The relocation section should not be allocated to any memory region */
  if (sect != NULL && (sect->sh_flags & SHF_ALLOC) == SHF_ALLOC) {
	seterror("Relocation section allocated to memory region", NULL);
	return(FALSE);
  }

  /* Read relocations from ELF input file */
  if (sect == NULL) {
	if (elf_header.e_type != ET_EXEC) {
		seterror("Missing relocation information", NULL);
		return(FALSE);
	}
	return(TRUE);
  }
  if (!(sect->sh_type == SHT_RELA ?
				slurp_rela_relocs(sect) :
				slurp_rel_relocs(sect)))
	return(FALSE);

  /* Check if the relocation section requires to load any symbols */
  if (reloc_headers == NULL || reloc_header_num == 0 || sect->sh_link == 0)
	return(TRUE);

  /* Check for correct symbol section number */
  if (sect->sh_link >= elf_header.e_shnum) {
	seterror("Invalid section index for symbol table", NULL);
	return(FALSE);
  }
  i = (unsigned int)(sect->sh_link);

  /* Check if symbol table already loaded */
  if (symtab != NULL && symtab_sectnum == i)
	return(TRUE);

  /* Clear any old symbol table */
  if (symtab != NULL) {
	free(symtab);
	symtab = NULL;
	symtab_num = 0;
	symtab_sectnum = 0;
  }

  /* Now read the symbol table from the input file */
  sect = &(section_headers[i]);
  if (sect->sh_type != SHT_SYMTAB) {
	seterror("Invalid symbol table section", NULL);
	return(FALSE);
  }
  if (!slurp_symbols(sect, i)) {
	seterror("Unable to read symbol table", NULL);
	return(FALSE);
  }

  /* Relocatable files need a symbol table */
  if (elf_header.e_type == ET_REL && symtab == NULL) {
	seterror("Missing symbol table", NULL);
	return(FALSE);
  }
  return(TRUE);
}



/*
 * Read a section buffer from ELF input file
 */
static int get_section_contents __F((sectnum), unsigned int sectnum)
{
  Elf_Internal_Shdr *sect;

  /* Determine current section */
  if (sectnum >= (unsigned int)elf_header.e_shnum) {
	seterror("Invalid section number", NULL);
	return(FALSE);
  }
  sect = &(section_headers[sectnum]);

  /* Delete any old section contents */
  if (cur_contents != NULL) {
	free(cur_contents);
	cur_contents = NULL;
  }

  /* Check if section contains any data */
  if (sect->sh_type == SHT_NOBITS || sect->sh_type == SHT_NULL)
	return(TRUE);

  /* Allocate new buffer and read contents from ELF input file */
  cur_contents = get_data(sect->sh_offset, sect->sh_size);
  cur_size = (cur_contents != NULL ? sect->sh_size : 0);
  return(cur_contents != NULL);
}



/*
 * Read contents of section as a memory image, e.g. with all relocations
 * applied.
 */
static int get_section __F((sectnum), unsigned int sectnum)
{
  /* Read section contents */
  if (!get_section_contents(sectnum)) {
	seterror("Unable to read section contents", NULL);
	return(FALSE);
  }

  /* Now read all section relocations */
  if (!get_section_relocs(sectnum)) {
	seterror("Unable to read section relocations", NULL);
	return(FALSE);
  }

  /* Apply all relocations to buffer contents */
  if (reloc_headers != NULL && !apply_relocs(sectnum)) {
	seterror("Unable to apply relocations", NULL);
	return(FALSE);
  }
  return(TRUE);
}



/*
 * Build program headers in case we don't have them in the file
 */
static void build_program_headers __F_NOARGS
{
  Elf_Internal_Shdr *sect;
  unsigned long align[2];
  unsigned long startaddr[2];
  unsigned long endaddr[2];
  unsigned long sectstart, sectend, sectalign;
  unsigned int i, num;

  /* Initialize arrays */
  for (i = 0; i < 2; i++) {
	startaddr[i] = 0;
	endaddr[i] = 0;
	align[i] = 4;
  }

  /*
   * The logic here is to build program headers for only loadable
   * segments. We define two loadable segments: one which is read-
   * only (and usually just contains the .text section), and one
   * which is writable.
   */
  program_headers = (Elf_Internal_Phdr *)nbcalloc(3, sizeof(Elf_Internal_Phdr));
  program_headers[0].p_type = PT_NULL;
  program_headers[0].p_flags = 0;
  program_headers[1].p_type = PT_LOAD;
  program_headers[1].p_flags = PF_R | PF_X;
  program_headers[2].p_type = PT_LOAD;
  program_headers[2].p_flags = PF_R | PF_W;
  elf_header.e_phnum = 3;
  text_segment = 1;
  data_segment = 2;

  /*
   * Now scan through the list of sections, and assign every section
   * to one of the program headers. We only care for allocatable
   * sections.
   */
  for (i = 0, sect = section_headers;
       i < (unsigned int)elf_header.e_shnum;
       i++, sect++) {
	if (sect->sh_type != SHT_NULL && 
	    (sect->sh_flags & SHF_ALLOC) == SHF_ALLOC) {
		num = ((sect->sh_flags & SHF_WRITE) == SHF_WRITE ? 1 : 0);
		sectstart = sect->sh_addr;
		sectend = sect->sh_addr + sect->sh_size;
		sectalign = sect->sh_addralign;
		if (endaddr[num] < sectend && startaddr[num] < sectend)
			endaddr[num] = sectend;
		if (startaddr[num] > sectstart && endaddr[num] > sectstart)
			startaddr[num] = sectstart;
		if (startaddr[num] == sectstart)
			align[num] = sectalign;
		sect->sh_phdridx = num + 1;
	}
  }

  /*
   * Finally build the program headers using the information we just
   * gathered.
   */
  for (i = 0; i < 2; i++) {
	if ((endaddr[i] - startaddr[i]) == 0) {
		program_headers[i + 1].p_type = PT_NULL;
		program_headers[i + 1].p_flags = 0;
		if (i == 0)
			text_segment = (unsigned int)(-1);
		else if (i == 1)
			data_segment = (unsigned int)(-1);
	} else {
		program_headers[i + 1].p_vaddr = startaddr[i];
		program_headers[i + 1].p_paddr = startaddr[i];
		program_headers[i + 1].p_memsz = endaddr[i] - startaddr[i];
		program_headers[i + 1].p_align = align[i];
	}
  }
}



/*
 * Read all program headers from input file
 */
static int get_program_headers __F((textofs, dataofs),
				unsigned long textofs AND
				unsigned long dataofs)
{
  Elf_Phdr *phdrs;
  Elf_Internal_Phdr *internal;
  Elf_Internal_Shdr *sect;
  unsigned long sectstart, sectend;
  unsigned long flags;
  unsigned int i, j, num;

  /* Table of segment types */
  static struct {
	unsigned long  flags;
	unsigned long  ofs;
	unsigned int  *index;
  } stypes[2] = {
	{ PF_R | PF_X,	0,	&text_segment },
	{ PF_R | PF_W,	0,	&data_segment }
  };

  /* Safety check */
  assert(program_headers == NULL);

  /* Rebuild program headers if there are none */
  if ((num = (unsigned int)elf_header.e_phnum) == 0) {
	build_program_headers();
	if (elf_header.e_type != ET_DYN) {
		if (text_segment != (unsigned int)(-1))
			program_headers[text_segment].p_paddr += textofs;
		if (data_segment != (unsigned int)(-1))
			program_headers[data_segment].p_paddr += dataofs;
	}
	return(TRUE);
  }

  /* Setup offsets in segment type table */
  stypes[0].ofs = textofs;
  stypes[1].ofs = dataofs;

  /* Read program headers from input file */
  if ((phdrs = get_data(elf_header.e_phoff,
				    elf_header.e_phentsize * num)) == NULL)
	return(FALSE);

  /* Get us a buffer for internal representation */
  program_headers = (Elf_Internal_Phdr *)nbcalloc(elf_header.e_phnum,
						sizeof(Elf_Internal_Phdr));

  /* Convert program headers into internal representation */
  for (i = 0, internal = program_headers; i < num; i++, internal++) {
	internal->p_type   = get_long(phdrs[i].p_type);
	internal->p_offset = get_long(phdrs[i].p_offset);
	internal->p_vaddr  = get_long(phdrs[i].p_vaddr);
	internal->p_paddr  = get_long(phdrs[i].p_paddr);
	internal->p_filesz = get_long(phdrs[i].p_filesz);
	internal->p_memsz  = get_long(phdrs[i].p_memsz);
	internal->p_flags  = get_long(phdrs[i].p_flags);
	internal->p_align  = get_long(phdrs[i].p_align);
  }
  free(phdrs);

  /* Scan through sections and assign each to a program header */
  for (i = 0, sect = section_headers;
       i < (unsigned int)elf_header.e_shnum;
       i++, sect++) {
	if ((sect->sh_flags & SHF_ALLOC) != SHF_ALLOC)
		continue;
	flags = PF_R;
	if ((sect->sh_flags & SHF_WRITE) == SHF_WRITE)
		flags |= PF_W;
	if ((sect->sh_flags & SHF_EXECINSTR) == SHF_EXECINSTR)
		flags |= PF_X;
	sectstart = sect->sh_addr;
	sectend = sect->sh_addr + sect->sh_size;
	for (j = 0, internal = program_headers; j < num; j++, internal++) {
		if (internal->p_type == PT_LOAD &&
		    (internal->p_flags & PF_MASK) == flags &&
		    sectstart >= internal->p_vaddr &&
		    sectend <= (internal->p_vaddr + internal->p_memsz)) {
			sect->sh_phdridx = j;
			break;
		}
	}
	if (j == num) {
		seterror("Section not assignable to segment", NULL);
		return(FALSE);
	}
  }

  /* Scan through all segments and determine text and data segment */
  for (j = 0; j < 2; j++) {
	flags = stypes[j].flags;
	for (i = 0; i < (unsigned int)elf_header.e_phnum; i++)
		if (program_headers[i].p_type == PT_LOAD &&
		    (program_headers[i].p_flags & PF_MASK) == flags)
			break;
	if (i < (unsigned int)elf_header.e_phnum) {
		if (elf_header.e_type != ET_DYN)
			program_headers[i].p_paddr += stypes[j].ofs;
		*stypes[j].index = i;
	} else
		*stypes[j].index = (unsigned int)(-1);
  }
  return(TRUE);
}



/*
 * Read all section headers from input file
 */
static int get_section_headers __F((num), unsigned int num)
{
  Elf_Shdr *shdrs;
  Elf_Internal_Shdr *internal;
  unsigned int i;

  /* Safety check */
  assert(section_headers == NULL);

  /* Determine number of section headers to read */
  if (num == 0 && (num = (unsigned int)elf_header.e_shnum) == 0)
	return(FALSE);

  /* Read section headers from input file */
  if ((shdrs = get_data(elf_header.e_shoff,
				elf_header.e_shentsize * num)) == NULL)
	return(FALSE);

  /* Get us a buffer for internal representation */
  section_headers = (Elf_Internal_Shdr *)nbcalloc(num,
						sizeof(Elf_Internal_Shdr));

  /* Convert section headers into internal representation */
  symtab_shndx = NULL;
  for (i = 0, internal = section_headers; i < num; i++, internal++) {
	internal->sh_name      = get_long(shdrs[i].sh_name);
	internal->sh_type      = get_long(shdrs[i].sh_type);
	internal->sh_flags     = get_long(shdrs[i].sh_flags);
	internal->sh_addr      = get_long(shdrs[i].sh_addr);
	internal->sh_offset    = get_long(shdrs[i].sh_offset);
	internal->sh_size      = get_long(shdrs[i].sh_size);
	internal->sh_link      = get_long(shdrs[i].sh_link);
	internal->sh_info      = get_long(shdrs[i].sh_info);
	internal->sh_addralign = get_long(shdrs[i].sh_addralign);
	internal->sh_entsize   = get_long(shdrs[i].sh_entsize);
	internal->sh_phdridx   = (unsigned int)(-1);
	if (internal->sh_type == SHT_SYMTAB_SHNDX) {
		if (symtab_shndx != NULL) {
			seterror("Too many symtab shndx tables", NULL);
			free(shdrs);
			return(FALSE);
		}
		symtab_shndx = internal;
	}
  }
  free(shdrs);
  return(TRUE);
}



/*
 * Read the ELF file header out of the input file
 */
static int get_file_header __F_NOARGS
{
  Elf_Ehdr ehdr;
  unsigned int i;

  /* Read in the ID entry */
  if (read(infile, &(ehdr.e_ident[0]), EI_NIDENT) != EI_NIDENT)
    return(FALSE);

  /* Check that it really is a ELF file */
  if (ehdr.e_ident[EI_MAG0] != ELFMAG0 ||
      ehdr.e_ident[EI_MAG1] != ELFMAG1 ||
      ehdr.e_ident[EI_MAG2] != ELFMAG2 ||
      ehdr.e_ident[EI_MAG3] != ELFMAG3)
	return(FALSE);

  /* We can only handle LSB data */
  if (ehdr.e_ident[EI_DATA] != ELFDATANONE &&
      ehdr.e_ident[EI_DATA] != ELFDATA2LSB)
	return(FALSE);

  /* For now we only support 32 bit ELF files */
  if (ehdr.e_ident[EI_CLASS] != ELFCLASS32)
	return(FALSE);

  /* Read in the rest of the header */
  i = sizeof(ehdr) - EI_NIDENT;
  if ((unsigned int)read(infile, &(ehdr.e_type), i) != i)
	return(FALSE);

  /* Copy the raw ELF header into our global repository */
  for (i = 0; i < EI_NIDENT; i++)
	elf_header.e_ident[i] = ehdr.e_ident[i];
  elf_header.e_type      = get_word(ehdr.e_type);
  elf_header.e_machine   = get_word(ehdr.e_machine);
  elf_header.e_version   = get_long(ehdr.e_version);
  elf_header.e_entry     = get_long(ehdr.e_entry);
  elf_header.e_phoff     = get_long(ehdr.e_phoff);
  elf_header.e_shoff     = get_long(ehdr.e_shoff);
  elf_header.e_flags     = get_long(ehdr.e_flags);
  elf_header.e_ehsize    = get_word(ehdr.e_ehsize);
  elf_header.e_phentsize = get_word(ehdr.e_phentsize);
  elf_header.e_phnum     = get_word(ehdr.e_phnum);
  elf_header.e_shentsize = get_word(ehdr.e_shentsize);
  elf_header.e_shnum     = get_word(ehdr.e_shnum);
  elf_header.e_shstrndx  = get_word(ehdr.e_shstrndx);

  /* Check for various error conditions */
  if (elf_header.e_version != EV_CURRENT ||
      elf_header.e_shnum >= UINT_MAX ||
      elf_header.e_phnum >= UINT_MAX ||
      (elf_header.e_shnum > 0 &&
       elf_header.e_shentsize < sizeof(Elf_Shdr)) ||
      (elf_header.e_phnum > 0 &&
       elf_header.e_phentsize < sizeof(Elf_Phdr)) ||
      (elf_header.e_type != ET_EXEC &&
       elf_header.e_type != ET_REL &&
       elf_header.e_type != ET_DYN) ||
      (elf_header.e_machine != EM_386 &&
       elf_header.e_machine != EM_486))
	return(FALSE);

  /* Read possible extensions in the first section header */
  if (elf_header.e_shoff > 0)
	(void)get_section_headers(1);
  if (section_headers != NULL) {
	if (elf_header.e_shnum == 0)
		elf_header.e_shnum = section_headers[0].sh_size;
	if (elf_header.e_shstrndx == SHN_XINDEX)
		elf_header.e_shstrndx = section_headers[0].sh_link;
	free(section_headers);
	section_headers = NULL;
	if (elf_header.e_shnum >= UINT_MAX ||
	    elf_header.e_shstrndx >= elf_header.e_shnum)
		return(FALSE);
  }
  return(TRUE);
}




/*
 **************************************************************************
 *
 *		Public routines
 *
 **************************************************************************
 */

/*
 * Return error message for last error
 */
char *elfgeterr __F_NOARGS
{
  if (!elf_error[0])
	return(NULL);
  return(elf_error);
}



/*
 * Close ELF input file
 */
void elfclose __F_NOARGS
{
  if (program_headers != NULL) {
	free(program_headers);
	program_headers = NULL;
  }
  if (section_headers != NULL) {
	free(section_headers);
	section_headers = NULL;
  }
  symtab_shndx = NULL;
  if (reloc_headers != NULL) {
	free(reloc_headers);
	reloc_headers = NULL;
	reloc_header_num = 0;
  }
  if (symtab != NULL) {
	free(symtab);
	symtab = NULL;
	symtab_num = 0;
  }
  if (cur_contents != NULL) {
	free(cur_contents);
	cur_contents = NULL;
  }
  if (infile != -1) {
	close(infile);
	infile = -1;
  }
}



/*
 * Open ELF input file
 */
int elfopen __F((fname, textofs, dataofs),
				const char *fname AND
				unsigned long textofs AND
				unsigned long dataofs)
{
  struct stat statbuf;

  /* Reset error pointer */
  seterror(NULL, NULL);

  /* Check if file is already open */
  if (infile != -1) {
	seterror("ELF input file '%s' already open", fname);
	return(FALSE);
  }

  /* Check that the input file really is a file */
  if (stat(fname, &statbuf) < 0) {
	seterror("ELF input file '%s' not found", fname);
	return(FALSE);
  }
  if (!S_ISREG(statbuf.st_mode)) {
	seterror("ELF input file '%s' is not an ordinary file", fname);
	return(FALSE);
  }

  /* Open the input file */
  if ((infile = open(fname, O_RDONLY | O_BINARY)) < 0) {
	seterror("Unable to open ELF input file '%s'", fname);
	return(FALSE);
  }

  /* Read the ELF file header */
  if (!get_file_header()) {
	seterror("Invalid ELF file header", NULL);
	elfclose();
	return(FALSE);
  }

  /* Read all section headers */
  if (!get_section_headers(0)) {
	seterror("Invalid ELF section headers", NULL);
	elfclose();
	return(FALSE);
  }

  /* Read all program headers */
  if (!get_program_headers(textofs, dataofs)) {
	seterror("Invalid ELF program headers", NULL);
	elfclose();
	return(FALSE);
  }
  cur_mode = ELF_MODE_NONE;
  return(TRUE);
}



/*
 * Initialize read access
 */
int elfset __F((mode, addr, size),
				unsigned int mode AND
				unsigned long *addr AND
				unsigned long *size)
{
  unsigned int i, index;
  unsigned long t_addr, t_size, t_flags;

  /* Check if we have an input file */
  if (infile == -1) {
	seterror("No ELF input file opened", NULL);
	return(FALSE);
  }

  /* Find segment or section */
  t_addr = 0;
  t_size = 0;
  index = (unsigned int)(-1);
  switch (mode) {
	case ELF_MODE_TEXT:
	case ELF_MODE_DATA:
		/* Find first (and only) segment */
		i = (mode == ELF_MODE_TEXT ? text_segment : data_segment);
		if (i != (unsigned int)(-1)) {
			t_addr = program_headers[i].p_paddr;
			t_size = program_headers[i].p_memsz;
			index = i;
		}
		break;
	case ELF_MODE_SYMTAB:
	case ELF_MODE_STRTAB:
		/* Just determine the size of the section */
		t_flags = (mode == ELF_MODE_SYMTAB ? SHT_SYMTAB : SHT_STRTAB);
		for (i = 0; i < (unsigned int)elf_header.e_shnum; i++)
			if (section_headers[i].sh_type == t_flags)
				t_size += section_headers[i].sh_size;
		break;
	case ELF_MODE_DYNAMIC:
		/* Determine the size of the section / segment */
		t_flags = PF_R | PF_W;
		for (i = 0; i < (unsigned int)elf_header.e_phnum; i++)
			if (program_headers[i].p_type == PT_DYNAMIC &&
			    (program_headers[i].p_flags & PF_MASK) == t_flags)
				break;
		if (i < (unsigned int)elf_header.e_phnum) {
			/*
			 * If there exists a dynamic segment, we have to set
			 * an index value. This index will not be used to find
			 * any sections, but only to determine the amount of
			 * fillup space between sections.
			 */
			t_addr = program_headers[i].p_paddr;
			t_size = program_headers[i].p_memsz;
			index = i;
		} else {
			for (i = 0; i < (unsigned int)elf_header.e_shnum; i++)
				if (section_headers[i].sh_type == SHT_DYNAMIC)
					t_size += section_headers[i].sh_size;
		}
		break;
	default:
		seterror("Invalid ELF read mode", NULL);
		return(FALSE);
  }
  *addr = t_addr;
  *size = t_size;

  /* Initialize all global values */
  if (cur_contents != NULL) {
	free(cur_contents);
	cur_contents = NULL;
  }
  cur_mode = mode;
  cur_size = 0;
  cur_sect_offset = 0;
  cur_seg_offset = 0;
  cur_section = 0;
  cur_segment = index;
  return(TRUE);
}



/*
 * Read next section buffer from ELF input file
 */
int elfread __F((buf, size), __u8 *buf AND size_t *size)
{
  unsigned long stype;
  unsigned long rdsize;
  unsigned int bufoffset;
  unsigned int i;

  /* Check if we have an input file */
  if (infile == -1) {
	seterror("No ELF input file opened", NULL);
	return(FALSE);
  }

  /*  Check current read mode and determine section type */
  switch (cur_mode) {
	case ELF_MODE_TEXT:
		stype = SHT_NULL;
		break;
	case ELF_MODE_DATA:
		stype = SHT_NULL;
		break;
	case ELF_MODE_SYMTAB:
		stype = SHT_SYMTAB;
		break;
	case ELF_MODE_STRTAB:
		stype = SHT_STRTAB;
		break;
	case ELF_MODE_DYNAMIC:
		stype = SHT_DYNAMIC;
		break;
	default:
		seterror("Invalid read mode selected", NULL);
		return(FALSE);
  }

  /* Check if we have anything to do at all */
  if (*size == 0)
	return(TRUE);

  /* Read next buffer from input file */
  bufoffset = 0;
  rdsize = 0;
  while (TRUE) {
	/* Fillup any space below current section */
	if (cur_contents != NULL &&
	    cur_sect_offset == 0 &&
	    cur_segment != (unsigned int)(-1)) {
		unsigned long sectoffset;
		Elf_Internal_Phdr *phdr;
		Elf_Internal_Shdr *shdr;

		/* Determine offset of section from start of segment */
		phdr = &(program_headers[cur_segment]);
		shdr = &(section_headers[cur_section]);
		sectoffset = 0;
		if (phdr->p_vaddr < shdr->sh_addr)
			sectoffset = shdr->sh_addr - phdr->p_vaddr;

		/* Fillup space below current section */
		if (cur_seg_offset < sectoffset) {
			/* Fill buffer with zero values */
			rdsize = sectoffset - cur_seg_offset;
			if (rdsize > (*size - bufoffset))
				rdsize = (*size - bufoffset);
			if (rdsize > 0) {
				memset(&(buf[bufoffset]), 0, (size_t)rdsize);
				bufoffset += (unsigned int)rdsize;
				cur_seg_offset += rdsize;
			}
			/* Return if buffer filled */
			if (*size == (size_t)bufoffset)
				return(TRUE);
		}

	}

	/* Read next buffer from current section */
	if (cur_contents != NULL) {
		assert(cur_size >= cur_sect_offset);
		rdsize = cur_size - cur_sect_offset;
		if (rdsize > (*size - bufoffset))
			rdsize = (*size - bufoffset);
		if (rdsize > 0) {
			/* Copy section data into buffer */
			memcpy(&(buf[bufoffset]),
					&(cur_contents[cur_sect_offset]),
					(size_t)rdsize);
			/* Advance offset values */
			bufoffset += (unsigned int)rdsize;
			cur_seg_offset += rdsize;
			cur_sect_offset += rdsize;
			/* Return if buffer filled */
			if (*size == (size_t)bufoffset)
				return(TRUE);
		}
	}

	/* Find next suitable section */
	if (cur_section < (unsigned int)elf_header.e_shnum) {
		i = (cur_contents == NULL ? 0 : cur_section + 1);
nextsect:
		while (i < (unsigned int)elf_header.e_shnum) {
			if (section_headers[i].sh_size > 0 &&
			    ((stype == SHT_NULL &&
			      section_headers[i].sh_phdridx == cur_segment) ||
			     (stype != SHT_NULL &&
			      section_headers[i].sh_type == stype)))
				break;
			i++;
		}
		cur_section = i;
		if (cur_contents != NULL) {
			free(cur_contents);
			cur_contents = NULL;
			cur_size = 0;
		}
	}

	/* Check if at end of all sections */
	if (cur_section == (unsigned int)elf_header.e_shnum) {
		if (*size > (size_t)bufoffset) {
			cur_mode = ELF_MODE_NONE;
			cur_segment = (unsigned int)(-1);
			cur_section = 0;	/* cur_contents is NULL */
			cur_seg_offset = 0;
			cur_sect_offset = 0;
		}
		*size = (size_t)bufoffset;
		return(TRUE);
	}

	/* Read contents of current section */
	if (cur_contents == NULL) {
		if (!get_section(cur_section)) {
			seterror("Unable to read section", NULL);
			return(FALSE);
		}
		cur_sect_offset = 0;
		if (cur_contents == NULL) {
			i = cur_section + 1;
			goto nextsect;
		}
	}
  }
}



#ifdef TEST
/*
 * Test program
 */
int main __F((argc, argv), int argc AND char **argv)
{
  unsigned long textaddr, dataaddr;
  unsigned long textsize, datasize;
  unsigned long cumrdsize;
  char *cp, *infname, *outfname;
  size_t rdsize;
  __u8 *buf;
  FILE *out;

  /* Get program name */
  progname = argv[0];
  if ((cp = strrchr(progname, '/')) != NULL)
	progname = cp + 1;

  /* Get input and output file names */
  if (argc != 3) {
	fprintf(stderr, "usage: %s <infile> <outfile>\n", progname);
	exit(1);
  }
  infname = argv[1];
  outfname = argv[2];

  /* Get us a read buffer */
  buf = (__u8 *)malloc(4096);
  if (buf == NULL) {
	fprintf(stderr, "%s: unable to allocate read buffer\n", progname);
	exit(1);
  }

  /* Open output file */
  if ((out = fopen(outfile, "wb")) == NULL) {
	fprintf(stderr, "%s: unable to open output file %s\n",
							progname, outfile);
	exit(1);
  }

  /* Open ELF input file */
  if (!elfopen(infname, 0, 0)) {
	cp = elfgeterr();
	fprintf(stderr, "%s: %s\n", progname, cp);
	exit(1);
  }

  /* Read text segment */
  if (!elfset(ELF_MODE_TEXT, &textaddr, &textsize)) {
	cp = elfgeterr();
	fprintf(stderr, "%s: %s\n", progname, cp);
	exit(1);
  }
  printf("Reading text segment at addr 0x%lx, size %lu\n", textaddr, textsize);
  rdsize = 4096;
  cumrdsize = 0;
  while (rdsize == 4096) {
	if (!elfread(buf, &rdsize)) {
		cp = elfgeterr();
		fprintf(stderr, "%s: %s\n", progname, cp);
		exit(1);
	}
	if (rdsize > 0 && fwrite(buf, rdsize, 1, out) != 1) {
		fprintf(stderr, "%s: unable to write to output file\n",
								progname);
		exit(1);
	}
	cumrdsize += rdsize;
  }
  printf("Wrote %lu bytes of text segment\n", cumrdsize);

  /* Fillup text segment to next paragraph boundary */
  if ((rdsize % 16) != 0) {
	rdsize = 16 - (rdsize % 16);
	memset(buf, 0, 16);
	if (fwrite(buf, rdsize, 1, out) != 1) {
		fprintf(stderr, "%s: unable to write to output file\n",
								progname);
		exit(1);
	}
  }

  /* Read data segment */
  if (!elfset(ELF_MODE_DATA, &dataaddr, &datasize)) {
	cp = elfgeterr();
	fprintf(stderr, "%s: %s\n", progname, cp);
	exit(1);
  }
  printf("Reading data segment at addr 0x%lx, size %lu\n", dataaddr, datasize);
  rdsize = 4096;
  cumrdsize = 0;
  while (rdsize == 4096) {
	if (!elfread(buf, &rdsize)) {
		cp = elfgeterr();
		fprintf(stderr, "%s: %s\n", progname, cp);
		exit(1);
	}
	if (rdsize > 0 && fwrite(buf, rdsize, 1, out) != 1) {
		fprintf(stderr, "%s: unable to write to output file\n",
								progname);
		exit(1);
	}
	cumrdsize += rdsize;
  }
  printf("Wrote %lu bytes of data segment\n", cumrdsize);

  /* Close everything */
  elfclose();
  fclose(out);
  return(0);
}
#endif

