/* BFD backend for SunOS binaries.
   Copyright (C) 1990, 91, 92, 93, 94 Free Software Foundation, Inc.
   Written by Cygnus Support.

This file is part of BFD, the Binary File Descriptor library.

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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#define ARCH 32
#define TARGETNAME "a.out-sunos-big"
#define MY(OP) CAT(sunos_big_,OP)

#include "bfd.h"

/* Static routines defined in this file.  */

static boolean sunos_read_dynamic_info PARAMS ((bfd *));
static long sunos_get_dynamic_symtab_upper_bound PARAMS ((bfd *));
static long sunos_canonicalize_dynamic_symtab PARAMS ((bfd *, asymbol **));
static long sunos_get_dynamic_reloc_upper_bound PARAMS ((bfd *));
static long sunos_canonicalize_dynamic_reloc
  PARAMS ((bfd *, arelent **, asymbol **));

#define MY_get_dynamic_symtab_upper_bound sunos_get_dynamic_symtab_upper_bound
#define MY_canonicalize_dynamic_symtab sunos_canonicalize_dynamic_symtab
#define MY_get_dynamic_reloc_upper_bound sunos_get_dynamic_reloc_upper_bound
#define MY_canonicalize_dynamic_reloc sunos_canonicalize_dynamic_reloc

/* Include the usual a.out support.  */
#include "aoutf1.h"

/* SunOS shared library support.  We store a pointer to this structure
   in obj_aout_dynamic_info (abfd).  */

struct sunos_dynamic_info
{
  /* Whether we found any dynamic information.  */
  boolean valid;
  /* Dynamic information.  */
  struct internal_sun4_dynamic_link dyninfo;
  /* Number of dynamic symbols.  */
  long dynsym_count;
  /* Read in nlists for dynamic symbols.  */
  struct external_nlist *dynsym;
  /* asymbol structures for dynamic symbols.  */
  aout_symbol_type *canonical_dynsym;
  /* Read in dynamic string table.  */
  char *dynstr;
  /* Number of dynamic relocs.  */
  long dynrel_count;
  /* Read in dynamic relocs.  This may be reloc_std_external or
     reloc_ext_external.  */
  PTR dynrel;
  /* arelent structures for dynamic relocs.  */
  arelent *canonical_dynrel;
};

/* Read in the basic dynamic information.  This locates the __DYNAMIC
   structure and uses it to find the dynamic_link structure.  It
   creates and saves a sunos_dynamic_info structure.  If it can't find
   __DYNAMIC, it sets the valid field of the sunos_dynamic_info
   structure to false to avoid doing this work again.  */

static boolean
sunos_read_dynamic_info (abfd)
     bfd *abfd;
{
  struct sunos_dynamic_info *info;
  asection *dynsec;
  file_ptr dynoff;
  struct external_sun4_dynamic dyninfo;
  unsigned long dynver;
  struct external_sun4_dynamic_link linkinfo;

  if (obj_aout_dynamic_info (abfd) != (PTR) NULL)
    return true;

  if ((abfd->flags & DYNAMIC) == 0)
    {
      bfd_set_error (bfd_error_invalid_operation);
      return false;
    }

  info = ((struct sunos_dynamic_info *)
	  bfd_zalloc (abfd, sizeof (struct sunos_dynamic_info)));
  if (!info)
    {
      bfd_set_error (bfd_error_no_memory);
      return false;
    }
  info->valid = false;
  info->dynsym = NULL;
  info->dynstr = NULL;
  info->canonical_dynsym = NULL;
  info->dynrel = NULL;
  info->canonical_dynrel = NULL;
  obj_aout_dynamic_info (abfd) = (PTR) info;

  /* This code used to look for the __DYNAMIC symbol to locate the dynamic
     linking information.
     However this inhibits recovering the dynamic symbols from a
     stripped object file, so blindly assume that the dynamic linking
     information is located at the start of the data section.
     We could verify this assumption later by looking through the dynamic
     symbols for the __DYNAMIC symbol.  */
  if ((abfd->flags & DYNAMIC) == 0)
    return true;
  if (! bfd_get_section_contents (abfd, obj_datasec (abfd), (PTR) &dyninfo,
				  (file_ptr) 0, sizeof dyninfo))
    return true;

  dynver = GET_WORD (abfd, dyninfo.ld_version);
  if (dynver != 2 && dynver != 3)
    return true;

  dynoff = GET_WORD (abfd, dyninfo.ld);

  /* dynoff is a virtual address.  It is probably always in the .data
     section, but this code should work even if it moves.  */
  if (dynoff < bfd_get_section_vma (abfd, obj_datasec (abfd)))
    dynsec = obj_textsec (abfd);
  else
    dynsec = obj_datasec (abfd);
  dynoff -= bfd_get_section_vma (abfd, dynsec);
  if (dynoff < 0 || dynoff > bfd_section_size (abfd, dynsec))
    return true;

  /* This executable appears to be dynamically linked in a way that we
     can understand.  */
  if (! bfd_get_section_contents (abfd, dynsec, (PTR) &linkinfo, dynoff,
				  (bfd_size_type) sizeof linkinfo))
    return true;

  /* Swap in the dynamic link information.  */
  info->dyninfo.ld_loaded = GET_WORD (abfd, linkinfo.ld_loaded);
  info->dyninfo.ld_need = GET_WORD (abfd, linkinfo.ld_need);
  info->dyninfo.ld_rules = GET_WORD (abfd, linkinfo.ld_rules);
  info->dyninfo.ld_got = GET_WORD (abfd, linkinfo.ld_got);
  info->dyninfo.ld_plt = GET_WORD (abfd, linkinfo.ld_plt);
  info->dyninfo.ld_rel = GET_WORD (abfd, linkinfo.ld_rel);
  info->dyninfo.ld_hash = GET_WORD (abfd, linkinfo.ld_hash);
  info->dyninfo.ld_stab = GET_WORD (abfd, linkinfo.ld_stab);
  info->dyninfo.ld_stab_hash = GET_WORD (abfd, linkinfo.ld_stab_hash);
  info->dyninfo.ld_buckets = GET_WORD (abfd, linkinfo.ld_buckets);
  info->dyninfo.ld_symbols = GET_WORD (abfd, linkinfo.ld_symbols);
  info->dyninfo.ld_symb_size = GET_WORD (abfd, linkinfo.ld_symb_size);
  info->dyninfo.ld_text = GET_WORD (abfd, linkinfo.ld_text);
  info->dyninfo.ld_plt_sz = GET_WORD (abfd, linkinfo.ld_plt_sz);

  /* The only way to get the size of the symbol information appears to
     be to determine the distance between it and the string table.  */
  info->dynsym_count = ((info->dyninfo.ld_symbols - info->dyninfo.ld_stab)
			/ EXTERNAL_NLIST_SIZE);
  BFD_ASSERT (info->dynsym_count * EXTERNAL_NLIST_SIZE
	      == info->dyninfo.ld_symbols - info->dyninfo.ld_stab);

  /* Similarly, the relocs end at the hash table.  */
  info->dynrel_count = ((info->dyninfo.ld_hash - info->dyninfo.ld_rel)
			/ obj_reloc_entry_size (abfd));
  BFD_ASSERT (info->dynrel_count * obj_reloc_entry_size (abfd)
	      == info->dyninfo.ld_hash - info->dyninfo.ld_rel);

  info->valid = true;

  return true;
}

/* Return the amount of memory required for the dynamic symbols.  */

static long
sunos_get_dynamic_symtab_upper_bound (abfd)
     bfd *abfd;
{
  struct sunos_dynamic_info *info;

  if (! sunos_read_dynamic_info (abfd))
    return -1;

  info = (struct sunos_dynamic_info *) obj_aout_dynamic_info (abfd);
  if (! info->valid)
    {
      bfd_set_error (bfd_error_no_symbols);
      return -1;
    }

  return (info->dynsym_count + 1) * sizeof (asymbol *);
}

/* Read in the dynamic symbols.  */

static long
sunos_canonicalize_dynamic_symtab (abfd, storage)
     bfd *abfd;
     asymbol **storage;
{
  struct sunos_dynamic_info *info;
  long i;

  /* Get the general dynamic information.  */
  if (obj_aout_dynamic_info (abfd) == NULL)
    {
      if (! sunos_read_dynamic_info (abfd))
	  return -1;
    }

  info = (struct sunos_dynamic_info *) obj_aout_dynamic_info (abfd);
  if (! info->valid)
    {
      bfd_set_error (bfd_error_no_symbols);
      return -1;
    }

  /* Get the dynamic nlist structures.  */
  if (info->dynsym == (struct external_nlist *) NULL)
    {
      info->dynsym = ((struct external_nlist *)
		      bfd_alloc (abfd,
				 (info->dynsym_count
				  * EXTERNAL_NLIST_SIZE)));
      if (info->dynsym == NULL && info->dynsym_count != 0)
	{
	  bfd_set_error (bfd_error_no_memory);
	  return -1;
	}
      if (bfd_seek (abfd, info->dyninfo.ld_stab, SEEK_SET) != 0
	  || (bfd_read ((PTR) info->dynsym, info->dynsym_count,
			EXTERNAL_NLIST_SIZE, abfd)
	      != info->dynsym_count * EXTERNAL_NLIST_SIZE))
	{
	  if (info->dynsym != NULL)
	    {
	      bfd_release (abfd, info->dynsym);
	      info->dynsym = NULL;
	    }
	  return -1;
	}
    }

  /* Get the dynamic strings.  */
  if (info->dynstr == (char *) NULL)
    {
      info->dynstr = (char *) bfd_alloc (abfd, info->dyninfo.ld_symb_size);
      if (info->dynstr == NULL && info->dyninfo.ld_symb_size != 0)
	{
	  bfd_set_error (bfd_error_no_memory);
	  return -1;
	}
      if (bfd_seek (abfd, info->dyninfo.ld_symbols, SEEK_SET) != 0
	  || (bfd_read ((PTR) info->dynstr, 1, info->dyninfo.ld_symb_size,
			abfd)
	      != info->dyninfo.ld_symb_size))
	{
	  if (info->dynstr != NULL)
	    {
	      bfd_release (abfd, info->dynstr);
	      info->dynstr = NULL;
	    }
	  return -1;
	}
    }

#ifdef CHECK_DYNAMIC_HASH
  /* Check my understanding of the dynamic hash table by making sure
     that each symbol can be located in the hash table.  */
  {
    bfd_size_type table_size;
    bfd_byte *table;
    bfd_size_type i;

    if (info->dyninfo.ld_buckets > info->dynsym_count)
      abort ();
    table_size = info->dyninfo.ld_stab - info->dyninfo.ld_hash;
    table = (bfd_byte *) malloc (table_size);
    if (table == NULL && table_size != 0)
      abort ();
    if (bfd_seek (abfd, info->dyninfo.ld_hash, SEEK_SET) != 0
	|| bfd_read ((PTR) table, 1, table_size, abfd) != table_size)
      abort ();
    for (i = 0; i < info->dynsym_count; i++)
      {
	unsigned char *name;
	unsigned long hash;

	name = ((unsigned char *) info->dynstr
		+ GET_WORD (abfd, info->dynsym[i].e_strx));
	hash = 0;
	while (*name != '\0')
	  hash = (hash << 1) + *name++;
	hash &= 0x7fffffff;
	hash %= info->dyninfo.ld_buckets;
	while (GET_WORD (abfd, table + 8 * hash) != i)
	  {
	    hash = GET_WORD (abfd, table + 8 * hash + 4);
	    if (hash == 0 || hash >= table_size / 8)
	      abort ();
	  }
      }
    free (table);
  }
#endif /* CHECK_DYNAMIC_HASH */

  /* Get the asymbol structures corresponding to the dynamic nlist
     structures.  */
  if (info->canonical_dynsym == (aout_symbol_type *) NULL)
    {
      info->canonical_dynsym = ((aout_symbol_type *)
				bfd_alloc (abfd,
					   (info->dynsym_count
					    * sizeof (aout_symbol_type))));
      if (info->canonical_dynsym == NULL && info->dynsym_count != 0)
	{
	  bfd_set_error (bfd_error_no_memory);
	  return -1;
	}

      if (! aout_32_translate_symbol_table (abfd, info->canonical_dynsym,
					    info->dynsym, info->dynsym_count,
					    info->dynstr,
					    info->dyninfo.ld_symb_size,
					    true))
	{
	  if (info->canonical_dynsym != NULL)
	    {
	      bfd_release (abfd, info->canonical_dynsym);
	      info->canonical_dynsym = NULL;
	    }
	  return -1;
	}
    }

  /* Return pointers to the dynamic asymbol structures.  */
  for (i = 0; i < info->dynsym_count; i++)
    *storage++ = (asymbol *) (info->canonical_dynsym + i);
  *storage = NULL;

  return info->dynsym_count;
}

/* Return the amount of memory required for the dynamic relocs.  */

static long
sunos_get_dynamic_reloc_upper_bound (abfd)
     bfd *abfd;
{
  struct sunos_dynamic_info *info;

  if (! sunos_read_dynamic_info (abfd))
    return -1;

  info = (struct sunos_dynamic_info *) obj_aout_dynamic_info (abfd);
  if (! info->valid)
    {
      bfd_set_error (bfd_error_no_symbols);
      return -1;
    }

  return (info->dynrel_count + 1) * sizeof (arelent *);
}

/* Read in the dynamic relocs.  */

static long
sunos_canonicalize_dynamic_reloc (abfd, storage, syms)
     bfd *abfd;
     arelent **storage;
     asymbol **syms;
{
  struct sunos_dynamic_info *info;
  long i;

  /* Get the general dynamic information.  */
  if (obj_aout_dynamic_info (abfd) == (PTR) NULL)
    {
      if (! sunos_read_dynamic_info (abfd))
	return -1;
    }

  info = (struct sunos_dynamic_info *) obj_aout_dynamic_info (abfd);
  if (! info->valid)
    {
      bfd_set_error (bfd_error_no_symbols);
      return -1;
    }

  /* Get the dynamic reloc information.  */
  if (info->dynrel == NULL)
    {
      info->dynrel = (PTR) bfd_alloc (abfd,
				      (info->dynrel_count
				       * obj_reloc_entry_size (abfd)));
      if (info->dynrel == NULL && info->dynrel_count != 0)
	{
	  bfd_set_error (bfd_error_no_memory);
	  return -1;
	}
      if (bfd_seek (abfd, info->dyninfo.ld_rel, SEEK_SET) != 0
	  || (bfd_read ((PTR) info->dynrel, info->dynrel_count,
			obj_reloc_entry_size (abfd), abfd)
	      != info->dynrel_count * obj_reloc_entry_size (abfd)))
	{
	  if (info->dynrel != NULL)
	    {
	      bfd_release (abfd, info->dynrel);
	      info->dynrel = NULL;
	    }
	  return -1;
	}
    }

  /* Get the arelent structures corresponding to the dynamic reloc
     information.  */
  if (info->canonical_dynrel == (arelent *) NULL)
    {
      arelent *to;

      info->canonical_dynrel = ((arelent *)
				bfd_alloc (abfd,
					   (info->dynrel_count
					    * sizeof (arelent))));
      if (info->canonical_dynrel == NULL && info->dynrel_count != 0)
	{
	  bfd_set_error (bfd_error_no_memory);
	  return -1;
	}
      
      to = info->canonical_dynrel;

      if (obj_reloc_entry_size (abfd) == RELOC_EXT_SIZE)
	{
	  register struct reloc_ext_external *p;
	  struct reloc_ext_external *pend;

	  p = (struct reloc_ext_external *) info->dynrel;
	  pend = p + info->dynrel_count;
	  for (; p < pend; p++, to++)
	    NAME(aout,swap_ext_reloc_in) (abfd, p, to, syms);
	}
      else
	{
	  register struct reloc_std_external *p;
	  struct reloc_std_external *pend;

	  p = (struct reloc_std_external *) info->dynrel;
	  pend = p + info->dynrel_count;
	  for (; p < pend; p++, to++)
	    NAME(aout,swap_std_reloc_in) (abfd, p, to, syms);
	}
    }

  /* Return pointers to the dynamic arelent structures.  */
  for (i = 0; i < info->dynrel_count; i++)
    *storage++ = info->canonical_dynrel + i;
  *storage = NULL;

  return info->dynrel_count;
}
