/* Routines to link ECOFF debugging information.
   Copyright 1993 Free Software Foundation, Inc.
   Written by Ian Lance Taylor, Cygnus Support, <ian@cygnus.com>.

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.  */

#include "bfd.h"
#include "sysdep.h"
#include "libbfd.h"
#include "coff/internal.h"
#include "coff/sym.h"
#include "coff/symconst.h"
#include "coff/ecoff.h"

static boolean ecoff_add_bytes PARAMS ((char **buf, char **bufend,
					size_t need));
static bfd_size_type ecoff_add_string PARAMS ((struct ecoff_debug_info *,
					       FDR *fdr, const char *string));
static void ecoff_align_debug PARAMS ((bfd *abfd,
				       struct ecoff_debug_info *debug,
				       const struct ecoff_debug_swap *swap));

/* The minimum amount of data to allocate.  */
#define ALLOC_SIZE (4064)

/* Add bytes to a buffer.  Return success.  */

static boolean
ecoff_add_bytes (buf, bufend, need)
     char **buf;
     char **bufend;
     size_t need;
{
  size_t have;
  size_t want;
  char *newbuf;

  have = *bufend - *buf;
  if (have > need)
    want = ALLOC_SIZE;
  else
    {
      want = need - have;
      if (want < ALLOC_SIZE)
	want = ALLOC_SIZE;
    }
  if (*buf == NULL)
    newbuf = (char *) malloc (have + want);
  else
    newbuf = (char *) realloc (*buf, have + want);
  if (newbuf == NULL)
    {
      bfd_error = no_memory;
      return false;
    }
  *buf = newbuf;
  *bufend = *buf + have + want;
  return true;
}

/* Accumulate the debugging information from INPUT_BFD into
   OUTPUT_BFD.  The INPUT_DEBUG argument points to some ECOFF
   debugging information which we want to link into the information
   pointed to by the OUTPUT_DEBUG argument.  OUTPUT_SWAP and
   INPUT_SWAP point to the swapping information needed.  */

/*ARGSUSED*/
boolean
bfd_ecoff_debug_accumulate (output_bfd, output_debug, output_swap,
			    input_bfd, input_debug, input_swap,
			    relocateable)
     bfd *output_bfd;
     struct ecoff_debug_info *output_debug;
     const struct ecoff_debug_swap *output_swap;
     bfd *input_bfd;
     struct ecoff_debug_info *input_debug;
     const struct ecoff_debug_swap *input_swap;
     boolean relocateable;
{
  void (* const swap_sym_in) PARAMS ((bfd *, PTR, SYMR *))
    = input_swap->swap_sym_in;
  void (* const swap_sym_out) PARAMS ((bfd *, const SYMR *, PTR))
    = output_swap->swap_sym_out;
  void (* const swap_fdr_out) PARAMS ((bfd *, const FDR *, PTR))
    = output_swap->swap_fdr_out;
  void (* const swap_rfd_out) PARAMS ((bfd *, const RFDT *, PTR))
    = output_swap->swap_rfd_out;
  HDRR *output_symhdr;
  HDRR *input_symhdr;
  bfd_vma section_adjust[scMax];
  asection *sec;
  char *sym_out;
  char *lraw_src;
  char *lraw_end;
  char *fdr_ptr;
  char *fdr_end;
  bfd_size_type fdr_add;
  char *fdr_out;

  output_symhdr = &output_debug->symbolic_header;
  input_symhdr = &input_debug->symbolic_header;

  /* Make sure the buffers are large enough.  */
#define CHECK(start, end, count, size)				\
  if ((char *) output_debug->end - (char *) output_debug->start	\
      < (output_symhdr->count + input_symhdr->count) * size)	\
    {								\
      if (! ecoff_add_bytes ((char **) &output_debug->start,	\
			     (char **) &output_debug->end,	\
			     ((output_symhdr->count		\
			       + input_symhdr->count)		\
			      * size)))				\
	return false;						\
    }

  CHECK (line, line_end, cbLine, sizeof (unsigned char));
#if 0
  /* I don't think dense number information is needed.  */
  CHECK (external_dnr, external_dnr_end, idnMax,
	 output_swap->external_dnr_size);
#endif
  CHECK (external_pdr, external_pdr_end, ipdMax,
	 output_swap->external_pdr_size);
  CHECK (external_sym, external_sym_end, isymMax,
	 output_swap->external_sym_size);
  CHECK (external_opt, external_opt_end, ioptMax,
	 output_swap->external_opt_size);
  CHECK (external_aux, external_aux_end, iauxMax,
	 sizeof (union aux_ext));
  CHECK (ss, ss_end, issMax, sizeof (char));
  CHECK (external_fdr, external_fdr_end, ifdMax,
	 output_swap->external_fdr_size);

  /* The RFD's are special, since we create them if needed.  */
  {
    bfd_size_type crfd;

    crfd = input_symhdr->crfd;
    if (input_symhdr->crfd == 0)
      input_symhdr->crfd = input_symhdr->ifdMax;
    CHECK (external_rfd, external_rfd_end, crfd,
	   output_swap->external_rfd_size);
    input_symhdr->crfd = crfd;
  }

#undef CHECK

  /* Use section_adjust to hold the value to add to a symbol in a
     particular section.  */
  memset ((PTR) section_adjust, 0, sizeof section_adjust);

#define SET(name, indx) \
  sec = bfd_get_section_by_name (input_bfd, name); \
  if (sec != NULL) \
    section_adjust[indx] = (sec->output_section->vma \
			    + sec->output_offset \
			    - sec->vma);

  SET (".text", scText);
  SET (".data", scData);
  SET (".bss", scBss);
  SET (".sdata", scSData);
  SET (".sbss", scSBss);
  /* scRdata section may be either .rdata or .rodata.  */
  SET (".rdata", scRData);
  SET (".rodata", scRData);
  SET (".init", scInit);
  SET (".fini", scFini);

#undef SET

  /* Swap in the local symbols, adjust their values, and swap them out
     again.  The external symbols are handled separately.  */
  sym_out = ((char *) output_debug->external_sym
	     + output_symhdr->isymMax * output_swap->external_sym_size);
  lraw_src = (char *) input_debug->external_sym;
  lraw_end = lraw_src + input_symhdr->isymMax * input_swap->external_sym_size;
  for (;  lraw_src < lraw_end;  lraw_src += input_swap->external_sym_size)
    {
      SYMR internal_sym;

      (*swap_sym_in) (input_bfd, (PTR) lraw_src, &internal_sym);

      BFD_ASSERT (internal_sym.sc != scCommon
		  && internal_sym.sc != scSCommon);

      /* Adjust the symbol value if appropriate.  */
      switch (internal_sym.st)
	{
	case stNil:
	  if (ECOFF_IS_STAB (&internal_sym))
	    break;
	  /* Fall through.  */
	case stGlobal:
	case stStatic:
	case stLabel:
	case stProc:
	case stStaticProc:
	  internal_sym.value += section_adjust[internal_sym.sc];
	  break;

	default:
	  break;
	}

      (*swap_sym_out) (output_bfd, &internal_sym, sym_out);
      sym_out += output_swap->external_sym_size;
    }

  /* Copy the information that does not need swapping.  */
  memcpy (output_debug->line + output_symhdr->cbLine,
	  input_debug->line,
	  (size_t) (input_symhdr->cbLine * sizeof (unsigned char)));
  memcpy (output_debug->external_aux + output_symhdr->iauxMax,
	  input_debug->external_aux,
	  (size_t) (input_symhdr->iauxMax * sizeof (union aux_ext)));
  memcpy (output_debug->ss + output_symhdr->issMax,
	  input_debug->ss,
	  (size_t) (input_symhdr->issMax * sizeof (char)));

  /* Some of the information may need to be swapped.  */
  if (output_bfd->xvec->header_byteorder_big_p
      == input_bfd->xvec->header_byteorder_big_p)
    {
      /* The two BFD's have the same endianness, so memcpy will
	 suffice.  */
#if 0
      /* I don't think dense number information is needed.  */
      BFD_ASSERT (output_swap->external_dnr_size
		  == input_swap->external_dnr_size);
      if (input_symhdr->idnMax > 0)
	memcpy (((char *) output_debug->external_dnr
		 + output_symhdr->idnMax * output_swap->external_dnr_size),
		input_debug->external_dnr,
		((size_t)
		 (input_symhdr->idnMax * output_swap->external_dnr_size)));
#endif
      BFD_ASSERT (output_swap->external_pdr_size
		  == input_swap->external_pdr_size);
      if (input_symhdr->ipdMax > 0)
	memcpy (((char *) output_debug->external_pdr
		 + output_symhdr->ipdMax * output_swap->external_pdr_size),
		input_debug->external_pdr,
		((size_t)
		 (input_symhdr->ipdMax * output_swap->external_pdr_size)));
      BFD_ASSERT (output_swap->external_opt_size
		  == input_swap->external_opt_size);
      if (input_symhdr->ioptMax > 0)
	memcpy (((char *) output_debug->external_opt
		 + output_symhdr->ioptMax * output_swap->external_opt_size),
		input_debug->external_opt,
		((size_t)
		 (input_symhdr->ioptMax * output_swap->external_opt_size)));
    }
  else
    {
      bfd_size_type outsz, insz;
      char *in;
      char *end;
      char *out;

      /* The two BFD's have different endianness, so we must swap
	 everything in and out.  This code would always work, but it
	 would be slow in the normal case.  */
#if 0
      /* I don't think dense number information is needed.  */
      outsz = output_swap->external_dnr_size;
      insz = input_swap->external_dnr_size;
      in = (char *) input_debug->external_dnr;
      end = in + input_symhdr->idnMax * insz;
      out = ((char *) output_debug->external_dnr
	     + output_symhdr->idnMax * outsz);
      for (; in < end; in += insz, out += outsz)
	{
	  DNR dnr;

	  (*input_swap->swap_dnr_in) (input_bfd, in, &dnr);
	  (*output_swap->swap_dnr_out) (output_bfd, &dnr, out);
	}
#endif

      outsz = output_swap->external_pdr_size;
      insz = input_swap->external_pdr_size;
      in = (char *) input_debug->external_pdr;
      end = in + input_symhdr->ipdMax * insz;
      out = ((char *) output_debug->external_pdr
	     + output_symhdr->ipdMax * outsz);
      for (; in < end; in += insz, out += outsz)
	{
	  PDR pdr;

	  (*input_swap->swap_pdr_in) (input_bfd, in, &pdr);
	  (*output_swap->swap_pdr_out) (output_bfd, &pdr, out);
	}

      outsz = output_swap->external_opt_size;
      insz = input_swap->external_opt_size;
      in = (char *) input_debug->external_opt;
      end = in + input_symhdr->ioptMax * insz;
      out = ((char *) output_debug->external_opt
	     + output_symhdr->ioptMax * outsz);
      for (; in < end; in += insz, out += outsz)
	{
	  OPTR opt;

	  (*input_swap->swap_opt_in) (input_bfd, in, &opt);
	  (*output_swap->swap_opt_out) (output_bfd, &opt, out);
	}
    }

  /* Set ifdbase so that the external symbols know how to adjust their
     ifd values.  */
  input_debug->ifdbase = output_symhdr->ifdMax;

  /* We need to handle the FDR's whether they are swapped or not.  */
  if (input_debug->fdr != (FDR *) NULL)
    {
      fdr_ptr = (char *) input_debug->fdr;
      fdr_add = sizeof (FDR);
    }
  else
    {
      fdr_ptr = (char *) input_debug->external_fdr;
      fdr_add = input_swap->external_fdr_size;
    }
  fdr_end = fdr_ptr + input_symhdr->ifdMax * fdr_add;
  fdr_out = ((char *) output_debug->external_fdr
	     + output_symhdr->ifdMax * output_swap->external_fdr_size);
  for (;
       fdr_ptr < fdr_end;
       fdr_ptr += fdr_add, fdr_out += output_swap->external_fdr_size)
    {
      FDR fdr;

      if (input_debug->fdr != (FDR *) NULL)
	fdr = *(FDR *) fdr_ptr;
      else
	(*input_swap->swap_fdr_in) (input_bfd, (PTR) fdr_ptr, &fdr);

      /* FIXME: It is conceivable that this FDR points to the .init or
	 .fini section, in which case this will not do the right
	 thing.  */
      fdr.adr += section_adjust[scText];

      fdr.issBase += output_symhdr->issMax;
      fdr.isymBase += output_symhdr->isymMax;
      fdr.ilineBase += output_symhdr->ilineMax;
      fdr.ioptBase += output_symhdr->ioptMax;
      fdr.ipdFirst += output_symhdr->ipdMax;
      fdr.iauxBase += output_symhdr->iauxMax;
      fdr.rfdBase += output_symhdr->crfd;

      /* If there are no RFD's, we are going to add some.  We don't
	 want to adjust crfd for this, so that all the FDR's can share
	 the RFD's.  */
      if (input_symhdr->crfd == 0)
	fdr.crfd = input_symhdr->ifdMax;

      if (fdr.cbLine != 0)
	fdr.cbLineOffset += output_symhdr->cbLine;

      (*swap_fdr_out) (output_bfd, &fdr, fdr_out);
    }

  if (input_symhdr->crfd > 0)
    {
      void (* const swap_rfd_in) PARAMS ((bfd *, PTR, RFDT *))
	= input_swap->swap_rfd_in;
      bfd_size_type outsz, insz;
      char *rfd_in;
      char *rfd_end;
      char *rfd_out;

      /* Swap and adjust the RFD's.  RFD's are only created by the
	 linker, so this will only be necessary if one of the input
	 files is the result of a partial link.  Presumably all
	 necessary RFD's are present.  */
      outsz = output_swap->external_rfd_size;
      insz = input_swap->external_rfd_size;
      rfd_in = (char *) input_debug->external_rfd;
      rfd_end = rfd_in + input_symhdr->crfd * insz;
      rfd_out = ((char *) output_debug->external_rfd
		 + output_symhdr->crfd * outsz);
      for (;
	   rfd_in < rfd_end;
	   rfd_in += insz, rfd_out += outsz)
	{
	  RFDT rfd;

	  (*swap_rfd_in) (input_bfd, rfd_in, &rfd);
	  rfd += output_symhdr->ifdMax;
	  (*swap_rfd_out) (output_bfd, &rfd, rfd_out);
	}
      output_symhdr->crfd += input_symhdr->crfd;
    }
  else
    {
      bfd_size_type outsz;
      char *rfd_out;
      char *rfd_end;
      RFDT rfd;

      /* Create RFD's.  Some of the debugging information includes
	 relative file indices.  These indices are taken as indices to
	 the RFD table if there is one, or to the global table if
	 there is not.  If we did not create RFD's, we would have to
	 parse and adjust all the debugging information which contains
	 file indices.  */
      outsz = output_swap->external_rfd_size;
      rfd = output_symhdr->ifdMax;
      rfd_out = ((char *) output_debug->external_rfd
		 + output_symhdr->crfd * outsz);
      rfd_end = (rfd_out + input_symhdr->ifdMax * outsz);
      for (; rfd_out < rfd_end; rfd_out += outsz, rfd++)
	(*swap_rfd_out) (output_bfd, &rfd, rfd_out);
      output_symhdr->crfd += input_symhdr->ifdMax;
    }

  /* Update the counts.  */
  output_symhdr->ilineMax += input_symhdr->ilineMax;
  output_symhdr->cbLine += input_symhdr->cbLine;
#if 0
  /* I don't think dense number information is needed.  */
  output_symhdr->idnMax += input_symhdr->idnMax;
#endif
  output_symhdr->ipdMax += input_symhdr->ipdMax;
  output_symhdr->isymMax += input_symhdr->isymMax;
  output_symhdr->ioptMax += input_symhdr->ioptMax;
  output_symhdr->iauxMax += input_symhdr->iauxMax;
  output_symhdr->issMax += input_symhdr->issMax;
  output_symhdr->ifdMax += input_symhdr->ifdMax;

  return true;
}

/* Add a string to the debugging information we are accumulating.
   Return the offset from the fdr string base.  */

static bfd_size_type
ecoff_add_string (output, fdr, string)
     struct ecoff_debug_info *output;
     FDR *fdr;
     const char *string;
{
  HDRR *symhdr;
  size_t len;
  bfd_size_type ret;

  symhdr = &output->symbolic_header;
  len = strlen (string);
  if (output->ss_end - output->ss < symhdr->issMax + len + 1)
    {
      if (ecoff_add_bytes (&output->ss, &output->ss_end,
			   symhdr->issMax + len + 1) == false)
	return (bfd_size_type) -1;
    }
  memcpy (output->ss + symhdr->issMax, string, len + 1);
  ret = fdr->cbSs;
  symhdr->issMax += len + 1;
  fdr->cbSs += len + 1;
  return ret;
}

/* Add debugging information from a non-ECOFF file.  */

boolean
bfd_ecoff_debug_link_other (output_bfd, output_debug, output_swap, input_bfd)
     bfd *output_bfd;
     struct ecoff_debug_info *output_debug;
     const struct ecoff_debug_swap *output_swap;
     bfd *input_bfd;
{
  void (* const swap_sym_out) PARAMS ((bfd *, const SYMR *, PTR))
    = output_swap->swap_sym_out;
  HDRR *output_symhdr = &output_debug->symbolic_header;
  FDR fdr;
  asection *sec;
  asymbol **symbols;
  asymbol **sym_ptr;
  asymbol **sym_end;

  memset (&fdr, 0, sizeof fdr);

  sec = bfd_get_section_by_name (input_bfd, ".text");
  if (sec != NULL)
    fdr.adr = sec->output_section->vma + sec->output_offset;
  else
    {
      /* FIXME: What about .init or .fini?  */
      fdr.adr = 0;
    }

  fdr.issBase = output_symhdr->issMax;
  fdr.cbSs = 0;
  fdr.rss = ecoff_add_string (output_debug, &fdr,
			      bfd_get_filename (input_bfd));
  if (fdr.rss == -1)
    return false;
  fdr.isymBase = output_symhdr->isymMax;

  /* Get the local symbols from the input BFD.  */
  symbols = (asymbol **) bfd_alloc (output_bfd,
				    get_symtab_upper_bound (input_bfd));
  if (symbols == (asymbol **) NULL)
    {
      bfd_error = no_memory;
      return false;
    }
  sym_end = symbols + bfd_canonicalize_symtab (input_bfd, symbols);

  /* Handle the local symbols.  Any external symbols are handled
     separately.  */
  fdr.csym = 0;
  for (sym_ptr = symbols; sym_ptr != sym_end; sym_ptr++)
    {
      SYMR internal_sym;

      if (((*sym_ptr)->flags & BSF_EXPORT) != 0)
	continue;
      memset (&internal_sym, 0, sizeof internal_sym);
      internal_sym.iss = ecoff_add_string (output_debug, &fdr,
					   (*sym_ptr)->name);

      if (internal_sym.iss == -1)
	return false;
      if (bfd_is_com_section ((*sym_ptr)->section)
	  || (*sym_ptr)->section == &bfd_und_section)
	internal_sym.value = (*sym_ptr)->value;
      else
	internal_sym.value = ((*sym_ptr)->value
			      + (*sym_ptr)->section->output_offset
			      + (*sym_ptr)->section->output_section->vma);
      internal_sym.st = stNil;
      internal_sym.sc = scUndefined;
      internal_sym.index = indexNil;

      if (((char *) output_debug->external_sym_end
	   - (char *) output_debug->external_sym)
	  < (output_symhdr->isymMax + 1) * output_swap->external_sym_size)
	{
	  if (! ecoff_add_bytes ((char **) &output_debug->external_sym,
				 (char **) &output_debug->external_sym_end,
				 ((output_symhdr->isymMax + 1)
				  * output_swap->external_sym_size)))
	    return false;
	}

      (*swap_sym_out) (output_bfd, &internal_sym,
		       ((char *) output_debug->external_sym
			+ (output_symhdr->isymMax
			   * output_swap->external_sym_size)));
      ++fdr.csym;
      ++output_symhdr->isymMax;
    }

  bfd_release (output_bfd, (PTR) symbols);

  if (((char *) output_debug->external_fdr_end
       - (char *) output_debug->external_fdr)
      < (output_symhdr->ifdMax + 1) * output_swap->external_fdr_size)
    {
      if (! ecoff_add_bytes ((char **) &output_debug->external_fdr,
			     (char **) &output_debug->external_fdr_end,
			     ((output_symhdr->ifdMax + 1)
			      * output_swap->external_fdr_size)))
	return false;
    }

  /* Leave everything else in the FDR zeroed out.  This will cause
     the lang field to be langC.  The fBigendian field will
     indicate little endian format, but it doesn't matter because
     it only applies to aux fields and there are none.  */
  (*output_swap->swap_fdr_out)
    (output_bfd, &fdr,
     ((char *) output_debug->external_fdr
      + output_symhdr->ifdMax * output_swap->external_fdr_size));
  ++output_symhdr->ifdMax;
  return true;
}

/* Set up ECOFF debugging information for the external symbols.  */

boolean
bfd_ecoff_debug_externals (abfd, debug, swap, relocateable, get_extr,
			   set_index)
     bfd *abfd;
     struct ecoff_debug_info *debug;
     const struct ecoff_debug_swap *swap;
     boolean relocateable;
     boolean (*get_extr) PARAMS ((asymbol *, EXTR *));
     void (*set_index) PARAMS ((asymbol *, bfd_size_type));
{
  HDRR * const symhdr = &debug->symbolic_header;
  asymbol **sym_ptr_ptr;
  size_t c;

  sym_ptr_ptr = bfd_get_outsymbols (abfd);
  if (sym_ptr_ptr == NULL)
    return true;

  for (c = bfd_get_symcount (abfd); c > 0; c--, sym_ptr_ptr++)
    {
      asymbol *sym_ptr;
      EXTR esym;

      sym_ptr = *sym_ptr_ptr;

      /* Get the external symbol information.  */
      if ((*get_extr) (sym_ptr, &esym) == false)
	continue;

      /* If we're producing an executable, move common symbols into
	 bss.  */
      if (relocateable == false)
	{
	  if (esym.asym.sc == scCommon)
	    esym.asym.sc = scBss;
	  else if (esym.asym.sc == scSCommon)
	    esym.asym.sc = scSBss;
	}


      if (bfd_is_com_section (sym_ptr->section)
	  || sym_ptr->section == &bfd_und_section)
	esym.asym.value = sym_ptr->value;
      else
	esym.asym.value = (sym_ptr->value
			   + sym_ptr->section->output_offset
			   + sym_ptr->section->output_section->vma);

      if (set_index)
	(*set_index) (sym_ptr, (bfd_size_type) symhdr->iextMax);

      if (! bfd_ecoff_debug_one_external (abfd, debug, swap,
					  sym_ptr->name, &esym))
	return false;
    }

  return true;
}

/* Add a single external symbol to the debugging information.  */

boolean
bfd_ecoff_debug_one_external (abfd, debug, swap, name, esym)
     bfd *abfd;
     struct ecoff_debug_info *debug;
     const struct ecoff_debug_swap *swap;
     const char *name;
     EXTR *esym;
{
  const bfd_size_type external_ext_size = swap->external_ext_size;
  void (* const swap_ext_out) PARAMS ((bfd *, const EXTR *, PTR))
    = swap->swap_ext_out;
  HDRR * const symhdr = &debug->symbolic_header;
  size_t namelen;

  namelen = strlen (name);

  if (debug->ssext_end - debug->ssext
      < symhdr->issExtMax + namelen + 1)
    {
      if (ecoff_add_bytes ((char **) &debug->ssext,
			   (char **) &debug->ssext_end,
			   symhdr->issExtMax + namelen + 1)
	  == false)
	return false;
    }
  if ((char *) debug->external_ext_end - (char *) debug->external_ext
      < (symhdr->iextMax + 1) * external_ext_size)
    {
      if (ecoff_add_bytes ((char **) &debug->external_ext,
			   (char **) &debug->external_ext_end,
			   (symhdr->iextMax + 1) * external_ext_size)
	  == false)
	return false;
    }

  esym->asym.iss = symhdr->issExtMax;

  (*swap_ext_out) (abfd, esym,
		   ((char *) debug->external_ext
		    + symhdr->iextMax * swap->external_ext_size));

  ++symhdr->iextMax;

  strcpy (debug->ssext + symhdr->issExtMax, name);
  symhdr->issExtMax += namelen + 1;

  return true;
}

/* Align the ECOFF debugging information.  */

/*ARGSUSED*/
static void
ecoff_align_debug (abfd, debug, swap)
     bfd *abfd;
     struct ecoff_debug_info *debug;
     const struct ecoff_debug_swap *swap;
{
  HDRR * const symhdr = &debug->symbolic_header;
  bfd_size_type debug_align, aux_align;
  size_t add;

  /* Adjust the counts so that structures are aligned.  The alignment
     of ALLOC_SIZE ensures that we do not have to worry about running
     off the end of the memory block when doing the memset.  */
  debug_align = swap->debug_align;
  aux_align = debug_align / sizeof (union aux_ext);

  add = debug_align - (symhdr->cbLine & (debug_align - 1));
  if (add != debug_align)
    {
      memset (debug->line + symhdr->cbLine, 0, add);
      symhdr->cbLine += add;
    }

  add = debug_align - (symhdr->issMax & (debug_align - 1));
  if (add != debug_align)
    {
      memset (debug->ss + symhdr->issMax, 0, add);
      symhdr->issMax += add;
    }

  add = debug_align - (symhdr->issExtMax & (debug_align - 1));
  if (add != debug_align)
    {
      memset (debug->ssext + symhdr->issExtMax, 0, add);
      symhdr->issExtMax += add;
    }

  add = aux_align - (symhdr->iauxMax & (aux_align - 1));
  if (add != aux_align)
    {
      memset (debug->external_aux + symhdr->iauxMax, 0,
	      add * sizeof (union aux_ext));
      symhdr->iauxMax += add;
    }
}

/* Return the size required by the ECOFF debugging information.  */

bfd_size_type
bfd_ecoff_debug_size (abfd, debug, swap)
     bfd *abfd;
     struct ecoff_debug_info *debug;
     const struct ecoff_debug_swap *swap;
{
  bfd_size_type tot;

  ecoff_align_debug (abfd, debug, swap);
  tot = swap->external_hdr_size;

#define ADD(count, size) \
  tot += debug->symbolic_header.count * size

  ADD (cbLine, sizeof (unsigned char));
  ADD (idnMax, swap->external_dnr_size);
  ADD (ipdMax, swap->external_pdr_size);
  ADD (isymMax, swap->external_sym_size);
  ADD (ioptMax, swap->external_opt_size);
  ADD (iauxMax, sizeof (union aux_ext));
  ADD (issMax, sizeof (char));
  ADD (issExtMax, sizeof (char));
  ADD (ifdMax, swap->external_fdr_size);
  ADD (crfd, swap->external_rfd_size);
  ADD (iextMax, swap->external_ext_size);

#undef ADD

  return tot;
}

/* Write out the ECOFF debugging information.  This function assumes
   that the information (the pointers and counts) in *DEBUG have been
   set correctly.  WHERE is the position in the file to write the
   information to.  This function fills in the file offsets in the
   symbolic header.  */

boolean
bfd_ecoff_write_debug (abfd, debug, swap, where)
     bfd *abfd;
     struct ecoff_debug_info *debug;
     const struct ecoff_debug_swap *swap;
     file_ptr where;
{
  HDRR * const symhdr = &debug->symbolic_header;
  char *buff;

  ecoff_align_debug (abfd, debug, swap);

  /* Go to the right location in the file.  */
  if (bfd_seek (abfd, where, SEEK_SET) != 0)
    return false;

  where += swap->external_hdr_size;

  /* Fill in the file offsets.  */
#define SET(offset, count, size) \
  if (symhdr->count == 0) \
    symhdr->offset = 0; \
  else \
    { \
      symhdr->offset = where; \
      where += symhdr->count * size; \
    }

  SET (cbLineOffset, cbLine, sizeof (unsigned char));
  SET (cbDnOffset, idnMax, swap->external_dnr_size);
  SET (cbPdOffset, ipdMax, swap->external_pdr_size);
  SET (cbSymOffset, isymMax, swap->external_sym_size);
  SET (cbOptOffset, ioptMax, swap->external_opt_size);
  SET (cbAuxOffset, iauxMax, sizeof (union aux_ext));
  SET (cbSsOffset, issMax, sizeof (char));
  SET (cbSsExtOffset, issExtMax, sizeof (char));
  SET (cbFdOffset, ifdMax, swap->external_fdr_size);
  SET (cbRfdOffset, crfd, swap->external_rfd_size);
  SET (cbExtOffset, iextMax, swap->external_ext_size);
#undef SET

  buff = (PTR) alloca (swap->external_hdr_size);
  (*swap->swap_hdr_out) (abfd, symhdr, buff);
  if (bfd_write (buff, 1, swap->external_hdr_size, abfd)
      != swap->external_hdr_size)
    return false;

#define WRITE(ptr, count, size, offset) \
  BFD_ASSERT (symhdr->offset == 0 || bfd_tell (abfd) == symhdr->offset); \
  if (bfd_write ((PTR) debug->ptr, size, symhdr->count, abfd) \
      != size * symhdr->count) \
    return false;

  WRITE (line, cbLine, sizeof (unsigned char), cbLineOffset);
  WRITE (external_dnr, idnMax, swap->external_dnr_size, cbDnOffset);
  WRITE (external_pdr, ipdMax, swap->external_pdr_size, cbPdOffset);
  WRITE (external_sym, isymMax, swap->external_sym_size, cbSymOffset);
  WRITE (external_opt, ioptMax, swap->external_opt_size, cbOptOffset);
  WRITE (external_aux, iauxMax, sizeof (union aux_ext), cbAuxOffset);
  WRITE (ss, issMax, sizeof (char), cbSsOffset);
  WRITE (ssext, issExtMax, sizeof (char), cbSsExtOffset);
  WRITE (external_fdr, ifdMax, swap->external_fdr_size, cbFdOffset);
  WRITE (external_rfd, crfd, swap->external_rfd_size, cbRfdOffset);
  WRITE (external_ext, iextMax, swap->external_ext_size, cbExtOffset);
#undef WRITE

  return true;
}
