/****************************************************************************
 * binfmt/elf.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

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

#include <nuttx/config.h>

#include <sys/param.h>
#include <sys/types.h>
#include <stdint.h>
#include <string.h>
#include <debug.h>
#include <errno.h>

#include <nuttx/arch.h>
#include <nuttx/binfmt/binfmt.h>
#include <nuttx/kmalloc.h>

#ifdef CONFIG_ELF

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* CONFIG_DEBUG_FEATURES, CONFIG_DEBUG_INFO, and CONFIG_DEBUG_BINFMT
 * have to be defined or CONFIG_LIBC_ELF_DUMPBUFFER does nothing.
 */

#if !defined(CONFIG_DEBUG_INFO) || !defined(CONFIG_DEBUG_BINFMT)
#  undef CONFIG_LIBC_ELF_DUMPBUFFER
#endif

#ifndef CONFIG_ELF_STACKSIZE
#  define CONFIG_ELF_STACKSIZE 2048
#endif

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int elf_loadbinary(FAR struct binary_s *binp,
                          FAR const char *filename,
                          FAR const struct symtab_s *exports,
                          int nexports);

static int elf_unloadbinary(FAR struct binary_s *binp);

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

static DEFINE_PER_CPU_BMP(struct binfmt_s, g_elfbinfmt) =
{
  NULL,             /* next */
  elf_loadbinary,   /* load */
  elf_unloadbinary, /* unload */
};
#define g_elfbinfmt this_cpu_var_bmp(g_elfbinfmt)

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

/****************************************************************************
 * Name: elf_loadbinary
 *
 * Description:
 *   Verify that the file is an ELF binary and, if so, load the ELF
 *   binary into memory
 *
 ****************************************************************************/

static int elf_loadbinary(FAR struct binary_s *binp,
                          FAR const char *filename,
                          FAR const struct symtab_s *exports,
                          int nexports)
{
  struct mod_loadinfo_s loadinfo;
  Elf_Sym sym;
  int ret;

  binfo("Loading file: %s\n", filename);

  /* Initialize the ELF library to load the program binary. */

  ret = libelf_initialize(filename, &loadinfo);
  libelf_dumploadinfo(&loadinfo);
  if (ret != 0)
    {
      goto errout_with_init;
    }

  /* Load the program binary */

  ret = libelf_load_with_addrenv(&loadinfo, false);
  libelf_dumploadinfo(&loadinfo);
  if (ret != 0)
    {
      berr("Failed to load ELF program binary: %d\n", ret);
      goto errout_with_init;
    }

  /* Bind the program to the exported symbol table */

  if (loadinfo.ehdr.e_type == ET_REL
#ifdef CONFIG_LIBC_ELF_GOT
      || loadinfo.gotindex >= 0
#endif
     )
    {
      ret = libelf_bind(&binp->mod, &loadinfo, exports, nexports);
      if (ret != 0)
        {
          berr("Failed to bind symbols program binary: %d\n", ret);
          goto errout_with_load;
        }

      binp->entrypt = (main_t)(loadinfo.textalloc + loadinfo.ehdr.e_entry);
    }
  else if (loadinfo.ehdr.e_type == ET_EXEC)
    {
      if (nexports > 0)
        {
          berr("Cannot bind exported symbols to a "
               "fully linked executable\n");
          ret = -ENOEXEC;
          goto errout_with_load;
        }

      /* The entrypoint for a fully linked executable can be found directly */

      binp->entrypt = (main_t)(loadinfo.ehdr.e_entry);
    }

  else
    {
      berr("Unexpected elf type %d\n", loadinfo.ehdr.e_type);
      ret = -ENOEXEC;
      goto errout_with_load;
    }

  /* Return the load information */

  ret = libelf_findsymbol(&loadinfo, "nx_stacksize", &sym);
  if (ret == 0)
    {
      binp->stacksize = sym.st_value;
    }
  else
    {
      binp->stacksize = CONFIG_ELF_STACKSIZE;
    }

  ret = libelf_findsymbol(&loadinfo, "nx_priority", &sym);
  if (ret == 0)
    {
      binp->priority = sym.st_value;
    }
  else
    {
      binp->priority = SCHED_PRIORITY_DEFAULT;
    }

#ifdef CONFIG_SCHED_USER_IDENTITY
  ret = libelf_findsymbol(&loadinfo, "nx_uid", &sym);
  if (ret == 0)
    {
      binp->uid = sym.st_value;
    }
  else
    {
      binp->uid = 0;
    }

  ret = libelf_findsymbol(&loadinfo, "nx_gid", &sym);
  if (ret == 0)
    {
      binp->gid = sym.st_value;
    }
  else
    {
      binp->gid = 0;
    }

  ret = libelf_findsymbol(&loadinfo, "nx_mode", &sym);
  if (ret == 0)
    {
      binp->mode = sym.st_value;
    }
  else
    {
      binp->mode = 0;
    }
#endif

#ifdef CONFIG_MM_TASK_HEAP
  ret = libelf_findsymbol(&loadinfo, "nx_heapsize", &sym);
  if (ret == 0)
    {
      binp->heapsize = sym.st_value;
    }
  else
    {
      binp->priority = CONFIG_MM_TASK_HEAP_DEFAULT_SIZE;
    }
#endif

  /* Add the ELF allocation to the alloc[] only if there is no address
   * environment.  If there is an address environment, it will automatically
   * be freed when the function exits
   *
   * REVISIT:  If the module is loaded then unloaded, wouldn't this cause
   * a memory leak?
   */

#ifdef CONFIG_ARCH_ADDRENV
  /* Save the address environment in the binfmt structure.  This will be
   * needed when the module is executed.
   */

  binp->addrenv = loadinfo.addrenv;

#else
#  ifdef CONFIG_ARCH_USE_SEPARATED_SECTION
  if (loadinfo.ehdr.e_type == ET_REL)
    {
      binp->mod.sectalloc = (FAR void *)loadinfo.sectalloc;
      binp->mod.nsect     = loadinfo.ehdr.e_shnum;
    }
#  endif

  binp->mod.textalloc = (FAR void *)loadinfo.textalloc;
  binp->mod.dataalloc = (FAR void *)loadinfo.datastart;
#  ifdef CONFIG_BINFMT_CONSTRUCTORS
  binp->mod.initarr = loadinfo.initarr;
  binp->mod.finiarr = loadinfo.finiarr;
#  endif
#endif

#ifdef CONFIG_BINFMT_CONSTRUCTORS
  /* Save information about constructors and destructors. */

  binp->mod.initarr = loadinfo.initarr;
  binp->mod.ninit   = loadinfo.ninit;

  binp->mod.finiarr = loadinfo.finiarr;
  binp->mod.nfini   = loadinfo.nfini;
#endif

#ifdef CONFIG_SCHED_USER_IDENTITY
  /* Save IDs and mode from file system */

  binp->uid  = loadinfo.fileuid;
  binp->gid  = loadinfo.filegid;
  binp->mode = loadinfo.filemode;
#endif

  libelf_dumpentrypt(&loadinfo);
#if defined(CONFIG_PIC) && defined(CONFIG_LIBC_ELF_GOT)
  if (loadinfo.gotindex >= 0)
    {
      FAR struct dspace_s *dspaces = kmm_zalloc(sizeof(struct dspace_s));

      if (dspaces == NULL)
        {
          ret = -ENOMEM;
          goto errout_with_load;
        }

      dspaces->region = (FAR void *)loadinfo.shdr[loadinfo.gotindex].sh_addr;
      dspaces->crefs = 1;
      binp->picbase = (FAR void *)dspaces;
    }
#endif

#ifdef HAVE_LIBC_ELF_NAMES
  /* Save the filename in the registry entry */

  strlcpy(binp->mod.modname, strrchr(filename, '/') == NULL ?
          filename: strrchr(filename, '/') + 1,
          sizeof(binp->mod.modname));
#endif

  libelf_uninitialize(&loadinfo);
  return OK;

errout_with_load:
  libelf_unload(&loadinfo);
errout_with_init:
  libelf_uninitialize(&loadinfo);
  return ret;
}

/****************************************************************************
 * Name: elf_unloadbinary
 *
 * Description:
 *   Unload the ELF binary that was loaded into memory by elf_loadbinary.
 *
 ****************************************************************************/

static int elf_unloadbinary(FAR struct binary_s *binp)
{
  binfo("Unloading %p\n", binp);
  libelf_uninit(&binp->mod);

  return OK;
}

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

/****************************************************************************
 * Name: elf_initialize
 *
 * Description:
 *   In order to use the ELF binary format, this function must be called
 *   during system initialization to register the ELF binary format.
 *
 * Returned Value:
 *   This is a NuttX internal function so it follows the convention that
 *   0 (OK) is returned on success and a negated errno is returned on
 *   failure.
 *
 ****************************************************************************/

int elf_initialize(void)
{
  int ret;

  /* Register ourselves as a binfmt loader */

  binfo("Registering ELF\n");

  ret = register_binfmt(&g_elfbinfmt);
  if (ret != 0)
    {
      berr("Failed to register binfmt: %d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: elf_uninitialize
 *
 * Description:
 *   Unregister the ELF binary loader
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void elf_uninitialize(void)
{
  unregister_binfmt(&g_elfbinfmt);
}

#endif /* CONFIG_ELF */