// -*- Mode: C++ -*-
//
// Copyright (C) 2020 Google, Inc.
//
// This file is part of the GNU Application Binary Interface Generic
// Analysis and Instrumentation Library (libabigail).  This library is
// free software; you can redistribute it and/or modify it under the
// terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 3, or (at your option) any
// later version.

// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Lesser Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this program; see the file COPYING-LGPLV3.  If
// not, see <http://www.gnu.org/licenses/>.

/// @file
///
/// This contains the definitions of the ELF utilities for the dwarf reader.

#include "abg-elf-helpers.h"

#include <elf.h>

#include "abg-tools-utils.h"

namespace abigail
{

namespace elf_helpers
{

/// Convert an elf symbol type (given by the ELF{32,64}_ST_TYPE
/// macros) into an elf_symbol::type value.
///
/// Note that this function aborts when given an unexpected value.
///
/// @param the symbol type value to convert.
///
/// @return the converted value.
elf_symbol::type
stt_to_elf_symbol_type(unsigned char stt)
{
  switch (stt)
    {
    case STT_NOTYPE:
      return elf_symbol::NOTYPE_TYPE;
    case STT_OBJECT:
      return elf_symbol::OBJECT_TYPE;
    case STT_FUNC:
      return elf_symbol::FUNC_TYPE;
    case STT_SECTION:
      return elf_symbol::SECTION_TYPE;
    case STT_FILE:
      return elf_symbol::FILE_TYPE;
    case STT_COMMON:
      return elf_symbol::COMMON_TYPE;
    case STT_TLS:
      return elf_symbol::TLS_TYPE;
    case STT_GNU_IFUNC:
      return elf_symbol::GNU_IFUNC_TYPE;
    default:
      // An unknown value that probably ought to be supported?  Let's
      // abort right here rather than yielding garbage.
      ABG_ASSERT_NOT_REACHED;
    }
}

/// Convert an elf symbol binding (given by the ELF{32,64}_ST_BIND
/// macros) into an elf_symbol::binding value.
///
/// Note that this function aborts when given an unexpected value.
///
/// @param the symbol binding value to convert.
///
/// @return the converted value.
elf_symbol::binding
stb_to_elf_symbol_binding(unsigned char stb)
{
  switch (stb)
    {
    case STB_LOCAL:
      return elf_symbol::LOCAL_BINDING;
    case STB_GLOBAL:
      return elf_symbol::GLOBAL_BINDING;
    case STB_WEAK:
      return elf_symbol::WEAK_BINDING;
    case STB_GNU_UNIQUE:
      return elf_symbol::GNU_UNIQUE_BINDING;
    default:
      ABG_ASSERT_NOT_REACHED;
    }
}

/// Convert an ELF symbol visiblity given by the symbols ->st_other
/// data member as returned by the GELF_ST_VISIBILITY macro into a
/// elf_symbol::visiblity value.
///
/// @param stv the value of the ->st_other data member of the ELF
/// symbol.
///
/// @return the converted elf_symbol::visiblity value.
elf_symbol::visibility
stv_to_elf_symbol_visibility(unsigned char stv)
{
  switch (stv)
    {
    case STV_DEFAULT:
      return elf_symbol::DEFAULT_VISIBILITY;
    case STV_INTERNAL:
      return elf_symbol::INTERNAL_VISIBILITY;
    case STV_HIDDEN:
      return elf_symbol::HIDDEN_VISIBILITY;
    case STV_PROTECTED:
      return elf_symbol::PROTECTED_VISIBILITY;
    default:
      ABG_ASSERT_NOT_REACHED;
    }
}

/// Convert the value of the e_machine field of GElf_Ehdr into a
/// string.  This is to get a string representing the architecture of
/// the elf file at hand.
///
/// @param e_machine the value of GElf_Ehdr::e_machine.
///
/// @return the string representation of GElf_Ehdr::e_machine.
std::string
e_machine_to_string(GElf_Half e_machine)
{
  switch (e_machine)
    {
    case EM_NONE:
      return "elf-no-arch";
    case EM_M32:
      return "elf-att-we-32100";
    case EM_SPARC:
      return "elf-sun-sparc";
    case EM_386:
      return "elf-intel-80386";
    case EM_68K:
      return "elf-motorola-68k";
    case EM_88K:
      return "elf-motorola-88k";
    case EM_860:
      return "elf-intel-80860";
    case EM_MIPS:
      return "elf-mips-r3000-be";
    case EM_S370:
      return "elf-ibm-s370";
    case EM_MIPS_RS3_LE:
      return "elf-mips-r3000-le";
    case EM_PARISC:
      return "elf-hp-parisc";
    case EM_VPP500:
      return "elf-fujitsu-vpp500";
    case EM_SPARC32PLUS:
      return "elf-sun-sparc-v8plus";
    case EM_960:
      return "elf-intel-80960";
    case EM_PPC:
      return "elf-powerpc";
    case EM_PPC64:
      return "elf-powerpc-64";
    case EM_S390:
      return "elf-ibm-s390";
    case EM_V800:
      return "elf-nec-v800";
    case EM_FR20:
      return "elf-fujitsu-fr20";
    case EM_RH32:
      return "elf-trw-rh32";
    case EM_RCE:
      return "elf-motorola-rce";
    case EM_ARM:
      return "elf-arm";
    case EM_FAKE_ALPHA:
      return "elf-digital-alpha";
    case EM_SH:
      return "elf-hitachi-sh";
    case EM_SPARCV9:
      return "elf-sun-sparc-v9-64";
    case EM_TRICORE:
      return "elf-siemens-tricore";
    case EM_ARC:
      return "elf-argonaut-risc-core";
    case EM_H8_300:
      return "elf-hitachi-h8-300";
    case EM_H8_300H:
      return "elf-hitachi-h8-300h";
    case EM_H8S:
      return "elf-hitachi-h8s";
    case EM_H8_500:
      return "elf-hitachi-h8-500";
    case EM_IA_64:
      return "elf-intel-ia-64";
    case EM_MIPS_X:
      return "elf-stanford-mips-x";
    case EM_COLDFIRE:
      return "elf-motorola-coldfire";
    case EM_68HC12:
      return "elf-motorola-68hc12";
    case EM_MMA:
      return "elf-fujitsu-mma";
    case EM_PCP:
      return "elf-siemens-pcp";
    case EM_NCPU:
      return "elf-sony-ncpu";
    case EM_NDR1:
      return "elf-denso-ndr1";
    case EM_STARCORE:
      return "elf-motorola-starcore";
    case EM_ME16:
      return "elf-toyota-me16";
    case EM_ST100:
      return "elf-stm-st100";
    case EM_TINYJ:
      return "elf-alc-tinyj";
    case EM_X86_64:
      return "elf-amd-x86_64";
    case EM_PDSP:
      return "elf-sony-pdsp";
    case EM_FX66:
      return "elf-siemens-fx66";
    case EM_ST9PLUS:
      return "elf-stm-st9+";
    case EM_ST7:
      return "elf-stm-st7";
    case EM_68HC16:
      return "elf-motorola-68hc16";
    case EM_68HC11:
      return "elf-motorola-68hc11";
    case EM_68HC08:
      return "elf-motorola-68hc08";
    case EM_68HC05:
      return "elf-motorola-68hc05";
    case EM_SVX:
      return "elf-sg-svx";
    case EM_ST19:
      return "elf-stm-st19";
    case EM_VAX:
      return "elf-digital-vax";
    case EM_CRIS:
      return "elf-axis-cris";
    case EM_JAVELIN:
      return "elf-infineon-javelin";
    case EM_FIREPATH:
      return "elf-firepath";
    case EM_ZSP:
      return "elf-lsi-zsp";
    case EM_MMIX:
      return "elf-don-knuth-mmix";
    case EM_HUANY:
      return "elf-harvard-huany";
    case EM_PRISM:
      return "elf-sitera-prism";
    case EM_AVR:
      return "elf-atmel-avr";
    case EM_FR30:
      return "elf-fujistu-fr30";
    case EM_D10V:
      return "elf-mitsubishi-d10v";
    case EM_D30V:
      return "elf-mitsubishi-d30v";
    case EM_V850:
      return "elf-nec-v850";
    case EM_M32R:
      return "elf-mitsubishi-m32r";
    case EM_MN10300:
      return "elf-matsushita-mn10300";
    case EM_MN10200:
      return "elf-matsushita-mn10200";
    case EM_PJ:
      return "elf-picojava";
    case EM_OPENRISC:
      return "elf-openrisc-32";
    case EM_ARC_A5:
      return "elf-arc-a5";
    case EM_XTENSA:
      return "elf-tensilica-xtensa";

#ifdef HAVE_EM_AARCH64_MACRO
    case EM_AARCH64:
      return "elf-arm-aarch64";
#endif

#ifdef HAVE_EM_TILEPRO_MACRO
    case EM_TILEPRO:
      return "elf-tilera-tilepro";
#endif

#ifdef HAVE_EM_TILEGX_MACRO
    case EM_TILEGX:
      return "elf-tilera-tilegx";
#endif

    case EM_NUM:
      return "elf-last-arch-number";
    case EM_ALPHA:
      return "elf-non-official-alpha";
    default:
      {
	std::ostringstream o;
	o << "elf-unknown-arch-value-" << e_machine;
	return o.str();
      }
    }
}

/// Find and return a section by its name and its type.
///
/// @param elf_handle the elf handle to use.
///
/// @param name the name of the section.
///
/// @param section_type the type of the section.  This is the
/// Elf32_Shdr::sh_type (or Elf64_Shdr::sh_type) data member.
/// Examples of values of this parameter are SHT_PROGBITS or SHT_NOBITS.
///
/// @return the section found, nor nil if none was found.
Elf_Scn*
find_section(Elf* elf_handle, const std::string& name, Elf64_Word section_type)
{
  size_t section_header_string_index = 0;
  if (elf_getshdrstrndx (elf_handle, &section_header_string_index) < 0)
    return 0;

  Elf_Scn* section = 0;
  GElf_Shdr header_mem, *header;
  while ((section = elf_nextscn(elf_handle, section)) != 0)
    {
      header = gelf_getshdr(section, &header_mem);
      if (header == NULL || header->sh_type != section_type)
      continue;

      const char* section_name =
	elf_strptr(elf_handle, section_header_string_index, header->sh_name);
      if (section_name && name == section_name)
	return section;
    }

  return 0;
}

/// Find the symbol table.
///
/// If we are looking at a relocatable or executable file, this
/// function will return the .symtab symbol table (of type
/// SHT_SYMTAB).  But if we are looking at a DSO it returns the
/// .dynsym symbol table (of type SHT_DYNSYM).
///
/// @param elf_handle the elf handle to consider.
///
/// @param symtab the symbol table found.
///
/// @return the symbol table section
Elf_Scn*
find_symbol_table_section(Elf* elf_handle)
{
  Elf_Scn* section = 0, *dynsym = 0, *sym_tab = 0;
  while ((section = elf_nextscn(elf_handle, section)) != 0)
    {
      GElf_Shdr header_mem, *header;
      header = gelf_getshdr(section, &header_mem);
      if (header->sh_type == SHT_DYNSYM)
	dynsym = section;
      else if (header->sh_type == SHT_SYMTAB)
	sym_tab = section;
    }

  if (dynsym || sym_tab)
    {
      GElf_Ehdr eh_mem;
      GElf_Ehdr* elf_header = gelf_getehdr(elf_handle, &eh_mem);
      if (elf_header->e_type == ET_REL
	  || elf_header->e_type == ET_EXEC)
	return sym_tab ? sym_tab : dynsym;
      else
	return dynsym ? dynsym : sym_tab;
    }
  return NULL;
}

/// Find the index (in the section headers table) of the symbol table
/// section.
///
/// If we are looking at a relocatable or executable file, this
/// function will return the index for the .symtab symbol table (of
/// type SHT_SYMTAB).  But if we are looking at a DSO it returns the
/// index for the .dynsym symbol table (of type SHT_DYNSYM).
///
/// @param elf_handle the elf handle to use.
///
/// @param symtab_index the index of the symbol_table, that was found.
///
/// @return true iff the symbol table section index was found.
bool
find_symbol_table_section_index(Elf* elf_handle, size_t& symtab_index)
{
  Elf_Scn* section = find_symbol_table_section(elf_handle);

  if (!section)
    return false;

  symtab_index = elf_ndxscn(section);
  return true;
}

/// Get the offset offset of the hash table section.
///
/// @param elf_handle the elf handle to use.
///
/// @param ht_section_offset this is set to the resulting offset
/// of the hash table section.  This is set iff the function returns true.
///
/// @param symtab_section_offset the offset of the section of the
/// symbol table the hash table refers to.
hash_table_kind
find_hash_table_section_index(Elf*	elf_handle,
			      size_t&	ht_section_index,
			      size_t&	symtab_section_index)
{
  if (!elf_handle)
    return NO_HASH_TABLE_KIND;

  GElf_Shdr header_mem, *section_header;
  bool found_sysv_ht = false, found_gnu_ht = false;
  for (Elf_Scn* section = elf_nextscn(elf_handle, 0);
       section != 0;
       section = elf_nextscn(elf_handle, section))
    {
      section_header= gelf_getshdr(section, &header_mem);
      if (section_header->sh_type != SHT_HASH
	  && section_header->sh_type != SHT_GNU_HASH)
	continue;

      ht_section_index = elf_ndxscn(section);
      symtab_section_index = section_header->sh_link;

      if (section_header->sh_type == SHT_HASH)
	found_sysv_ht = true;
      else if (section_header->sh_type == SHT_GNU_HASH)
	found_gnu_ht = true;
    }

  if (found_gnu_ht)
    return GNU_HASH_TABLE_KIND;
  else if (found_sysv_ht)
    return SYSV_HASH_TABLE_KIND;
  else
    return NO_HASH_TABLE_KIND;
}

/// Find and return the .text section.
///
/// @param elf_handle the elf handle to use.
///
/// @return the .text section found.
Elf_Scn*
find_text_section(Elf* elf_handle)
{return find_section(elf_handle, ".text", SHT_PROGBITS);}

/// Find and return the .bss section.
///
/// @param elf_handle.
///
/// @return the .bss section found.
Elf_Scn*
find_bss_section(Elf* elf_handle)
{return find_section(elf_handle, ".bss", SHT_NOBITS);}

/// Find and return the .rodata section.
///
/// @param elf_handle.
///
/// @return the .rodata section found.
Elf_Scn*
find_rodata_section(Elf* elf_handle)
{return find_section(elf_handle, ".rodata", SHT_PROGBITS);}

/// Find and return the .data section.
///
/// @param elf_handle the elf handle to use.
///
/// @return the .data section found.
Elf_Scn*
find_data_section(Elf* elf_handle)
{return find_section(elf_handle, ".data", SHT_PROGBITS);}

/// Find and return the .data1 section.
///
/// @param elf_handle the elf handle to use.
///
/// @return the .data1 section found.
Elf_Scn*
find_data1_section(Elf* elf_handle)
{return find_section(elf_handle, ".data1", SHT_PROGBITS);}

/// Return the "Official Procedure descriptors section."  This
/// section is named .opd, and is usually present only on PPC64
/// ELFv1 binaries.
///
/// @param elf_handle the elf handle to consider.
///
/// @return the .opd section, if found.  Return nil otherwise.
Elf_Scn*
find_opd_section(Elf* elf_handle)
{return find_section(elf_handle, ".opd", SHT_PROGBITS);}

/// Return the SHT_GNU_versym, SHT_GNU_verdef and SHT_GNU_verneed
/// sections that are involved in symbol versionning.
///
/// @param elf_handle the elf handle to use.
///
/// @param versym_section the SHT_GNU_versym section found.  If the
/// section wasn't found, this is set to nil.
///
/// @param verdef_section the SHT_GNU_verdef section found.  If the
/// section wasn't found, this is set to nil.
///
/// @param verneed_section the SHT_GNU_verneed section found.  If the
/// section wasn't found, this is set to nil.
///
/// @return true iff at least one of the sections where found.
bool
get_symbol_versionning_sections(Elf*		elf_handle,
				Elf_Scn*&	versym_section,
				Elf_Scn*&	verdef_section,
				Elf_Scn*&	verneed_section)
{
  Elf_Scn* section = NULL;
  GElf_Shdr mem;
  Elf_Scn* versym = NULL, *verdef = NULL, *verneed = NULL;

  while ((section = elf_nextscn(elf_handle, section)) != NULL)
    {
      GElf_Shdr* h = gelf_getshdr(section, &mem);
      if (h->sh_type == SHT_GNU_versym)
	versym = section;
      else if (h->sh_type == SHT_GNU_verdef)
	verdef = section;
      else if (h->sh_type == SHT_GNU_verneed)
	verneed = section;
    }

  if (versym || verdef || verneed)
    {
      // At least one the versionning sections was found.  Return it.
      versym_section = versym;
      verdef_section = verdef;
      verneed_section = verneed;
      return true;
    }

  return false;
}

/// Return the __ksymtab section of a linux kernel ELF file (either
/// a vmlinux binary or a kernel module).
///
/// @param elf_handle the elf handle to consider.
///
/// @return the __ksymtab section if found, nil otherwise.
Elf_Scn*
find_ksymtab_section(Elf* elf_handle)
{return find_section(elf_handle, "__ksymtab", SHT_PROGBITS);}

/// Return the __ksymtab_gpl section of a linux kernel ELF file (either
/// a vmlinux binary or a kernel module).
///
/// @param elf_handle the elf handle to consider.
///
/// @return the __ksymtab section if found, nil otherwise.
Elf_Scn*
find_ksymtab_gpl_section(Elf* elf_handle)
{return find_section(elf_handle, "__ksymtab_gpl", SHT_PROGBITS);}

/// Find the __ksymtab_strings section of a Linux kernel binary.
///
/// @param elf_handle the elf handle to use.
///
/// @return the find_ksymtab_strings_section of the linux kernel
/// binary denoted by @p elf_handle, or nil if such a section could
/// not be found.
Elf_Scn*
find_ksymtab_strings_section(Elf *elf_handle)
{
  if (is_linux_kernel(elf_handle))
    return find_section(elf_handle, "__ksymtab_strings", SHT_PROGBITS);
  return 0;
}

/// Return the .rel{a,} section corresponding to a given section.
///
/// @param elf_handle the elf handle to consider.
///
/// @param target_section the section to search the relocation section for
///
/// @return the .rel{a,} section if found, null otherwise.
Elf_Scn*
find_relocation_section(Elf* elf_handle, Elf_Scn* target_section)
{
  if (target_section)
    {
      // the relo section we are searching for has this index as sh_info
      size_t target_index = elf_ndxscn(target_section);

      // now iterate over all the sections, look for relocation sections and
      // find the one that points to the section we are searching for
      Elf_Scn*	section = 0;
      GElf_Shdr header_mem, *header;
      while ((section = elf_nextscn(elf_handle, section)) != 0)
	{
	  header = gelf_getshdr(section, &header_mem);
	  if (header == NULL
	      || (header->sh_type != SHT_RELA && header->sh_type != SHT_REL))
	    continue;

	  if (header->sh_info == target_index)
	    return section;
	}
    }
  return NULL;
}

/// Get the version definition (from the SHT_GNU_verdef section) of a
/// given symbol represented by a pointer to GElf_Versym.
///
/// @param elf_hande the elf handle to use.
///
/// @param versym the symbol to get the version definition for.
///
/// @param verdef_section the SHT_GNU_verdef section.
///
/// @param version the resulting version definition.  This is set iff
/// the function returns true.
///
/// @return true upon successful completion, false otherwise.
bool
get_version_definition_for_versym(Elf*			 elf_handle,
				  GElf_Versym*		 versym,
				  Elf_Scn*		 verdef_section,
				  elf_symbol::version&	 version)
{
  Elf_Data* verdef_data = elf_getdata(verdef_section, NULL);
  GElf_Verdef verdef_mem;
  GElf_Verdef* verdef = gelf_getverdef(verdef_data, 0, &verdef_mem);
  size_t vd_offset = 0;

  for (;; vd_offset += verdef->vd_next)
    {
      for (;verdef != 0;)
	{
	  if (verdef->vd_ndx == (*versym & 0x7fff))
	    // Found the version of the symbol.
	    break;
	  vd_offset += verdef->vd_next;
	  verdef = (verdef->vd_next == 0
		    ? 0
		    : gelf_getverdef(verdef_data, vd_offset, &verdef_mem));
	}

      if (verdef != 0)
	{
	  GElf_Verdaux verdaux_mem;
	  GElf_Verdaux *verdaux = gelf_getverdaux(verdef_data,
						  vd_offset + verdef->vd_aux,
						  &verdaux_mem);
	  GElf_Shdr header_mem;
	  GElf_Shdr* verdef_section_header = gelf_getshdr(verdef_section,
							  &header_mem);
	  size_t verdef_stridx = verdef_section_header->sh_link;
	  version.str(elf_strptr(elf_handle, verdef_stridx, verdaux->vda_name));
	  if (*versym & 0x8000)
	    version.is_default(false);
	  else
	    version.is_default(true);
	  return true;
	}
      if (!verdef || verdef->vd_next == 0)
	break;
    }
  return false;
}

/// Get the version needed (from the SHT_GNU_verneed section) to
/// resolve an undefined symbol represented by a pointer to
/// GElf_Versym.
///
/// @param elf_hande the elf handle to use.
///
/// @param versym the symbol to get the version definition for.
///
/// @param verneed_section the SHT_GNU_verneed section.
///
/// @param version the resulting version definition.  This is set iff
/// the function returns true.
///
/// @return true upon successful completion, false otherwise.
bool
get_version_needed_for_versym(Elf*			elf_handle,
			      GElf_Versym*		versym,
			      Elf_Scn*			verneed_section,
			      elf_symbol::version&	version)
{
  if (versym == 0 || elf_handle == 0 || verneed_section == 0)
    return false;

  size_t vn_offset = 0;
  Elf_Data* verneed_data = elf_getdata(verneed_section, NULL);
  GElf_Verneed verneed_mem;
  GElf_Verneed* verneed = gelf_getverneed(verneed_data, 0, &verneed_mem);

  for (;verneed; vn_offset += verneed->vn_next)
    {
      size_t vna_offset = vn_offset;
      GElf_Vernaux vernaux_mem;
      GElf_Vernaux *vernaux = gelf_getvernaux(verneed_data,
					      vn_offset + verneed->vn_aux,
					      &vernaux_mem);
      for (;vernaux != 0 && verneed;)
	{
	  if (vernaux->vna_other == *versym)
	    // Found the version of the symbol.
	    break;
	  vna_offset += verneed->vn_next;
	  verneed = (verneed->vn_next == 0
		     ? 0
		     : gelf_getverneed(verneed_data, vna_offset, &verneed_mem));
	}

      if (verneed != 0 && vernaux != 0 && vernaux->vna_other == *versym)
	{
	  GElf_Shdr header_mem;
	  GElf_Shdr* verneed_section_header = gelf_getshdr(verneed_section,
							   &header_mem);
	  size_t verneed_stridx = verneed_section_header->sh_link;
	  version.str(elf_strptr(elf_handle,
				 verneed_stridx,
				 vernaux->vna_name));
	  if (*versym & 0x8000)
	    version.is_default(false);
	  else
	    version.is_default(true);
	  return true;
	}

      if (!verneed || verneed->vn_next == 0)
	break;
    }
  return false;
}

/// Return the version for a symbol that is at a given index in its
/// SHT_SYMTAB section.
///
/// @param elf_handle the elf handle to use.
///
/// @param symbol_index the index of the symbol to consider.
///
/// @param get_def_version if this is true, it means that that we want
/// the version for a defined symbol; in that case, the version is
/// looked for in a section of type SHT_GNU_verdef.  Otherwise, if
/// this parameter is false, this means that we want the version for
/// an undefined symbol; in that case, the version is the needed one
/// for the symbol to be resolved; so the version is looked fo in a
/// section of type SHT_GNU_verneed.
///
/// @param version the version found for symbol at @p symbol_index.
///
/// @return true iff a version was found for symbol at index @p
/// symbol_index.
bool
get_version_for_symbol(Elf*			elf_handle,
		       size_t			symbol_index,
		       bool			get_def_version,
		       elf_symbol::version&	version)
{
  Elf_Scn *versym_section = NULL,
    *verdef_section = NULL,
    *verneed_section = NULL;

  if (!get_symbol_versionning_sections(elf_handle,
				       versym_section,
				       verdef_section,
				       verneed_section))
    return false;

  GElf_Versym versym_mem;
  Elf_Data* versym_data = (versym_section)
    ? elf_getdata(versym_section, NULL)
    : NULL;
  GElf_Versym* versym = (versym_data)
    ? gelf_getversym(versym_data, symbol_index, &versym_mem)
    : NULL;

  if (versym == 0 || *versym <= 1)
    // I got these value from the code of readelf.c in elfutils.
    // Apparently, if the symbol version entry has these values, the
    // symbol must be discarded. This is not documented in the
    // official specification.
    return false;

  if (get_def_version)
    {
      if (*versym == 0x8001)
	// I got this value from the code of readelf.c in elfutils
	// too.  It's not really documented in the official
	// specification.
	return false;

      if (verdef_section
	  && get_version_definition_for_versym(elf_handle, versym,
					       verdef_section, version))
	return true;
    }
  else
    {
      if (verneed_section
	  && get_version_needed_for_versym(elf_handle, versym,
					   verneed_section, version))
	return true;
    }

  return false;
}

/// Test if the architecture of the current binary is ppc64.
///
/// @param elf_handle the ELF handle to consider.
///
/// @return true iff the architecture of the current binary is ppc64.
bool
architecture_is_ppc64(Elf* elf_handle)
{
  GElf_Ehdr  eh_mem;
  GElf_Ehdr* elf_header = gelf_getehdr(elf_handle, &eh_mem);
  return (elf_header && elf_header->e_machine == EM_PPC64);
}

/// Test if the endianness of the current binary is Big Endian.
///
/// https://en.wikipedia.org/wiki/Endianness.
///
/// @param elf_handle the ELF handle to consider.
///
/// @return true iff the current binary is Big Endian.
bool
architecture_is_big_endian(Elf* elf_handle)
{
  GElf_Ehdr  elf_header;
  gelf_getehdr(elf_handle, &elf_header);

  bool is_big_endian = (elf_header.e_ident[EI_DATA] == ELFDATA2MSB);

  if (!is_big_endian)
    ABG_ASSERT(elf_header.e_ident[EI_DATA] == ELFDATA2LSB);

  return is_big_endian;
}

/// Test if the ELF binary denoted by a given ELF handle is a Linux
/// Kernel Module.
///
/// @param elf_handle the ELF handle to consider.
///
/// @return true iff the binary denoted by @p elf_handle is a Linux
/// kernel module.
bool
is_linux_kernel_module(Elf *elf_handle)
{
  return (find_section(elf_handle, ".modinfo", SHT_PROGBITS)
	  && find_section(elf_handle,
			  ".gnu.linkonce.this_module",
			  SHT_PROGBITS));
}

/// Test if the ELF binary denoted by a given ELF handle is a Linux
/// Kernel binary (either vmlinux or a kernel module).
///
/// @param elf_handle the ELF handle to consider.
///
/// @return true iff the binary denoted by @p elf_handle is a Linux
/// kernel binary
bool
is_linux_kernel(Elf *elf_handle)
{
  return (find_section(elf_handle,
		       "__ksymtab_strings",
		       SHT_PROGBITS)
	  || is_linux_kernel_module(elf_handle));
}

/// Get the address at which a given binary is loaded in memory.
///
/// @param elf_handle the elf handle for the binary to consider.
///
/// @param load_address the address where the binary is loaded.  This
/// is set by the function iff it returns true.
///
/// @return true if the function could get the binary load address
/// and assign @p load_address to it.
bool
get_binary_load_address(Elf* elf_handle, GElf_Addr& load_address)
{
  GElf_Ehdr elf_header;
  gelf_getehdr(elf_handle, &elf_header);
  size_t num_segments = elf_header.e_phnum;
  GElf_Phdr *program_header = NULL;
  GElf_Addr result;
  bool found_loaded_segment = false;
  GElf_Phdr ph_mem;

  for (unsigned i = 0; i < num_segments; ++i)
    {
      program_header = gelf_getphdr(elf_handle, i, &ph_mem);
      if (program_header && program_header->p_type == PT_LOAD)
	{
	  if (!found_loaded_segment)
	    {
	      result = program_header->p_vaddr;
	      found_loaded_segment = true;
	    }

	  if (program_header->p_vaddr < result)
	    // The resulting load address we want is the lowest
	    // load address of all the loaded segments.
	    result = program_header->p_vaddr;
	}
    }

  if (found_loaded_segment)
    {
      load_address = result;
      return true;
    }
  return false;
}

/// Return the size of a word for the current architecture.
///
/// @param elf_handle the ELF handle to consider.
///
/// @return the size of a word.
unsigned char
get_architecture_word_size(Elf* elf_handle)
{
  unsigned char word_size = 0;
  GElf_Ehdr	elf_header;
  gelf_getehdr(elf_handle, &elf_header);
  if (elf_header.e_ident[EI_CLASS] == ELFCLASS32)
    word_size = 4;
  else if (elf_header.e_ident[EI_CLASS] == ELFCLASS64)
    word_size = 8;
  else
    ABG_ASSERT_NOT_REACHED;
  return word_size;
}

/// Test if the elf file being read is an executable.
///
/// @param elf_handle the ELF handle to consider.
///
/// @return true iff the elf file being read is an / executable.
bool
is_executable(Elf* elf_handle)
{
  GElf_Ehdr  elf_header;
  gelf_getehdr(elf_handle, &elf_header);
  return elf_header.e_type == ET_EXEC;
}

/// Test if the elf file being read is a dynamic shared / object.
///
/// @param elf_handle the ELF handle to consider.
///
/// @return true iff the elf file being read is a / dynamic shared object.
bool
is_dso(Elf* elf_handle)
{
  GElf_Ehdr  elf_header;
  gelf_getehdr(elf_handle, &elf_header);
  return elf_header.e_type == ET_DYN;
}

/// Translate a section-relative symbol address (i.e, symbol value)
/// into an absolute symbol address by adding the address of the
/// section the symbol belongs to, to the address value.
///
/// This is useful when looking at symbol values coming from
/// relocatable files (of ET_REL kind).  If the binary is not
/// ET_REL, then the function does nothing and returns the input
/// address unchanged.
///
/// @param elf_handle the elf handle for the binary to consider.
///
/// @param sym the symbol whose address to possibly needs to be
/// translated.
///
/// @return the section-relative address, translated into an
/// absolute address, if @p sym is from an ET_REL binary.
/// Otherwise, return the address of @p sym, unchanged.
GElf_Addr
maybe_adjust_et_rel_sym_addr_to_abs_addr(Elf* elf_handle, GElf_Sym* sym)
{
  Elf_Scn*  symbol_section = elf_getscn(elf_handle, sym->st_shndx);
  GElf_Addr addr = sym->st_value;

  if (!symbol_section)
    return addr;

  GElf_Ehdr elf_header;
  if (!gelf_getehdr(elf_handle, &elf_header))
    return addr;

  if (elf_header.e_type != ET_REL)
    return addr;

  GElf_Shdr section_header;
  if (!gelf_getshdr(symbol_section, &section_header))
    return addr;

  return addr + section_header.sh_addr;
}

} // end namespace elf_helpers
} // end namespace abigail
