/* Pedantic checking of ELF files compliance with gABI/psABI spec.
   Copyright (C) 2001, 2002, 2003, 2004 Red Hat, Inc.
   Written by Ulrich Drepper <drepper@redhat.com>, 2001.

   This program is Open Source software; you can redistribute it and/or
   modify it under the terms of the Open Software License version 1.0 as
   published by the Open Source Initiative.

   You should have received a copy of the Open Software License along
   with this program; if not, you may obtain a copy of the Open Software
   License version 1.0 from http://www.opensource.org/licenses/osl.php or
   by writing the Open Source Initiative c/o Lawrence Rosen, Esq.,
   3001 King Ranch Road, Ukiah, CA 95482.   */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <argp.h>
#include <assert.h>
#include <byteswap.h>
#include <endian.h>
#include <error.h>
#include <fcntl.h>
#include <gelf.h>
#include <inttypes.h>
#include <libebl.h>
#include <libintl.h>
#include <locale.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/param.h>

#include <elf-knowledge.h>
#include <system.h>


/* Name and version of program.  */
static void print_version (FILE *stream, struct argp_state *state);
void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;


#define ARGP_strict	300
#define ARGP_gnuld	301

/* Definitions of arguments for argp functions.  */
static const struct argp_option options[] =
{

  { "strict", ARGP_strict, NULL, 0,
    N_("Be extremely strict, flag level 2 features.") },
  { "quiet", 'q', NULL, 0, N_("Do not print anything if successful") },
  { "gnu-ld", ARGP_gnuld, NULL, 0,
    N_("Binary has been created with GNU ld and is therefore known to be \
broken in certain ways") },
  { NULL, 0, NULL, 0, NULL }
};

/* Short description of program.  */
static const char doc[] = N_("\
Pedantic checking of ELF files compliance with gABI/psABI spec.");

/* Strings for arguments in help texts.  */
static const char args_doc[] = N_("FILE...");

/* Prototype for option handler.  */
static error_t parse_opt (int key, char *arg, struct argp_state *state);

/* Function to print some extra text in the help message.  */
static char *more_help (int key, const char *text, void *input);

/* Data structure to communicate with argp functions.  */
static struct argp argp =
{
  options, parse_opt, args_doc, doc, NULL, more_help
};


/* Declarations of local functions.  */
static void process_file (int fd, Elf *elf, const char *prefix,
			  const char *suffix, const char *fname, size_t size,
			  bool only_one);
static void process_elf_file (Elf *elf, const char *prefix, const char *suffix,
			      const char *fname, size_t size, bool only_one);

/* Report an error.  */
#define ERROR(str, args...) \
  do {									      \
    printf (str, ##args);						      \
    ++error_count;							      \
  } while (0)
static int error_count;

/* True if we should perform very strict testing.  */
static bool be_strict;

/* True if no message is to be printed if the run is succesful.  */
static bool be_quiet;

/* True if binary is assumed to be generated with GNU ld.  */
static bool gnuld;

/* Index of section header string table.  */
static uint32_t shstrndx;

/* Array to count references in section groups.  */
static int *scnref;


int
main (int argc, char *argv[])
{
  int remaining;
  bool only_one;

  /* Set locale.  */
  setlocale (LC_ALL, "");

  /* Initialize the message catalog.  */
  textdomain (PACKAGE);

  /* Parse and process arguments.  */
  argp_parse (&argp, argc, argv, 0, &remaining, NULL);

  /* If no ELF file is given punt.  */
  if (remaining >= argc)
    {
      argp_help (&argp, stdout, ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR,
		 program_invocation_short_name);
      exit (1);
    }

  /* Before we start tell the ELF library which version we are using.  */
  elf_version (EV_CURRENT);

  /* Now process all the files given at the command line.  */
  only_one = remaining + 1 == argc;
  do
    {
      int fd;
      Elf *elf;

      /* Open the file.  */
      fd = open (argv[remaining], O_RDONLY);
      if (fd == -1)
	{
	  error (0, errno, gettext ("cannot open input file"));
	  continue;
	}

      /* Create an `Elf' descriptor.  */
      elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
      if (elf == NULL)
	ERROR (gettext ("cannot generate Elf descriptor: %s\n"),
	       elf_errmsg (-1));
      else
	{
	  unsigned int prev_error_count = error_count;
	  struct stat64 st;

	  if (fstat64 (fd, &st) != 0)
	    {
	      printf ("cannot stat '%s': %m\n", argv[remaining]);
	      close (fd);
	      continue;
	    }

	  process_file (fd, elf, NULL, NULL, argv[remaining], st.st_size,
			only_one);

	  /* Now we can close the descriptor.  */
	  if (elf_end (elf) != 0)
	    ERROR (gettext ("error while closing Elf descriptor: %s\n"),
		   elf_errmsg (-1));

	  if (prev_error_count == error_count && !be_quiet)
	    puts (gettext ("No errors"));
	}

      close (fd);
    }
  while (++remaining < argc);

  return error_count != 0;
}


/* Handle program arguments.  */
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
  switch (key)
    {
    case ARGP_strict:
      be_strict = true;
      break;

    case 'q':
      be_quiet = true;
      break;

    case ARGP_gnuld:
      gnuld = true;
      break;

    default:
      return ARGP_ERR_UNKNOWN;
    }
  return 0;
}


static char *
more_help (int key, const char *text, void *input)
{
  char *buf;

  switch (key)
    {
    case ARGP_KEY_HELP_EXTRA:
      /* We print some extra information.  */
      if (asprintf (&buf, gettext ("Please report bugs to %s.\n"),
		    PACKAGE_BUGREPORT) < 0)
	buf = NULL;
      return buf;

    default:
      break;
    }
  return (char *) text;
}


/* Print the version information.  */
static void
print_version (FILE *stream, struct argp_state *state)
{
  fprintf (stream, "elflint (%s) %s\n", PACKAGE_NAME, VERSION);
  fprintf (stream, gettext ("\
Copyright (C) %s Red Hat, Inc.\n\
This is free software; see the source for copying conditions.  There is NO\n\
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
"), "2004");
  fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
}


/* Process one file.  */
static void
process_file (int fd, Elf *elf, const char *prefix, const char *suffix,
	      const char *fname, size_t size, bool only_one)
{
  /* We can handle two types of files: ELF files and archives.  */
  Elf_Kind kind = elf_kind (elf);

  switch (kind)
    {
    case ELF_K_ELF:
      /* Yes!  It's an ELF file.  */
      process_elf_file (elf, prefix, suffix, fname, size, only_one);
      break;

    case ELF_K_AR:
      {
	Elf *subelf;
	Elf_Cmd cmd = ELF_C_READ_MMAP;
	size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
	size_t fname_len = strlen (fname) + 1;
	char new_prefix[prefix_len + 1 + fname_len];
	char new_suffix[(suffix == NULL ? 0 : strlen (suffix)) + 2];
	char *cp = new_prefix;

	/* Create the full name of the file.  */
	if (prefix != NULL)
	  {
	    cp = mempcpy (cp, prefix, prefix_len);
	    *cp++ = '(';
	    strcpy (stpcpy (new_suffix, suffix), ")");
	  }
	else
	  new_suffix[0] = '\0';
	memcpy (cp, fname, fname_len);

	/* It's an archive.  We process each file in it.  */
	while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
	  {
	    kind = elf_kind (subelf);

	    /* Call this function recursively.  */
	    if (kind == ELF_K_ELF || kind == ELF_K_AR)
	      {
		Elf_Arhdr *arhdr = elf_getarhdr (subelf);
		assert (arhdr != NULL);

		process_file (fd, subelf, new_prefix, new_suffix,
			      arhdr->ar_name, arhdr->ar_size, false);
	      }

	    /* Get next archive element.  */
	    cmd = elf_next (subelf);
	    if (elf_end (subelf) != 0)
	      ERROR (gettext (" error while freeing sub-ELF descriptor: %s\n"),
		     elf_errmsg (-1));
	  }
      }
      break;

    default:
      /* We cannot do anything.  */
      ERROR (gettext ("\
Not an ELF file - it has the wrong magic bytes at the start"));
      break;
    }
}


static const char *
section_name (Ebl *ebl, GElf_Ehdr *ehdr, int idx)
{
  GElf_Shdr shdr_mem;
  GElf_Shdr *shdr;

  shdr = gelf_getshdr (elf_getscn (ebl->elf, idx), &shdr_mem);

  return elf_strptr (ebl->elf, shstrndx, shdr->sh_name);
}


static const int valid_e_machine[] =
  {
    EM_M32, EM_SPARC, EM_386, EM_68K, EM_88K, EM_860, EM_MIPS, EM_S370,
    EM_MIPS_RS3_LE, EM_PARISC, EM_VPP500, EM_SPARC32PLUS, EM_960, EM_PPC,
    EM_PPC64, EM_S390, EM_V800, EM_FR20, EM_RH32, EM_RCE, EM_ARM,
    EM_FAKE_ALPHA, EM_SH, EM_SPARCV9, EM_TRICORE, EM_ARC, EM_H8_300,
    EM_H8_300H, EM_H8S, EM_H8_500, EM_IA_64, EM_MIPS_X, EM_COLDFIRE,
    EM_68HC12, EM_MMA, EM_PCP, EM_NCPU, EM_NDR1, EM_STARCORE, EM_ME16,
    EM_ST100, EM_TINYJ, EM_X86_64, EM_PDSP, EM_FX66, EM_ST9PLUS, EM_ST7,
    EM_68HC16, EM_68HC11, EM_68HC08, EM_68HC05, EM_SVX, EM_ST19, EM_VAX,
    EM_CRIS, EM_JAVELIN, EM_FIREPATH, EM_ZSP, EM_MMIX, EM_HUANY, EM_PRISM,
    EM_AVR, EM_FR30, EM_D10V, EM_D30V, EM_V850, EM_M32R, EM_MN10300,
    EM_MN10200, EM_PJ, EM_OPENRISC, EM_ARC_A5, EM_XTENSA
  };
#define nvalid_e_machine \
  (sizeof (valid_e_machine) / sizeof (valid_e_machine[0]))


/* Number of sections.  */
static unsigned int shnum;


static void
check_elf_header (Ebl *ebl, GElf_Ehdr *ehdr, size_t size)
{
  char buf[512];
  size_t cnt;

  /* Check e_ident field.  */
  if (ehdr->e_ident[EI_MAG0] != ELFMAG0)
    ERROR ("e_ident[%d] != '%c'\n", EI_MAG0, ELFMAG0);
  if (ehdr->e_ident[EI_MAG1] != ELFMAG1)
    ERROR ("e_ident[%d] != '%c'\n", EI_MAG1, ELFMAG1);
  if (ehdr->e_ident[EI_MAG2] != ELFMAG2)
    ERROR ("e_ident[%d] != '%c'\n", EI_MAG2, ELFMAG2);
  if (ehdr->e_ident[EI_MAG3] != ELFMAG3)
    ERROR ("e_ident[%d] != '%c'\n", EI_MAG3, ELFMAG3);

  if (ehdr->e_ident[EI_CLASS] != ELFCLASS32
      && ehdr->e_ident[EI_CLASS] != ELFCLASS64)
    ERROR (gettext ("e_ident[%d] == %d is no known class\n"),
	   EI_CLASS, ehdr->e_ident[EI_CLASS]);

  if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB
      && ehdr->e_ident[EI_DATA] != ELFDATA2MSB)
    ERROR (gettext ("e_ident[%d] == %d is no known data encoding\n"),
	   EI_DATA, ehdr->e_ident[EI_DATA]);

  if (ehdr->e_ident[EI_VERSION] != EV_CURRENT)
    ERROR (gettext ("unknown ELF header version number e_ident[%d] == %d\n"),
	   EI_VERSION, ehdr->e_ident[EI_VERSION]);

  /* We currently don't handle any OS ABIs.  */
  if (ehdr->e_ident[EI_OSABI] != ELFOSABI_NONE)
    ERROR (gettext ("unsupported OS ABI e_ident[%d] == \"%s\"\n"),
	   EI_OSABI,
	   ebl_osabi_name (ebl, ehdr->e_ident[EI_OSABI], buf, sizeof (buf)));

  /* No ABI versions other than zero supported either.  */
  if (ehdr->e_ident[EI_ABIVERSION] != 0)
    ERROR (gettext ("unsupport ABI version e_ident[%d] == %d\n"),
	   EI_ABIVERSION, ehdr->e_ident[EI_ABIVERSION]);

  for (cnt = EI_PAD; cnt < EI_NIDENT; ++cnt)
    if (ehdr->e_ident[cnt] != 0)
      ERROR (gettext ("e_ident[%zu] is not zero\n"), cnt);

  /* Check the e_type field.  */
  if (ehdr->e_type != ET_REL && ehdr->e_type != ET_EXEC
      && ehdr->e_type != ET_DYN && ehdr->e_type != ET_CORE)
    ERROR (gettext ("unknown object file type %d\n"), ehdr->e_type);

  /* Check the e_machine field.  */
  for (cnt = 0; cnt < nvalid_e_machine; ++cnt)
    if (valid_e_machine[cnt] == ehdr->e_machine)
      break;
  if (cnt == nvalid_e_machine)
    ERROR (gettext ("unknown machine type %d\n"), ehdr->e_machine);

  /* Check the e_version field.  */
  if (ehdr->e_version != EV_CURRENT)
    ERROR (gettext ("unknown object file version\n"));

  /* Check the e_phoff and e_phnum fields.  */
  if (ehdr->e_phoff == 0)
    {
      if (ehdr->e_phnum != 0)
	ERROR (gettext ("invalid program header offset\n"));
      else if (ehdr->e_type == ET_EXEC || ehdr->e_type == ET_DYN)
	ERROR (gettext ("\
executables and DSOs cannot have zero program header offset\n"));
    }
  else if (ehdr->e_phnum == 0)
    ERROR (gettext ("invalid number of program header entries\n"));

  /* Check the e_shoff field.  */
  shnum = ehdr->e_shnum;
  shstrndx = ehdr->e_shstrndx;
  if (ehdr->e_shoff == 0)
    {
      if (ehdr->e_shnum != 0)
	ERROR (gettext ("invalid section header table offset\n"));
      else if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN
	       && ehdr->e_type != ET_CORE)
	ERROR (gettext ("section header table must be present\n"));
    }
  else
    {
      if (ehdr->e_shnum == 0)
	{
	  /* Get the header of the zeroth section.  The sh_size field
	     might contain the section number.  */
	  GElf_Shdr shdr_mem;
	  GElf_Shdr *shdr;

	  shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
	  if (shdr != NULL)
	    {
	      /* The error will be reported later.  */
	      if (shdr->sh_size == 0)
		ERROR (gettext ("\
invalid number of section header table entries\n"));
	      else
		shnum = shdr->sh_size;
	    }
	}

      if (ehdr->e_shstrndx == SHN_XINDEX)
	{
	  /* Get the header of the zeroth section.  The sh_size field
	     might contain the section number.  */
	  GElf_Shdr shdr_mem;
	  GElf_Shdr *shdr;

	  shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
	  if (shdr != NULL)
	    {
	      /* The error will be reported later.  */
	      if (shdr->sh_link >= shnum)
		ERROR (gettext ("invalid section header index\n"));
	      else
		shstrndx = shdr->sh_link;
	    }
	}
      else if (shstrndx >= shnum)
	ERROR (gettext ("invalid section header index\n"));
    }

  /* Check the e_flags field.  */
  if (!ebl_machine_flag_check (ebl, ehdr->e_flags))
    ERROR (gettext ("invalid machine flags: %s\n"),
	   ebl_machine_flag_name (ebl, ehdr->e_flags, buf, sizeof (buf)));

  /* Check e_ehsize, e_phentsize, and e_shentsize fields.  */
  if (gelf_getclass (ebl->elf) == ELFCLASS32)
    {
      if (ehdr->e_ehsize != 0 && ehdr->e_ehsize != sizeof (Elf32_Ehdr))
	ERROR (gettext ("invalid ELF header size: %hd\n"), ehdr->e_ehsize);

      if (ehdr->e_phentsize != 0 && ehdr->e_phentsize != sizeof (Elf32_Phdr))
	ERROR (gettext ("invalid program header size: %hd\n"),
	       ehdr->e_phentsize);
      else if (ehdr->e_phoff + ehdr->e_phnum * ehdr->e_phentsize > size)
	ERROR (gettext ("invalid program header position or size\n"));

      if (ehdr->e_shentsize != 0 && ehdr->e_shentsize != sizeof (Elf32_Shdr))
	ERROR (gettext ("invalid section header size: %hd\n"),
	       ehdr->e_shentsize);
      else if (ehdr->e_shoff + ehdr->e_shnum * ehdr->e_shentsize > size)
	ERROR (gettext ("invalid section header position or size\n"));
    }
  else if (gelf_getclass (ebl->elf) == ELFCLASS64)
    {
      if (ehdr->e_ehsize != 0 && ehdr->e_ehsize != sizeof (Elf64_Ehdr))
	ERROR (gettext ("invalid ELF header size: %hd\n"), ehdr->e_ehsize);

      if (ehdr->e_phentsize != 0 && ehdr->e_phentsize != sizeof (Elf64_Phdr))
	ERROR (gettext ("invalid program header size: %hd\n"),
	       ehdr->e_phentsize);
      else if (ehdr->e_phoff + ehdr->e_phnum * ehdr->e_phentsize > size)
	ERROR (gettext ("invalid program header position or size\n"));

      if (ehdr->e_shentsize != 0 && ehdr->e_shentsize != sizeof (Elf64_Shdr))
	ERROR (gettext ("invalid section header size: %hd\n"),
	       ehdr->e_shentsize);
      else if (ehdr->e_shoff + ehdr->e_shnum * ehdr->e_shentsize > size)
	ERROR (gettext ("invalid section header position or size\n"));
    }
}


/* Check that there is a section group section with index < IDX which
   contains section IDX and that there is exactly one.  */
static void
check_scn_group (Ebl *ebl, GElf_Ehdr *ehdr, int idx)
{
  if (scnref[idx] == 0)
    {
      /* No reference so far.  Search following sections, maybe the
	 order is wrong.  */
      size_t cnt;

      for (cnt = idx + 1; cnt < shnum; ++cnt)
	{
	  Elf_Scn *scn;
	  GElf_Shdr shdr_mem;
	  GElf_Shdr *shdr;
	  Elf_Data *data;
	  Elf32_Word *grpdata;
	  size_t inner;

	  scn = elf_getscn (ebl->elf, cnt);
	  shdr = gelf_getshdr (scn, &shdr_mem);
	  if (shdr == NULL)
	    /* We cannot get the section header so we cannot check it.
	       The error to get the section header will be shown
	       somewhere else.  */
	    continue;

	  if (shdr->sh_type != SHT_GROUP)
	    continue;

	  data = elf_getdata (scn, NULL);
	  if (data == NULL || data->d_size < sizeof (Elf32_Word))
	    /* Cannot check the section.  */
	    continue;

	  grpdata = (Elf32_Word *) data->d_buf;
	  for (inner = 1; inner < data->d_size / sizeof (Elf32_Word); ++inner)
	    if (grpdata[inner] == (Elf32_Word) idx)
	      goto out;
	}

    out:
      if (cnt == shnum)
	ERROR (gettext ("\
section [%2d] '%s': section with SHF_GROUP flag set not part of a section group\n"),
	       idx, section_name (ebl, ehdr, idx));
      else
	ERROR (gettext ("\
section [%2d] '%s': section group [%2zu] '%s' does not preceed group member\n"),
	       idx, section_name (ebl, ehdr, idx),
	       cnt, section_name (ebl, ehdr, cnt));
    }
}


static void
check_symtab (Ebl *ebl, GElf_Ehdr *ehdr, int idx)
{
  bool no_xndx_warned = false;
  int no_pt_tls = 0;

  Elf_Scn *scn = elf_getscn (ebl->elf, idx);
  GElf_Shdr shdr_mem;
  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
  GElf_Shdr strshdr_mem;
  GElf_Shdr *strshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
				     &strshdr_mem);
  if (shdr == NULL || strshdr == NULL)
    return;
  Elf_Data *data = elf_getdata (scn, NULL);
  if (data == NULL)
    {
      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
	     idx, section_name (ebl, ehdr, idx));
      return;
    }

  if (strshdr->sh_type != SHT_STRTAB)
    ERROR (gettext ("section [%2d] '%s': referenced as string table for section [%2d] '%s' but type is not SHT_STRTAB\n"),
	   shdr->sh_link, section_name (ebl, ehdr, shdr->sh_link),
	   idx, section_name (ebl, ehdr, idx));

  /* Search for an extended section index table section.  */
  size_t cnt;
  GElf_Shdr xndxshdr_mem;
  GElf_Shdr *xndxshdr = NULL;
  Elf_Data *xndxdata = NULL;
  Elf32_Word xndxscnidx = 0;
  for (cnt = 1; cnt < shnum; ++cnt)
    if (cnt != (size_t) idx)
      {
	Elf_Scn *xndxscn = elf_getscn (ebl->elf, cnt);
	xndxshdr = gelf_getshdr (xndxscn, &xndxshdr_mem);
	xndxdata = elf_getdata (xndxscn, NULL);
	xndxscnidx = elf_ndxscn (xndxscn);

	if (xndxshdr == NULL || xndxdata == NULL)
	  continue;

	if (xndxshdr->sh_type == SHT_SYMTAB_SHNDX
	    && xndxshdr->sh_link == (GElf_Word) idx)
	  break;
      }
  if (cnt == shnum)
    {
      xndxshdr = NULL;
      xndxdata = NULL;
    }

  if (shdr->sh_entsize != gelf_fsize (ebl->elf, ELF_T_SYM, 1, EV_CURRENT))
    ERROR (gettext ("\
section [%2zu] '%s': entry size is does not match ElfXX_Sym\n"),
	   cnt, section_name (ebl, ehdr, cnt));

  /* Test the zeroth entry.  */
  GElf_Sym sym_mem;
  Elf32_Word xndx;
  GElf_Sym *sym = gelf_getsymshndx (data, xndxdata, 0, &sym_mem, &xndx);
  if (sym == NULL)
      ERROR (gettext ("section [%2d] '%s': cannot get symbol %d: %s\n"),
	     idx, section_name (ebl, ehdr, idx), 0, elf_errmsg (-1));
  else
    {
      if (sym->st_name != 0)
	ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
	       idx, section_name (ebl, ehdr, idx), "st_name");
      if (sym->st_value != 0)
	ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
	       idx, section_name (ebl, ehdr, idx), "st_value");
      if (sym->st_size != 0)
	ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
	       idx, section_name (ebl, ehdr, idx), "st_size");
      if (sym->st_info != 0)
	ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
	       idx, section_name (ebl, ehdr, idx), "st_info");
      if (sym->st_other != 0)
	ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
	       idx, section_name (ebl, ehdr, idx), "st_other");
      if (sym->st_shndx != 0)
	ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
	       idx, section_name (ebl, ehdr, idx), "st_shndx");
      if (xndxdata != NULL && xndx != 0)
	ERROR (gettext ("\
section [%2d] '%s': XINDEX for zeroth entry not zero\n"),
	       xndxscnidx, section_name (ebl, ehdr, xndxscnidx));
    }

  for (cnt = 1; cnt < shdr->sh_size / shdr->sh_entsize; ++cnt)
    {
      sym = gelf_getsymshndx (data, xndxdata, cnt, &sym_mem, &xndx);
      if (sym == NULL)
	{
	  ERROR (gettext ("section [%2d] '%s': cannot get symbol %zu: %s\n"),
		 idx, section_name (ebl, ehdr, idx), cnt, elf_errmsg (-1));
	  continue;
	}

      const char *name = NULL;
      if (sym->st_name >= strshdr->sh_size)
	ERROR (gettext ("\
section [%2d] '%s': symbol %zu: invalid name value\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);
      else
	{
	  name = elf_strptr (ebl->elf, shdr->sh_link, sym->st_name);
	  assert (name != NULL);
	}

      if (sym->st_shndx == SHN_XINDEX)
	{
	  if (xndxdata == NULL)
	    {
	      ERROR (gettext ("\
section [%2d] '%s': symbol %zu: too large section index but no extended section index section\n"),
		     idx, section_name (ebl, ehdr, idx), cnt);
	      no_xndx_warned = true;
	    }
	  else if (xndx < SHN_LORESERVE)
	    ERROR (gettext ("\
section [%2d] '%s': symbol %zu: XINDEX used for index which would fit in st_shndx (%" PRIu32 ")\n"),
		   xndxscnidx, section_name (ebl, ehdr, xndxscnidx), cnt,
		   xndx);
	}
      else if ((sym->st_shndx >= SHN_LORESERVE
		// && sym->st_shndx <= SHN_HIRESERVE    always true
		&& sym->st_shndx != SHN_ABS
		&& sym->st_shndx != SHN_COMMON)
	       || (sym->st_shndx >= shnum
		   && (sym->st_shndx < SHN_LORESERVE
		       /* || sym->st_shndx > SHN_HIRESERVE  always false */)))
	ERROR (gettext ("\
section [%2d] '%s': symbol %zu: invalid section index\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);
      else
	xndx = sym->st_shndx;

      if (GELF_ST_TYPE (sym->st_info) >= STT_NUM)
	ERROR (gettext ("section [%2d] '%s': symbol %zu: unknown type\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);

      if (GELF_ST_BIND (sym->st_info) >= STB_NUM)
	ERROR (gettext ("\
section [%2d] '%s': symbol %zu: unknown symbol binding\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);

      if (xndx == SHN_COMMON)
	{
	  /* Common symbols can only appear in relocatable files.  */
	  if (ehdr->e_type != ET_REL)
	    ERROR (gettext ("\
section [%2d] '%s': symbol %zu: COMMON only allowed in relocatable files\n"),
		   idx, section_name (ebl, ehdr, idx), cnt);
	  if (cnt < shdr->sh_info)
	    ERROR (gettext ("\
section [%2d] '%s': symbol %zu: local COMMON symbols are nonsense\n"),
		   idx, section_name (ebl, ehdr, idx), cnt);
	  if (GELF_R_TYPE (sym->st_info) == STT_FUNC)
	    ERROR (gettext ("\
section [%2d] '%s': symbol %zu: function in COMMON section is nonsense\n"),
		   idx, section_name (ebl, ehdr, idx), cnt);
	}
      else if (xndx > 0 && xndx < shnum)
	{
	  GElf_Shdr destshdr_mem;
	  GElf_Shdr *destshdr;

	  destshdr = gelf_getshdr (elf_getscn (ebl->elf, xndx), &destshdr_mem);
	  if (destshdr != NULL)
	    {
	      if (GELF_ST_TYPE (sym->st_info) != STT_TLS)
		{
		  if ((sym->st_value - destshdr->sh_addr) > destshdr->sh_size)
		    ERROR (gettext ("\
section [%2d] '%s': symbol %zu: st_value out of bounds\n"),
			   idx, section_name (ebl, ehdr, idx), cnt);
		  else if ((sym->st_value - destshdr->sh_addr + sym->st_size)
			   > destshdr->sh_size)
		    ERROR (gettext ("\
section [%2d] '%s': symbol %zu does not fit completely in referenced section [%2d] '%s'\n"),
			   idx, section_name (ebl, ehdr, idx), cnt,
			   (int) xndx, section_name (ebl, ehdr, xndx));
		}
	      else
		{
		  if ((destshdr->sh_flags & SHF_TLS) == 0)
		    ERROR (gettext ("\
section [%2d] '%s': symbol %zu: referenced section [%2d] '%s' does not have SHF_TLS flag set\n"),
			   idx, section_name (ebl, ehdr, idx), cnt,
			   (int) xndx, section_name (ebl, ehdr, xndx));

		  if (ehdr->e_type == ET_REL)
		    {
		      /* For object files the symbol value must fall
                         into the section.  */
		      if (sym->st_value > destshdr->sh_size)
			ERROR (gettext ("\
section [%2d] '%s': symbol %zu: st_value out of bounds of referenced section [%2d] '%s'\n"),
			       idx, section_name (ebl, ehdr, idx), cnt,
			       (int) xndx, section_name (ebl, ehdr, xndx));
		      else if (sym->st_value + sym->st_size
			       > destshdr->sh_size)
			ERROR (gettext ("\
section [%2d] '%s': symbol %zu does not fit completely in referenced section [%2d] '%s'\n"),
			       idx, section_name (ebl, ehdr, idx), cnt,
			       (int) xndx, section_name (ebl, ehdr, xndx));
		    }
		  else
		    {
		      GElf_Phdr phdr_mem;
		      GElf_Phdr *phdr = NULL;
		      int pcnt;

		      for (pcnt = 0; pcnt < ehdr->e_phnum; ++pcnt)
			{
			  phdr = gelf_getphdr (ebl->elf, pcnt, &phdr_mem);
			  if (phdr != NULL && phdr->p_type == PT_TLS)
			    break;
			}

		      if (pcnt == ehdr->e_phnum)
			{
			  if (no_pt_tls++ == 0)
			    ERROR (gettext ("\
section [%2d] '%s': symbol %zu: TLS symbol but no TLS program header entry\n"),
				   idx, section_name (ebl, ehdr, idx), cnt);
			}
		      else
			{
			  if (sym->st_value
			      < destshdr->sh_offset - phdr->p_offset)
			    ERROR (gettext ("\
section [%2d] '%s': symbol %zu: st_value short of referenced section [%2d] '%s'\n"),
				   idx, section_name (ebl, ehdr, idx), cnt,
				   (int) xndx, section_name (ebl, ehdr, xndx));
			  else if (sym->st_value
				   > (destshdr->sh_offset - phdr->p_offset
				      + destshdr->sh_size))
			    ERROR (gettext ("\
section [%2d] '%s': symbol %zu: st_value out of bounds of referenced section [%2d] '%s'\n"),
				   idx, section_name (ebl, ehdr, idx), cnt,
				   (int) xndx, section_name (ebl, ehdr, xndx));
			  else if (sym->st_value + sym->st_size
				   > (destshdr->sh_offset - phdr->p_offset
				      + destshdr->sh_size))
			    ERROR (gettext ("\
section [%2d] '%s': symbol %zu does not fit completely in referenced section [%2d] '%s'\n"),
				   idx, section_name (ebl, ehdr, idx), cnt,
				   (int) xndx, section_name (ebl, ehdr, xndx));
			}
		    }
		}
	    }
	}

      if (GELF_ST_BIND (sym->st_info) == STB_LOCAL)
	{
	  if (cnt >= shdr->sh_info)
	    ERROR (gettext ("\
section [%2d] '%s': symbol %zu: local symbol outside range described in sh_info\n"),
		   idx, section_name (ebl, ehdr, idx), cnt);
	}
      else
	{
	  if (cnt < shdr->sh_info)
	    ERROR (gettext ("\
section [%2d] '%s': symbol %zu: non-local symbol outside range described in sh_info\n"),
		   idx, section_name (ebl, ehdr, idx), cnt);
	}

      if (GELF_ST_TYPE (sym->st_info) == STT_SECTION
	  && GELF_ST_BIND (sym->st_info) != STB_LOCAL)
	ERROR (gettext ("\
section [%2d] '%s': symbol %zu: non-local section symbol\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);

      if (name != NULL)
	{
	  if (strcmp (name, "_GLOBAL_OFFSET_TABLE_") == 0)
	    {
	      /* Check that address and size match the global offset
		 table.  We have to locate the GOT by searching for a
		 section named ".got".  */
	      Elf_Scn *gscn = NULL;

	      while ((gscn = elf_nextscn (ebl->elf, gscn)) != NULL)
		{
		  GElf_Shdr gshdr_mem;
		  GElf_Shdr *gshdr = gelf_getshdr (gscn, &gshdr_mem);
		  assert (gshdr != NULL);

		  const char *sname = elf_strptr (ebl->elf, ehdr->e_shstrndx,
						  gshdr->sh_name);
		  if (sname != NULL && strcmp (sname, ".got") == 0)
		    {
		      /* Found it.  */
		      if (sym->st_value != gshdr->sh_addr)
			/* This test is more strict than the psABIs
			   which usually allow the symbol to be in the
			   middle of the .got section, allowing
			   negative offsets.  */
			ERROR (gettext ("\
section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol value %#" PRIx64 " does not match .got section address %#" PRIx64 "\n"),
			       idx, section_name (ebl, ehdr, idx),
			       (uint64_t) sym->st_value,
			       (uint64_t) gshdr->sh_addr);

		      if (sym->st_size != gshdr->sh_size)
			ERROR (gettext ("\
section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol size %" PRIu64 " does not match .got section size %" PRIu64 "\n"),
			       idx, section_name (ebl, ehdr, idx),
			       (uint64_t) sym->st_size,
			       (uint64_t) gshdr->sh_size);

		      break;
		    }
		}

	      if (gscn == NULL)
		ERROR (gettext ("\
section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol present, but no .got section\n"),
		       idx, section_name (ebl, ehdr, idx));
	    }
	  else if (strcmp (name, "_DYNAMIC") == 0)
	    {
	      /* Check that address and size match the dynamic
		 section.  We locate the dynamic section via the
		 program header entry.  */
	      int pcnt;

	      for (pcnt = 0; pcnt < ehdr->e_phnum; ++pcnt)
		{
		  GElf_Phdr phdr_mem;
		  GElf_Phdr *phdr = gelf_getphdr (ebl->elf, pcnt, &phdr_mem);

		  if (phdr != NULL && phdr->p_type == PT_DYNAMIC)
		    {
		      if (sym->st_value != phdr->p_vaddr)
			ERROR (gettext ("\
section [%2d] '%s': _DYNAMIC_ symbol value %#" PRIx64 " does not match dynamic segment address %#" PRIx64 "\n"),
			       idx, section_name (ebl, ehdr, idx),
			       (uint64_t) sym->st_value,
			       (uint64_t) phdr->p_vaddr);

		      if (sym->st_size != phdr->p_memsz)
			ERROR (gettext ("\
section [%2d] '%s': _DYNAMIC symbol size %" PRIu64 " does not match dynamic segment size %" PRIu64 "\n"),
			       idx, section_name (ebl, ehdr, idx),
			       (uint64_t) sym->st_size,
			       (uint64_t) phdr->p_memsz);

		      break;
		    }
		}
	    }
	}
    }
}


static bool
is_rel_dyn (Ebl *ebl, GElf_Ehdr *ehdr, int idx, GElf_Shdr *shdr, bool rela)
{
  /* If this is no executable or DSO it cannot be a .rel.dyn section.  */
  if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)
    return false;

  /* Check the section name.  Unfortunately necessary.  */
  if (strcmp (section_name (ebl, ehdr, idx), rela ? ".rela.dyn" : ".rel.dyn"))
    return false;

  /* When a .rel.dyn section is used a DT_RELCOUNT dynamic section
     entry can be present as well.  */
  Elf_Scn *scn = NULL;
  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
    {
      GElf_Shdr rcshdr_mem;
      const GElf_Shdr *rcshdr = gelf_getshdr (scn, &rcshdr_mem);
      assert (rcshdr != NULL);

      if (rcshdr->sh_type == SHT_DYNAMIC)
	{
	  /* Found the dynamic section.  Look through it.  */
	  Elf_Data *d = elf_getdata (scn, NULL);
	  int cnt;

	  for (cnt = 1; cnt < rcshdr->sh_size / rcshdr->sh_entsize; ++cnt)
	    {
	      GElf_Dyn dyn_mem;
	      GElf_Dyn *dyn = gelf_getdyn (d, cnt, &dyn_mem);
	      assert (dyn != NULL);

	      if (dyn->d_tag == DT_RELCOUNT)
		{
		  /* Found it.  One last check: does the number
		     specified number of relative relocations exceed
		     the total number of relocations?  */
		  if (dyn->d_un.d_val > shdr->sh_size / shdr->sh_entsize)
		    ERROR (gettext ("\
section [%2d] '%s': DT_RELCOUNT value %d too high for this section\n"),
			   idx, section_name (ebl, ehdr, idx),
			   (int) dyn->d_un.d_val);
		}
	    }

	  break;
	}
    }

  return true;
}


static void
check_rela (Ebl *ebl, GElf_Ehdr *ehdr, int idx)
{
  Elf_Scn *scn;
  GElf_Shdr shdr_mem;
  GElf_Shdr *shdr;
  Elf_Data *data;
  GElf_Shdr destshdr_mem;
  GElf_Shdr *destshdr = NULL;
  size_t cnt;
  bool reldyn = false;
  bool known_broken = gnuld;

  scn = elf_getscn (ebl->elf, idx);
  shdr = gelf_getshdr (scn, &shdr_mem);
  if (shdr == NULL)
    return;
  data = elf_getdata (scn, NULL);
  if (data == NULL)
    {
      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
	     idx, section_name (ebl, ehdr, idx));
      return;
    }

  /* Check whether the link to the section we relocate is reasonable.  */
  if (shdr->sh_info >= shnum)
    ERROR (gettext ("section [%2d] '%s': invalid destination section index\n"),
	   idx, section_name (ebl, ehdr, idx));
  else
    {
      destshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_info),
			       &destshdr_mem);
      if (destshdr != NULL)
	{
	  if(destshdr->sh_type != SHT_PROGBITS
	     && destshdr->sh_type != SHT_NOBITS)
	    {
	      reldyn = is_rel_dyn (ebl, ehdr, idx, shdr, true);
	      if (!reldyn)
		ERROR (gettext ("\
section [%2d] '%s': invalid destination section type\n"),
		       idx, section_name (ebl, ehdr, idx));
	      else
		{
		  /* There is no standard, but we require that .rela.dyn
		     sections have a sh_info value of zero.  */
		  if (shdr->sh_info != 0)
		    ERROR (gettext ("\
section [%2d] '%s': sh_info should be zero\n"),
			   idx, section_name (ebl, ehdr, idx));
		}
	    }

	  if ((destshdr->sh_flags & (SHF_MERGE | SHF_STRINGS)) != 0)
	    ERROR (gettext ("\
section [%2d] '%s': no relocations for merge-able sections possible\n"),
		   idx, section_name (ebl, ehdr, idx));
	}
    }

  if (shdr->sh_entsize != gelf_fsize (ebl->elf, ELF_T_RELA, 1, EV_CURRENT))
    ERROR (gettext ("\
section [%2d] '%s': section entry size does not match ElfXX_Rela\n"),
	   idx, section_name (ebl, ehdr, idx));

  Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
  GElf_Shdr symshdr_mem;
  GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
  Elf_Data *symdata = elf_getdata (symscn, NULL);

  for (cnt = 0; cnt < shdr->sh_size / shdr->sh_entsize; ++cnt)
    {
      GElf_Rela rela_mem;
      GElf_Rela *rela;

      rela = gelf_getrela (data, cnt, &rela_mem);
      if (rela == NULL)
	{
	  ERROR (gettext ("\
section [%2d] '%s': cannot get relocation %zu: %s\n"),
		 idx, section_name (ebl, ehdr, idx), cnt, elf_errmsg (-1));
	  continue;
	}

      if (!ebl_reloc_type_check (ebl, GELF_R_TYPE (rela->r_info)))
	ERROR (gettext ("section [%2d] '%s': relocation %zu: invalid type\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);
      else if (!ebl_reloc_valid_use (ebl, GELF_R_TYPE (rela->r_info)))
	ERROR (gettext ("\
section [%2d] '%s': relocation %zu: relocation type invalid for the file type\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);

      if (symshdr != NULL
	  && ((GELF_R_SYM (rela->r_info) + 1)
	      * gelf_fsize (ebl->elf, ELF_T_SYM, 1, EV_CURRENT)
	      > symshdr->sh_size))
	ERROR (gettext ("\
section [%2d] '%s': relocation %zu: invalid symbol index\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);

      if (ebl_gotpc_reloc_check (ebl, GELF_R_TYPE (rela->r_info)))
	{
	  const char *name;
	  char buf[64];
	  GElf_Sym sym_mem;
	  GElf_Sym *sym = gelf_getsym (symdata, GELF_R_SYM (rela->r_info),
				       &sym_mem);
	  if (sym != NULL
	      /* Get the name for the symbol.  */
	      && (name = elf_strptr (ebl->elf, symshdr->sh_link, sym->st_name))
	      && strcmp (name, "_GLOBAL_OFFSET_TABLE_") !=0 )
	    ERROR (gettext ("\
section [%2d] '%s': relocation %zu: only symbol '_GLOBAL_OFFSET_TABLE_' can be used with %s\n"),
		   idx, section_name (ebl, ehdr, idx), cnt,
		   ebl_reloc_type_name (ebl, GELF_R_SYM (rela->r_info),
					buf, sizeof (buf)));
	}

      if (reldyn)
	{
	  // XXX TODO Check .rel.dyn section addresses.
	}
      else if (!known_broken)
	{
	  if (destshdr != NULL
	      && (rela->r_offset - destshdr->sh_addr) >= destshdr->sh_size)
	    ERROR (gettext ("\
section [%2d] '%s': relocation %zu: offset out of bounds\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);
	}
    }
}


static void
check_rel (Ebl *ebl, GElf_Ehdr *ehdr, int idx)
{
  Elf_Scn *scn;
  GElf_Shdr shdr_mem;
  GElf_Shdr *shdr;
  Elf_Data *data;
  GElf_Shdr destshdr_mem;
  GElf_Shdr *destshdr = NULL;
  size_t cnt;
  bool reldyn = false;
  bool known_broken = gnuld;

  scn = elf_getscn (ebl->elf, idx);
  shdr = gelf_getshdr (scn, &shdr_mem);
  if (shdr == NULL)
    return;
  data = elf_getdata (scn, NULL);
  if (data == NULL)
    {
      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
	     idx, section_name (ebl, ehdr, idx));
      return;
    }

  /* Check whether the link to the section we relocate is reasonable.  */
  if (shdr->sh_info >= shnum)
    ERROR (gettext ("section [%2d] '%s': invalid destination section index\n"),
	   idx, section_name (ebl, ehdr, idx));
  else
    {
      destshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_info),
			       &destshdr_mem);
      if (destshdr != NULL)
	{
	  if (destshdr->sh_type != SHT_PROGBITS
	      && destshdr->sh_type != SHT_NOBITS)
	    {
	      reldyn = is_rel_dyn (ebl, ehdr, idx, shdr, false);
	      if (!reldyn)
		ERROR (gettext ("\
section [%2d] '%s': invalid destination section type\n"),
		       idx, section_name (ebl, ehdr, idx));
	      else
		{
		  /* There is no standard, but we require that .rela.dyn
		     sections have a sh_info value of zero.  */
		  if (shdr->sh_info != 0)
		    ERROR (gettext ("\
section [%2d] '%s': sh_info should be zero\n"),
			   idx, section_name (ebl, ehdr, idx));
		}
	    }

	  if ((destshdr->sh_flags & (SHF_MERGE | SHF_STRINGS)) != 0)
	    ERROR (gettext ("\
section [%2d] '%s': no relocations for merge-able sections possible\n"),
		   idx, section_name (ebl, ehdr, idx));
	}
    }

  if (shdr->sh_entsize != gelf_fsize (ebl->elf, ELF_T_REL, 1, EV_CURRENT))
    ERROR (gettext ("\
section [%2d] '%s': section entry size does not match ElfXX_Rel\n"),
	   idx, section_name (ebl, ehdr, idx));

  Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
  GElf_Shdr symshdr_mem;
  GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
  Elf_Data *symdata = elf_getdata (symscn, NULL);

  for (cnt = 0; cnt < shdr->sh_size / shdr->sh_entsize; ++cnt)
    {
      GElf_Rel rel_mem;
      GElf_Rel *rel;

      rel = gelf_getrel (data, cnt, &rel_mem);
      if (rel == NULL)
	{
	  ERROR (gettext ("\
section [%2d] '%s': cannot get relocation %zu: %s\n"),
		 idx, section_name (ebl, ehdr, idx), cnt, elf_errmsg (-1));
	  continue;
	}

      if (!ebl_reloc_type_check (ebl, GELF_R_TYPE (rel->r_info)))
	ERROR (gettext ("section [%2d] '%s': relocation %zu: invalid type\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);
      else if (!ebl_reloc_valid_use (ebl, GELF_R_TYPE (rel->r_info)))
	ERROR (gettext ("\
section [%2d] '%s': relocation %zu: relocation type invalid for the file type\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);

      if (symshdr != NULL
	  && ((GELF_R_SYM (rel->r_info) + 1)
	      * gelf_fsize (ebl->elf, ELF_T_SYM, 1, EV_CURRENT)
	      > symshdr->sh_size))
	ERROR (gettext ("\
section [%2d] '%s': relocation %zu: invalid symbol index\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);

      if (ebl_gotpc_reloc_check (ebl, GELF_R_TYPE (rel->r_info)))
	{
	  const char *name;
	  char buf[64];
	  GElf_Sym sym_mem;
	  GElf_Sym *sym = gelf_getsym (symdata, GELF_R_SYM (rel->r_info),
				       &sym_mem);
	  if (sym != NULL
	      /* Get the name for the symbol.  */
	      && (name = elf_strptr (ebl->elf, symshdr->sh_link, sym->st_name))
	      && strcmp (name, "_GLOBAL_OFFSET_TABLE_") !=0 )
	    ERROR (gettext ("\
section [%2d] '%s': relocation %zu: only symbol '_GLOBAL_OFFSET_TABLE_' can be used with %s\n"),
		   idx, section_name (ebl, ehdr, idx), cnt,
		   ebl_reloc_type_name (ebl, GELF_R_SYM (rel->r_info),
					buf, sizeof (buf)));
	}

      if (reldyn)
	{
	  // XXX TODO Check .rel.dyn section addresses.
	}
      else if (!known_broken)
	{
	  if (destshdr != NULL
	      && GELF_R_TYPE (rel->r_info) != 0
	      && (rel->r_offset - destshdr->sh_addr) >= destshdr->sh_size)
	    ERROR (gettext ("\
section [%2d] '%s': relocation %zu: offset out of bounds\n"),
		   idx, section_name (ebl, ehdr, idx), cnt);
	}
    }
}


/* Number of dynamic sections.  */
static int ndynamic;


static void
check_dynamic (Ebl *ebl, GElf_Ehdr *ehdr, int idx)
{
  Elf_Scn *scn;
  GElf_Shdr shdr_mem;
  GElf_Shdr *shdr;
  Elf_Data *data;
  GElf_Shdr strshdr_mem;
  GElf_Shdr *strshdr;
  size_t cnt;
  static const bool dependencies[DT_NUM][DT_NUM] =
    {
      [DT_NEEDED] = { [DT_STRTAB] = true },
      [DT_PLTRELSZ] = { [DT_JMPREL] = true },
      [DT_HASH] = { [DT_SYMTAB] = true },
      [DT_STRTAB] = { [DT_STRSZ] = true },
      [DT_SYMTAB] = { [DT_STRTAB] = true, [DT_HASH] = true,
		      [DT_SYMENT] = true },
      [DT_RELA] = { [DT_RELASZ] = true, [DT_RELAENT] = true },
      [DT_RELASZ] = { [DT_RELA] = true },
      [DT_RELAENT] = { [DT_RELA] = true },
      [DT_STRSZ] = { [DT_STRTAB] = true },
      [DT_SYMENT] = { [DT_SYMTAB] = true },
      [DT_SONAME] = { [DT_STRTAB] = true },
      [DT_RPATH] = { [DT_STRTAB] = true },
      [DT_REL] = { [DT_RELSZ] = true, [DT_RELENT] = true },
      [DT_RELSZ] = { [DT_REL] = true },
      [DT_RELENT] = { [DT_REL] = true },
      [DT_JMPREL] = { [DT_PLTRELSZ] = true, [DT_PLTREL] = true },
      [DT_RUNPATH] = { [DT_STRTAB] = true },
      [DT_PLTREL] = { [DT_JMPREL] = true },
      [DT_PLTRELSZ] = { [DT_JMPREL] = true }
    };
  bool has_dt[DT_NUM];
  static const bool level2[DT_NUM] =
    {
      [DT_RPATH] = true,
      [DT_SYMBOLIC] = true,
      [DT_TEXTREL] = true,
      [DT_BIND_NOW] = true
    };
  static const bool mandatory[DT_NUM] =
    {
      [DT_NULL] = true,
      [DT_HASH] = true,
      [DT_STRTAB] = true,
      [DT_SYMTAB] = true,
      [DT_STRSZ] = true,
      [DT_SYMENT] = true
    };
  GElf_Addr reladdr = 0;
  GElf_Word relsz = 0;
  GElf_Addr pltreladdr = 0;
  GElf_Word pltrelsz = 0;

  memset (has_dt, '\0', sizeof (has_dt));

  if (++ndynamic == 2)
    ERROR (gettext ("more than one dynamic section present\n"));

  scn = elf_getscn (ebl->elf, idx);
  shdr = gelf_getshdr (scn, &shdr_mem);
  if (shdr == NULL)
    return;
  data = elf_getdata (scn, NULL);
  if (data == NULL)
    {
      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
	     idx, section_name (ebl, ehdr, idx));
      return;
    }

  strshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link), &strshdr_mem);
  if (strshdr != NULL && strshdr->sh_type != SHT_STRTAB)
    ERROR (gettext ("\
section [%2d] '%s': referenced as string table for section [%2d] '%s' but type is not SHT_STRTAB\n"),
	   shdr->sh_link, section_name (ebl, ehdr, shdr->sh_link),
	   idx, section_name (ebl, ehdr, idx));

  if (shdr->sh_entsize != gelf_fsize (ebl->elf, ELF_T_DYN, 1, EV_CURRENT))
    ERROR (gettext ("\
section [%2d] '%s': section entry size does not match ElfXX_Dyn\n"),
	   idx, section_name (ebl, ehdr, idx));

  if (shdr->sh_info != 0)
    ERROR (gettext ("section [%2d] '%s': sh_info not zero\n"),
	   idx, section_name (ebl, ehdr, idx));

  bool non_null_warned = false;
  for (cnt = 0; cnt < shdr->sh_size / shdr->sh_entsize; ++cnt)
    {
      GElf_Dyn dyn_mem;
      GElf_Dyn *dyn;

      dyn = gelf_getdyn (data, cnt, &dyn_mem);
      if (dyn == NULL)
	{
	  ERROR (gettext ("\
section [%2d] '%s': cannot get dynamic section entry %zu: %s\n"),
		 idx, section_name (ebl, ehdr, idx), cnt, elf_errmsg (-1));
	  continue;
	}

      if (has_dt[DT_NULL] && dyn->d_tag != DT_NULL && ! non_null_warned)
	{
	  ERROR (gettext ("\
section [%2d] '%s': non-DT_NULL entries follow DT_NULL entry\n"),
		 idx, section_name (ebl, ehdr, idx));
	  non_null_warned = true;
	}

      if (!ebl_dynamic_tag_check (ebl, dyn->d_tag))
	ERROR (gettext ("section [%2d] '%s': entry %zu: unknown tag\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);

      if (dyn->d_tag < DT_NUM)
	{
	  if (has_dt[dyn->d_tag]
	      && dyn->d_tag != DT_NEEDED
	      && dyn->d_tag != DT_NULL
	      && dyn->d_tag != DT_POSFLAG_1)
	    {
	      char buf[50];
	      ERROR (gettext ("\
section [%2d] '%s': entry %zu: more than one entry with tag %s\n"),
		     idx, section_name (ebl, ehdr, idx), cnt,
		     ebl_dynamic_tag_name (ebl, dyn->d_tag,
					   buf, sizeof (buf)));
	    }

	  if (be_strict && level2[dyn->d_tag])
	    {
	      char buf[50];
	      ERROR (gettext ("\
section [%2d] '%s': entry %zu: level 2 tag %s used\n"),
		     idx, section_name (ebl, ehdr, idx), cnt,
		     ebl_dynamic_tag_name (ebl, dyn->d_tag,
					   buf, sizeof (buf)));
	    }

	  has_dt[dyn->d_tag] = true;
	}

      if (dyn->d_tag == DT_PLTREL && dyn->d_un.d_val != DT_REL
	  && dyn->d_un.d_val != DT_RELA)
	ERROR (gettext ("\
section [%2d] '%s': entry %zu: DT_PLTREL value must be DT_REL or DT_RELA\n"),
	       idx, section_name (ebl, ehdr, idx), cnt);

      if (dyn->d_tag == DT_REL)
	reladdr = dyn->d_un.d_ptr;
      if (dyn->d_tag == DT_RELSZ)
	relsz = dyn->d_un.d_val;
      if (dyn->d_tag == DT_JMPREL)
	pltreladdr = dyn->d_un.d_ptr;
      if (dyn->d_tag == DT_PLTRELSZ)
	pltrelsz = dyn->d_un.d_val;
    }

  for (cnt = 1; cnt < DT_NUM; ++cnt)
    if (has_dt[cnt])
      {
	int inner;

	for (inner = 0; inner < DT_NUM; ++inner)
	  if (dependencies[cnt][inner] && ! has_dt[inner])
	    {
	      char buf1[50];
	      char buf2[50];

	      ERROR (gettext ("\
section [%2d] '%s': contains %s entry but not %s\n"),
		     idx, section_name (ebl, ehdr, idx),
		     ebl_dynamic_tag_name (ebl, cnt, buf1, sizeof (buf1)),
		     ebl_dynamic_tag_name (ebl, inner, buf2, sizeof (buf2)));
	    }
      }
    else
      {
	if (mandatory[cnt])
	  {
	    char buf[50];
	    ERROR (gettext ("\
section [%2d] '%s': mandatory tag %s not present\n"),
		   idx, section_name (ebl, ehdr, idx),
		   ebl_dynamic_tag_name (ebl, cnt, buf, sizeof (buf)));
	  }
      }

  /* Check the rel/rela tags.  At least one group must be available.  */
  if ((has_dt[DT_RELA] || has_dt[DT_RELASZ] || has_dt[DT_RELAENT])
      && (!has_dt[DT_RELA] || !has_dt[DT_RELASZ] || !has_dt[DT_RELAENT]))
    ERROR (gettext ("\
section [%2d] '%s': not all of %s, %s, and %s are present\n"),
	   idx, section_name (ebl, ehdr, idx),
	   "DT_RELA", "DT_RELASZ", "DT_RELAENT");

  if ((has_dt[DT_REL] || has_dt[DT_RELSZ] || has_dt[DT_RELENT])
      && (!has_dt[DT_REL] || !has_dt[DT_RELSZ] || !has_dt[DT_RELENT]))
    ERROR (gettext ("\
section [%2d] '%s': not all of %s, %s, and %s are present\n"),
	   idx, section_name (ebl, ehdr, idx),
	   "DT_REL", "DT_RELSZ", "DT_RELENT");
}


static void
check_symtab_shndx (Ebl *ebl, GElf_Ehdr *ehdr, int idx)
{
  Elf_Scn *scn;
  GElf_Shdr shdr_mem;
  GElf_Shdr *shdr;
  GElf_Shdr symshdr_mem;
  GElf_Shdr *symshdr;
  Elf_Scn *symscn;
  size_t cnt;
  Elf_Data *data;
  Elf_Data *symdata;

  scn = elf_getscn (ebl->elf, idx);
  shdr = gelf_getshdr (scn, &shdr_mem);
  if (shdr == NULL)
    return;

  symscn = elf_getscn (ebl->elf, shdr->sh_link);
  symshdr = gelf_getshdr (symscn, &symshdr_mem);
  if (symshdr != NULL && symshdr->sh_type != SHT_SYMTAB)
    ERROR (gettext ("\
section [%2d] '%s': extended section index section not for symbol table\n"),
	   idx, section_name (ebl, ehdr, idx));
  symdata = elf_getdata (symscn, NULL);
  if (symdata == NULL)
    ERROR (gettext ("cannot get data for symbol section\n"));

  if (shdr->sh_entsize != sizeof (Elf32_Word))
    ERROR (gettext ("\
section [%2d] '%s': entry size does not match Elf32_Word\n"),
	   idx, section_name (ebl, ehdr, idx));

  if (symshdr != NULL
      && (shdr->sh_size / shdr->sh_entsize
	  < symshdr->sh_size / symshdr->sh_entsize))
    ERROR (gettext ("\
section [%2d] '%s': extended index table too small for symbol table\n"),
	   idx, section_name (ebl, ehdr, idx));

  if (shdr->sh_info != 0)
    ERROR (gettext ("section [%2d] '%s': sh_info not zero\n"),
	   idx, section_name (ebl, ehdr, idx));

  for (cnt = idx + 1; cnt < shnum; ++cnt)
    {
      GElf_Shdr rshdr_mem;
      GElf_Shdr *rshdr;

      rshdr = gelf_getshdr (elf_getscn (ebl->elf, cnt), &rshdr_mem);
      if (rshdr != NULL && rshdr->sh_type == SHT_SYMTAB_SHNDX
	  && rshdr->sh_link == shdr->sh_link)
	{
	  ERROR (gettext ("\
section [%2d] '%s': extended section index in section [%2zu] '%s' refers to same symbol table\n"),
		 idx, section_name (ebl, ehdr, idx),
		 cnt, section_name (ebl, ehdr, cnt));
	  break;
	}
    }

  data = elf_getdata (scn, NULL);

  if (*((Elf32_Word *) data->d_buf) != 0)
    ERROR (gettext ("symbol 0 should have zero extended section index\n"));

  for (cnt = 1; cnt < data->d_size / sizeof (Elf32_Word); ++cnt)
    {
      Elf32_Word xndx = ((Elf32_Word *) data->d_buf)[cnt];

      if (xndx != 0)
	{
	  GElf_Sym sym_data;
	  GElf_Sym *sym = gelf_getsym (symdata, cnt, &sym_data);
	  if (sym == NULL)
	    {
	      ERROR (gettext ("cannot get data for symbol %zu\n"), cnt);
	      continue;
	    }

	  if (sym->st_shndx != SHN_XINDEX)
	    ERROR (gettext ("\
extended section index is %" PRIu32 " but symbol index is not XINDEX\n"),
		   (uint32_t) xndx);
	}
    }
}


static void
check_hash (Ebl *ebl, GElf_Ehdr *ehdr, int idx)
{
  Elf_Scn *scn;
  GElf_Shdr shdr_mem;
  GElf_Shdr *shdr;
  Elf_Data *data;
  Elf32_Word nbucket;
  Elf32_Word nchain;
  GElf_Shdr symshdr_mem;
  GElf_Shdr *symshdr;

  scn = elf_getscn (ebl->elf, idx);
  shdr = gelf_getshdr (scn, &shdr_mem);
  if (shdr == NULL)
    return;
  data = elf_getdata (scn, NULL);
  if (data == NULL)
    {
      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
	     idx, section_name (ebl, ehdr, idx));
      return;
    }

  symshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link), &symshdr_mem);
  if (symshdr != NULL && symshdr->sh_type != SHT_DYNSYM)
    ERROR (gettext ("\
section [%2d] '%s': hash table not for dynamic symbol table\n"),
	   idx, section_name (ebl, ehdr, idx));

  if (shdr->sh_entsize != sizeof (Elf32_Word))
    ERROR (gettext ("\
section [%2d] '%s': entry size does not match Elf32_Word\n"),
	   idx, section_name (ebl, ehdr, idx));

  if ((shdr->sh_flags & SHF_ALLOC) == 0)
    ERROR (gettext ("section [%2d] '%s': not marked to be allocated\n"),
	   idx, section_name (ebl, ehdr, idx));

  if (shdr->sh_size < 2 * shdr->sh_entsize)
    {
      ERROR (gettext ("\
section [%2d] '%s': hash table has not even room for nbucket and nchain\n"),
	     idx, section_name (ebl, ehdr, idx));
      return;
    }

  nbucket = ((Elf32_Word *) data->d_buf)[0];
  nchain = ((Elf32_Word *) data->d_buf)[1];

  if (shdr->sh_size < (2 + nbucket + nchain) * shdr->sh_entsize)
    ERROR (gettext ("\
section [%2d] '%s': hash table section is too small (is %ld, expected %ld)\n"),
	   idx, section_name (ebl, ehdr, idx), (long int) shdr->sh_size,
	   (long int) ((2 + nbucket + nchain) * shdr->sh_entsize));

  if (symshdr != NULL)
    {
      size_t symsize = symshdr->sh_size / symshdr->sh_entsize;
      size_t cnt;

      if (nchain < symshdr->sh_size / symshdr->sh_entsize)
	ERROR (gettext ("section [%2d] '%s': chain array not large enough\n"),
	       idx, section_name (ebl, ehdr, idx));

      for (cnt = 2; cnt < 2 + nbucket; ++cnt)
	if (((Elf32_Word *) data->d_buf)[cnt] >= symsize)
	  ERROR (gettext ("\
section [%2d] '%s': hash bucket reference %zu out of bounds\n"),
		 idx, section_name (ebl, ehdr, idx), cnt - 2);

      for (; cnt < 2 + nbucket + nchain; ++cnt)
	if (((Elf32_Word *) data->d_buf)[cnt] >= symsize)
	  ERROR (gettext ("\
section [%2d] '%s': hash chain reference %zu out of bounds\n"),
		 idx, section_name (ebl, ehdr, idx), cnt - 2 - nbucket);
    }
}


static void
check_null (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
{
#define TEST(name, extra) \
  if (extra && shdr->sh_##name != 0)					      \
    ERROR (gettext ("section [%2d] '%s': nonzero sh_%s for NULL section\n"),  \
	   idx, section_name (ebl, ehdr, idx), #name)

  TEST (name, 1);
  TEST (flags, 1);
  TEST (addr, 1);
  TEST (offset, 1);
  TEST (size, idx != 0);
  TEST (link, idx != 0);
  TEST (info, 1);
  TEST (addralign, 1);
  TEST (entsize, 1);
}


static void
check_group (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
{
  if (ehdr->e_type != ET_REL)
    {
      ERROR (gettext ("\
section [%2d] '%s': section groups only allowed in relocatable object files\n"),
	     idx, section_name (ebl, ehdr, idx));
      return;
    }

  /* Check that sh_link is an index of a symbol table.  */
  GElf_Shdr symshdr_mem;
  GElf_Shdr *symshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
				     &symshdr_mem);
  if (symshdr == NULL)
    ERROR (gettext ("section [%2d] '%s': cannot get symbol table: %s\n"),
	   idx, section_name (ebl, ehdr, idx), elf_errmsg (-1));
  else
    {
      if (symshdr->sh_type != SHT_SYMTAB)
	ERROR (gettext ("\
section [%2d] '%s': section reference in sh_link is no symbol table\n"),
	       idx, section_name (ebl, ehdr, idx));

      if (shdr->sh_info >= symshdr->sh_size / gelf_fsize (ebl->elf, ELF_T_SYM,
							  1, EV_CURRENT))
	ERROR (gettext ("\
section [%2d] '%s': invalid symbol index in sh_info\n"),
	       idx, section_name (ebl, ehdr, idx));

      if (shdr->sh_flags != 0)
	ERROR (gettext ("section [%2d] '%s': sh_flags not zero\n"),
	       idx, section_name (ebl, ehdr, idx));

      if (be_strict
	  && shdr->sh_entsize != elf32_fsize (ELF_T_WORD, 1, EV_CURRENT))
	ERROR (gettext ("section [%2d] '%s': sh_flags not set correctly\n"),
	       idx, section_name (ebl, ehdr, idx));
    }

  Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
  if (data == NULL)
    ERROR (gettext ("section [%2d] '%s': cannot get data: %s\n"),
	   idx, section_name (ebl, ehdr, idx), elf_errmsg (-1));
  else
    {
      size_t elsize = elf32_fsize (ELF_T_WORD, 1, EV_CURRENT);
      size_t cnt;
      Elf32_Word val;

      if (data->d_size % elsize != 0)
	ERROR (gettext ("\
section [%2d] '%s': section size not multiple of sizeof(Elf32_Word)\n"),
	       idx, section_name (ebl, ehdr, idx));

      if (data->d_size < elsize)
	ERROR (gettext ("\
section [%2d] '%s': section group without flags word\n"),
	       idx, section_name (ebl, ehdr, idx));
      else if (be_strict)
	{
	  if (data->d_size < 2 * elsize)
	    ERROR (gettext ("\
section [%2d] '%s': section group without member\n"),
		   idx, section_name (ebl, ehdr, idx));
	  else if (data->d_size < 3 * elsize)
	    ERROR (gettext ("\
section [%2d] '%s': section group with only one member\n"),
		   idx, section_name (ebl, ehdr, idx));
	}

#if ALLOW_UNALIGNED
      val = *((Elf32_Word *) data->d_buf);
#else
      memcpy (&val, data->d_buf, elsize);
#endif
      if ((val & ~GRP_COMDAT) != 0)
	ERROR (gettext ("section [%2d] '%s': unknown section group flags\n"),
	       idx, section_name (ebl, ehdr, idx));

      for (cnt = elsize; cnt < data->d_size; cnt += elsize)
	{
#if ALLOW_UNALIGNED
	  val = *((Elf32_Word *) ((char *) data->d_buf + cnt));
#else
	  memcpy (&val, (char *) data->d_buf + cnt, elsize);
#endif

	  if (val > shnum)
	    ERROR (gettext ("\
section [%2d] '%s': section index %Zu out of range\n"),
		   idx, section_name (ebl, ehdr, idx), cnt / elsize);
	  else
	    {
	      GElf_Shdr refshdr_mem;
	      GElf_Shdr *refshdr;

	      refshdr = gelf_getshdr (elf_getscn (ebl->elf, val),
				      &refshdr_mem);
	      if (refshdr == NULL)
		ERROR (gettext ("\
section [%2d] '%s': cannot get section header for element %zu: %s\n"),
		       idx, section_name (ebl, ehdr, idx), cnt / elsize,
		       elf_errmsg (-1));
	      else
		{
		  if (refshdr->sh_type == SHT_GROUP)
		    ERROR (gettext ("\
section [%2d] '%s': section group contains another group [%2d] '%s'\n"),
			   idx, section_name (ebl, ehdr, idx),
			   val, section_name (ebl, ehdr, val));

		  if ((refshdr->sh_flags & SHF_GROUP) == 0)
		    ERROR (gettext ("\
section [%2d] '%s': element %Zu references section [%2d] '%s' without SHF_GROUP flag set\n"),
			   idx, section_name (ebl, ehdr, idx), cnt / elsize,
			   val, section_name (ebl, ehdr, val));
		}

	      if (++scnref[val] == 2)
		ERROR (gettext ("\
section [%2d] '%s' is contained in more than one section group\n"),
		       val, section_name (ebl, ehdr, val));
	    }
	}
    }
}


static bool has_loadable_segment;
static bool has_interp_segment;

static const struct
{
  const char *name;
  size_t namelen;
  GElf_Word type;
  enum { unused, exact, atleast } attrflag;
  GElf_Word attr;
  GElf_Word attr2;
} special_sections[] =
  {
    /* See figure 4-14 in the gABI.  */
    { ".bss", 5, SHT_NOBITS, exact, SHF_ALLOC | SHF_WRITE, 0 },
    { ".comment", 8, SHT_PROGBITS, exact, 0, 0 },
    { ".data", 6, SHT_PROGBITS, exact, SHF_ALLOC | SHF_WRITE, 0 },
    { ".data1", 7, SHT_PROGBITS, exact, SHF_ALLOC | SHF_WRITE, 0 },
    { ".debug", 7, SHT_PROGBITS, exact, 0, 0 },
    { ".dynamic", 9, SHT_DYNAMIC, atleast, SHF_ALLOC, SHF_WRITE },
    { ".dynstr", 8, SHT_STRTAB, exact, SHF_ALLOC, 0 },
    { ".dynsym", 8, SHT_DYNSYM, exact, SHF_ALLOC, 0 },
    { ".fini", 6, SHT_PROGBITS, exact, SHF_ALLOC | SHF_EXECINSTR, 0 },
    { ".fini_array", 12, SHT_FINI_ARRAY, exact, SHF_ALLOC | SHF_WRITE, 0 },
    { ".got", 5, SHT_PROGBITS, unused, 0, 0 }, // XXX more info?
    { ".hash", 6, SHT_HASH, exact, SHF_ALLOC, 0 },
    { ".init", 6, SHT_PROGBITS, exact, SHF_ALLOC | SHF_EXECINSTR, 0 },
    { ".init_array", 12, SHT_INIT_ARRAY, exact, SHF_ALLOC | SHF_WRITE, 0 },
    { ".interp", 8, SHT_PROGBITS, atleast, 0, SHF_ALLOC }, // XXX more tests?
    { ".line", 6, SHT_PROGBITS, exact, 0, 0 },
    { ".note", 6, SHT_NOTE, exact, 0, 0 },
    { ".plt", 5, SHT_PROGBITS, unused, 0, 0 }, // XXX more tests
    { ".preinit_array", 15, SHT_PREINIT_ARRAY, exact, SHF_ALLOC | SHF_WRITE, 0 },
    { ".rela", 5, SHT_RELA, atleast, 0, SHF_ALLOC }, // XXX more tests
    { ".rel", 4, SHT_REL, atleast, 0, SHF_ALLOC }, // XXX more tests
    { ".rodata", 8, SHT_PROGBITS, exact, SHF_ALLOC, 0 },
    { ".rodata1", 9, SHT_PROGBITS, exact, SHF_ALLOC, 0 },
    { ".shstrtab", 10, SHT_STRTAB, exact, 0, 0 },
    { ".strtab", 8, SHT_STRTAB, atleast, 0, SHF_ALLOC }, // XXX more tests
    { ".symtab", 8, SHT_SYMTAB, atleast, 0, SHF_ALLOC }, // XXX more tests
    { ".symtab_shndx", 14, SHT_SYMTAB_SHNDX, atleast, 0, SHF_ALLOC }, // XXX more tests
    { ".tbss", 6, SHT_NOBITS, exact, SHF_ALLOC | SHF_WRITE | SHF_TLS, 0 },
    { ".tdata", 7, SHT_PROGBITS, exact, SHF_ALLOC | SHF_WRITE | SHF_TLS, 0 },
    { ".tdata1", 8, SHT_PROGBITS, exact, SHF_ALLOC | SHF_WRITE | SHF_TLS, 0 },
    { ".text", 6, SHT_PROGBITS, exact, SHF_ALLOC | SHF_EXECINSTR, 0 }
  };
#define nspecial_sections \
  (sizeof (special_sections) / sizeof (special_sections[0]))


static const char *
section_flags_string (GElf_Word flags, char *buf, size_t len)
{
  static const struct
  {
    GElf_Word flag;
    const char *name;
  } known_flags[] =
    {
#define NEWFLAG(name) { SHF_##name, #name }
      NEWFLAG (WRITE),
      NEWFLAG (ALLOC),
      NEWFLAG (EXECINSTR),
      NEWFLAG (MERGE),
      NEWFLAG (STRINGS),
      NEWFLAG (INFO_LINK),
      NEWFLAG (LINK_ORDER),
      NEWFLAG (OS_NONCONFORMING),
      NEWFLAG (GROUP),
      NEWFLAG (TLS)
    };
#undef NEWFLAG
  const size_t nknown_flags = sizeof (known_flags) / sizeof (known_flags[0]);

  char *cp = buf;
  size_t cnt;

  for (cnt = 0; cnt < nknown_flags; ++cnt)
    if (flags & known_flags[cnt].flag)
      {
	if (cp != buf && len > 1)
	  {
	    *cp++ = '|';
	    --len;
	  }

	size_t ncopy = MIN (len - 1, strlen (known_flags[cnt].name));
	cp = mempcpy (cp, known_flags[cnt].name, ncopy);
	len -= ncopy;

	flags ^= known_flags[cnt].flag;
      }

  if (flags != 0 || cp == buf)
    snprintf (cp, len - 1, "%" PRIx64, (uint64_t) flags);

  *cp = '\0';

  return buf;
}


static void
check_versym (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
{
  /* The number of elements in the version symbol table must be the
     same as the number of symbols.  */
  GElf_Shdr symshdr_mem;
  GElf_Shdr *symshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
				     &symshdr_mem);
  if (symshdr == NULL)
    /* The error has already been reported.  */
    return;

  if (symshdr->sh_type != SHT_DYNSYM)
    {
      ERROR (gettext ("\
section [%2d] '%s' refers in sh_link to section [%2d] '%s' which is no dynamic symbol table\n"),
	     idx, section_name (ebl, ehdr, idx),
	     shdr->sh_link, section_name (ebl, ehdr, shdr->sh_link));
      return;
    }

  if (shdr->sh_size / shdr->sh_entsize
      != symshdr->sh_size / symshdr->sh_entsize)
    ERROR (gettext ("\
section [%2d] '%s' has different number of entries than symbol table [%2d] '%s'\n"),
	   idx, section_name (ebl, ehdr, idx),
	   shdr->sh_link, section_name (ebl, ehdr, shdr->sh_link));

  // XXX TODO A lot more tests
  // check value of the fields.  local symbols must have zero entries.
  // nonlocal symbols refer to valid version.  Check that version index
  // in bound.
}


static void
check_sections (Ebl *ebl, GElf_Ehdr *ehdr)
{
  GElf_Shdr shdr_mem;
  GElf_Shdr *shdr;
  size_t cnt;
  bool dot_interp_section = false;

  if (ehdr->e_shoff == 0)
    /* No section header.  */
    return;

  /* Allocate array to count references in section groups.  */
  scnref = (int *) xcalloc (shnum, sizeof (int));

  /* Check the zeroth section first.  It must not have any contents
     and the section header must contain nonzero value at most in the
     sh_size and sh_link fields.  */
  shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
  if (shdr == NULL)
    ERROR (gettext ("cannot get section header of zeroth section\n"));
  else
    {
      if (shdr->sh_name != 0)
	ERROR (gettext ("zeroth section has nonzero name\n"));
      if (shdr->sh_type != 0)
	ERROR (gettext ("zeroth section has nonzero type\n"));
      if (shdr->sh_flags != 0)
	ERROR (gettext ("zeroth section has nonzero flags\n"));
      if (shdr->sh_addr != 0)
	ERROR (gettext ("zeroth section has nonzero address\n"));
      if (shdr->sh_offset != 0)
	ERROR (gettext ("zeroth section has nonzero offset\n"));
      if (shdr->sh_info != 0)
	ERROR (gettext ("zeroth section has nonzero info field\n"));
      if (shdr->sh_addralign != 0)
	ERROR (gettext ("zeroth section has nonzero align value\n"));
      if (shdr->sh_entsize != 0)
	ERROR (gettext ("zeroth section has nonzero entry size value\n"));

      if (shdr->sh_size != 0 && ehdr->e_shnum != 0)
	ERROR (gettext ("\
zeroth section has nonzero size value while ELF header has nonzero shnum value\n"));

      if (shdr->sh_link != 0 && ehdr->e_shstrndx != SHN_XINDEX)
	ERROR (gettext ("\
zeroth section has nonzero link value while ELF header does not signal overflow in shstrndx\n"));
    }

  for (cnt = 1; cnt < shnum; ++cnt)
    {
      Elf_Scn *scn;

      scn = elf_getscn (ebl->elf, cnt);
      shdr = gelf_getshdr (scn, &shdr_mem);
      if (shdr == NULL)
	{
	  ERROR (gettext ("\
cannot get section header for section [%2zu] '%s': %s\n"),
		 cnt, section_name (ebl, ehdr, cnt), elf_errmsg (-1));
	  continue;
	}

      const char *scnname = elf_strptr (ebl->elf, shstrndx, shdr->sh_name);

      if (scnname == NULL)
	ERROR (gettext ("section [%2zu]: invalid name\n"), cnt);
      else
	{
	  /* Check whether it is one of the special sections defined in
	     the gABI.  */
	  size_t s;
	  for (s = 0; s < nspecial_sections; ++s)
	    if (strncmp (scnname, special_sections[s].name,
			 special_sections[s].namelen) == 0)
	      {
		char stbuf1[100];
		char stbuf2[100];
		char stbuf3[100];

		if (shdr->sh_type != special_sections[s].type)
		  ERROR (gettext ("\
section [%2d] '%s' has wrong type: expected %s, is %s\n"),
			 (int) cnt, scnname,
			 ebl_section_type_name (ebl, special_sections[s].type,
						stbuf1, sizeof (stbuf1)),
			 ebl_section_type_name (ebl, shdr->sh_type,
						stbuf2, sizeof (stbuf2)));

		if (special_sections[s].attrflag == exact)
		  {
		    /* Except for the link order and group bit all the
		       other bits should match exactly.  */
		    if ((shdr->sh_flags & ~(SHF_LINK_ORDER | SHF_GROUP))
			!= special_sections[s].attr)
		      ERROR (gettext ("\
section [%2zu] '%s' has wrong flags: expected %s, is %s\n"),
			     cnt, scnname,
			     section_flags_string (special_sections[s].attr,
						   stbuf1, sizeof (stbuf1)),
			     section_flags_string (shdr->sh_flags
						   & ~SHF_LINK_ORDER,
						   stbuf2, sizeof (stbuf2)));
		  }
		else if (special_sections[s].attrflag == atleast)
		  {
		    if ((shdr->sh_flags & special_sections[s].attr)
			!= special_sections[s].attr
			|| ((shdr->sh_flags & ~(SHF_LINK_ORDER | SHF_GROUP
						| special_sections[s].attr
						| special_sections[s].attr2))
			    != 0))
		      ERROR (gettext ("\
section [%2zu] '%s' has wrong flags: expected %s and possibly %s, is %s\n"),
			     cnt, scnname,
			     section_flags_string (special_sections[s].attr,
						   stbuf1, sizeof (stbuf1)),
			     section_flags_string (special_sections[s].attr2,
						   stbuf2, sizeof (stbuf2)),
			     section_flags_string (shdr->sh_flags
						   & ~(SHF_LINK_ORDER
						       | SHF_GROUP),
						   stbuf3, sizeof (stbuf3)));
		  }

		if (strcmp (scnname, ".interp") == 0)
		  {
		    dot_interp_section = true;

		    if (ehdr->e_type == ET_REL)
		      ERROR (gettext ("\
section [%2zu] '%s' present in object file\n"),
			     cnt, scnname);

		    if ((shdr->sh_flags & SHF_ALLOC) != 0
			&& !has_loadable_segment)
		      ERROR (gettext ("\
section [%2zu] '%s' has SHF_ALLOC flag set but there is no loadable segment\n"),
			     cnt, scnname);
		    else if ((shdr->sh_flags & SHF_ALLOC) == 0
			     && has_loadable_segment)
		      ERROR (gettext ("\
section [%2zu] '%s' has SHF_ALLOC flag not set but there are loadable segments\n"),
			     cnt, scnname);
		  }
		else
		  {
		    if (strcmp (scnname, ".symtab_shndx") == 0
			&& ehdr->e_type != ET_REL)
		      ERROR (gettext ("\
section [%2zu] '%s' is extension section index table in non-object file\n"),
			     cnt, scnname);

		    /* These sections must have the SHF_ALLOC flag set iff
		       a loadable segment is available.

		       .relxxx
		       .strtab
		       .symtab
		       .symtab_shndx

		       Check that if there is a reference from the
		       loaded section these sections also have the
		       ALLOC flag set.  */
#if 0
		    // XXX TODO
		    if ((shdr->sh_flags & SHF_ALLOC) != 0
			&& !has_loadable_segment)
		      ERROR (gettext ("\
section [%2zu] '%s' has SHF_ALLOC flag set but there is no loadable segment\n"),
			     cnt, scnname);
		    else if ((shdr->sh_flags & SHF_ALLOC) == 0
			     && has_loadable_segment)
		      ERROR (gettext ("\
section [%2zu] '%s' has SHF_ALLOC flag not set but there are loadable segments\n"),
			     cnt, scnname);
#endif
		  }

		break;
	      }
	}

      if (shdr->sh_entsize != 0 && shdr->sh_size % shdr->sh_entsize)
	ERROR (gettext ("\
section [%2zu] '%s': size not multiple of entry size\n"),
	       cnt, section_name (ebl, ehdr, cnt));

      if (elf_strptr (ebl->elf, shstrndx, shdr->sh_name) == NULL)
	ERROR (gettext ("cannot get section header\n"));

      if (shdr->sh_type >= SHT_NUM
	  && shdr->sh_type != SHT_GNU_LIBLIST
	  && shdr->sh_type != SHT_CHECKSUM
	  && shdr->sh_type != SHT_GNU_verdef
	  && shdr->sh_type != SHT_GNU_verneed
	  && shdr->sh_type != SHT_GNU_versym)
	ERROR (gettext ("unsupported section type %d\n"), (int) shdr->sh_type);

#define ALL_SH_FLAGS (SHF_WRITE | SHF_ALLOC | SHF_EXECINSTR | SHF_MERGE \
		      | SHF_STRINGS | SHF_INFO_LINK | SHF_LINK_ORDER \
		      | SHF_OS_NONCONFORMING | SHF_GROUP | SHF_TLS)
      if (shdr->sh_flags & ~ALL_SH_FLAGS)
	ERROR (gettext ("section [%2zu] '%s' contain unknown flag(s) %d\n"),
	       cnt, section_name (ebl, ehdr, cnt),
	       (int) shdr->sh_flags & ~ALL_SH_FLAGS);
      else if (shdr->sh_flags & SHF_TLS)
	{
	  // XXX Correct?
	  if (shdr->sh_addr != 0 && !gnuld)
	    ERROR (gettext ("\
section [%2zu] '%s': thread-local data sections address not zero\n"),
		   cnt, section_name (ebl, ehdr, cnt));

	  // XXX TODO more tests!?
	}

      if (shdr->sh_link >= shnum)
	ERROR (gettext ("\
section [%2zu] '%s': invalid section reference in link value\n"),
	       cnt, section_name (ebl, ehdr, cnt));

      if (SH_INFO_LINK_P (shdr) && shdr->sh_info >= shnum)
	ERROR (gettext ("\
section [%2zu] '%s': invalid section reference in info value\n"),
	       cnt, section_name (ebl, ehdr, cnt));

      if ((shdr->sh_flags & SHF_MERGE) == 0
	  && (shdr->sh_flags & SHF_STRINGS) != 0
	  && be_strict)
	ERROR (gettext ("\
section [%2zu] '%s': strings flag set without merge flag\n"),
	       cnt, section_name (ebl, ehdr, cnt));

      if ((shdr->sh_flags & SHF_MERGE) != 0 && shdr->sh_entsize == 0)
	ERROR (gettext ("\
section [%2zu] '%s': merge flag set but entry size is zero\n"),
	       cnt, section_name (ebl, ehdr, cnt));

      if (shdr->sh_flags & SHF_GROUP)
	check_scn_group (ebl, ehdr, cnt);

      if (ehdr->e_type != ET_REL && (shdr->sh_flags & SHF_ALLOC) != 0)
	{
	  /* Make sure the section is contained in a loaded segment
	     and that the initialization part matches NOBITS sections.  */
	  int pcnt;
	  GElf_Phdr phdr_mem;
	  GElf_Phdr *phdr;

	  for (pcnt = 0; pcnt < ehdr->e_phnum; ++pcnt)
	    if ((phdr = gelf_getphdr (ebl->elf, pcnt, &phdr_mem)) != NULL
		&& ((phdr->p_type == PT_LOAD
		     && (shdr->sh_flags & SHF_TLS) == 0)
		    || (phdr->p_type == PT_TLS
			&& (shdr->sh_flags & SHF_TLS) != 0))
		&& phdr->p_offset <= shdr->sh_offset
		&& phdr->p_offset + phdr->p_memsz > shdr->sh_offset)
	      {
		/* Found the segment.  */
		if (phdr->p_offset + phdr->p_memsz
		    < shdr->sh_offset + shdr->sh_size)
		  ERROR (gettext ("\
section [%2zu] '%s' not fully contained in segment of program header entry %d\n"),
			 cnt, section_name (ebl, ehdr, cnt), pcnt);

		if (shdr->sh_type == SHT_NOBITS)
		  {
		    if (shdr->sh_offset < phdr->p_offset + phdr->p_filesz)
		      ERROR (gettext ("\
section [%2zu] '%s' has type NOBITS but is read from the file in segment of program header entry %d\n"),
			 cnt, section_name (ebl, ehdr, cnt), pcnt);
		  }
		else
		  {
		    if (shdr->sh_offset >= phdr->p_offset + phdr->p_filesz)
		      ERROR (gettext ("\
section [%2zu] '%s' has not type NOBITS but is not read from the file in segment of program header entry %d\n"),
			 cnt, section_name (ebl, ehdr, cnt), pcnt);
		  }

		break;
	      }

	  if (pcnt == ehdr->e_phnum)
	    ERROR (gettext ("\
section [%2zu] '%s': alloc flag set but section not in any loaded segment\n"),
		   cnt, section_name (ebl, ehdr, cnt));
	}

      if (cnt == shstrndx && shdr->sh_type != SHT_STRTAB)
	ERROR (gettext ("\
section [%2zu] '%s': ELF header says this is the section header string table but type is not SHT_TYPE\n"),
	       cnt, section_name (ebl, ehdr, cnt));

      switch (shdr->sh_type)
	{
	case SHT_SYMTAB:
	case SHT_DYNSYM:
	  check_symtab (ebl, ehdr, cnt);
	  break;

	case SHT_RELA:
	  check_rela (ebl, ehdr, cnt);
	  break;

	case SHT_REL:
	  check_rel (ebl, ehdr, cnt);
	  break;

	case SHT_DYNAMIC:
	  check_dynamic (ebl, ehdr, cnt);
	  break;

	case SHT_SYMTAB_SHNDX:
	  check_symtab_shndx (ebl, ehdr, cnt);
	  break;

	case SHT_HASH:
	  check_hash (ebl, ehdr, cnt);
	  break;

	case SHT_NULL:
	  check_null (ebl, ehdr, shdr, cnt);
	  break;

	case SHT_GROUP:
	  check_group (ebl, ehdr, shdr, cnt);
	  break;

	case SHT_GNU_versym:
	  check_versym (ebl, ehdr, shdr, cnt);
	  break;

	default:
	  /* Nothing.  */
	  break;
	}
    }

  if (has_interp_segment && !dot_interp_section)
    ERROR (gettext ("INTERP program header entry but no .interp section\n"));

  free (scnref);
}


static void
check_note (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Phdr *phdr, int cnt)
{
  if (ehdr->e_type != ET_CORE && ehdr->e_type != ET_REL
      && ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)
    ERROR (gettext ("\
phdr[%d]: no note entries defined for the type of file\n"),
	   cnt);

  char *notemem = gelf_rawchunk (ebl->elf, phdr->p_offset, phdr->p_filesz);

  /* ELF64 files often use note section entries in the 32-bit format.
     The p_align field is set to 8 in case the 64-bit format is used.
     In case the p_align value is 0 or 4 the 32-bit format is
     used.  */
  GElf_Xword align = phdr->p_align == 0 || phdr->p_align == 4 ? 4 : 8;
#define ALIGNED_LEN(len) (((len) + align - 1) & ~(align - 1))

  GElf_Xword idx = 0;
  while (idx < phdr->p_filesz)
    {
      uint64_t namesz;
      uint64_t descsz;
      uint64_t type;
      uint32_t namesz32;
      uint32_t descsz32;

      if (align == 4)
	{
	  uint32_t *ptr = (uint32_t *) (notemem + idx);

	  if ((__BYTE_ORDER == __LITTLE_ENDIAN
	       && ehdr->e_ident[EI_DATA] == ELFDATA2MSB)
	      || (__BYTE_ORDER == __BIG_ENDIAN
		  && ehdr->e_ident[EI_DATA] == ELFDATA2LSB))
	    {
	      namesz32 = namesz = bswap_32 (*ptr);
	      ++ptr;
	      descsz32 = descsz = bswap_32 (*ptr);
	      ++ptr;
	      type = bswap_32 (*ptr);
	    }
	  else
	    {
	      namesz32 = namesz = *ptr++;
	      descsz32 = descsz = *ptr++;
	      type = *ptr;
	    }
	}
      else
	{
	  uint64_t *ptr = (uint64_t *) (notemem + idx);
	  uint32_t *ptr32 = (uint32_t *) (notemem + idx);

	  if ((__BYTE_ORDER == __LITTLE_ENDIAN
	       && ehdr->e_ident[EI_DATA] == ELFDATA2MSB)
	      || (__BYTE_ORDER == __BIG_ENDIAN
		  && ehdr->e_ident[EI_DATA] == ELFDATA2LSB))
	    {
	      namesz = bswap_64 (*ptr);
	      ++ptr;
	      descsz = bswap_64 (*ptr);
	      ++ptr;
	      type = bswap_64 (*ptr);

	      namesz32 = bswap_32 (*ptr32);
	      ++ptr32;
	      descsz32 = bswap_32 (*ptr32);
	    }
	  else
	    {
	      namesz = *ptr++;
	      descsz = *ptr++;
	      type = *ptr;

	      namesz32 = *ptr32++;
	      descsz32 = *ptr32;
	    }
	}

      if (idx + 3 * align > phdr->p_filesz
	  || (idx + 3 * align + ALIGNED_LEN (namesz) + ALIGNED_LEN (descsz)
	      > phdr->p_filesz))
	{
	  if (ehdr->e_ident[EI_CLASS] == ELFCLASS64
	      && idx + 3 * 4 <= phdr->p_filesz
	      && (idx + 3 * 4 + ALIGNED_LEN (namesz32) + ALIGNED_LEN (descsz32)
		  <= phdr->p_filesz))
	    ERROR (gettext ("\
phdr[%d]: note entries probably in form of a 32-bit ELF file\n"), cnt);
	  else
	    ERROR (gettext ("phdr[%d]: extra %zu bytes after last note\n"),
		   cnt, (size_t) (phdr->p_filesz - idx));
	  break;
	}

      /* Make sure it is one of the note types we know about.  */
      if (ehdr->e_type == ET_CORE)
	{
	  switch (type)
	    {
	    case NT_PRSTATUS:
	    case NT_FPREGSET:
	    case NT_PRPSINFO:
	    case NT_TASKSTRUCT:		/* NT_PRXREG on Solaris.  */
	    case NT_PLATFORM:
	    case NT_AUXV:
	    case NT_GWINDOWS:
	    case NT_ASRS:
	    case NT_PSTATUS:
	    case NT_PSINFO:
	    case NT_PRCRED:
	    case NT_UTSNAME:
	    case NT_LWPSTATUS:
	    case NT_LWPSINFO:
	    case NT_PRFPXREG:
	      /* Known type.  */
	      break;

	    default:
	      ERROR (gettext ("\
phdr[%d]: unknown core file note type %" PRIu64 " at offset %" PRIu64 "\n"),
		     cnt, type, idx);
	    }
	}
      else
	{
	  if (type != NT_VERSION)
	    ERROR (gettext ("\
phdr[%d]: unknown object file note type %" PRIu64 " at offset %" PRIu64 "\n"),
		   cnt, type, idx);
	}

      /* Move to the next entry.  */
      idx += 3 * align + ALIGNED_LEN (namesz) + ALIGNED_LEN (descsz);

    }

  gelf_freechunk (ebl->elf, notemem);
}


static void
check_program_header (Ebl *ebl, GElf_Ehdr *ehdr)
{
  if (ehdr->e_phoff == 0)
    return;

  if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN
      && ehdr->e_type != ET_CORE)
    ERROR (gettext ("\
only executables, shared objects, and core files can have program headers\n"));

  int num_pt_interp = 0;
  int num_pt_tls = 0;
  int num_pt_relro = 0;

  for (int cnt = 0; cnt < ehdr->e_phnum; ++cnt)
    {
      GElf_Phdr phdr_mem;
      GElf_Phdr *phdr;

      phdr = gelf_getphdr (ebl->elf, cnt, &phdr_mem);
      if (phdr == NULL)
	{
	  ERROR (gettext ("cannot get program header entry %d: %s\n"),
		 cnt, elf_errmsg (-1));
	  continue;
	}

      if (phdr->p_type >= PT_NUM && phdr->p_type != PT_GNU_EH_FRAME
	  && phdr->p_type != PT_GNU_STACK)
	ERROR (gettext ("\
program header entry %d: unknown program header entry type\n"),
	       cnt);

      if (phdr->p_type == PT_LOAD)
	has_loadable_segment = true;
      else if (phdr->p_type == PT_INTERP)
	{
	  if (++num_pt_interp != 1)
	    {
	      if (num_pt_interp == 2)
		ERROR (gettext ("\
more than one INTERP entry in program header\n"));
	    }
	  has_interp_segment = true;
	}
      else if (phdr->p_type == PT_TLS)
	{
	  if (++num_pt_tls == 2)
	    ERROR (gettext ("more than one TLS entry in program header\n"));
	}
      else if (phdr->p_type == PT_NOTE)
	check_note (ebl, ehdr, phdr, cnt);
      else if (phdr->p_type == PT_DYNAMIC
	       && ehdr->e_type == ET_EXEC && ! has_interp_segment)
	ERROR (gettext ("static executable cannot have dynamic sections\n"));
      else if (phdr->p_type == PT_GNU_RELRO)
	{
	  if (++num_pt_relro == 2)
	    ERROR (gettext ("\
more than one GNU_RELRO entry in program header\n"));
	  else
	    {
	      /* Check that the region is in a writable segment.  */
	      int inner;
	      for (inner = 0; inner < ehdr->e_phnum; ++inner)
		{
		  GElf_Phdr phdr2_mem;
		  GElf_Phdr *phdr2;

		  phdr2 = gelf_getphdr (ebl->elf, cnt, &phdr2_mem);
		  if (phdr2 == NULL)
		    continue;

		  if (phdr2->p_type == PT_LOAD
		      && phdr->p_vaddr >= phdr2->p_vaddr
		      && (phdr->p_vaddr + phdr->p_memsz
			  <= phdr2->p_vaddr + phdr2->p_memsz))
		    {
		      if ((phdr2->p_flags & PF_W) == 0)
			ERROR (gettext ("\
loadable segment GNU_RELRO applies to is not writable\n"));
		      if ((phdr2->p_flags & PF_X) != 0)
			ERROR (gettext ("\
loadable segment GNU_RELRO applies to is executable\n"));
		      break;
		    }
		}

	      if (inner >= ehdr->e_phnum)
		ERROR (gettext ("\
GNU_RELRO segment not contained in a loaded segment\n"));
	    }
	}

      if (phdr->p_filesz > phdr->p_memsz)
	ERROR (gettext ("\
program header entry %d: file size greater than memory size\n"),
	       cnt);

      if (phdr->p_align > 1)
	{
	  if (!powerof2 (phdr->p_align))
	    ERROR (gettext ("\
program header entry %d: alignment not a power of 2\n"), cnt);
	  else if ((phdr->p_vaddr - phdr->p_offset) % phdr->p_align != 0)
	    ERROR (gettext ("\
program header entry %d: file offset and virtual address not module of alignment\n"), cnt);
	}
    }
}


/* Process one file.  */
static void
process_elf_file (Elf *elf, const char *prefix, const char *suffix,
		  const char *fname, size_t size, bool only_one)
{
  /* Reset variables.  */
  ndynamic = 0;

  GElf_Ehdr ehdr_mem;
  GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
  Ebl *ebl;

  /* Print the file name.  */
  if (!only_one)
    {
      if (prefix != NULL)
	printf ("\n%s(%s)%s:\n", prefix, fname, suffix);
      else
	printf ("\n%s:\n", fname);
    }

  if (ehdr == NULL)
    {
      ERROR (gettext ("cannot read ELF header: %s\n"), elf_errmsg (-1));
      return;
    }

  ebl = ebl_openbackend (elf);
  /* If there is no appropriate backend library we cannot test
     architecture and OS specific features.  Any encountered extension
     is an error.  */

  /* Go straight by the gABI, check all the parts in turn.  */
  check_elf_header (ebl, ehdr, size);

  /* Check the program header.  */
  check_program_header (ebl, ehdr);

  /* Next the section headers.  It is OK if there are no section
     headers at all.  */
  check_sections (ebl, ehdr);

  /* Free the resources.  */
  ebl_closebackend (ebl);
}