/* BFD back-end for raw ARM a.out binaries.
   Copyright 1994, 1995, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2005,
   2007, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
   Contributed by Richard Earnshaw (rwe@pegasus.esprit.ec.org)

   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 3 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., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.  */

#include "sysdep.h"
#include "bfd.h"

/* Avoid multiple definitions from aoutx if supporting standard a.out
   as well as our own.  */
/* Do not "beautify" the CONCAT* macro args.  Traditional C will not
   remove whitespace added here, and thus will fail to concatenate
   the tokens.  */
#define NAME(x,y) CONCAT3 (aoutarm,_32_,y)

#define N_TXTADDR(x)						\
  ((N_MAGIC (x) == NMAGIC)					\
   ? (bfd_vma) 0x8000						\
   : ((N_MAGIC (x) != ZMAGIC)					\
      ? (bfd_vma) 0						\
      : ((N_SHARED_LIB (x))					\
	 ? ((x).a_entry & ~(bfd_vma) (TARGET_PAGE_SIZE - 1))	\
	 : (bfd_vma) TEXT_START_ADDR)))

#define TEXT_START_ADDR 0x8000
#define TARGET_PAGE_SIZE 0x8000
#define SEGMENT_SIZE TARGET_PAGE_SIZE
#define DEFAULT_ARCH bfd_arch_arm

#define MY(OP) CONCAT2 (aoutarm_,OP)
#define N_BADMAG(x) ((((x).a_info & ~007200) != ZMAGIC) && \
                     (((x).a_info & ~006000) != OMAGIC) && \
                     ((x).a_info != NMAGIC))
#define N_MAGIC(x) ((x).a_info & ~07200)

#define MY_bfd_reloc_type_lookup aoutarm_bfd_reloc_type_lookup
#define MY_bfd_reloc_name_lookup aoutarm_bfd_reloc_name_lookup

#include "libaout.h"
#include "aout/aout64.h"


static bfd_reloc_status_type
  MY (fix_pcrel_26) (bfd *, arelent *, asymbol *, void *, asection *, bfd *, char **);
static bfd_reloc_status_type
  MY (fix_pcrel_26_done) (bfd *, arelent *, asymbol *, void *, asection *, bfd *, char **);

reloc_howto_type MY (howto_table)[] =
{
  /* Type rs size bsz pcrel bitpos ovrf sf name part_inpl
     readmask setmask pcdone.  */
  HOWTO (0, 0, 0, 8, FALSE, 0, complain_overflow_bitfield, 0, "8", TRUE,
	 0x000000ff, 0x000000ff, FALSE),
  HOWTO (1, 0, 1, 16, FALSE, 0, complain_overflow_bitfield, 0, "16", TRUE,
	 0x0000ffff, 0x0000ffff, FALSE),
  HOWTO (2, 0, 2, 32, FALSE, 0, complain_overflow_bitfield, 0, "32", TRUE,
	 0xffffffff, 0xffffffff, FALSE),
  HOWTO (3, 2, 2, 26, TRUE, 0, complain_overflow_signed, MY (fix_pcrel_26),
	 "ARM26", TRUE, 0x00ffffff, 0x00ffffff, TRUE),
  HOWTO (4, 0, 0, 8, TRUE, 0, complain_overflow_signed, 0, "DISP8", TRUE,
	 0x000000ff, 0x000000ff, TRUE),
  HOWTO (5, 0, 1, 16, TRUE, 0, complain_overflow_signed, 0, "DISP16", TRUE,
	 0x0000ffff, 0x0000ffff, TRUE),
  HOWTO (6, 0, 2, 32, TRUE, 0, complain_overflow_signed, 0, "DISP32", TRUE,
	 0xffffffff, 0xffffffff, TRUE),
  HOWTO (7, 2, 2, 26, FALSE, 0, complain_overflow_signed,
	 MY (fix_pcrel_26_done), "ARM26D", TRUE, 0x0, 0x0,
	 FALSE),
  EMPTY_HOWTO (-1),
  HOWTO (9, 0, -1, 16, FALSE, 0, complain_overflow_bitfield, 0, "NEG16", TRUE,
	 0x0000ffff, 0x0000ffff, FALSE),
  HOWTO (10, 0, -2, 32, FALSE, 0, complain_overflow_bitfield, 0, "NEG32", TRUE,
	 0xffffffff, 0xffffffff, FALSE)
};

#define RELOC_ARM_BITS_NEG_BIG      ((unsigned int) 0x08)
#define RELOC_ARM_BITS_NEG_LITTLE   ((unsigned int) 0x10)

static reloc_howto_type *
MY (reloc_howto) (bfd *abfd,
		  struct reloc_std_external *rel,
		  int *r_index,
		  int *r_extern,
		  int *r_pcrel)
{
  unsigned int r_length;
  unsigned int r_pcrel_done;
  unsigned int r_neg;
  int howto_index;

  *r_pcrel = 0;
  if (bfd_header_big_endian (abfd))
    {
      *r_index     =  ((rel->r_index[0] << 16)
		       | (rel->r_index[1] << 8)
		       | rel->r_index[2]);
      *r_extern    = (0 != (rel->r_type[0] & RELOC_STD_BITS_EXTERN_BIG));
      r_pcrel_done = (0 != (rel->r_type[0] & RELOC_STD_BITS_PCREL_BIG));
      r_neg 	   = (0 != (rel->r_type[0] & RELOC_ARM_BITS_NEG_BIG));
      r_length     = ((rel->r_type[0] & RELOC_STD_BITS_LENGTH_BIG)
		      >> RELOC_STD_BITS_LENGTH_SH_BIG);
    }
  else
    {
      *r_index     = ((rel->r_index[2] << 16)
		      | (rel->r_index[1] << 8)
		      | rel->r_index[0]);
      *r_extern    = (0 != (rel->r_type[0] & RELOC_STD_BITS_EXTERN_LITTLE));
      r_pcrel_done = (0 != (rel->r_type[0] & RELOC_STD_BITS_PCREL_LITTLE));
      r_neg 	   = (0 != (rel->r_type[0] & RELOC_ARM_BITS_NEG_LITTLE));
      r_length     = ((rel->r_type[0] & RELOC_STD_BITS_LENGTH_LITTLE)
		      >> RELOC_STD_BITS_LENGTH_SH_LITTLE);
    }
  howto_index = r_length + 4 * r_pcrel_done + 8 * r_neg;
  if (howto_index == 3)
    *r_pcrel = 1;

  return MY (howto_table) + howto_index;
}

#define MY_reloc_howto(BFD, REL, IN, EX, PC) \
	MY (reloc_howto) (BFD, REL, &IN, &EX, &PC)

static void
MY (put_reloc) (bfd *abfd,
		int r_extern,
		int r_index,
		bfd_vma value,
		reloc_howto_type *howto,
		struct reloc_std_external *reloc)
{
  unsigned int r_length;
  int r_pcrel;
  int r_neg;

  PUT_WORD (abfd, value, reloc->r_address);
  /* Size as a power of two.  */
  r_length = howto->size;

  /* Special case for branch relocations.  */
  if (howto->type == 3 || howto->type == 7)
    r_length = 3;

  r_pcrel  = howto->type & 4; 	/* PC Relative done?  */
  r_neg = howto->type & 8;	/* Negative relocation.  */

  if (bfd_header_big_endian (abfd))
    {
      reloc->r_index[0] = r_index >> 16;
      reloc->r_index[1] = r_index >> 8;
      reloc->r_index[2] = r_index;
      reloc->r_type[0] =
	((r_extern ?     RELOC_STD_BITS_EXTERN_BIG : 0)
	 | (r_pcrel ?    RELOC_STD_BITS_PCREL_BIG : 0)
	 | (r_neg ?	 RELOC_ARM_BITS_NEG_BIG : 0)
	 | (r_length <<  RELOC_STD_BITS_LENGTH_SH_BIG));
    }
  else
    {
      reloc->r_index[2] = r_index >> 16;
      reloc->r_index[1] = r_index >> 8;
      reloc->r_index[0] = r_index;
      reloc->r_type[0] =
	((r_extern ?     RELOC_STD_BITS_EXTERN_LITTLE : 0)
	 | (r_pcrel ?    RELOC_STD_BITS_PCREL_LITTLE : 0)
	 | (r_neg ?	 RELOC_ARM_BITS_NEG_LITTLE : 0)
	 | (r_length <<  RELOC_STD_BITS_LENGTH_SH_LITTLE));
    }
}

#define MY_put_reloc(BFD, EXT, IDX, VAL, HOWTO, RELOC) \
  MY (put_reloc) (BFD, EXT, IDX, VAL, HOWTO, RELOC)

static void
MY (relocatable_reloc) (reloc_howto_type *howto,
			bfd *abfd,
			struct reloc_std_external *reloc,
			bfd_vma *amount,
			bfd_vma r_addr)
{
  if (howto->type == 3)
    {
      if (reloc->r_type[0]
	  & (bfd_header_big_endian (abfd)
	     ? RELOC_STD_BITS_EXTERN_BIG : RELOC_STD_BITS_EXTERN_LITTLE))
	/* The reloc is still external, so don't modify anything.  */
	*amount = 0;
      else
	{
	  *amount -= r_addr;
	  /* Change the r_pcrel value -- on the ARM, this bit is set once the
	     relocation is done.  */
	  if (bfd_header_big_endian (abfd))
	    reloc->r_type[0] |= RELOC_STD_BITS_PCREL_BIG;
	  else
	    reloc->r_type[0] |= RELOC_STD_BITS_PCREL_LITTLE;
	}
    }
  else if (howto->type == 7)
    *amount = 0;
}

#define MY_relocatable_reloc(HOW, BFD, REL, AMOUNT, ADDR) \
  MY (relocatable_reloc) (HOW, BFD, REL, &(AMOUNT), ADDR)

static bfd_reloc_status_type
MY (fix_pcrel_26_done) (bfd *abfd ATTRIBUTE_UNUSED,
			arelent *reloc_entry ATTRIBUTE_UNUSED,
			asymbol *symbol ATTRIBUTE_UNUSED,
			void * data ATTRIBUTE_UNUSED,
			asection *input_section ATTRIBUTE_UNUSED,
			bfd *output_bfd ATTRIBUTE_UNUSED,
			char **error_message ATTRIBUTE_UNUSED)
{
  /* This is dead simple at present.  */
  return bfd_reloc_ok;
}

static bfd_reloc_status_type
MY (fix_pcrel_26) (bfd *abfd,
		   arelent *reloc_entry,
		   asymbol *symbol,
		   void * data,
		   asection *input_section,
		   bfd *output_bfd,
		   char **error_message ATTRIBUTE_UNUSED)
{
  bfd_vma relocation;
  bfd_size_type addr = reloc_entry->address;
  bfd_vma target = bfd_get_32 (abfd, (bfd_byte *) data + addr);
  bfd_reloc_status_type flag = bfd_reloc_ok;

  /* If this is an undefined symbol, return error.  */
  if (bfd_is_und_section (symbol->section)
      && (symbol->flags & BSF_WEAK) == 0)
    return output_bfd ? bfd_reloc_ok : bfd_reloc_undefined;

  /* If the sections are different, and we are doing a partial relocation,
     just ignore it for now.  */
  if (symbol->section->name != input_section->name
      && output_bfd != NULL)
    return bfd_reloc_ok;

  relocation = (target & 0x00ffffff) << 2;
  relocation = (relocation ^ 0x02000000) - 0x02000000; /* Sign extend.  */
  relocation += symbol->value;
  relocation += symbol->section->output_section->vma;
  relocation += symbol->section->output_offset;
  relocation += reloc_entry->addend;
  relocation -= input_section->output_section->vma;
  relocation -= input_section->output_offset;
  relocation -= addr;
  if (relocation & 3)
    return bfd_reloc_overflow;

  /* Check for overflow.  */
  if (relocation & 0x02000000)
    {
      if ((relocation & ~ (bfd_vma) 0x03ffffff) != ~ (bfd_vma) 0x03ffffff)
	flag = bfd_reloc_overflow;
    }
  else if (relocation & ~ (bfd_vma) 0x03ffffff)
    flag = bfd_reloc_overflow;

  target &= ~ (bfd_vma) 0x00ffffff;
  target |= (relocation >> 2) & 0x00ffffff;
  bfd_put_32 (abfd, target, (bfd_byte *) data + addr);

  /* Now the ARM magic... Change the reloc type so that it is marked as done.
     Strictly this is only necessary if we are doing a partial relocation.  */
  reloc_entry->howto = &MY (howto_table)[7];

  return flag;
}

static reloc_howto_type *
MY (bfd_reloc_type_lookup) (bfd *abfd,
			    bfd_reloc_code_real_type code)
{
#define ASTD(i,j)       case i: return & MY (howto_table)[j]

  if (code == BFD_RELOC_CTOR)
    switch (bfd_arch_bits_per_address (abfd))
      {
      case 32:
        code = BFD_RELOC_32;
        break;
      default:
	return NULL;
      }

  switch (code)
    {
      ASTD (BFD_RELOC_16, 1);
      ASTD (BFD_RELOC_32, 2);
      ASTD (BFD_RELOC_ARM_PCREL_BRANCH, 3);
      ASTD (BFD_RELOC_8_PCREL, 4);
      ASTD (BFD_RELOC_16_PCREL, 5);
      ASTD (BFD_RELOC_32_PCREL, 6);
    default:
      return NULL;
    }
}

static reloc_howto_type *
MY (bfd_reloc_name_lookup) (bfd *abfd ATTRIBUTE_UNUSED,
			    const char *r_name)
{
  unsigned int i;

  for (i = 0;
       i < sizeof (MY (howto_table)) / sizeof (MY (howto_table)[0]);
       i++)
    if (MY (howto_table)[i].name != NULL
	&& strcasecmp (MY (howto_table)[i].name, r_name) == 0)
      return &MY (howto_table)[i];

  return NULL;
}

#define MY_swap_std_reloc_in     MY (swap_std_reloc_in)
#define MY_swap_std_reloc_out    MY (swap_std_reloc_out)
#define MY_get_section_contents  _bfd_generic_get_section_contents

void MY_swap_std_reloc_in (bfd *, struct reloc_std_external *, arelent *, asymbol **, bfd_size_type);
void MY_swap_std_reloc_out (bfd *, arelent *, struct reloc_std_external *);

#include "aoutx.h"

void
MY_swap_std_reloc_in (bfd *abfd,
		      struct reloc_std_external *bytes,
		      arelent *cache_ptr,
		      asymbol **symbols,
		      bfd_size_type symcount ATTRIBUTE_UNUSED)
{
  int r_index;
  int r_extern;
  int r_pcrel;
  struct aoutdata *su = &(abfd->tdata.aout_data->a);

  cache_ptr->address = H_GET_32 (abfd, bytes->r_address);

  cache_ptr->howto = MY_reloc_howto (abfd, bytes, r_index, r_extern, r_pcrel);

  MOVE_ADDRESS (0);
}

void
MY_swap_std_reloc_out (bfd *abfd,
		       arelent *g,
		       struct reloc_std_external *natptr)
{
  int r_index;
  asymbol *sym = *(g->sym_ptr_ptr);
  int r_extern;
  int r_length;
  int r_pcrel;
  int r_neg = 0;	/* Negative relocs use the BASEREL bit.  */
  asection *output_section = sym->section->output_section;

  PUT_WORD (abfd, g->address, natptr->r_address);

  r_length = g->howto->size ;   /* Size as a power of two.  */
  if (r_length < 0)
    {
      r_length = -r_length;
      r_neg = 1;
    }

  r_pcrel  = (int) g->howto->pc_relative; /* Relative to PC?  */

  /* For RISC iX, in pc-relative relocs the r_pcrel bit means that the
     relocation has been done already (Only for the 26-bit one I think).  */
  if (g->howto->type == 3)
    {
      r_length = 3;
      r_pcrel = 0;
    }
  else if (g->howto->type == 7)
    {
      r_length = 3;
      r_pcrel = 1;
    }

  /* Name was clobbered by aout_write_syms to be symbol index.  */

  /* If this relocation is relative to a symbol then set the
     r_index to the symbols index, and the r_extern bit.

     Absolute symbols can come in in two ways, either as an offset
     from the abs section, or as a symbol which has an abs value.
     check for that here.  */

  if (bfd_is_com_section (output_section)
      || bfd_is_abs_section (output_section)
      || bfd_is_und_section (output_section))
    {
      if (bfd_abs_section_ptr->symbol == sym)
	{
	  /* Whoops, looked like an abs symbol, but is really an offset
	     from the abs section.  */
	  r_index = 0;
	  r_extern = 0;
	}
      else
	{
	  /* Fill in symbol.  */
	  r_extern = 1;
	  r_index = (*(g->sym_ptr_ptr))->KEEPIT;
	}
    }
  else
    {
      /* Just an ordinary section.  */
      r_extern = 0;
      r_index  = output_section->target_index;
    }

  /* Now the fun stuff.  */
  if (bfd_header_big_endian (abfd))
    {
      natptr->r_index[0] = r_index >> 16;
      natptr->r_index[1] = r_index >> 8;
      natptr->r_index[2] = r_index;
      natptr->r_type[0] =
	(  (r_extern ?   RELOC_STD_BITS_EXTERN_BIG: 0)
	 | (r_pcrel  ?   RELOC_STD_BITS_PCREL_BIG: 0)
	 | (r_neg    ?   RELOC_ARM_BITS_NEG_BIG: 0)
	 | (r_length <<  RELOC_STD_BITS_LENGTH_SH_BIG));
    }
  else
    {
      natptr->r_index[2] = r_index >> 16;
      natptr->r_index[1] = r_index >> 8;
      natptr->r_index[0] = r_index;
      natptr->r_type[0] =
	(  (r_extern ?   RELOC_STD_BITS_EXTERN_LITTLE: 0)
	 | (r_pcrel  ?   RELOC_STD_BITS_PCREL_LITTLE: 0)
	 | (r_neg    ?   RELOC_ARM_BITS_NEG_LITTLE: 0)
	 | (r_length <<  RELOC_STD_BITS_LENGTH_SH_LITTLE));
    }
}

#define MY_BFD_TARGET

#include "aout-target.h"

extern const bfd_target aout_arm_big_vec;

const bfd_target aout_arm_little_vec =
{
  "a.out-arm-little",           /* Name.  */
  bfd_target_aout_flavour,
  BFD_ENDIAN_LITTLE,            /* Target byte order (little).  */
  BFD_ENDIAN_LITTLE,            /* Target headers byte order (little).  */
  (HAS_RELOC | EXEC_P |         /* Object flags.  */
   HAS_LINENO | HAS_DEBUG |
   HAS_SYMS | HAS_LOCALS | DYNAMIC | WP_TEXT | D_PAGED),
  (SEC_HAS_CONTENTS | SEC_ALLOC | SEC_LOAD | SEC_RELOC | SEC_CODE | SEC_DATA),
  MY_symbol_leading_char,
  AR_PAD_CHAR,                  /* AR_pad_char.  */
  15,                           /* AR_max_namelen.  */
  0,				/* match priority.  */
  bfd_getl64, bfd_getl_signed_64, bfd_putl64,
  bfd_getl32, bfd_getl_signed_32, bfd_putl32,
  bfd_getl16, bfd_getl_signed_16, bfd_putl16,	/* Data.  */
  bfd_getl64, bfd_getl_signed_64, bfd_putl64,
  bfd_getl32, bfd_getl_signed_32, bfd_putl32,
  bfd_getl16, bfd_getl_signed_16, bfd_putl16,	/* Headers.  */
  {_bfd_dummy_target, MY_object_p,		/* bfd_check_format.  */
   bfd_generic_archive_p, MY_core_file_p},
  {bfd_false, MY_mkobject,			/* bfd_set_format.  */
   _bfd_generic_mkarchive, bfd_false},
  {bfd_false, MY_write_object_contents,		/* bfd_write_contents.  */
   _bfd_write_archive_contents, bfd_false},

  BFD_JUMP_TABLE_GENERIC (MY),
  BFD_JUMP_TABLE_COPY (MY),
  BFD_JUMP_TABLE_CORE (MY),
  BFD_JUMP_TABLE_ARCHIVE (MY),
  BFD_JUMP_TABLE_SYMBOLS (MY),
  BFD_JUMP_TABLE_RELOCS (MY),
  BFD_JUMP_TABLE_WRITE (MY),
  BFD_JUMP_TABLE_LINK (MY),
  BFD_JUMP_TABLE_DYNAMIC (MY),

  & aout_arm_big_vec,

  (void *) MY_backend_data,
};

const bfd_target aout_arm_big_vec =
{
  "a.out-arm-big",              /* Name.  */
  bfd_target_aout_flavour,
  BFD_ENDIAN_BIG,               /* Target byte order (big).  */
  BFD_ENDIAN_BIG,               /* Target headers byte order (big).  */
  (HAS_RELOC | EXEC_P |         /* Object flags.  */
   HAS_LINENO | HAS_DEBUG |
   HAS_SYMS | HAS_LOCALS | DYNAMIC | WP_TEXT | D_PAGED),
  (SEC_HAS_CONTENTS | SEC_ALLOC | SEC_LOAD | SEC_RELOC | SEC_CODE | SEC_DATA),
  MY_symbol_leading_char,
  AR_PAD_CHAR,			/* AR_pad_char.  */
  15,				/* AR_max_namelen.  */
  0,				/* match priority.  */
  bfd_getb64, bfd_getb_signed_64, bfd_putb64,
  bfd_getb32, bfd_getb_signed_32, bfd_putb32,
  bfd_getb16, bfd_getb_signed_16, bfd_putb16,	/* Data.  */
  bfd_getb64, bfd_getb_signed_64, bfd_putb64,
  bfd_getb32, bfd_getb_signed_32, bfd_putb32,
  bfd_getb16, bfd_getb_signed_16, bfd_putb16,	/* Headers.  */
  {_bfd_dummy_target, MY_object_p,		/* bfd_check_format.  */
   bfd_generic_archive_p, MY_core_file_p},
  {bfd_false, MY_mkobject,			/* bfd_set_format.  */
   _bfd_generic_mkarchive, bfd_false},
  {bfd_false, MY_write_object_contents,		/* bfd_write_contents.  */
   _bfd_write_archive_contents, bfd_false},

  BFD_JUMP_TABLE_GENERIC (MY),
  BFD_JUMP_TABLE_COPY (MY),
  BFD_JUMP_TABLE_CORE (MY),
  BFD_JUMP_TABLE_ARCHIVE (MY),
  BFD_JUMP_TABLE_SYMBOLS (MY),
  BFD_JUMP_TABLE_RELOCS (MY),
  BFD_JUMP_TABLE_WRITE (MY),
  BFD_JUMP_TABLE_LINK (MY),
  BFD_JUMP_TABLE_DYNAMIC (MY),

  & aout_arm_little_vec,

  (void *) MY_backend_data,
};
