/* Create a section containing all the symbols for an object.

   Copyright 2000 Keith Owens <kaos@ocs.com.au> April 2000

   This file was hacked from the Linux modutils.  It performs the same function
   as the normal kallsyms but has been hardwired to assume a pcc bit target for
   the kernel, no matter which build machine is used.  This will only work if
   the build machine is little endian.  It is a temporary measure to allow
   i386->ppc cross compile.  modutils 2.5 will eventually have full cross
   compile support.

   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 (at your
   option) 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */

#include <alloca.h>
#include <fcntl.h>
#include <getopt.h>
#include <malloc.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <elf.h>
#include <netinet/in.h>
#include <stdarg.h>

/*======================================================================*/

/* Machine-specific elf macros for the PowerPC.  */

#define ELFCLASSM	ELFCLASS32
#define ELFDATAM	ELFDATA2MSB

#define MATCH_MACHINE(x)  (x == EM_PPC)

#define SHT_RELM	SHT_RELA
#define Elf32_RelM	Elf32_Rela

#ifndef ElfW
# if ELFCLASSM == ELFCLASS32
#  define ElfW(x)  Elf32_ ## x
#  define ELFW(x)  ELF32_ ## x
# else
#  define ElfW(x)  Elf64_ ## x
#  define ELFW(x)  ELF64_ ## x
# endif
#endif

/*======================================================================*/

/* Format of data in the kallsyms section.
 * Most of the fields are small numbers but the total size and all
 * offsets can be large so use the 32/64 bit types for these fields.
 *
 * Do not use sizeof() on these structures, modutils may be using extra
 * fields.  Instead use the size fields in the header to access the
 * other bits of data.
 */

struct kallsyms_header {
	int		size;		/* Size of this header */
	ElfW(Word)	total_size;	/* Total size of kallsyms data */
	int		sections;	/* Number of section entries */
	ElfW(Off)	section_off;	/* Offset to first section entry */
	int		section_size;	/* Size of one section entry */
	int		symbols;	/* Number of symbol entries */
	ElfW(Off)	symbol_off;	/* Offset to first symbol entry */
	int		symbol_size;	/* Size of one symbol entry */
	ElfW(Off)	string_off;	/* Offset to first string */
	ElfW(Addr)	start;		/* Start address of first section */
	ElfW(Addr)	end;		/* End address of last section */
};

struct kallsyms_section {
	ElfW(Addr)	start;		/* Start address of section */
	ElfW(Word)	size;		/* Size of this section */
	ElfW(Off)	name_off;	/* Offset to section name */
	ElfW(Word)	flags;		/* Flags from section */
};

struct kallsyms_symbol {
	ElfW(Off)	section_off;	/* Offset to section that owns this symbol */
	ElfW(Addr)	symbol_addr;	/* Address of symbol */
	ElfW(Off)	name_off;	/* Offset to symbol name */
};

#define KALLSYMS_SEC_NAME "__kallsyms"
#define KALLSYMS_IDX 2			/* obj_kallsyms creates kallsyms as section 2 */

/*======================================================================*/

#define MATCH_MACHINE(x)  (x == EM_PPC)
#define MODUTILS_VERSION "kallsyms i386->ppc"

struct obj_string_patch_struct;
struct obj_symbol_patch_struct;

struct obj_section
{
  ElfW(Shdr) header;
  const char *name;
  char *contents;
  struct obj_section *load_next;
  int idx;
};

struct obj_symbol
{
  struct obj_symbol *next;	/* hash table link */
  const char *name;
  long value;
  unsigned long size;
  int secidx;			/* the defining section index/module */
  int info;
  int ksymidx;			/* for export to the kernel symtab */
  int r_type;			/* relocation type */
};

/* Hardcode the hash table size.  We shouldn't be needing so many
   symbols that we begin to degrade performance, and we get a big win
   by giving the compiler a constant divisor.  */

#define HASH_BUCKETS  521

struct obj_file
{
  ElfW(Ehdr) header;
  ElfW(Addr) baseaddr;
  struct obj_section **sections;
  struct obj_section *load_order;
  struct obj_section **load_order_search_start;
  struct obj_string_patch_struct *string_patches;
  struct obj_symbol_patch_struct *symbol_patches;
  int (*symbol_cmp)(const char *, const char *);
  unsigned long (*symbol_hash)(const char *);
  unsigned long local_symtab_size;
  struct obj_symbol **local_symtab;
  struct obj_symbol *symtab[HASH_BUCKETS];
  const char *filename;
  char *persist;
};

enum obj_reloc
{
  obj_reloc_ok,
  obj_reloc_overflow,
  obj_reloc_dangerous,
  obj_reloc_unhandled,
  obj_reloc_constant_gp
};

/* Error logging */
int errors;
const char *error_file;

void error(const char *fmt, ...) __attribute__((format(printf, 1, 2)));

void error(const char *fmt, ...)
{
	va_list args;

	if (error_file)
		fprintf(stderr, "%s: ", error_file);
	va_start(args, fmt);
	vfprintf(stderr, fmt, args);
	va_end(args);
	putc('\n', stderr);

	errors++;
}

/*======================================================================*/

static void *
xmalloc(size_t size)
{
  void *ptr = malloc(size);
  if (!ptr)
    {
      error("Out of memory");
      exit(1);
    }
  return ptr;
}

/*======================================================================*/

static void *
xrealloc(void *old, size_t size)
{
  void *ptr = realloc(old, size);
  if (!ptr)
    {
      error("Out of memory");
      exit(1);
    }
  return ptr;
}

/*======================================================================*/

static char *
xstrdup(const char *s)
{
  char *n = strdup(s);
  if (!n)
    {
      error("Out of memory");
      exit(1);
    }
  return n;
}


#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE  ~FALSE
#endif

/*======================================================================*/

struct ppc_plt_entry
{
  struct ppc_plt_entry *next;
  ElfW(Addr) addend;
  int offset;
  int inited;
};

struct ppc_file
{
  struct obj_file file;
  struct obj_section *plt;
};

struct ppc_symbol
{
  struct obj_symbol sym;
  struct ppc_plt_entry *plt_entries;
};

struct obj_file *
arch_new_file (void)
{
  struct ppc_file *f;

  f = xmalloc(sizeof(struct ppc_file));
  f->plt = NULL;
  return &f->file;
}

struct obj_section *
arch_new_section (void)
{
  return xmalloc(sizeof(struct obj_section));
}

struct obj_symbol *
arch_new_symbol (void)
{
  struct ppc_symbol *p;

  p = xmalloc(sizeof(struct ppc_symbol));
  p->plt_entries = NULL;
  return &p->sym;
}

#if defined (ARCH_alpha)
#define ARCH_SHF_SHORT	SHF_ALPHA_GPREL
#elif defined (ARCH_ia64)
#define ARCH_SHF_SHORT	SHF_IA_64_SHORT
#else
#define ARCH_SHF_SHORT	0
#endif

static int
obj_load_order_prio(struct obj_section *a)
{
  unsigned long af, ac;

  af = a->header.sh_flags;

  ac = 0;
  if (a->name[0] != '.'
      || strlen(a->name) != 10
      || strcmp(a->name + 5, ".init"))
    ac |= 64;
  if (af & SHF_ALLOC) ac |= 32;
  if (af & SHF_EXECINSTR) ac |= 16;
  if (!(af & SHF_WRITE)) ac |= 8;
  if (a->header.sh_type != SHT_NOBITS) ac |= 4;
  /* Desired order is
		P S  AC & 7
	.data	1 0  4
	.got	1 1  3
	.sdata  1 1  1
	.sbss   0 1  1
	.bss    0 0  0  */
  if (strcmp (a->name, ".got") == 0) ac |= 2;
  if (af & ARCH_SHF_SHORT)
    ac = (ac & ~4) | 1;

  return ac;
}

static void
obj_insert_section_load_order (struct obj_file *f, struct obj_section *sec)
{
  struct obj_section **p;
  int prio = obj_load_order_prio(sec);
  for (p = f->load_order_search_start; *p ; p = &(*p)->load_next)
    if (obj_load_order_prio(*p) < prio)
      break;
  sec->load_next = *p;
  *p = sec;
}

/*======================================================================*/

static int
arch_load_proc_section(struct obj_section *sec, int fp)
{
    /* Assume it's just a debugging section that we can safely
       ignore ...  */
    sec->contents = NULL;

    return 0;
}

/*======================================================================*/

/* Standard ELF hash function.  */
static inline unsigned long
obj_elf_hash_n(const char *name, unsigned long n)
{
  unsigned long h = 0;
  unsigned long g;
  unsigned char ch;

  while (n > 0)
    {
      ch = *name++;
      h = (h << 4) + ch;
      if ((g = (h & 0xf0000000)) != 0)
	{
	  h ^= g >> 24;
	  h &= ~g;
	}
      n--;
    }
  return h;
}

static unsigned long
obj_elf_hash (const char *name)
{
  return obj_elf_hash_n(name, strlen(name));
}

static struct obj_symbol *
obj_add_symbol (struct obj_file *f, const char *name, unsigned long symidx,
		int info, int secidx, ElfW(Addr) value, unsigned long size)
{
  struct obj_symbol *sym;
  unsigned long hash = f->symbol_hash(name) % HASH_BUCKETS;
  int n_type = ELFW(ST_TYPE)(info);
  int n_binding = ELFW(ST_BIND)(info);

  for (sym = f->symtab[hash]; sym; sym = sym->next)
    if (f->symbol_cmp(sym->name, name) == 0)
      {
	int o_secidx = sym->secidx;
	int o_info = sym->info;
	int o_type = ELFW(ST_TYPE)(o_info);
	int o_binding = ELFW(ST_BIND)(o_info);

	/* A redefinition!  Is it legal?  */

	if (secidx == SHN_UNDEF)
	  return sym;
	else if (o_secidx == SHN_UNDEF)
	  goto found;
	else if (n_binding == STB_GLOBAL && o_binding == STB_LOCAL)
	  {
	    /* Cope with local and global symbols of the same name
	       in the same object file, as might have been created
	       by ld -r.  The only reason locals are now seen at this
	       level at all is so that we can do semi-sensible things
	       with parameters.  */

	    struct obj_symbol *nsym, **p;

	    nsym = arch_new_symbol();
	    nsym->next = sym->next;
	    nsym->ksymidx = -1;

	    /* Excise the old (local) symbol from the hash chain.  */
	    for (p = &f->symtab[hash]; *p != sym; p = &(*p)->next)
	      continue;
	    *p = sym = nsym;
	    goto found;
	  }
	else if (n_binding == STB_LOCAL)
	  {
	    /* Another symbol of the same name has already been defined.
	       Just add this to the local table.  */
	    sym = arch_new_symbol();
	    sym->next = NULL;
	    sym->ksymidx = -1;
	    f->local_symtab[symidx] = sym;
	    goto found;
	  }
	else if (n_binding == STB_WEAK)
	  return sym;
	else if (o_binding == STB_WEAK)
	  goto found;
	/* Don't unify COMMON symbols with object types the programmer
	   doesn't expect.  */
	else if (secidx == SHN_COMMON
		 && (o_type == STT_NOTYPE || o_type == STT_OBJECT))
	  return sym;
	else if (o_secidx == SHN_COMMON
		 && (n_type == STT_NOTYPE || n_type == STT_OBJECT))
	  goto found;
	else
	  {
	    /* Don't report an error if the symbol is coming from
	       the kernel or some external module.  */
	    if (secidx <= SHN_HIRESERVE)
	      error("%s multiply defined", name);
	    return sym;
	  }
      }

  /* Completely new symbol.  */
  sym = arch_new_symbol();
  sym->next = f->symtab[hash];
  f->symtab[hash] = sym;
  sym->ksymidx = -1;

  if (ELFW(ST_BIND)(info) == STB_LOCAL && symidx != -1) {
    if (symidx >= f->local_symtab_size)
      error("local symbol %s with index %ld exceeds local_symtab_size %ld",
        name, (long) symidx, (long) f->local_symtab_size);
    else
      f->local_symtab[symidx] = sym;
  }

found:
  sym->name = name;
  sym->value = value;
  sym->size = size;
  sym->secidx = secidx;
  sym->info = info;
  sym->r_type = 0;	/* should be R_arch_NONE for all arch */

  return sym;
}

static struct obj_symbol *
obj_find_symbol (struct obj_file *f, const char *name)
{
  struct obj_symbol *sym;
  unsigned long hash = f->symbol_hash(name) % HASH_BUCKETS;

  for (sym = f->symtab[hash]; sym; sym = sym->next)
    if (f->symbol_cmp(sym->name, name) == 0)
      return sym;

  return NULL;
}

/* Standard method of finding relocation symbols, sets isym */
#define obj_find_relsym(isym, f, find, rel, symtab, strtab) \
	{ \
		unsigned long symndx = ELFW(R_SYM)((rel)->r_info); \
		ElfW(Sym) *extsym = (symtab)+symndx; \
		if (ELFW(ST_BIND)(extsym->st_info) == STB_LOCAL) { \
			isym = (typeof(isym)) (f)->local_symtab[symndx]; \
		} \
		else { \
			const char *name; \
			if (extsym->st_name) \
				name = (strtab) + extsym->st_name; \
			else \
				name = (f)->sections[extsym->st_shndx]->name; \
			isym = (typeof(isym)) obj_find_symbol((find), name); \
		} \
	}

/*======================================================================*/

static void
kallsyms_usage (void)
{
    fputs("Usage:\n"
	  "kallsyms [-Vh] file\n"

	  "\n"
	  "  -V, --version         Show version\n"
	  "  -h, --help            Print this message.\n"
	  "  file                  Filename of a kernel (e.g. vmlinux)\n"
	  ,stderr);
}

/*======================================================================*/

static void
fix_Ehdr(ElfW(Ehdr) *header)
{
	header->e_type = ntohs(header->e_type);
	header->e_machine = ntohs(header->e_machine);
	header->e_version = ntohl(header->e_version);
	header->e_entry = ntohl(header->e_entry);
	header->e_phoff = ntohl(header->e_phoff);
	header->e_shoff = ntohl(header->e_shoff);
	header->e_flags = ntohl(header->e_flags);
	header->e_ehsize = ntohs(header->e_ehsize);
	header->e_phentsize = ntohs(header->e_phentsize);
	header->e_phnum = ntohs(header->e_phnum);
	header->e_shentsize = ntohs(header->e_shentsize);
	header->e_shnum = ntohs(header->e_shnum);
	header->e_shstrndx = ntohs(header->e_shstrndx);
}

static void
unfix_Ehdr(ElfW(Ehdr) *header)
{
	header->e_type = htons(header->e_type);
	header->e_machine = htons(header->e_machine);
	header->e_version = htonl(header->e_version);
	header->e_entry = htonl(header->e_entry);
	header->e_phoff = htonl(header->e_phoff);
	header->e_shoff = htonl(header->e_shoff);
	header->e_flags = htonl(header->e_flags);
	header->e_ehsize = htons(header->e_ehsize);
	header->e_phentsize = htons(header->e_phentsize);
	header->e_phnum = htons(header->e_phnum);
	header->e_shentsize = htons(header->e_shentsize);
	header->e_shnum = htons(header->e_shnum);
	header->e_shstrndx = htons(header->e_shstrndx);
}

static void
fix_Shdr(ElfW(Shdr) *header)
{
	header->sh_name = ntohl(header->sh_name);
	header->sh_type = ntohl(header->sh_type);
	header->sh_flags = ntohl(header->sh_flags);
	header->sh_addr = ntohl(header->sh_addr);
	header->sh_offset = ntohl(header->sh_offset);
	header->sh_size = ntohl(header->sh_size);
	header->sh_link = ntohl(header->sh_link);
	header->sh_info = ntohl(header->sh_info);
	header->sh_addralign = ntohl(header->sh_addralign);
	header->sh_entsize = ntohl(header->sh_entsize);
}

static void
unfix_Shdr(ElfW(Shdr) *header)
{
	header->sh_name = htonl(header->sh_name);
	header->sh_type = htonl(header->sh_type);
	header->sh_flags = htonl(header->sh_flags);
	header->sh_addr = htonl(header->sh_addr);
	header->sh_offset = htonl(header->sh_offset);
	header->sh_size = htonl(header->sh_size);
	header->sh_link = htonl(header->sh_link);
	header->sh_info = htonl(header->sh_info);
	header->sh_addralign = htonl(header->sh_addralign);
	header->sh_entsize = htonl(header->sh_entsize);
}

static void
fix_Sym(ElfW(Sym) *sym)
{
	sym->st_name = ntohl(sym->st_name);
	sym->st_value = ntohl(sym->st_value);
	sym->st_size = ntohl(sym->st_size);
	sym->st_shndx = ntohs(sym->st_shndx);
}

static void
unfix_Sym(ElfW(Sym) *sym)
{
	sym->st_name = htonl(sym->st_name);
	sym->st_value = htonl(sym->st_value);
	sym->st_size = htonl(sym->st_size);
	sym->st_shndx = htons(sym->st_shndx);
}

static void
fix_RelM(ElfW(RelM) *rel)
{
	rel->r_offset = ntohl(rel->r_offset);
	rel->r_info = ntohl(rel->r_info);
#if Elf32_RelM == Elf32_Rela
	rel->r_addend = ntohl(rel->r_addend);
#endif
}

static void
unfix_RelM(ElfW(RelM) *rel)
{
	rel->r_offset = htonl(rel->r_offset);
	rel->r_info = htonl(rel->r_info);
#if Elf32_RelM == Elf32_Rela
	rel->r_addend = htonl(rel->r_addend);
#endif
}

static void
unfix_kallsyms_header(struct kallsyms_header *hdr)
{
	hdr->size = htonl(hdr->size);
	hdr->total_size = htonl(hdr->total_size);
	hdr->sections = htonl(hdr->sections);
	hdr->section_off = htonl(hdr->section_off);
	hdr->section_size = htonl(hdr->section_size);
	hdr->symbols = htonl(hdr->symbols);
	hdr->symbol_off = htonl(hdr->symbol_off);
	hdr->symbol_size = htonl(hdr->symbol_size);
	hdr->string_off = htonl(hdr->string_off);
	hdr->start = htonl(hdr->start);
	hdr->end = htonl(hdr->end);
}

static void
unfix_kallsyms_section(struct kallsyms_section *sec)
{
	sec->start = htonl(sec->start);
	sec->size = htonl(sec->size);
	sec->name_off = htonl(sec->name_off);
	sec->flags = htonl(sec->flags);
}

static void
unfix_kallsyms_symbols(struct kallsyms_symbol *syms, int count)
{
	int i;

	for (i = 0; i < count; i++) {
		syms[i].section_off = htonl(syms[i].section_off);
		syms[i].symbol_addr = htonl(syms[i].symbol_addr);
		syms[i].name_off = htonl(syms[i].name_off);
	}
}

static struct obj_file *
obj_load (int fp, Elf32_Half e_type, const char *filename)
{
  struct obj_file *f;
  ElfW(Shdr) *section_headers;
  int shnum, i;
  char *shstrtab;

  /* Read the file header.  */

  f = arch_new_file();
  memset(f, 0, sizeof(*f));
  f->symbol_cmp = strcmp;
  f->symbol_hash = obj_elf_hash;
  f->load_order_search_start = &f->load_order;

  lseek(fp, 0, SEEK_SET);
  if (read(fp, &f->header, sizeof(f->header)) != sizeof(f->header))
    {
      error("error reading ELF header %s: %m", filename);
      return NULL;
    }
  fix_Ehdr(&f->header);

  if (f->header.e_ident[EI_MAG0] != ELFMAG0
      || f->header.e_ident[EI_MAG1] != ELFMAG1
      || f->header.e_ident[EI_MAG2] != ELFMAG2
      || f->header.e_ident[EI_MAG3] != ELFMAG3)
    {
      error("%s is not an ELF file", filename);
      return NULL;
    }
  if (f->header.e_ident[EI_CLASS] != ELFCLASSM
      || f->header.e_ident[EI_DATA] != ELFDATAM
      || f->header.e_ident[EI_VERSION] != EV_CURRENT
      || !MATCH_MACHINE(f->header.e_machine))
    {
      error("ELF file %s not for this architecture", filename);
      return NULL;
    }
  if (f->header.e_type != e_type && e_type != ET_NONE)
    {
      switch (e_type) {
      case ET_REL:
	error("ELF file %s not a relocatable object", filename);
	break;
      case ET_EXEC:
	error("ELF file %s not an executable object", filename);
	break;
      default:
	error("ELF file %s has wrong type, expecting %d got %d",
		filename, e_type, f->header.e_type);
	break;
      }
      return NULL;
    }

  /* Read the section headers.  */

  if (f->header.e_shentsize != sizeof(ElfW(Shdr)))
    {
      error("section header size mismatch %s: %lu != %lu",
	    filename,
	    (unsigned long)f->header.e_shentsize,
	    (unsigned long)sizeof(ElfW(Shdr)));
      return NULL;
    }

  shnum = f->header.e_shnum;
  f->sections = xmalloc(sizeof(struct obj_section *) * shnum);
  memset(f->sections, 0, sizeof(struct obj_section *) * shnum);

  section_headers = alloca(sizeof(ElfW(Shdr)) * shnum);
  lseek(fp, f->header.e_shoff, SEEK_SET);
  if (read(fp, section_headers, sizeof(ElfW(Shdr))*shnum) != sizeof(ElfW(Shdr))*shnum)
    {
      error("error reading ELF section headers %s: %m", filename);
      return NULL;
    }

  /* Read the section data.  */

  for (i = 0; i < shnum; ++i)
    {
      struct obj_section *sec;

      f->sections[i] = sec = arch_new_section();
      memset(sec, 0, sizeof(*sec));

      sec->header = section_headers[i];
      fix_Shdr(&sec->header);
      sec->idx = i;

      switch (sec->header.sh_type)
	{
	case SHT_NULL:
	case SHT_NOTE:
	case SHT_NOBITS:
	  /* ignore */
	  break;

	case SHT_PROGBITS:
	case SHT_SYMTAB:
	case SHT_STRTAB:
	case SHT_RELM:
	  if (sec->header.sh_size > 0)
	    {
	      sec->contents = xmalloc(sec->header.sh_size);
	      lseek(fp, sec->header.sh_offset, SEEK_SET);
	      if (read(fp, sec->contents, sec->header.sh_size) != sec->header.sh_size)
		{
		  error("error reading ELF section data %s: %m", filename);
		  return NULL;
		}
	    }
	  else
	    sec->contents = NULL;
	  break;

#if SHT_RELM == SHT_REL
	case SHT_RELA:
	  if (sec->header.sh_size) {
	    error("RELA relocations not supported on this architecture %s", filename);
	    return NULL;
	  }
	  break;
#else
	case SHT_REL:
	  if (sec->header.sh_size) {
	    error("REL relocations not supported on this architecture %s", filename);
	    return NULL;
	  }
	  break;
#endif

	default:
	  if (sec->header.sh_type >= SHT_LOPROC)
	    {
	      if (arch_load_proc_section(sec, fp) < 0)
		return NULL;
	      break;
	    }

	  error("can't handle sections of type %ld %s",
		(long)sec->header.sh_type, filename);
	  return NULL;
	}
    }

  /* Do what sort of interpretation as needed by each section.  */

  shstrtab = f->sections[f->header.e_shstrndx]->contents;

  for (i = 0; i < shnum; ++i)
    {
      struct obj_section *sec = f->sections[i];
      sec->name = shstrtab + sec->header.sh_name;
    }

  for (i = 0; i < shnum; ++i)
    {
      struct obj_section *sec = f->sections[i];

      /* .modinfo and .modstring should be contents only but gcc has no
       *  attribute for that.  The kernel may have marked these sections as
       *  ALLOC, ignore the allocate bit.
       */
      if (strcmp(sec->name, ".modinfo") == 0 ||
	  strcmp(sec->name, ".modstring") == 0)
	sec->header.sh_flags &= ~SHF_ALLOC;

      if (sec->header.sh_flags & SHF_ALLOC)
	obj_insert_section_load_order(f, sec);

      switch (sec->header.sh_type)
	{
	case SHT_SYMTAB:
	  {
	    unsigned long nsym, j;
	    char *strtab;
	    ElfW(Sym) *sym;

	    if (sec->header.sh_entsize != sizeof(ElfW(Sym)))
	      {
		error("symbol size mismatch %s: %lu != %lu",
		      filename,
		      (unsigned long)sec->header.sh_entsize,
		      (unsigned long)sizeof(ElfW(Sym)));
		return NULL;
	      }

	    nsym = sec->header.sh_size / sizeof(ElfW(Sym));
	    strtab = f->sections[sec->header.sh_link]->contents;
	    sym = (ElfW(Sym) *) sec->contents;

	    /* Allocate space for a table of local symbols.  */
	    j = f->local_symtab_size = sec->header.sh_info;
	    f->local_symtab = xmalloc(j *= sizeof(struct obj_symbol *));
	    memset(f->local_symtab, 0, j);

	    /* Insert all symbols into the hash table.  */
	    for (j = 1, ++sym; j < nsym; ++j, ++sym)
	      {
		const char *name;
		fix_Sym(sym);
		if (sym->st_name)
		  name = strtab+sym->st_name;
		else
		  name = f->sections[sym->st_shndx]->name;

		obj_add_symbol(f, name, j, sym->st_info, sym->st_shndx,
			       sym->st_value, sym->st_size);

		unfix_Sym(sym);
	      }
	  }
	break;
	}
    }

  /* second pass to add relocation data to symbols */
  for (i = 0; i < shnum; ++i)
    {
      struct obj_section *sec = f->sections[i];
      switch (sec->header.sh_type)
	{
	case SHT_RELM:
	  {
	    unsigned long nrel, j, nsyms;
	    ElfW(RelM) *rel;
	    struct obj_section *symtab;
	    char *strtab;
	    if (sec->header.sh_entsize != sizeof(ElfW(RelM)))
	      {
		error("relocation entry size mismatch %s: %lu != %lu",
		      filename,
		      (unsigned long)sec->header.sh_entsize,
		      (unsigned long)sizeof(ElfW(RelM)));
		return NULL;
	      }

	    nrel = sec->header.sh_size / sizeof(ElfW(RelM));
	    rel = (ElfW(RelM) *) sec->contents;
	    fix_RelM(rel);
	    symtab = f->sections[sec->header.sh_link];
	    nsyms = symtab->header.sh_size / symtab->header.sh_entsize;
	    strtab = f->sections[symtab->header.sh_link]->contents;

	    /* Save the relocate type in each symbol entry.  */
	    for (j = 0; j < nrel; ++j, ++rel)
	      {
		struct obj_symbol *intsym;
		unsigned long symndx;
		symndx = ELFW(R_SYM)(rel->r_info);
		if (symndx)
		  {
		    if (symndx >= nsyms)
		      {
			error("%s: Bad symbol index: %08lx >= %08lx",
			      filename, symndx, nsyms);
			continue;
		      }

		    obj_find_relsym(intsym, f, f, rel, (ElfW(Sym) *)(symtab->contents), strtab);
		    intsym->r_type = ELFW(R_TYPE)(rel->r_info);
		  }
	      }
	    unfix_RelM(rel);
	  }
	  break;
	}
    }

  f->filename = xstrdup(filename);

  return f;
}

/*======================================================================*/

#define EXPAND_BY 4096  /* Arbitrary */

/* Append a string to the big list of strings */

static void
append_string (const char *s, char **strings,
	       ElfW(Word) *strings_size, ElfW(Word) *strings_left)
{
    int l = strlen(s) + 1;
    while (l > *strings_left) {
	*strings = xrealloc(*strings, *strings_size += EXPAND_BY);
	*strings_left += EXPAND_BY;
    }
    memcpy((char *)*strings+*strings_size-*strings_left, s, l);
    *strings_left -= l;
}


/* Append a symbol to the big list of symbols */

static void
append_symbol (const struct kallsyms_symbol *s,
	       struct kallsyms_symbol **symbols,
	       ElfW(Word) *symbols_size, ElfW(Word) *symbols_left)
{
    int l = sizeof(*s);
    while (l > *symbols_left) {
	*symbols = xrealloc(*symbols, *symbols_size += EXPAND_BY);
	*symbols_left += EXPAND_BY;
    }
    memcpy((char *)*symbols+*symbols_size-*symbols_left, s, l);
    *symbols_left -= l;
}

/* qsort compare routine to sort symbols */

static const char *sym_strings;

static int
symbol_compare (const void *a, const void *b)
{
    struct kallsyms_symbol *c = (struct kallsyms_symbol *) a;
    struct kallsyms_symbol *d = (struct kallsyms_symbol *) b;

    if (c->symbol_addr > d->symbol_addr)
	return(1);
    if (c->symbol_addr < d->symbol_addr)
	return(-1);
    return(strcmp(sym_strings+c->name_off, sym_strings+d->name_off));
}


/* Extract all symbols from the input obj_file, ignore ones that are
 * no use for debugging, build an output obj_file containing only the
 * kallsyms section.
 *
 * The kallsyms section is a bit unusual.  It deliberately has no
 * relocatable data, all "pointers" are represented as byte offsets
 * into the the section.  This means it can be stored anywhere without
 * relocation problems.  In particular it can be stored within a kernel
 * image, it can be stored separately from the kernel image, it can be
 * appended to a module just before loading, it can be stored in a
 * separate area etc.
 *
 * Format of the kallsyms section.
 *
 * Header:
 *   Size of header.
 *   Total size of kallsyms data, including strings.
 *   Number of loaded sections.
 *   Offset to first section entry from start of header.
 *   Size of each section entry, excluding the name string.
 *   Number of symbols.
 *   Offset to first symbol entry from start of header.
 *   Size of each symbol entry, excluding the name string.
 *
 * Section entry - one per loaded section.
 *   Start of section[1].
 *   Size of section.
 *   Offset to name of section, from start of strings.
 *   Section flags.
 *
 * Symbol entry - one per symbol in the input file[2].
 *   Offset of section that owns this symbol, from start of section data.
 *   Address of symbol within the real section[1].
 *   Offset to name of symbol, from start of strings.
 *
 * Notes: [1] This is an exception to the "represent pointers as
 *            offsets" rule, it is a value, not an offset.  The start
 *            address of a section or a symbol is extracted from the
 *            obj_file data which may contain absolute or relocatable
 *            addresses.  If the addresses are relocatable then the
 *            caller must adjust the section and/or symbol entries in
 *            kallsyms after relocation.
 *        [2] Only symbols that fall within loaded sections are stored.
 */

static int
obj_kallsyms (struct obj_file *fin, struct obj_file **fout_result)
{
    struct obj_file *fout;
    int i, loaded = 0, *fin_to_allsym_map;
    struct obj_section *isec, *osec;
    struct kallsyms_header *a_hdr;
    struct kallsyms_section *a_sec;
    ElfW(Off) sec_off;
    struct kallsyms_symbol *symbols = NULL, a_sym;
    ElfW(Word) symbols_size = 0, symbols_left = 0;
    char *strings = NULL, *p;
    ElfW(Word) strings_size = 0, strings_left = 0;
    ElfW(Off) file_offset;
    static char strtab[] = "\000" KALLSYMS_SEC_NAME;

    /* Create the kallsyms section.  */
    fout = arch_new_file();
    memset(fout, 0, sizeof(*fout));
    fout->symbol_cmp = strcmp;
    fout->symbol_hash = obj_elf_hash;
    fout->load_order_search_start = &fout->load_order;

    /* Copy file characteristics from input file and modify to suit */
    memcpy(&fout->header, &fin->header, sizeof(fout->header));
    fout->header.e_type = ET_REL;		/* Output is relocatable */
    fout->header.e_entry = 0;			/* No entry point */
    fout->header.e_phoff = 0;			/* No program header */
    file_offset = sizeof(fout->header);		/* Step over Elf header */
    fout->header.e_shoff = file_offset;		/* Section headers next */
    fout->header.e_phentsize = 0;		/* No program header */
    fout->header.e_phnum = 0;			/* No program header */
    fout->header.e_shnum = KALLSYMS_IDX+1;	/* Initial, strtab, kallsyms */
    fout->header.e_shstrndx = KALLSYMS_IDX-1;	/* strtab */
    file_offset += fout->header.e_shentsize * fout->header.e_shnum;

    /* Populate the section data for kallsyms itself */
    fout->sections = xmalloc(sizeof(*(fout->sections))*fout->header.e_shnum);
    memset(fout->sections, 0, sizeof(*(fout->sections))*fout->header.e_shnum);

    fout->sections[0] = osec = arch_new_section();
    memset(osec, 0, sizeof(*osec));
    osec->header.sh_type = SHT_NULL;
    osec->header.sh_link = SHN_UNDEF;

    fout->sections[KALLSYMS_IDX-1] = osec = arch_new_section();
    memset(osec, 0, sizeof(*osec));
    osec->name = ".strtab";
    osec->header.sh_type = SHT_STRTAB;
    osec->header.sh_link = SHN_UNDEF;
    osec->header.sh_offset = file_offset;
    osec->header.sh_size = sizeof(strtab);
    osec->contents = xmalloc(sizeof(strtab));
    memcpy(osec->contents, strtab, sizeof(strtab));
    file_offset += osec->header.sh_size;

    fout->sections[KALLSYMS_IDX] = osec = arch_new_section();
    memset(osec, 0, sizeof(*osec));
    osec->name = KALLSYMS_SEC_NAME;
    osec->header.sh_name = 1;			/* Offset in strtab */
    osec->header.sh_type = SHT_PROGBITS;	/* Load it */
    osec->header.sh_flags = SHF_ALLOC;		/* Read only data */
    osec->header.sh_link = SHN_UNDEF;
    osec->header.sh_addralign = sizeof(ElfW(Word));
    file_offset = (file_offset + osec->header.sh_addralign - 1)
	& -(osec->header.sh_addralign);
    osec->header.sh_offset = file_offset;

    /* How many loaded sections are there? */
    for (i = 0; i < fin->header.e_shnum; ++i) {
	if (fin->sections[i]->header.sh_flags & SHF_ALLOC)
	    ++loaded;
    }

    /* Initial contents, header + one entry per input section.  No strings. */
    osec->header.sh_size = sizeof(*a_hdr) + loaded*sizeof(*a_sec);
    a_hdr = (struct kallsyms_header *) osec->contents =
	xmalloc(osec->header.sh_size);
    memset(osec->contents, 0, osec->header.sh_size);
    a_hdr->size = sizeof(*a_hdr);
    a_hdr->sections = loaded;
    a_hdr->section_off = a_hdr->size;
    a_hdr->section_size = sizeof(*a_sec);
    a_hdr->symbol_off = osec->header.sh_size;
    a_hdr->symbol_size = sizeof(a_sym);
    a_hdr->start = (ElfW(Addr))(~0);

    /* Map input section numbers to kallsyms section offsets. */
    sec_off = 0;	/* Offset to first kallsyms section entry */
    fin_to_allsym_map = xmalloc(sizeof(*fin_to_allsym_map)*fin->header.e_shnum);
    for (i = 0; i < fin->header.e_shnum; ++i) {
	isec = fin->sections[i];
	if (isec->header.sh_flags & SHF_ALLOC) {
	    fin_to_allsym_map[isec->idx] = sec_off;
	    sec_off += a_hdr->section_size;
	}
	else
	    fin_to_allsym_map[isec->idx] = -1;	/* Ignore this section */
    }

    /* Copy the loaded section data. */
    a_sec = (struct kallsyms_section *) ((char *) a_hdr + a_hdr->section_off);
    for (i = 0; i < fin->header.e_shnum; ++i) {
	isec = fin->sections[i];
	if (!(isec->header.sh_flags & SHF_ALLOC))
	    continue;
	a_sec->start = isec->header.sh_addr;
	a_sec->size = isec->header.sh_size;
	a_sec->flags = isec->header.sh_flags;
	a_sec->name_off = strings_size - strings_left;
	append_string(isec->name, &strings, &strings_size, &strings_left);
	if (a_sec->start < a_hdr->start)
	    a_hdr->start = a_sec->start;
	if (a_sec->start+a_sec->size > a_hdr->end)
	    a_hdr->end = a_sec->start+a_sec->size;
	unfix_kallsyms_section(a_sec);
	++a_sec;
    }

    /* Build the kallsyms symbol table from the symbol hashes. */
    for (i = 0; i < HASH_BUCKETS; ++i) {
	struct obj_symbol *sym = fin->symtab[i];
	for (sym = fin->symtab[i]; sym ; sym = sym->next) {
	    if (!sym || sym->secidx >= fin->header.e_shnum)
		continue;
	    if ((a_sym.section_off = fin_to_allsym_map[sym->secidx]) == -1)
		continue;
	    if (strcmp(sym->name, "gcc2_compiled.") == 0 ||
		strncmp(sym->name, "__insmod_", 9) == 0)
		continue;
	    a_sym.symbol_addr = sym->value;
	    if (fin->header.e_type == ET_REL)
		a_sym.symbol_addr += fin->sections[sym->secidx]->header.sh_addr;
	    a_sym.name_off = strings_size - strings_left;
	    append_symbol(&a_sym, &symbols, &symbols_size, &symbols_left);
	    append_string(sym->name, &strings, &strings_size, &strings_left);
	    ++a_hdr->symbols;
	}
    }
    free(fin_to_allsym_map);

    /* Sort the symbols into ascending order by address and name */
    sym_strings = strings;	/* For symbol_compare */
    qsort((char *) symbols, (unsigned) a_hdr->symbols,
	sizeof(* symbols), symbol_compare);
    sym_strings = NULL;

    /* Put the lot together */
    osec->header.sh_size = a_hdr->total_size =
	a_hdr->symbol_off +
	a_hdr->symbols*a_hdr->symbol_size +
	strings_size - strings_left;
    a_hdr = (struct kallsyms_header *) osec->contents =
	xrealloc(a_hdr, a_hdr->total_size);
    p = (char *)a_hdr + a_hdr->symbol_off;
    unfix_kallsyms_symbols(symbols, a_hdr->symbols);
    memcpy(p, symbols, a_hdr->symbols*a_hdr->symbol_size);
    free(symbols);
    p += a_hdr->symbols*a_hdr->symbol_size;
    a_hdr->string_off = p - (char *)a_hdr;
    memcpy(p, strings, strings_size - strings_left);
    free(strings);
    unfix_kallsyms_header(a_hdr);

    *fout_result = fout;
    return 0;
}

int main (int argc, char **argv)
{
    struct option long_opts[] = {
	    {"version", 0, 0, 'V'},
	    {"help", 0, 0, 'h'},
	    {0, 0, 0, 0}
    };
    char *filename = NULL;
    int fp;
    struct obj_file *fin, *fout;
    int i, c;

    error_file = "kallsyms_i386_ppc";

    /* To handle repeated calls from combined modprobe */
    errors = optind = 0;

    /* Process the command line.  */
    while ((c = getopt_long(argc, argv, "Vh",
			    &long_opts[0], NULL)) != EOF)
	switch (c) {
	case 'V':
	    fputs("kallsyms version " MODUTILS_VERSION "\n", stderr);
	    return(0);
	case 'h':       /* Print the usage message. */
	    kallsyms_usage();
	    return(0);
	default:
	    kallsyms_usage();
	    return(1);
	}

    if (optind != argc-1) {
	kallsyms_usage();
	return(1);
    }

    filename = argv[optind++];
    error_file = filename;
    if ((fp = open(filename, O_RDONLY)) < 0) {
	error("%s: %m", filename);
	return 1;
    }

    if ((fin = obj_load(fp, ET_EXEC, filename)) == NULL) {
	close(fp);
	return 1;
    }
    close(fp);

    error_file = "kallsyms_i386_ppc";
    if (obj_kallsyms(fin, &fout))
	return 1;

    /* Write the extracted data */
    unfix_Ehdr(&fout->header);
    fwrite(&fout->header, sizeof(fout->header), 1, stdout);
    fix_Ehdr(&fout->header);
    for (i = 0; i < fout->header.e_shnum; ++i) {
	unfix_Shdr(&fout->sections[i]->header);
	fwrite(&fout->sections[i]->header, fout->header.e_shentsize, 1, stdout);
	fix_Shdr(&fout->sections[i]->header);
    }
    for (i = 0; i < fout->header.e_shnum; ++i) {
	if (fout->sections[i]->header.sh_size) {
	    fseek(stdout, fout->sections[i]->header.sh_offset, SEEK_SET);
	    fwrite(fout->sections[i]->contents,
		fout->sections[i]->header.sh_size, 1, stdout);
	}
    }

    return 0;
}
