/*
 * Copyright (c) 2011-2014, Chris Johns <chrisj@rtems.org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
/**
 * @file
 *
 * @ingroup rtems_rld
 *
 * @brief RTEMS Symbols Main manages opions, sequence of operations and exceptions.
 *
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <fstream>
#include <iomanip>
#include <iostream>
#include <regex>

#include <cxxabi.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <getopt.h>

#include <rld.h>
#include <rld-cc.h>
#include <rld-outputter.h>
#include <rld-process.h>
#include <rld-symbols.h>
#include <rld-rtems.h>

#ifndef HAVE_KILL
#define kill(p,s) raise(s)
#endif

/**
 * Header text.
 */
static const char* const c_header[] =
{
  "/*",
  " * RTEMS Global Symbol Table",
  " *  Automatically generated. Do not edit..",
  " */",
  "",
  "#include <stddef.h>",
  "#include <stdint.h>",
  "",
  "extern void* rtems_rtl_tls_get_base (void);",
  "",
  "extern const unsigned char rtems__rtl_base_globals[];",
  "extern const unsigned int rtems__rtl_base_globals_size[];",
  "",
  "typedef size_t (*rtems_rtl_tls_offset_func)(void);",
  "typedef struct rtems_rtl_tls_offset {",
  "  size_t index;",
  "  rtems_rtl_tls_offset_func offset;",
  "} rtems_rtl_tls_offset;",
  "",
  "void rtems_rtl_base_sym_global_add (const unsigned char* , unsigned int,",
  "                                    const rtems_rtl_tls_offset*, size_t );",
  "",
  "#if __SIZEOF_POINTER__ == 8",
  "#define SYM_VALUE \".quad\"",
  "#else",
  "#define SYM_VALUE \".long\"",
  "#endif",
  "",
  "asm(",
  "\"  .pushsection \\\".rodata\\\"\\n\"",
  "\"  .align   4\\n\"",
  "\"  .local   rtems__rtl_base_globals\\n\"",
  "\"rtems__rtl_base_globals:\\n\"",
  "#if __mips__",
  "\"  .align 0\\n\"",
  "#else",
  "\"  .balign 1\\n\"",
  "#endif",
  0
};

static const char* const c_sym_table_end[] =
{
  "\"  .byte    0\\n\"",
  "\"  .ascii   \\\"\\xde\\xad\\xbe\\xef\\\"\\n\"",
  "",
  "/*",
  " * Symbol table size.",
  " */",
  "\"  .align   4\\n\"",
  "\"  .local   rtems__rtl_base_globals_size\\n\"",
  "\"rtems__rtl_base_globals_size:\\n\"",
  "\"  .long rtems__rtl_base_globals_size - rtems__rtl_base_globals\\n\"",
  "\"  .popsection\\n\"",
  ");",
  "",
  0
};

static const char* const c_tls_call_table_start[] =
{
  "const rtems_rtl_tls_offset rtems_rtl_tls_offsets[] = {",
  0
};

static const char* const c_tls_call_table_end[] =
{
  "};",
  "#define RTEMS_RTL_TLS_OFFSETS_NUM " \
  "(sizeof(rtems_rtl_tls_offsets) / (sizeof(rtems_rtl_tls_offsets[0])))",
  "",
  0
};

static const char* const c_trailer[] =
{
  0
};

static const char* const c_rtl_call_body_embeded[] =
{
  "{",
  "  rtems_rtl_base_sym_global_add (&rtems__rtl_base_globals[0],",
  "                                 rtems__rtl_base_globals_size[0],",
  "                                 &rtems_rtl_tls_offsets[0],",
  "                                 RTEMS_RTL_TLS_OFFSETS_NUM);",
  "}",
  0
};

static const char* const c_rtl_call_body[] =
{
  "{",
  "  rtems_rtl_base_sym_global_add (&rtems__rtl_base_globals[0],",
  "                                 rtems__rtl_base_globals_size[0],",
  "                                 NULL,",
  "                                 0);",
  "}",
  0
};

/**
 * Paint the data to the temporary file.
 */
static void
temporary_file_paint (rld::process::tempfile& t, const char* const lines[])
{
  for (int l = 0; lines[l]; ++l)
    t.write_line (lines[l]);
}

/**
 * The constructor trailer.
 */
static void
c_constructor_trailer (rld::process::tempfile& c)
{
  c.write_line ("static void init(void) __attribute__ ((constructor));");
  c.write_line ("static void init(void)");
  temporary_file_paint (c, c_rtl_call_body);
}

/**
 * The embedded trailer.
 */
static void
c_embedded_trailer (rld::process::tempfile& c)
{
  c.write_line ("void rtems_rtl_base_global_syms_init(void);");
  c.write_line ("void rtems_rtl_base_global_syms_init(void)");
  temporary_file_paint (c, c_rtl_call_body_embeded);
}

/**
 * Filter the symbols given a list of regx expressions.
 */
class symbol_filter
{
public:
  typedef std::vector < std::string > expressions;

  symbol_filter ();

  void load (const std::string& file);
  void add (const std::string& re);

  void filter (const rld::symbols::symtab& symbols,
               rld::symbols::symtab&       filtered_symbols);

private:

  expressions expr;
};

symbol_filter::symbol_filter ()
{
}

void
symbol_filter::load (const std::string& file)
{
  std::ifstream in (file);
  std::string   re;
  while (in >> re)
    add (re);
}

void
symbol_filter::add (const std::string& re)
{
  expr.push_back (re);
}

void
symbol_filter::filter (const rld::symbols::symtab& symbols,
                       rld::symbols::symtab&       filtered_symbols)
{
  if (expr.size () > 0)
  {
    for (auto& re : expr)
    {
      const std::regex sym_re(re);
      for (const auto& sym : symbols)
      {
        std::smatch      m;
        if (std::regex_match (sym.second->demangled (), m, sym_re))
          filtered_symbols[sym.first] = sym.second;
      }
    }
  }
  else
  {
    for (const auto& sym : symbols)
      filtered_symbols[sym.first] = sym.second;
  }
}

/**
 * Generate the symbol map object file for loading or linking into
 * a running RTEMS machine.
 */

struct output_sym
{
  enum struct output_mode {
    symbol,
    tls_func,
    tls_call_table
  };
  rld::process::tempfile& c;
  const bool              embed;
  const bool              weak;
  const output_mode       mode;
  size_t&                 index;

  output_sym(rld::process::tempfile& c_,
             bool                    embed_,
             bool                    weak_,
             output_mode             mode_,
             size_t&                 index_)
    : c (c_),
      embed (embed_),
      weak (weak_),
      mode (mode_),
      index (index_) {
  }

  void operator ()(const rld::symbols::symtab::value_type& value);
};

void
output_sym::operator ()(const rld::symbols::symtab::value_type& value)
{
  const rld::symbols::symbol& sym = *(value.second);

  /*
   * Weak symbols without a value are probably unresolved externs. Ignore them.
   */
  if (weak && sym.value () == 0)
    return;

  switch (mode) {
    case output_mode::symbol:
    default:
      /*
       * Set TLS value to 0. It is filled in at run time by the call
       * table.
       */
      c.write_line ("\"  .asciz \\\"" + sym.name () + "\\\"\\n\"");
      {
        std::string val;
        if (sym.type () == STT_TLS) {
          val = "0";
        } else {
          if (embed) {
            val = sym.name ();
          } else {
            std::stringstream oss;
            oss << std::hex << std::showbase << std::internal <<
              std::setfill ('0') << std::setw (10) << sym.value ();
            val = oss.str ();
          }
        }
        c.write_line ("\"  \" SYM_VALUE \" " + val + "\\n\"");
      }
      break;
    case output_mode::tls_func:
      if (sym.type () == STT_TLS) {
        c.write_line ("#define RTEMS_TLS_INDEX_" + sym.name () + " " + std::to_string(index));
        c.write_line ("extern __thread char "  + sym.name () +  "[];");
        c.write_line ("static size_t rtems_rtl_tls_" + sym.name () + "(void) {");
        c.write_line ("  size_t tls_base = (size_t) rtems_rtl_tls_get_base ();");
        c.write_line ("  size_t tls_addr = (size_t) "  + sym.name () +  ";");
        c.write_line ("  return tls_addr - tls_base;");
        c.write_line ("}");
        c.write_line ("");
      }
      break;
    case output_mode::tls_call_table:
      if (sym.type () == STT_TLS) {
        c.write_line ("  { RTEMS_TLS_INDEX_" + sym.name () +
                      ", rtems_rtl_tls_" + sym.name () + " },");
      }
      break;
  }
  ++index;
}

static void
generate_c (rld::process::tempfile& c,
            rld::symbols::symtab&   symbols,
            bool                    embed)
{
  if (rld::verbose ())
    std::cout << "symbol C file: " << c.name () << std::endl;

  c.open (true);
  temporary_file_paint (c, c_header);

  /*
   * Add the symbols. The is the globals and the weak symbols that have been
   * linked into the base image. A weak symbols present in the base image is no
   * longer weak and should be consider a global symbol. You cannot link a
   * global symbol with the same in a dynamically loaded module.
   */
  size_t index = 0;
  std::for_each (symbols.begin (),
                 symbols.end (),
                 output_sym (c, embed, false,
                             output_sym::output_mode::symbol, index));

  temporary_file_paint (c, c_sym_table_end);

  if (embed) {
    index = 0;
    std::for_each (symbols.begin (),
                   symbols.end (),
                   output_sym (c, embed, false,
                               output_sym::output_mode::tls_func, index));
    temporary_file_paint (c, c_tls_call_table_start);
    index = 0;
    std::for_each (symbols.begin (),
                   symbols.end (),
                   output_sym (c, embed, false,
                               output_sym::output_mode::tls_call_table, index));
    temporary_file_paint (c, c_tls_call_table_end);
  }

  temporary_file_paint (c, c_trailer);

  if (embed)
    c_embedded_trailer (c);
  else
    c_constructor_trailer (c);
}

static void
generate_symmap (rld::process::tempfile& c,
                 const std::string&      output,
                 rld::symbols::symtab&   symbols,
                 bool                    embed)
{
  generate_c (c, symbols, embed);

  if (rld::verbose ())
    std::cout << "symbol O file: " << output << std::endl;

  rld::process::arg_container args;

  rld::cc::make_cc_command (args);
  rld::cc::append_flags (rld::cc::ft_cflags, args);

  args.push_back ("-O2");
  args.push_back ("-c");
  args.push_back ("-o");
  args.push_back (output);
  args.push_back (c.name ());

  rld::process::tempfile out;
  rld::process::tempfile err;
  rld::process::status   status;

  status = rld::process::execute (rld::cc::get_cc (),
                                  args,
                                  out.name (),
                                  err.name ());


  if ((status.type != rld::process::status::normal) ||
      (status.code != 0))
  {
    err.output (rld::cc::get_cc (), std::cout);
    throw rld::error ("Compiler error", "compiling wrapper");
  }
}

/**
 * RTEMS Symbols options.
 */
static struct option rld_opts[] = {
  { "help",        no_argument,            NULL,           'h' },
  { "version",     no_argument,            NULL,           'V' },
  { "verbose",     no_argument,            NULL,           'v' },
  { "warn",        no_argument,            NULL,           'w' },
  { "keep",        no_argument,            NULL,           'k' },
  { "embed",       no_argument,            NULL,           'e' },
  { "symc",        required_argument,      NULL,           'S' },
  { "output",      required_argument,      NULL,           'o' },
  { "map",         required_argument,      NULL,           'm' },
  { "cc",          required_argument,      NULL,           'C' },
  { "exec-prefix", required_argument,      NULL,           'E' },
  { "cflags",      required_argument,      NULL,           'c' },
  { "filter",      required_argument,      NULL,           'f' },
  { "filter-re",   required_argument,      NULL,           'F' },
  { NULL,          0,                      NULL,            0 }
};

void
usage (int exit_code)
{
  std::cout << "rtems-syms [options] kernel" << std::endl
            << "Options and arguments:" << std::endl
            << " -h        : help (also --help)" << std::endl
            << " -V        : print version number and exit (also --version)" << std::endl
            << " -v        : verbose (trace import parts), can supply multiple times" << std::endl
            << "             to increase verbosity (also --verbose)" << std::endl
            << " -w        : generate warnings (also --warn)" << std::endl
            << " -k        : keep temporary files (also --keep)" << std::endl
            << " -e        : embedded symbol table (also --embed)" << std::endl
            << " -S file   : symbol's C file (also --symc)" << std::endl
            << " -o file   : output object file (also --output)" << std::endl
            << " -m file   : output a map file (also --map)" << std::endl
            << " -C file   : target C compiler executable (also --cc)" << std::endl
            << " -E prefix : the RTEMS tool prefix (also --exec-prefix)" << std::endl
            << " -c cflags : C compiler flags (also --cflags)" << std::endl
            << " -f file   : file of symbol filters (also --filter)" << std::endl
            << " -F re     : filter regx expression (also --filter-re)" << std::endl;
  ::exit (exit_code);
}

static void
fatal_signal (int signum)
{
  signal (signum, SIG_DFL);

  rld::process::temporaries_clean_up ();

  /*
   * Get the same signal again, this time not handled, so its normal effect
   * occurs.
   */
  kill (getpid (), signum);
}

static void
setup_signals (void)
{
  if (signal (SIGINT, SIG_IGN) != SIG_IGN)
    signal (SIGINT, fatal_signal);
#ifdef SIGHUP
  if (signal (SIGHUP, SIG_IGN) != SIG_IGN)
    signal (SIGHUP, fatal_signal);
#endif
  if (signal (SIGTERM, SIG_IGN) != SIG_IGN)
    signal (SIGTERM, fatal_signal);
#ifdef SIGPIPE
  if (signal (SIGPIPE, SIG_IGN) != SIG_IGN)
    signal (SIGPIPE, fatal_signal);
#endif
#ifdef SIGCHLD
  signal (SIGCHLD, SIG_DFL);
#endif
}

int
main (int argc, char* argv[])
{
  int ec = 0;

  setup_signals ();

  try
  {
    rld::files::cache   kernel;
    rld::symbols::table symbols;
    symbol_filter       filter;
    std::string         kernel_name;
    std::string         output;
    std::string         map;
    std::string         cc;
    std::string         symc;
    bool                embed = false;

    rld::set_cmdline (argc, argv);

    while (true)
    {
      int opt = ::getopt_long (argc, argv, "hvVwkef:S:o:m:E:c:C:f:F:", rld_opts, NULL);
      if (opt < 0)
        break;

      switch (opt)
      {
        case 'V':
          std::cout << "rtems-syms (RTEMS Symbols) " << rld::version ()
                    << ", RTEMS revision " << rld::rtems::version ()
                    << std::endl;
          ::exit (0);
          break;

        case 'v':
          rld::verbose_inc ();
          break;

        case 'w':
          break;

        case 'k':
          rld::process::set_keep_temporary_files ();
          break;

        case 'e':
          embed = true;
          break;

        case 'o':
          output = optarg;
          break;

        case 'm':
          map = optarg;
          break;

        case 'C':
          if (rld::cc::is_exec_prefix_set ())
            std::cerr << "warning: exec-prefix ignored when CC provided" << std::endl;
          rld::cc::set_cc (optarg);
          break;

        case 'E':
          if (rld::cc::is_cc_set ())
            std::cerr << "warning: exec-prefix ignored when CC provided" << std::endl;
          rld::cc::set_exec_prefix (optarg);
          break;

        case 'c':
          rld::cc::set_flags (optarg, rld::cc::ft_cflags);
          break;

        case 'S':
          symc = optarg;
          break;

        case 'f':
          filter.load (optarg);
          break;

        case 'F':
          filter.add (optarg);
          break;

        case '?':
          usage (3);
          break;

        case 'h':
          usage (0);
          break;
      }
    }

    /*
     * Set the program name.
     */
    rld::set_progname (argv[0]);

    argc -= optind;
    argv += optind;

    if (rld::verbose ())
      std::cout << "RTEMS Kernel Symbols " << rld::version () << std::endl;

    /*
     * If there are no object files there is nothing to link.
     */
    if (argc == 0)
      throw rld::error ("no kernel file", "options");
    if (argc != 1)
      throw rld::error ("only one kernel file", "options");
    if (output.empty () && symc.empty() && map.empty ())
      throw rld::error ("no output, symbol C file, or map", "options");

    kernel_name = *argv;

    if (rld::verbose ())
      std::cout << "kernel: " << kernel_name << std::endl;

    /*
     * Load the symbols from the kernel.
     */
    try
    {
      /*
       * Load the kernel ELF file symbol table.
       */
      kernel.open ();
      kernel.add (kernel_name);
      kernel.load_symbols (symbols, true);

      /*
       * If the full path to CC is not provided and the exec-prefix is not set
       * by the command line see if it can be detected from the object file
       * types. This must be after we have added the object files because they
       * are used when detecting.
       */
      if (!cc.empty ())
        rld::cc::set_cc (cc);
      if (!rld::cc::is_cc_set () && !rld::cc::is_exec_prefix_set ())
        rld::cc::set_exec_prefix (rld::elf::machine_type ());

      /*
       * Filter the symbols.
       */
      rld::symbols::symtab filter_symbols;
      filter.filter (symbols.globals (), filter_symbols);
      filter.filter (symbols.weaks (), filter_symbols);
      if (filter_symbols.size () == 0)
        throw rld::error ("no filtered symbols", "filter");
      if (rld::verbose ())
        std::cout << "Filtered symbols: " << filter_symbols.size () << std::endl;

      /*
       * Create a map file if asked too.
       */
      if (!map.empty ())
      {
        std::ofstream mout;
        mout.open (map.c_str());
        if (!mout.is_open ())
          throw rld::error ("map file open failed", "map");
        mout << "RTEMS Kernel Symbols Map" << std::endl
             << " kernel: " << kernel_name << std::endl
             << std::endl;
        rld::symbols::output (mout, filter_symbols);
        mout.close ();
      }

      /*
       * Create an output file if asked too.
       */
      if (!output.empty () || !symc.empty())
      {
        rld::process::tempfile c (".c");

        if (!symc.empty ())
        {
          c.override (symc, false);
          c.keep ();
        }

        /*
         * Generate and if requested compile the symbol map.
         */
        if (output.empty())
          generate_c (c, filter_symbols, embed);
        else
          generate_symmap (c, output, filter_symbols, embed);
      }

      kernel.close ();
    }
    catch (...)
    {
      kernel.close ();
      throw;
    }

    kernel.close ();
  }
  catch (rld::error re)
  {
    std::cerr << "error: "
              << re.where << ": " << re.what
              << std::endl;
    ec = 10;
  }
  catch (std::exception& e)
  {
    int   status;
    char* realname;
    realname = abi::__cxa_demangle (e.what(), 0, 0, &status);
    std::cerr << "error: exception: " << realname << " [";
    ::free (realname);
    const std::type_info &ti = typeid (e);
    realname = abi::__cxa_demangle (ti.name(), 0, 0, &status);
    std::cerr << realname << "] " << e.what () << std::endl;
    ::free (realname);
    ec = 11;
  }
  catch (...)
  {
    /*
     * Helps to know if this happens.
     */
    std::cout << "error: unhandled exception" << std::endl;
    ec = 12;
  }

  return ec;
}
