/***********************************************************************
 * toolchain/nxflat/readnxflat.c
 *
 *   Copyright (C) 2009 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <spudmonkey@racsa.co.cr>
 *
 * Modified from readxflat (see http://xflat.org):
 *
 *   Copyright (c) 2002, 2006, Cadenux, LLC.  All rights reserved.
 *   Copyright (c) 2002, 2006, Gregory Nutt.  All rights reserved.
 *   Author: Gregory Nutt <spudmonkey@racsa.co.cr>
 *
 * 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. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "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
 * COPYRIGHT OWNER OR CONTRIBUTORS 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.
 *
 ***********************************************************************/

/***********************************************************************
 * Compilation Flags
 ***********************************************************************/

#define SWAP_BYTES 1

/***********************************************************************
 * Included Files
 ***********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <netinet/in.h>         /* ntohl and friends */
#include "nxflat.h"

/***********************************************************************
 * Compilation Switches
 ***********************************************************************/

/* #define RELOCS_IN_NETWORK_ORDER */

/***********************************************************************
 * Definitions
 ***********************************************************************/
 
#define NXFLAT_HDR_SIZE   sizeof(struct nxflat_hdr_s)

/***********************************************************************
 * Private Data
 ***********************************************************************/

static const char *program_name;
static const char *nxflat_filename;

static int dump_header = 0;
static int dump_relocs = 0;
static int dump_imports = 0;
static int dump_text = 0;
static int dump_data = 0;
static int verbose = 0;

static int num_errors = 0;

#ifdef ARCH_BIG_ENDIAN
static int big_endian = 1;      /* Assume big-endian */
#else
static int big_endian = 0;      /* Assume little-endian */
#endif

/***********************************************************************
 * Private Constant Data
 ***********************************************************************/

static const char unknown[] = "UNKNOWN";

static const char hdr_reloc_rel32i[]  = "RELOC_REL32I";
static const char hdr_reloc_rel32d[]  = "RELOC_REL32D";
#ifdef NXFLAT_RELOC_TYPE_REL32ID
static const char hdr_reloc_rel32id[] = "RELOC_REL32ID";
#endif

static const char *reloc_type_string[] = {
  hdr_reloc_rel32i,
  hdr_reloc_rel32d,
#ifdef NXFLAT_RELOC_TYPE_REL32ID
  hdr_reloc_rel32id,
#else
  unknown,
#endif
  unknown
};

/***********************************************************************
 * Public Function Prototypes
 ***********************************************************************/

extern void __attribute__ ((weak)) print_insn_arm(u_int32_t pc,
                                                  FILE * stream,
                                                  u_int32_t given);

/***********************************************************************
 * Private Functions
 ***********************************************************************/

/***********************************************************************
 * swap32
 ***********************************************************************/

static inline u_int32_t nxflat_swap32(u_int32_t little)
{
  u_int32_t big =
    ((little >> 24) & 0xff) |
    (((little >> 16) & 0xff) << 8) |
    (((little >> 8) & 0xff) << 16) | ((little & 0xff) << 24);
  return big;
}

/***********************************************************************
 * get_nxflat32
 ***********************************************************************/

static inline u_int32_t get_nxflat32(u_int32_t * addr32)
{
  return ntohl(*addr32);
}

/***********************************************************************
 * get_nxflat16
 ***********************************************************************/

static inline u_int16_t get_nxflat16(u_int16_t * addr16)
{
  return ntohs(*addr16);
}

/***********************************************************************
 * dump_hex_data
 ***********************************************************************/

static void dump_hex_data(FILE * in_stream, struct nxflat_hdr_s *header)
{
  u_int32_t data_start = get_nxflat32(&header->h_datastart);
  u_int32_t data_end = get_nxflat32(&header->h_dataend);
  int32_t words_left = (data_end - data_start) / sizeof(u_int32_t);
  u_int32_t addr;
  u_int32_t buffer[64];

  printf("\nXFLAT DATA SEGMENT:\n\n");

  /* Seek to the beginning of data in the file */

  if (fseek(in_stream, data_start, SEEK_SET) != 0)
    {
      fprintf(stderr,
              "ERROR: Failed to seek to data in file: offset: %08x\n",
              data_start);
      return;
    }

  /* Now dump all of the data reading 64 words at a time */

  addr = 0;
  while (words_left > 0)
    {
      size_t nread = fread(buffer, sizeof(u_int32_t), 64, in_stream);
      if (nread >= 0)
        {
          union
            {
              u_int32_t l[4];
              unsigned char b[16];
            } row;
          int32_t i, j, k;

          for (i = 0; i < nread; i += 4)
            {
              for (j = 0; j < 4; j++)
                {
                  row.l[j] = buffer[i + j];
                }

              printf("%08x: ", addr);

              for (j = 0; j < 4; j++)
                {
                  printf("%08x ", row.l[j]);
                }

              printf("  ");

              for (j = 0; j < 4 * sizeof(u_int32_t); j += sizeof(u_int32_t))
                {
                  for (k = 0; k < sizeof(u_int32_t); k++)
                    {
                      if (isprint(row.b[j + k]))
                        putchar(row.b[j + k]);
                      else
                        putchar('.');
                    }
                }

              putchar('\n');
              addr += 4 * sizeof(u_int32_t);
            }
          words_left -= nread;
        }
      else
        break;
    }
  putchar('\n');
}

/***********************************************************************
 * disassemble_text
 ***********************************************************************/

static void disassemble_text(FILE * in_stream, struct nxflat_hdr_s *header)
{
  if (print_insn_arm)
    {
      u_int32_t text_start = NXFLAT_HDR_SIZE;
      u_int32_t text_end = get_nxflat32(&header->h_datastart);
      int32_t insns_left = (text_end - text_start) / sizeof(u_int32_t);
      u_int32_t addr;
      u_int32_t buffer[64];

      printf("\nXFLAT TEXT:\n\n");

      /* Seek to the beginning of text in the file */

      if (fseek(in_stream, text_start, SEEK_SET) != 0)
        {
          fprintf(stderr,
                  "ERROR: Failed to seek to text in file: offset: %08x\n",
                  text_start);
          return;
        }

      /* Now dump all of the data reading 64 insns at a time */

      addr = text_start;
      while (insns_left > 0)
        {
          size_t nread = fread(buffer, sizeof(u_int32_t), 64, in_stream);
          if (nread > 0)
            {
              int i;
              for (i = 0; i < nread; i++)
                {
                  u_int32_t insn = buffer[i];
                  if (big_endian)
                    {
                      insn = nxflat_swap32(insn);
                    }

                  printf("%08x %08x\t", addr, insn);
                  print_insn_arm(addr, stdout, insn);
                  putchar('\n');
                  addr += sizeof(u_int32_t);
                }
              insns_left -= nread;
            }
          else
            break;
          putchar('\n');
        }
    }
}

/***********************************************************************
 * dump_imported_symbols
 ***********************************************************************/

static void dump_imported_symbols(FILE *in_stream, struct nxflat_hdr_s *header)
{
  struct nxflat_import_s import;
  u_int32_t import_offset;
  u_int32_t data_start;
  u_int32_t struct_offset;
  u_int32_t name_offset;
  char imported_symbol_name[NXFLAT_MAX_STRING_SIZE];
  int status;
  int i;

  printf("\nIMPORTED SYMBOLS:\n");
  printf("      OFFSET   ADDRESS  SYMBOL NAME\n\n");

  import_offset = get_nxflat32(&header->h_importsymbols);
  data_start    = get_nxflat32(&header->h_datastart);

  for (i = 0; i < get_nxflat16(&header->h_importcount); i++)
    {
      /* Seek to the next imported symbol */

      struct_offset = i * sizeof(struct nxflat_import_s) + import_offset;

      if (fseek(in_stream, struct_offset, SEEK_SET) != 0)
        {
          fprintf(stderr, "ERROR: fseek to imported symbol %d struct failed\n",
                  i);
          fprintf(stderr, "       struct_offset: %d: %s\n",
                  struct_offset, strerror(errno));
          exit(1);
        }

      /* Read the next import entry. */

      status = fread((void *)&import,
                     sizeof(struct nxflat_import_s), 1, in_stream);
      if (status != 1)
        {
          if (ferror(in_stream))
            {
              fprintf(stderr, "ERROR: Read imported symbol %d struct failed: %s\n",
                      i + 1, strerror(errno));
            }
          else
            {
              fprintf(stderr, "ERROR: Read imported symbol %d struct: End-of-file after %d\n",
                      i + 1, struct_offset);
            }

          exit(1);
        }

      if (big_endian)
        {
          import.i_funcname    = nxflat_swap32(import.i_funcname);
          import.i_funcaddress = nxflat_swap32(import.i_funcaddress);
        }

      if (verbose)
        {
          /* Print the raw info */

          printf("[Import: %4d Offset: %08x Name: %08x Address: %08x]\n",
                 i + 1, struct_offset, import.i_funcname, import.i_funcaddress);
        }

      /* Seek to the function name in the file */

      name_offset = import.i_funcname + NXFLAT_HDR_SIZE;

      if (fseek(in_stream, name_offset, SEEK_SET) != 0)
        {
          fprintf(stderr, "ERROR: fseek to imported symbol %d name failed\n",
                  i);
          fprintf(stderr, "       name_offset: %d %s\n",
                  name_offset, strerror(errno));
          exit(1);
        }

      /* Then, read the imported symbol name (assuming it is less than
       * NXFLAT_MAX_STRING_SIZE in length). */

      status = fread((void *)imported_symbol_name, NXFLAT_MAX_STRING_SIZE,
                     1, in_stream);

      if (status != 1)
        {
          if (ferror(in_stream))
            {
              fprintf(stderr, "ERROR: Read imported symbol %d name failed: %s\n",
                      i + 1, strerror(errno));
            }
          else
            {
              fprintf(stderr, "ERROR: Read imported symbol %d name: End-of-file after offset=%d\n",
                      i + 1, name_offset);
            }
          exit(1);
        }

      imported_symbol_name[NXFLAT_MAX_STRING_SIZE - 1] = '\0';

      /* And print it */

      printf("%5d %08x ", i + 1, (int)struct_offset - data_start);

      if (import.i_funcaddress)
        {
          printf("%08x ", import.i_funcaddress);
        }
      else
        {
          printf("UNKNOWN  ");
        }

      printf("%s\n", imported_symbol_name);
    }
}

/***********************************************************************
 * dump_relocation_entries
 ***********************************************************************/

static void dump_relocation_entries(FILE * in_stream, struct nxflat_hdr_s *header)
{
  struct nxflat_reloc_s reloc;
  int status;
  int i;

  /* Seek to the beginning of the relocation records. */

  if (0 != fseek(in_stream, get_nxflat32(&header->h_relocstart), SEEK_SET))
    {
      fprintf(stderr, "ERROR: fseek to reloc records failed: %s\n",
              strerror(errno));
      exit(1);
    }

  printf("\nRELOCATION ENTRIES:\n");
  printf("      OFFSET   RELOC TYPE\n\n");

  for (i = 0; i < get_nxflat16(&header->h_reloccount); i++)
    {
      /* Read the next reloction entry. */

      status = fread((void *)&reloc, sizeof(struct nxflat_reloc_s), 1, in_stream);
      if (status != 1)
        {
          if (ferror(in_stream))
            {
              fprintf(stderr, "ERROR: Read reloc record %d: %s\n",
                      i + 1, strerror(errno));
            }
          else
            {
              fprintf(stderr, "ERROR: Read reloc record %d: End-of-file\n",
                      i + 1);
            }
          exit(1);
        }
 
#ifdef RELOCS_IN_NETWORK_ORDER
      {
        u_int32_t *ptmp;
        ptmp = (u_int32_t *) & reloc;
        *ptmp = get_nxflat32(ptmp);
      }
#endif

      if (NXFLAT_RELOC_TYPE(reloc.r_info) >= NXFLAT_RELOC_TYPE_NUM)
        {
          printf("%5d %08x UNKNOWN(%d)\n", i + 1,
                 NXFLAT_RELOC_OFFSET(reloc.r_info), NXFLAT_RELOC_TYPE(reloc.r_info));
          fprintf(stderr, "Error eloc type out of range(%d)\n",
                  NXFLAT_RELOC_TYPE(reloc.r_info));
          num_errors++;
        }
      else
        {
          printf("%5d %08x %-13s\n",
                 i + 1, NXFLAT_RELOC_OFFSET(reloc.r_info),
                 reloc_type_string[NXFLAT_RELOC_TYPE(reloc.r_info)]);
        }
    }
}

/***********************************************************************
 * dump_hdr
 ***********************************************************************/

static void dump_hdr(struct nxflat_hdr_s *header)
{
  /* Print the contents of the FLT header */

  printf("\nXFLAT HEADER:\n");
  printf("\nMagic           %c%c%c%c\n",
         header->h_magic[0], header->h_magic[1],
         header->h_magic[2], header->h_magic[3]);

  printf("\nMEMORY MAP:\n");
  printf("  Text start    %08lx\n", (long)NXFLAT_HDR_SIZE);
  printf("  Entry point   %08x\n", get_nxflat32(&header->h_entry));
  printf("  Data start    %08x\n", get_nxflat32(&header->h_datastart));
  printf("  Data end      %08x\n", get_nxflat32(&header->h_dataend) - 1);
  printf("  Bss start     %08x\n", get_nxflat32(&header->h_dataend));
  printf("  Bss end       %08x\n", get_nxflat32(&header->h_bssend) - 1);
  printf("TOTAL SIZE      %08x\n\n", get_nxflat32(&header->h_bssend));
  printf("Stack size      %08x\n", get_nxflat32(&header->h_stacksize));
  printf("\nRELOCATIONS:\n");
  printf("  Reloc start   %08x\n", get_nxflat32(&header->h_relocstart));
  printf("  reloc count   %d\n", get_nxflat16(&header->h_reloccount));
  printf("\nIMPORTED SYMBOLS:\n");
  printf("  Import start  %08x\n", get_nxflat32(&header->h_importsymbols));
  printf("  Import count  %d\n", get_nxflat16(&header->h_importcount));
}

/***********************************************************************
 * show_usage
 ***********************************************************************/

static void show_usage(void)
{
  fprintf(stderr, "Usage: %s [options] <flat-filename>\n\n", program_name);
#if 1
  fprintf(stderr, "Where options are one or more of the following:\n\n");
#else
  fprintf(stderr, "Where options are one or more of the following.  Note\n");
  fprintf(stderr, "that a space is always required between the option and\n");
  fprintf(stderr, "any following arguments\n\n");
#endif
  fprintf(stderr, "  -h Dump the XFLAT file header     [not dumped]\n");
  fprintf(stderr, "  -r Dump relocation entries        [not dumped]\n");
  fprintf(stderr, "  -i Dump the imported symbol table [not dumped]\n");
  fprintf(stderr, "  -x Dump xFLT loader pathname      [not dumped]\n");
  fprintf(stderr, "  -c Disassemble the text section   [not dumped]\n");
  fprintf(stderr, "  -d Dump data section (hex)        [not dumped]\n");
  fprintf(stderr, "  -a Dump all of the above          [not dumped]\n");
#ifdef ARCH_BIG_ENDIAN
  fprintf(stderr, "  -b Assume little-endian byteorder [big endian]\n");
#else
  fprintf(stderr, "  -b Assume big-endian byteorder    [little endian]\n");
#endif
  fprintf(stderr, "  -v Output verbose debug info      [no output]\n");
  fprintf(stderr, "\n");
  exit(1);
}

/***********************************************************************
 * parse_args
 ***********************************************************************/

static void parse_args(int argc, char **argv)
{
  int opt;

  /* Save our name (for show_usage) */

  program_name = argv[0];

  /* At least three things must appear on the program line: the program name,
   * the BFD filname, and at least one option. */

  if (argc < 3)
    {
      fprintf(stderr, "ERROR: Missing required arguments\n\n");
      show_usage();
    }

  /* Get miscellaneous options from the command line. */

  while ((opt = getopt(argc, argv, "hrieLlxcbdav")) != -1)
    {
      switch (opt)
        {

        case 'h':              /* Dump the flat file header */
          dump_header++;
          break;

        case 'r':              /* Dump the flat file header */
          dump_relocs++;
          break;

        case 'i':              /* Dump the imported symbol table */
          dump_imports++;
          break;

        case 'c':              /* Disassembly text */
          if (print_insn_arm)
            {
              dump_text++;
            }
          else
            {
              printf("-c ignored: No disassembler available\n");
            }
          break;

        case 'b':              /* other-endian */
#ifdef ARCH_BIG_ENDIAN
          big_endian = 0;
#else
          big_endian++;
#endif
          break;

        case 'd':              /* Dump data */
          dump_data++;
          break;

        case 'a':              /* Dump everying */
          dump_header++;
          dump_relocs++;
          dump_imports++;
          dump_text++;
          dump_data++;
          break;

        case 'v':              /* Output verbose debug information */
          verbose++;
          break;

        default:
          fprintf(stderr, "%s Unknown option\n\n", argv[0]);
          show_usage();
          break;
        }
    }

  /* Get the name of the input BFD file. */

  nxflat_filename = argv[argc - 1];
}

/***********************************************************************
 * Public Functions
 ***********************************************************************/

/***********************************************************************
 * main
 ***********************************************************************/

int main(int argc, char **argv, char **envp)
{
  FILE *in_stream;
  struct nxflat_hdr_s header;
  int status;

  /* Get the input parameters */

  parse_args(argc, argv);

  /* Open the FLT file */

  in_stream = fopen(nxflat_filename, "rb");
  if (NULL == in_stream)
    {
      fprintf(stderr, "Cannot open file %s for reading\n", nxflat_filename);
      exit(1);
    }

  /* Read the FLT header */

  status = fread((void *)&header, sizeof(struct nxflat_hdr_s), 1, in_stream);
  if (status != 1)
    {
      if (ferror(in_stream))
        {
          fprintf(stderr, "ERROR: Read flat header: %s\n", strerror(errno));
        }
      else
        {
          fprintf(stderr, "ERROR: Read flat header: End-of-file\n");
        }
      exit(1);
    }

  printf("Dumping Flat Binary File: %s\n", nxflat_filename);

  /* Dump the contents of the FLT header */

  if (dump_header)
    {
      dump_hdr(&header);
    }

  /* Dump the relocation entries */

  if (dump_relocs)
    {
      dump_relocation_entries(in_stream, &header);
    }

  /* Dump all imported symbols */

  if (dump_imports)
    {
      dump_imported_symbols(in_stream, &header);
    }

   if (dump_text)
    {
      disassemble_text(in_stream, &header);
    }

  if (dump_data)
    {
      dump_hex_data(in_stream, &header);
    }

  fclose(in_stream);

  if (num_errors > 0)
    {
      fprintf(stderr, "Finished with %d errors\n", num_errors);
    }

  return 0;
}
