/*
 * Copyright (c) 1997  Richard Earnshaw.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Richard Earnshaw.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* Simple utility to find instructions in a binary that cause the
   SA110 rev<3 bug when doing a load-multiple or ldr which writes
   the PC.  */

/* NO ATTEMPT HAS BEEN MADE TO MAKE THIS WORK IN A CROSS ENVIRONMENT.
   IN PARTICULAR THE CODE ASSUMES THAT THE ENDIANNESS OF THE HOST
   MATCHES THAT OF THE CODE BEING FIXED. */

/* This file patches binaries as follows. 

     ldm<cond> reg, {reglist, pc} 
   is replaced with
     b<cond> patcharea
     ...
   patcharea:
     ldm reg, {reglist, pc}

   It also spots, but does not attempt to fix the more complex
     ldr<cond> pc, [pc, reg, lsl #2]  @ <cond> is normally ls
     b <addr>

   which could be replaced with
     str pc, [sp, #-4]!
     b patcharea
     ...
   patcharea:
     add<~cond> sp, sp, #4
     b<~cond> <addr>
     str treg, [sp, #-4]!	@ treg != reg
     ldr treg, [sp, #4]
     ldr treg, [treg, reg, lsl #2]
     str treg, [sp, #4]
     ldmia sp!, {treg, pc}

   This patch would be more dangerous, since it is possible for the second
   instruction to be the target of another branch, which would then be
   incorrect.  I don't think this case can cause a failure unless the
   branch table contains more than 1023 entries (since otherwise the load
   will take a data abort causing the page to be correctly mapped in).

   Perhaps a more significant case which is detected but not fixed is an
   indirect call:

     mov<cond>  lr, pc
     ldr<cond>  pc, [reg, ...]		@ pc not used in address

   This case could be fixed up by moving the ldr instruction to the
   patch area (leaving the condition in the branch).
 */

#include <stdio.h>
#include <a.out.h>
#include <stdlib.h>

/* All programs are loaded at this base address. */
#define LOAD_ADDR 0x1000

typedef struct fix_s
{
  struct fix_s *next;
  unsigned long where;
  unsigned long reloc;
  unsigned long inst;
  unsigned long nextinst;
} fix;

fix *fixlist = NULL;
fix *curfix = NULL;
int fixcount = 0;

static void usage(void)
{
  fprintf(stderr, "Usage: findbad <infile> <outfile>\n");
}

fix *newfix(unsigned long where, unsigned long inst)
{
  fix *new;

  if ((new = (fix *)(malloc(sizeof(fix)))) == NULL)
    {
      fprintf(stderr, "Out of memory during Malloc\n");
      exit(1);
    }

  new->next = NULL;
  new->where = where;
  new->inst = inst;
  new->reloc = new->nextinst = 0;

  if (fixlist == NULL)
    fixlist = new;
  else
    curfix->next = new;

  curfix = new;
  fixcount++;
  return new;
}

int main(int argc, char *argv[])
{
  FILE *in, *out;
  struct exec header;
  unsigned long size, max_size;
  unsigned long i;
  unsigned long fixup;
  union
  {
    char buf[4096];
    unsigned long a[1024];
  } x;

  if (argc != 3)
    {
      usage();
      exit(1);
    }

  if ((in = fopen(argv[1], "r")) == NULL)
    {
      perror(argv[1]);
      exit(1);
    }

  fread(&header, sizeof (header), 1, in);

  if (N_BADMAG(header) || N_GETMAGIC(header) != ZMAGIC)
    {
      fprintf(stderr, "%s: Not a valid executable file\n", argv[1]);
      exit(1);
    }

  /* For some types of Magic, the text segment includes the header in
     the address calculations.  XXX does this affect the length of the
     segment? */
  if (fseek(in, /*N_TXTOFF(header)*/ 0, SEEK_SET) < 0)
    {
      fprintf(stderr, "%s: Not a valid executable file\nBad text offset\n",
	      argv[1]);
      exit(1);
    }

  max_size = header.a_text;
  for (size = 0; size < max_size; size += 4096)
    {
      unsigned long inst;

      fread(x.buf, 1, max_size - size > 4096 ? 4096: max_size - size, in);

      /* Can't fix up a ldr pc, [pc,...] instruction just by moving it, so
	 we have to be a bit more clever */
      if (curfix != NULL && curfix->where + 4 == size
	  /* ldr?? pc, [pc, reg, lsl #2] */
	  && (curfix->inst & 0x0ffffff0) == 0x079ff100)
	{
	  curfix->nextinst = x.a[0];
	  printf("  %08x:\t", size + LOAD_ADDR);
#ifdef DISASS
	  disass(x.a[0]);
#else
	  printf(" %08x\tldr\tpc, [pc, ...]\n", x.a[0]);
#endif
	}

      inst = x.a[1023];
      if ((inst & 0x0e108000) == 0x08108000)
	{
	  printf("%08x:\t", size + 0xffc + LOAD_ADDR);
#ifdef DISASS
	  disass(inst);
#else
	  printf("%08x\tldm\treg, {..., pc}\n", inst);
#endif
	  newfix(size + 0xffc, inst);
	}
      else if ((inst & 0x0c10f000) == 0x0410f000)
	{
	  printf("%08x:\t", size + 0xffc + LOAD_ADDR);
#ifdef DISASS
	  disass(inst);
#else
	  printf("%08x\tldr\tpc, [reg...]\n", inst);
#endif
	  newfix(size + 0xffc, inst);
	}
    }

  if (fixcount == 0)
    {
      fprintf(stderr, "Nothing needs fixing\n");
      exit (0);
    }

  /* HACK, This assumes that max_size is a multiple of 4096.  */
  for (i = 1024; i-- > 0;)
    if (x.a[i] != 0)
      break;

  i++;
  i = 1024 - i;

  printf("%d words of zero in last page of text.\n", i);

  /* We can do better than this.  For load-multiple instructions, all we 
need
     to do is find an equivalent, non-conditional instruction elsewhere in
     the code segment.  We can then branch to that.  We only need patch 
space
     for instructions that do not appear elsewhere, and for the ldr pc 
     hackery.  */
  if (fixcount > i - 1)
    fprintf(stderr, "Insufficient space for patch instructions (need %d 
words)\n", fixcount);

  if ((out = fopen(argv[2], "w")) == NULL)
    {
      perror(argv[2]);
      exit(1);
    }

  fseek(in, 0, SEEK_SET);
  curfix = fixlist;
  /* Can't use the last word of the page, since that would fail also! */
  fixup = max_size - 8;
  for (size = 0; size < max_size; size += 4096)
    {
      fread(x.buf, 1, max_size - size > 4096 ? 4096 : max_size - size, in);

      /* This page needs fixing.  */
      if (curfix && (curfix->where & ~0xfff) == size)
	{
	  if ((curfix->inst & 0x0e108000) == 0x08108000) /* ldm */
	    {
	      curfix->reloc = fixup;
	      fixup -= 4;
	      if (x.a[1023] != curfix->inst)
		abort();
	      x.a[1023] = ((curfix->inst & 0xf0000000) | 0x0a000000
			   | ((curfix->reloc - (curfix->where + 8)) >> 2));
	    }
	  else
	    fprintf(stderr, "ldr pc not yet fixed up\n");

	  curfix = curfix->next;
	}

      if (max_size - size > 4096)
	fwrite(x.buf, 1, 4096, out);
    }

  for (curfix = fixlist; curfix != NULL; curfix = curfix->next)
    {
      if (curfix->reloc != 0)
	{
	  int word = (curfix->reloc & 0xfff) >> 2;

	  x.a[word] = 0xe0000000 | (curfix->inst & 0x0fffffff);
	}
    }
  fwrite(x.buf, 1, max_size - (size - 4096), out);

  while ((size = fread(x.buf, 1, 4096, in)) != 0)
    fwrite(x.buf, 1, size, out);

  fclose(in);
  fclose(out);

  return 0;
}