#!/usr/bin/perl -w

# A simple toy script displaying the contents of an ELF file.

# (c) 2015-2019 - Xavier Guerrin
# This program is free software. It comes without any warranty, to
# the extent permitted by applicable law. You can redistribute it
# and/or modify it under the terms of the Do What The Fuck You Want
# To Public License, Version 2, as published by Sam Hocevar. See
# http://www.wtfpl.net/ for more details.

# About formats implemented:

# System V Application Binary Interface:
#   http://www.sco.com/developers/gabi/latest/contents.html
# System V Application Binary Interface, Intel386 Architecture Processor Supplement:
#   http://refspecs.linuxfoundation.org/elf/abi386-4.pdf
# System V Application Binary Interface, AMD64 Architecture Processor Supplement:
#    http://refspecs.linux-foundation.org/elf/x86_64-abi-0.98.pdf
# Linux Standard Base Core Specification, Generic Part:
#    http://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/tocobjformat.html
# Linux Standard Base Core Specification for X86-64:
#    http://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-AMD64/LSB-Core-AMD64/tocobjformat.html
# Oracle Solaris OS: Linker and Libraries Guide
#    http://docs.oracle.com/cd/E18752_01/pdf/817-1984.pdf
# GNU Hash ELF Sections by Ali Bahrami
#    https://blogs.oracle.com/ali/entry/gnu_hash_elf_sections

# About the naming of functions: this script uses camel case for functions.
# I wish to quote a friend of mine about that:
#   < gradator> c'est d'la merde le CamelCase
# That's it.

use strict;
use Fcntl;
use Term::ANSIColor qw(coloralias colored);
use Data::Dumper;

# Constants
use constant {
	UNSIGNED => 0,
	SIGNED => 1,
	LITTLE_ENDIAN => 0,
	BIG_ENDIAN => 1,
	ELFCLASS32 => 1,
	ELFCLASS64 => 2,
	R_FIELD_NONE => 0,
	R_FIELD_WORD8 => 8,
	R_FIELD_WORD16 => 16,
	R_FIELD_WORD32 => 32,
	R_FIELD_WORD64 => 64,
};

# ELF data types
# Notes:
#   - alignment was not included as it always equals size.
#   - see http://perldoc.perl.org/constant.html#CAVEATS if you wonder about the
#     parenthesis after ELFCLASS constants.
our $elf_data_types = {
	# Figure 4-2: Data Types
	ELFCLASS32() => {
		'Addr'   => { 'size' => 4, 'formatter' => '0x%08X',  'signed' => UNSIGNED },
		'Half'   => { 'size' => 2, 'formatter' => '0x%04X',  'signed' => UNSIGNED },
		'Off'    => { 'size' => 4, 'formatter' => '0x%08X',  'signed' => UNSIGNED },
		'Sword'  => { 'size' => 4, 'formatter' => '0x%08X',  'signed' =>   SIGNED },
		'Word'   => { 'size' => 4, 'formatter' => '0x%08X',  'signed' => UNSIGNED },
		'char'   => { 'size' => 1, 'formatter' => '%d',      'signed' => UNSIGNED },
	},
	ELFCLASS64() => {
		'Addr'   => { 'size' => 8, 'formatter' => '0x%016X', 'signed' => UNSIGNED },
		'Off'    => { 'size' => 8, 'formatter' => '0x%016X', 'signed' => UNSIGNED },
		'Half'   => { 'size' => 2, 'formatter' => '0x%04X',  'signed' => UNSIGNED },
		'Word'   => { 'size' => 4, 'formatter' => '0x%08X',  'signed' => UNSIGNED },
		'Sword'  => { 'size' => 4, 'formatter' => '0x%08X',  'signed' =>   SIGNED },
		'Xword'  => { 'size' => 8, 'formatter' => '0x%016X', 'signed' => UNSIGNED },
		'Sxword' => { 'size' => 8, 'formatter' => '0x%016X', 'signed' =>   SIGNED },
		'char'   => { 'size' => 1, 'formatter' => '%d',      'signed' => UNSIGNED },
	},
};

our $elf_structures = {
	'e_ident' => {
		'ordered_entries' => [
			'EI_CLASS',
			'EI_DATA',
			'EI_VERSION',
			'EI_OSABI',
			'EI_ABIVERSION',
		],
		'EI_CLASS' => 'char',
		'EI_DATA' => 'char',
		'EI_VERSION' => 'char',
		'EI_OSABI' => 'char',
		'EI_ABIVERSION' => 'char',
	},
	# Figure 4-3: ELF Header
	'file_header' => {
		'ordered_entries' => [
			'e_type',
			'e_machine',
			'e_version',
			'e_entry',
			'e_phoff',
			'e_shoff',
			'e_flags',
			'e_ehsize',
			'e_phentsize',
			'e_phnum',
			'e_shentsize',
			'e_shnum',
			'e_shstrndx',
		],
		'e_type' => 'Half',
		'e_machine' => 'Half',
		'e_version' => 'Word',
		'e_entry' => 'Addr',
		'e_phoff' => 'Off',
		'e_shoff' => 'Off',
		'e_flags' => 'Word',
		'e_ehsize' => 'Half',
		'e_phentsize' => 'Half',
		'e_phnum' => 'Half',
		'e_shentsize' => 'Half',
		'e_shnum' => 'Half',
		'e_shstrndx' => 'Half',
	},
	# Figure 3. ELF-64 Section Header
	'section_header' => {
		ELFCLASS32() => {
			'ordered_entries' => [
				'sh_name',
				'sh_type',
				'sh_flags',
				'sh_addr',
				'sh_offset',
				'sh_size',
				'sh_link',
				'sh_info',
				'sh_addralign',
				'sh_entsize',
			],
			'sh_name' => 'Word',
			'sh_type' => 'Word',
			'sh_flags' => 'Word',
			'sh_addr' => 'Addr',
			'sh_offset' => 'Off',
			'sh_link' => 'Word',
			'sh_size' => 'Word',
			'sh_info' => 'Word',
			'sh_addralign' => 'Word',
			'sh_entsize' => 'Word',
		},
		ELFCLASS64() => {
			'ordered_entries' => [
				'sh_name',
				'sh_type',
				'sh_flags',
				'sh_addr',
				'sh_offset',
				'sh_size',
				'sh_link',
				'sh_info',
				'sh_addralign',
				'sh_entsize',
			],
			'sh_name' => 'Word',
			'sh_type' => 'Word',
			'sh_flags' => 'Xword',
			'sh_addr' => 'Addr',
			'sh_offset' => 'Off',
			'sh_size' => 'Xword',
			'sh_link' => 'Word',
			'sh_info' => 'Word',
			'sh_addralign' => 'Xword',
			'sh_entsize' => 'Xword',
		},
	},
	'symbol_table' => {
		ELFCLASS32() => {
			'ordered_entries' => [
				'st_name',
				'st_value',
				'st_size',
				'st_info',
				'st_other',
				'st_shndx',
			],
			'st_name' => 'Word',
			'st_value' => 'Addr',
			'st_size' => 'Word',
			'st_info' => 'char',
			'st_other' => 'char',
			'st_shndx' => 'Half',
		},
		ELFCLASS64() => {
			'ordered_entries' => [
				'st_name',
				'st_info',
				'st_other',
				'st_shndx',
				'st_value',
				'st_size',
			],
			'st_name' => 'Word',
			'st_info' => 'char',
			'st_other' => 'char',
			'st_shndx' => 'Half',
			'st_value' => 'Addr',
			'st_size' => 'Xword',
		},
	},
	'rel' => {
		ELFCLASS32() => {
			'ordered_entries' => [
				'r_offset',
				'r_info',
			],
			'r_offset' => 'Addr',
			'r_info' => 'Word',
		},
		ELFCLASS64() => {
			'ordered_entries' => [
				'r_offset',
				'r_info',
			],
			'r_offset' => 'Addr',
			'r_info' => 'Xword',
		},
	},
	'rela' => {
		ELFCLASS32() => {
			'ordered_entries' => [
				'r_offset',
				'r_info',
				'r_addend',
			],
			'r_offset' => 'Addr',
			'r_info' => 'Word',
			'r_addend' => 'Sword',
		},
		ELFCLASS64() => {
			'ordered_entries' => [
				'r_offset',
				'r_info',
				'r_addend',
			],
			'r_offset' => 'Addr',
			'r_info' => 'Xword',
			'r_addend' => 'Sxword',
		},
	},
	'dynamic' => {
		ELFCLASS32() => {
			'ordered_entries' => [
				'd_tag',
				'd_un',
			],
			'd_tag' => 'Sword',
			'd_un' => 'Word',
		},
		ELFCLASS64() => {
			'ordered_entries' => [
				'd_tag',
				'd_un',
			],
			'd_tag' => 'Sxword',
			'd_un' => 'Xword',
		},
	},
	'note' => {
		ELFCLASS32() => {
			'ordered_entries' => [
				'namesz',
				'descsz',
				'type'
			],
			'namesz' => 'Word',
			'descsz' => 'Word',
			'type' => 'Word',
		},
		ELFCLASS64() => {
			'ordered_entries' => [
				'namesz',
				'descsz',
				'type'
			],
			# The SysV ABI states those should be Xword but it appears reality
			# chose a different path.
			'namesz' => 'Word',
			'descsz' => 'Word',
			'type' => 'Word',
		},
	},
	'program_header' => {
		ELFCLASS32() => {
			'ordered_entries' => [
				'p_type',
				'p_offset',
				'p_vaddr',
				'p_paddr',
				'p_filesz',
				'p_memsz',
				'p_flags',
				'p_align',
			],
			'p_type' => 'Word',
			'p_offset' => 'Off',
			'p_vaddr' => 'Addr',
			'p_paddr' => 'Addr',
			'p_filesz' => 'Word',
			'p_memsz' => 'Word',
			'p_flags' => 'Word',
			'p_align' => 'Word',
		},
		ELFCLASS64() => {
			'ordered_entries' => [
				'p_type',
				'p_flags',
				'p_offset',
				'p_vaddr',
				'p_paddr',
				'p_filesz',
				'p_memsz',
				'p_align',
			],
			'p_type' => 'Word',
			'p_flags' => 'Word',
			'p_offset' => 'Off',
			'p_vaddr' => 'Addr',
			'p_paddr' => 'Addr',
			'p_filesz' => 'Xword',
			'p_memsz' => 'Xword',
			'p_align' => 'Xword',
		},
	},
	'verdef' => {
		'ordered_entries' => [
			'vd_version',
			'vd_flags',
			'vd_ndx',
			'vd_cnt',
			'vd_hash',
			'vd_aux',
			'vd_next',
		],
		'vd_version' => 'Half',
		'vd_flags' => 'Half',
		'vd_ndx' => 'Half',
		'vd_cnt' => 'Half',
		'vd_hash' => 'Word',
		'vd_aux' => 'Word',
		'vd_next' => 'Word',
	},
	'verdaux' => {
		'ordered_entries' => [
			'vda_name',
			'vda_next',
		],
		'vda_name' => 'Word',
		'vda_next' => 'Word',
	},
	'verneed' => {
		'ordered_entries' => [
			'vn_version',
			'vn_cnt',
			'vn_file',
			'vn_aux',
			'vn_next',
		],
		'vn_version' => 'Half',
		'vn_cnt' => 'Half',
		'vn_file' => 'Word',
		'vn_aux' => 'Word',
		'vn_next' => 'Word',
	},
	'vernaux' => {
		'ordered_entries' => [
			'vna_hash',
			'vna_flags',
			'vna_other',
			'vna_name',
			'vna_next',
		],
		'vna_hash' => 'Word',
		'vna_flags' => 'Half',
		'vna_other' => 'Half',
		'vna_name' => 'Word',
		'vna_next' => 'Word',
	},
	'gnu_hash' => {
		'ordered_entries' => [
			'nbuckets',
			'symndx',
			'maskwords',
			'shift2',
		],
		'nbuckets' => 'Word',
		'symndx' => 'Word',
		'maskwords' => 'Word',
		'shift2' => 'Word',
	},
};

our $elf = {
	'e_ident' => {
		'_constants' => {
			# Figure 4-4: e_ident[] Identification Indexes
			'EI_MAG0' => 0,
			'EI_MAG1' => 1,
			'EI_MAG2' => 2,
			'EI_MAG3' => 3,
			'EI_CLASS' => 4,
			'EI_DATA' => 5,
			'EI_VERSION' => 6,
			'EI_OSABI' => 7,
			'EI_ABIVERSION' => 8,
			'EI_PAD' => 9,
			'EI_NIDENT' => 16,
		},
		'_magic' => {
			'EI_MAG0' => 0x7f,
			'EI_MAG1' => 0x45, # 'E'
			'EI_MAG2' => 0x4C, # 'L'
			'EI_MAG3' => 0x46, # 'F'
		},
	},
	'EI_CLASS' => {
		'unexplained_value' => 'fatal',
		'name' => {
			0 => 'ELFCLASSNONE',
			1 => 'ELFCLASS32',
			2 => 'ELFCLASS64',
		},
		'meaning' => {
			0 => 'invalid class',
			1 => '32-bit objects',
			2 => '64-bit objects',
		},
	},
	'EI_DATA' => {
		'unexplained_value' => 'fatal',
		'name' => {
			0 => 'ELFDATANONE',
			1 => 'ELFDATA2LSB',
			2 => 'ELFDATA2MSB',
		},
		'meaning' => {
			0 => 'invalid data encoding',
			1 => 'object file data structure are little-endian',
			2 => 'object file data structure are big-endian',
		},
	},
	'EI_VERSION' => {
		'unexplained_value' => 'i.e. not EV_CURRENT, ignoring',
		'name' => { 1 => 'EV_CURRENT' },
		'meaning' => { 1 => 'this is the expected version of the object file format' },
	},
	'EI_OSABI' => {
		'unexplained_value' => 'fatal',
		'name' => {
			0   => 'ELFOSABI_NONE',
			1   => 'ELFOSABI_HPUX',
			2   => 'ELFOSABI_NETBSD',
			3   => 'ELFOSABI_GNU',
			6   => 'ELFOSABI_SOLARIS',
			7   => 'ELFOSABI_AIX',
			8   => 'ELFOSABI_IRIX',
			9   => 'ELFOSABI_FREEBSD',
			10  => 'ELFOSABI_TRU64',
			11  => 'ELFOSABI_MODESTO',
			12  => 'ELFOSABI_OPENBSD',
			13  => 'ELFOSABI_OPENVMS',
			14  => 'ELFOSABI_NSK',
			15  => 'ELFOSABI_AROS',
			16  => 'ELFOSABI_FENIXOS',
			17  => 'ELFOSABI_CLOUDABI',
			18  => 'ELFOSABI_OPENVOS',
		},
		'meaning' => {
			0   => 'System V ABI',
			1   => 'Hewlett-Packard HP-UX',
			2   => 'NetBSD',
			3   => 'GNU',
			6   => 'Sun Solaris',
			7   => 'AIX',
			8   => 'IRIX',
			9   => 'FreeBSD',
			10  => 'Compaq TRU64 UNIX',
			11  => 'Novell Modesto',
			12  => 'Open BSD',
			13  => 'Open VMS',
			14  => 'Hewlett-Packard Non-Stop Kernel',
			15  => 'Amiga Research OS',
			16  => 'The FenixOS highly scalable multi-core OS',
			17  => 'Nuxi CloudABI',
			18  => 'Stratus Technologies OpenVOS',
		},
		'ranges' => {
			'ARCH' => {
				'meaning' => 'architecture-specific',
				'low_value' => 64,
				'low_name' => '64',
				'high_value' => 255,
				'high_name' => '255',
			},
		},
	},
	'EI_ABIVERSION' => {
		'single_meaning' => 'version of the ABI for which the object is prepared'
	},
	'e_type' => {
		'unexplained_value' => 'fatal',
		'name' => {
			0 => 'ET_NONE',
			1 => 'ET_REL',
			2 => 'ET_EXEC',
			3 => 'ET_DYN',
			4 => 'ET_CORE',
		},
		'meaning' => {
			 0 => 'no file type',
			 1 => 'relocatable object file',
			 2 => 'executable file',
			 3 => 'shared object file',
			 4 => 'core file',
		},
		'ranges' => {
			'OS' => {
				'meaning' => 'environment-specific use',
				'low_value' => 0xFE00,
				'low_name' => 'ET_LOOS',
				'high_value' => 0xFEFF,
				'high_name' => 'ET_HIOS',
			},
			'PROC' => {
				'meaning' => 'processor-specific use',
				'low_value' => 0xFF00,
				'low_name' => 'ET_LOPROC',
				'high_value' => 0xFFFF,
				'high_name' => 'ET_HIPROC',
			},
		},
	},
	'e_machine' => {
		'name' => {
			0 => 'EM_NONE',
			1 => 'EM_M32',
			2 => 'EM_SPARC',
			3 => 'EM_386',
			4 => 'EM_68K',
			5 => 'EM_88K',
			6 => 'EM_IAMCU',
			7 => 'EM_860',
			8 => 'EM_MIPS',
			9 => 'EM_S370',
			10 => 'EM_MIPS_RS3_LE',
			11 => 'reserved',
			12 => 'reserved',
			13 => 'reserved',
			14 => 'reserved',
			15 => 'EM_PARISC',
			16 => 'reserved',
			17 => 'EM_VPP500',
			18 => 'EM_SPARC32PLUS',
			19 => 'EM_960',
			20 => 'EM_PPC',
			21 => 'EM_PPC64',
			22 => 'EM_S390',
			23 => 'EM_SPU',
			24 => 'reserved',
			25 => 'reserved',
			26 => 'reserved',
			27 => 'reserved',
			28 => 'reserved',
			29 => 'reserved',
			30 => 'reserved',
			31 => 'reserved',
			32 => 'reserved',
			33 => 'reserved',
			34 => 'reserved',
			35 => 'reserved',
			36 => 'EM_V800',
			37 => 'EM_FR20',
			38 => 'EM_RH32',
			39 => 'EM_RCE',
			40 => 'EM_ARM',
			41 => 'EM_ALPHA',
			42 => 'EM_SH',
			43 => 'EM_SPARCV9',
			44 => 'EM_TRICORE',
			45 => 'EM_ARC',
			46 => 'EM_H8_300',
			47 => 'EM_H8_300H',
			48 => 'EM_H8S',
			49 => 'EM_H8_500',
			50 => 'EM_IA_64',
			51 => 'EM_MIPS_X',
			52 => 'EM_COLDFIRE',
			53 => 'EM_68HC12',
			54 => 'EM_MMA',
			55 => 'EM_PCP',
			56 => 'EM_NCPU',
			57 => 'EM_NDR1',
			58 => 'EM_STARCORE',
			59 => 'EM_ME16',
			60 => 'EM_ST100',
			61 => 'EM_TINYJ',
			62 => 'EM_X86_64',
			63 => 'EM_PDSP',
			64 => 'EM_PDP10',
			65 => 'EM_PDP11',
			66 => 'EM_FX66',
			67 => 'EM_ST9PLUS',
			68 => 'EM_ST7',
			69 => 'EM_68HC16',
			70 => 'EM_68HC11',
			71 => 'EM_68HC08',
			72 => 'EM_68HC05',
			73 => 'EM_SVX',
			74 => 'EM_ST19',
			75 => 'EM_VAX',
			76 => 'EM_CRIS',
			77 => 'EM_JAVELIN',
			78 => 'EM_FIREPATH',
			79 => 'EM_ZSP',
			80 => 'EM_MMIX',
			81 => 'EM_HUANY',
			82 => 'EM_PRISM',
			83 => 'EM_AVR',
			84 => 'EM_FR30',
			85 => 'EM_D10V',
			86 => 'EM_D30V',
			87 => 'EM_V850',
			88 => 'EM_M32R',
			89 => 'EM_MN10300',
			90 => 'EM_MN10200',
			91 => 'EM_PJ',
			92 => 'EM_OPENRISC',
			93 => 'EM_ARC_COMPACT',
			94 => 'EM_XTENSA',
			95 => 'EM_VIDEOCORE',
			96 => 'EM_TMM_GPP',
			97 => 'EM_NS32K',
			98 => 'EM_TPC',
			99 => 'EM_SNP1K',
			100 => 'EM_ST200',
			101 => 'EM_IP2K',
			102 => 'EM_MAX',
			103 => 'EM_CR',
			104 => 'EM_F2MC16',
			105 => 'EM_MSP430',
			106 => 'EM_BLACKFIN',
			107 => 'EM_SE_C33',
			108 => 'EM_SEP',
			109 => 'EM_ARCA',
			110 => 'EM_UNICORE',
			111 => 'EM_EXCESS',
			112 => 'EM_DXP',
			113 => 'EM_ALTERA_NIOS2',
			114 => 'EM_CRX',
			115 => 'EM_XGATE',
			116 => 'EM_C166',
			117 => 'EM_M16C',
			118 => 'EM_DSPIC30F',
			119 => 'EM_CE',
			120 => 'EM_M32C',
			121 => 'reserved',
			122 => 'reserved',
			123 => 'reserved',
			124 => 'reserved',
			125 => 'reserved',
			126 => 'reserved',
			127 => 'reserved',
			128 => 'reserved',
			129 => 'reserved',
			130 => 'reserved',
			131 => 'EM_TSK3000',
			132 => 'EM_RS08',
			133 => 'EM_SHARC',
			134 => 'EM_ECOG2',
			135 => 'EM_SCORE7',
			136 => 'EM_DSP24',
			137 => 'EM_VIDEOCORE3',
			138 => 'EM_LATTICEMICO32',
			139 => 'EM_SE_C17',
			140 => 'EM_TI_C6000',
			141 => 'EM_TI_C2000',
			142 => 'EM_TI_C5500',
			143 => 'EM_TI_ARP32',
			144 => 'EM_TI_PRU',
			145 => 'reserved',
			146 => 'reserved',
			147 => 'reserved',
			148 => 'reserved',
			149 => 'reserved',
			150 => 'reserved',
			151 => 'reserved',
			152 => 'reserved',
			153 => 'reserved',
			154 => 'reserved',
			155 => 'reserved',
			156 => 'reserved',
			157 => 'reserved',
			158 => 'reserved',
			159 => 'reserved',
			160 => 'EM_MMDSP_PLUS',
			161 => 'EM_CYPRESS_M8C',
			162 => 'EM_R32C',
			163 => 'EM_TRIMEDIA',
			164 => 'EM_QDSP6',
			165 => 'EM_8051',
			166 => 'EM_STXP7X',
			167 => 'EM_NDS32',
			168 => 'EM_ECOG1',
			168 => 'EM_ECOG1X',
			169 => 'EM_MAXQ30',
			170 => 'EM_XIMO16',
			171 => 'EM_MANIK',
			172 => 'EM_CRAYNV2',
			173 => 'EM_RX',
			174 => 'EM_METAG',
			175 => 'EM_MCST_ELBRUS',
			176 => 'EM_ECOG16',
			177 => 'EM_CR16',
			178 => 'EM_ETPU',
			179 => 'EM_SLE9X',
			180 => 'EM_L10M',
			181 => 'EM_K10M',
			182 => 'reserved',
			183 => 'EM_AARCH64',
			184 => 'reserved',
			185 => 'EM_AVR32',
			186 => 'EM_STM8',
			187 => 'EM_TILE64',
			188 => 'EM_TILEPRO',
			189 => 'EM_MICROBLAZE',
			190 => 'EM_CUDA',
			191 => 'EM_TILEGX',
			192 => 'EM_CLOUDSHIELD',
			193 => 'EM_COREA_1ST',
			194 => 'EM_COREA_2ND',
			195 => 'EM_ARC_COMPACT2',
			196 => 'EM_OPEN8',
			197 => 'EM_RL78',
			198 => 'EM_VIDEOCORE5',
			199 => 'EM_78KOR',
			200 => 'EM_56800EX',
			201 => 'EM_BA1',
			202 => 'EM_BA2',
			203 => 'EM_XCORE',
			204 => 'EM_MCHP_PIC',
			205 => 'EM_INTEL205',
			206 => 'EM_INTEL206',
			207 => 'EM_INTEL207',
			208 => 'EM_INTEL208',
			209 => 'EM_INTEL209',
			210 => 'EM_KM32',
			211 => 'EM_KMX32',
			212 => 'EM_KMX16',
			213 => 'EM_KMX8',
			214 => 'EM_KVARC',
			215 => 'EM_CDP',
			216 => 'EM_COGE',
			217 => 'EM_COOL',
			218 => 'EM_NORC',
			219 => 'EM_CSR_KALIMBA',
			220 => 'EM_Z80',
			221 => 'EM_VISIUM',
			222 => 'EM_FT32',
			223 => 'EM_MOXIE',
			224 => 'EM_AMDGPU',
		},
		'meaning' => {
			0 => 'No machine',
			1 => 'AT&T WE 32100',
			2 => 'SPARC',
			3 => 'Intel 80386',
			4 => 'Motorola 68000',
			5 => 'Motorola 88000',
			6 => 'Intel MCU',
			7 => 'Intel 80860',
			8 => 'MIPS I Architecture',
			9 => 'IBM System/370 Processor',
			10 => 'MIPS RS3000 Little-endian',
			11 => 'Reserved for future use',
			12 => 'Reserved for future use',
			13 => 'Reserved for future use',
			14 => 'Reserved for future use',
			15 => 'Hewlett-Packard PA-RISC',
			16 => 'Reserved for future use',
			17 => 'Fujitsu VPP500',
			18 => 'Enhanced instruction set SPARC',
			19 => 'Intel 80960',
			20 => 'PowerPC',
			21 => '64-bit PowerPC',
			22 => 'IBM System/390 Processor',
			23 => 'IBM SPU/SPC',
			24 => 'Reserved for future use',
			25 => 'Reserved for future use',
			26 => 'Reserved for future use',
			27 => 'Reserved for future use',
			28 => 'Reserved for future use',
			29 => 'Reserved for future use',
			30 => 'Reserved for future use',
			31 => 'Reserved for future use',
			32 => 'Reserved for future use',
			33 => 'Reserved for future use',
			34 => 'Reserved for future use',
			35 => 'Reserved for future use',
			36 => 'NEC V800',
			37 => 'Fujitsu FR20',
			38 => 'TRW RH-32',
			39 => 'Motorola RCE',
			40 => 'ARM 32-bit architecture (AARCH32)',
			41 => 'Digital Alpha',
			42 => 'Hitachi SH',
			43 => 'SPARC Version 9',
			44 => 'Siemens TriCore embedded processor',
			45 => 'Argonaut RISC Core, Argonaut Technologies Inc.',
			46 => 'Hitachi H8/300',
			47 => 'Hitachi H8/300H',
			48 => 'Hitachi H8S',
			49 => 'Hitachi H8/500',
			50 => 'Intel IA-64 processor architecture',
			51 => 'Stanford MIPS-X',
			52 => 'Motorola ColdFire',
			53 => 'Motorola M68HC12',
			54 => 'Fujitsu MMA Multimedia Accelerator',
			55 => 'Siemens PCP',
			56 => 'Sony nCPU embedded RISC processor',
			57 => 'Denso NDR1 microprocessor',
			58 => 'Motorola Star*Core processor',
			59 => 'Toyota ME16 processor',
			60 => 'STMicroelectronics ST100 processor',
			61 => 'Advanced Logic Corp. TinyJ embedded processor family',
			62 => 'AMD x86-64 architecture',
			63 => 'Sony DSP Processor',
			64 => 'Digital Equipment Corp. PDP-10',
			65 => 'Digital Equipment Corp. PDP-11',
			66 => 'Siemens FX66 microcontroller',
			67 => 'STMicroelectronics ST9+ 8/16 bit microcontroller',
			68 => 'STMicroelectronics ST7 8-bit microcontroller',
			69 => 'Motorola MC68HC16 Microcontroller',
			70 => 'Motorola MC68HC11 Microcontroller',
			71 => 'Motorola MC68HC08 Microcontroller',
			72 => 'Motorola MC68HC05 Microcontroller',
			73 => 'Silicon Graphics SVx',
			74 => 'STMicroelectronics ST19 8-bit microcontroller',
			75 => 'Digital VAX',
			76 => 'Axis Communications 32-bit embedded processor',
			77 => 'Infineon Technologies 32-bit embedded processor',
			78 => 'Element 14 64-bit DSP Processor',
			79 => 'LSI Logic 16-bit DSP Processor',
			80 => 'Donald Knuth\'s educational 64-bit processor',
			81 => 'Harvard University machine-independent object files',
			82 => 'SiTera Prism',
			83 => 'Atmel AVR 8-bit microcontroller',
			84 => 'Fujitsu FR30',
			85 => 'Mitsubishi D10V',
			86 => 'Mitsubishi D30V',
			87 => 'NEC v850',
			88 => 'Mitsubishi M32R',
			89 => 'Matsushita MN10300',
			90 => 'Matsushita MN10200',
			91 => 'picoJava',
			92 => 'OpenRISC 32-bit embedded processor',
			93 => 'ARC International ARCompact processor (old spelling/synonym: EM_ARC_A5)',
			94 => 'Tensilica Xtensa Architecture',
			95 => 'Alphamosaic VideoCore processor',
			96 => 'Thompson Multimedia General Purpose Processor',
			97 => 'National Semiconductor 32000 series',
			98 => 'Tenor Network TPC processor',
			99 => 'Trebia SNP 1000 processor',
			100 => 'STMicroelectronics (www.st.com) ST200 microcontroller',
			101 => 'Ubicom IP2xxx microcontroller family',
			102 => 'MAX Processor',
			103 => 'National Semiconductor CompactRISC microprocessor',
			104 => 'Fujitsu F2MC16',
			105 => 'Texas Instruments embedded microcontroller msp430',
			106 => 'Analog Devices Blackfin (DSP) processor',
			107 => 'S1C33 Family of Seiko Epson processors',
			108 => 'Sharp embedded microprocessor',
			109 => 'Arca RISC Microprocessor',
			110 => 'Microprocessor series from PKU-Unity Ltd. and MPRC of Peking University',
			111 => 'eXcess: 16/32/64-bit configurable embedded CPU',
			112 => 'Icera Semiconductor Inc. Deep Execution Processor',
			113 => 'Altera Nios II soft-core processor',
			114 => 'National Semiconductor CompactRISC CRX microprocessor',
			115 => 'Motorola XGATE embedded processor',
			116 => 'Infineon C16x/XC16x processor',
			117 => 'Renesas M16C series microprocessors',
			118 => 'Microchip Technology dsPIC30F Digital Signal Controller',
			119 => 'Freescale Communication Engine RISC core',
			120 => 'Renesas M32C series microprocessors',
			121 => 'Reserved for future use',
			122 => 'Reserved for future use',
			123 => 'Reserved for future use',
			124 => 'Reserved for future use',
			125 => 'Reserved for future use',
			126 => 'Reserved for future use',
			127 => 'Reserved for future use',
			128 => 'Reserved for future use',
			129 => 'Reserved for future use',
			130 => 'Reserved for future use',
			131 => 'Altium TSK3000 core',
			132 => 'Freescale RS08 embedded processor',
			133 => 'Analog Devices SHARC family of 32-bit DSP processors',
			134 => 'Cyan Technology eCOG2 microprocessor',
			135 => 'Sunplus S+core7 RISC processor',
			136 => 'New Japan Radio (NJR) 24-bit DSP Processor',
			137 => 'Broadcom VideoCore III processor',
			138 => 'RISC processor for Lattice FPGA architecture',
			139 => 'Seiko Epson C17 family',
			140 => 'The Texas Instruments TMS320C6000 DSP family',
			141 => 'The Texas Instruments TMS320C2000 DSP family',
			142 => 'The Texas Instruments TMS320C55x DSP family',
			143 => 'Texas Instruments Application Specific RISC Processor, 32bit fetch',
			144 => 'Texas Instruments Programmable Realtime Unit',
			145 => 'Reserved for future use',
			146 => 'Reserved for future use',
			147 => 'Reserved for future use',
			148 => 'Reserved for future use',
			149 => 'Reserved for future use',
			150 => 'Reserved for future use',
			151 => 'Reserved for future use',
			152 => 'Reserved for future use',
			153 => 'Reserved for future use',
			154 => 'Reserved for future use',
			155 => 'Reserved for future use',
			156 => 'Reserved for future use',
			157 => 'Reserved for future use',
			158 => 'Reserved for future use',
			159 => 'Reserved for future use',
			160 => 'STMicroelectronics 64bit VLIW Data Signal Processor',
			161 => 'Cypress M8C microprocessor',
			162 => 'Renesas R32C series microprocessors',
			163 => 'NXP Semiconductors TriMedia architecture family',
			164 => 'QUALCOMM DSP6 Processor',
			165 => 'Intel 8051 and variants',
			166 => 'STMicroelectronics STxP7x family of configurable and extensible RISC processors',
			167 => 'Andes Technology compact code size embedded RISC processor family',
			168 => 'Cyan Technology eCOG1X family',
			168 => 'Cyan Technology eCOG1X family',
			169 => 'Dallas Semiconductor MAXQ30 Core Micro-controllers',
			170 => 'New Japan Radio (NJR) 16-bit DSP Processor',
			171 => 'M2000 Reconfigurable RISC Microprocessor',
			172 => 'Cray Inc. NV2 vector architecture',
			173 => 'Renesas RX family',
			174 => 'Imagination Technologies META processor architecture',
			175 => 'MCST Elbrus general purpose hardware architecture',
			176 => 'Cyan Technology eCOG16 family',
			177 => 'National Semiconductor CompactRISC CR16 16-bit microprocessor',
			178 => 'Freescale Extended Time Processing Unit',
			179 => 'Infineon Technologies SLE9X core',
			180 => 'Intel L10M',
			181 => 'Intel K10M',
			182 => 'Reserved for future Intel use',
			183 => 'ARM 64-bit architecture (AARCH64)',
			184 => 'Reserved for future ARM use',
			185 => 'Atmel Corporation 32-bit microprocessor family',
			186 => 'STMicroeletronics STM8 8-bit microcontroller',
			187 => 'Tilera TILE64 multicore architecture family',
			188 => 'Tilera TILEPro multicore architecture family',
			189 => 'Xilinx MicroBlaze 32-bit RISC soft processor core',
			190 => 'NVIDIA CUDA architecture',
			191 => 'Tilera TILE-Gx multicore architecture family',
			192 => 'CloudShield architecture family',
			193 => 'KIPO-KAIST Core-A 1st generation processor family',
			194 => 'KIPO-KAIST Core-A 2nd generation processor family',
			195 => 'Synopsys ARCompact V2',
			196 => 'Open8 8-bit RISC soft processor core',
			197 => 'Renesas RL78 family',
			198 => 'Broadcom VideoCore V processor',
			199 => 'Renesas 78KOR family',
			200 => 'Freescale 56800EX Digital Signal Controller (DSC)',
			201 => 'Beyond BA1 CPU architecture',
			202 => 'Beyond BA2 CPU architecture',
			203 => 'XMOS xCORE processor family',
			204 => 'Microchip 8-bit PIC(r) family',
			205 => 'Reserved by Intel',
			206 => 'Reserved by Intel',
			207 => 'Reserved by Intel',
			208 => 'Reserved by Intel',
			209 => 'Reserved by Intel',
			210 => 'KM211 KM32 32-bit processor',
			211 => 'KM211 KMX32 32-bit processor',
			212 => 'KM211 KMX16 16-bit processor',
			213 => 'KM211 KMX8 8-bit processor',
			214 => 'KM211 KVARC processor',
			215 => 'Paneve CDP architecture family',
			216 => 'Cognitive Smart Memory Processor',
			217 => 'iCelero CoolEngine',
			218 => 'Nanoradio Optimized RISC',
			219 => 'CSR Kalimba architecture family',
			220 => 'Zilog Z80',
			221 => 'Controls and Data Services VISIUMcore processor',
			222 => 'FTDI Chip FT32 high performance 32-bit RISC architecture',
			223 => 'Moxie processor family',
			224 => 'AMD GPU architecture',
		},
	},
	'e_version' => {
		'name' => {
			0 => 'EV_NONE',
			1 => 'EV_CURRENT',
		},
		'meaning' => {
			0 => 'invalid version',
			1 => 'original version of the object file format',
		},
		'unexplained_value' => 'i.e. not EV_CURRENT, ignoring',
	},
	'e_entry' => {
		'single_meaning' => 'contains the virtual address of the program entry point',
	},
	'e_phoff' => {
		'single_meaning' => 'contains the file offset, in bytes, of the program header table',
	},
	'e_shoff' => {
		'single_meaning' => 'contains the file offset, in bytes, of the section header table',
	},
	'e_flags' => {
		'single_meaning' => 'contains processor-specific flags',
	},
	'e_ehsize' => {
		'single_meaning' => 'contains the size, in bytes, of the ELF header',
	},
	'e_phentsize' => {
		'single_meaning' => 'contains the size, in bytes, of a program header table entry',
	},
	'e_phnum' => {
		'single_meaning' => 'contains the number of entries in the program header table',
	},
	'e_shentsize' => {
		'single_meaning' => 'contains the size, in bytes, of a section header table entry',
	},
	'e_shnum' => {
		'single_meaning' => 'contains the number of entries in the section header table',
	},
	'e_shstrndx' => {
		'single_meaning' => 'this is, within the section header table, the index of the section containing the section names',
	},
	'sh_name' => {
		'single_meaning' => 'this offset is used to locate the section name',
	},
	# Figure 4-7: Special section Indices
	'section_indices' => {
		'name' => {
			0 => 'SHN_UNDEF',
			0xFFF1 => 'SHN_ABS',
			0xFFF2 => 'SHN_COMMON',
			0xFFFF => 'SHN_XINDEX',
		},
		'meaning' => {
			0 => 'undefined or meaningless section reference',
			0xFFF1 => 'the corresponding reference is an absolute value',
			0xFFF2 => 'a symbol that has been declared as a common block',
			0xFFFF => 'an escape value meaning that the actual section header index is too large to fit in the containing field (specific to the structure where it appears)',
		},
		'ranges' => {
			'PROC' => {
				'meaning' => 'processor-specific semantics',
				'low_value' => 0xFF00,
				'low_name' => 'SHN_LOPROC',
				'high_value' => 0xFF1F,
				'high_name' => 'SHN_HIPROC',
			},
			'OS' => {
				'meaning' => 'operating system-specific semantics',
				'low_value' => 0xFF20,
				'low_name' => 'SHN_LOOS',
				'high_value' => 0xFF3F,
				'high_name' => 'SHN_HIOS',
			},
		},
	},
	# Figure 4-9: Section Types, sh_type
	'sh_type' => {
		'name' => {
			0 => 'SHT_NULL',
			1 => 'SHT_PROGBITS',
			2 => 'SHT_SYMTAB',
			3 => 'SHT_STRTAB',
			4 => 'SHT_RELA',
			5 => 'SHT_HASH',
			6 => 'SHT_DYNAMIC',
			7 => 'SHT_NOTE',
			8 => 'SHT_NOBITS',
			9 => 'SHT_REL',
			10 => 'SHT_SHLIB',
			11 => 'SHT_DYNSYM',
			14 => 'SHT_INIT_ARRAY',
			15 => 'SHT_FINI_ARRAY',
			16 => 'SHT_PREINIT_ARRAY',
			17 => 'SHT_GROUP',
			18 => 'SHT_SYMTAB_SHNDX',
			# 0X6FFFFFF5 to 0X6FFFFFFC come from the GNU C Library's elf.h.
			0X6FFFFFF5 => 'SHT_GNU_ATTRIBUTES',
			0x6FFFFFF6 => 'SHT_GNU_HASH',
			0X6FFFFFF7 => 'SHT_GNU_LIBLIST',
			0X6FFFFFF8 => 'SHT_CHECKSUM',
			0X6FFFFFFA => 'SHT_SUNW_move',
			0X6FFFFFFB => 'SHT_SUNW_COMDAT',
			0X6FFFFFFC => 'SHT_SUNW_syminfo',
			# These are the additional section types defined by the LSB.
			0X6FFFFFFD => 'SHT_GNU_verdef',
			0X6FFFFFFE => 'SHT_GNU_verneed',
			0X6FFFFFFF => 'SHT_GNU_versym',
		},
		'meaning' => {
			0 => 'marks an unused section header',
			1 => 'contains information defined by the program',
			2 => 'contains a linker symbol table',
			3 => 'contains a string table',
			4 => 'contains "Rela" type relocation entries',
			5 => 'contains a symbol hash table',
			6 => 'contains dynamic linking tables',
			7 => 'contains note information',
			8 => 'contains uninitialized space; does not occupy any space in the file',
			9 => 'contains "Rel" type relocation entries',
			10 => 'reserved',
			11 => 'contains a dynamic loader symbol table',
			14 => 'contains an array of pointers to initialization functions',
			15 => 'contains an array of pointers to termination functions',
			16 => 'contains an array of pointers to functions that are invoked before all other initialization functions',
			17 => 'defines a section group, i.e. a set of sections that are related and that must be treated specially by the linker',
			18 => 'used with SHN_XINDEX',
			0X6FFFFFF5 => 'Object attributes',
			0x6FFFFFF6 => 'GNU-style hash table',
			0X6FFFFFF7 => 'Prelink library list',
			0X6FFFFFF8 => 'Checksum for DSO content',
			0X6FFFFFFA => 'Sun-specific',
			0X6FFFFFFB => 'Sun-specific',
			0X6FFFFFFC => 'Sun-specific',
			0X6FFFFFFD => 'contains the symbol versions that are provided',
			0X6FFFFFFE => 'contains the symbol versions that are required',
			0X6FFFFFFF => 'contains the Symbol Version Table',
		},
		'ranges' => {
			'OS' => {
				'meaning' => 'environment-specific use',
				'low_value' => 0x60000000,
				'low_name' => 'SHT_LOOS',
				'high_value' => 0x6FFFFFFF,
				'high_name' => 'SHT_HIOS',
			},
			'PROC' => {
				'meaning' => 'processor-specific use',
				'low_value' => 0x70000000,
				'low_name' => 'SHT_LOPROC',
				'high_value' => 0x7FFFFFFF,
				'high_name' => 'SHT_HIPROC',
			},
		},
	},
	# Figure 4-11. Section Attribute Flags
	'sh_flags' => {
		'is_flag' => 1,
		'name' => {
			0x001 => 'SHF_WRITE',
			0x002 => 'SHF_ALLOC',
			0x004 => 'SHF_EXECINSTR',
			0x010 => 'SHF_MERGE',
			0x020 => 'SHF_STRINGS',
			0x040 => 'SHF_INFO_LINK',
			0x080 => 'SHF_LINK_ORDER',
			0x100 => 'SHF_OS_NONCONFORMING',
			0x200 => 'SHF_GROUP',
			0x400 => 'SHF_TLS',
			0x800 => 'SHF_COMPRESSED',
			0x0FF00000 => 'SHT_MASKOS',
			0xF0000000 => 'SHT_MASKPROC',
		},
		'meaning' => {
			0x001 => 'section contains writable data',
			0x002 => 'section is allocated in memory image of program',
			0x004 => 'section contains executable instructions',
			0x010 => 'data in the section may be merged to eliminate duplication',
			0x020 => 'data elements in the section consist of null-terminated character strings',
			0x040 => 'the sh_info field of this section header holds a section header table index',
			0x080 => 'add special ordering requirements for link editors',
			0x100 => 'this section requires special OS-specific processing (beyond the standard linking rules) to avoid incorrect behavior',
			0x200 => 'this section is a member (perhaps the only one) of a section group',
			0x400 => 'this section holds Thread-Local Storage, meaning that each separate execution flow has its own distinct instance of this data',
			0x800 => 'section contains compressed data',
			0x0FF00000 => 'operating system-specific semantics',
			0xF0000000 => 'processor-specific semantics',
		},
	},
	'sh_addr' => {
		'single_meaning' => 'virtual address of the section in memory',
	},
	'sh_offset' => {
		'single_meaning' => 'offset, in bytes, of the section contents in the file',
	},
	'sh_size' => {
		'single_meaning' => 'size, in bytes, of the section',
	},
	'sh_link' => {
		'single_meaning' => 'index of an associated section',
		'_use' => {
			# Figure 4-14: sh_link and sh_info Interpretation
			6  => 'a string table used by entries in this section',
			5  => 'a symbol table to which the hash table applies',
			9  => 'the associated symbol table',
			4  => 'the associated symbol table',
			2  => 'the associated string table',
			11 => 'the associated string table',
			17 => 'the associated symbol table',
			18 => 'the associated symbol table section',
		}
	},
	'sh_info' => {
		'single_meaning' => 'extra information about the section',
		'_use' => {
			# Figure 4-14: sh_link and sh_info Interpretation
			9  => 'the index of the section to which relocation applies',
			4  => 'the index of the section to which relocation applies',
			2  => '1 greater than the symbol table index of the last local symbol (binding STB_LOCAL)',
			11 => '1 greater than the symbol table index of the last local symbol (binding STB_LOCAL)',
			17 => 'the symbol table index of an entry in the associated symbol table',
		}
	},
	'sh_addralign' => {
		'single_meaning' => 'contains the required alignment of the section',
	},
	'sh_entsize' => {
		'single_meaning' => 'size, in bytes, of each entry; only makes sense for sections that contain fixed-size entries',
	},
	# Figure 4-16: Special Sections
	'elf_special_sections' => {
		'short' => {
			'.bss' => 'uninitialized data',
			'.comment' => 'version control information',
			'.data'  => 'initialized data',
			'.data1' => 'initialized data',
			'.debug' => 'information for symbolic debugging',
			'.dynamic' => 'dynamic linking information',
			'.dynstr' => 'symbol names for dynamic linking',
			'.dynsym' => 'symbol table for dynamic linking',
			'.fini' => 'executable instructions for process termination',
			'.fini_array' => 'array of functions for process termination',
			'.got' => 'global offset table',
			'.hash' => 'symbol hash table',
			'.init' => 'executable instructions for process initialization',
			'.init_array' => 'array of functions for process initialization',
			'.interp' => 'path to program interpreter',
			'.line' => 'line numbers for debugging',
			'.note' => 'special information',
			'.plt' => 'procedure linkage table',
			'.preinit_array' => 'array of functions for process pre-initialization',
			'.rel' => 'relocation information for section %s',
			'.rodata'  => 'read-only data',
			'.rodata1' => 'read-only data',
			'.shstrtab' => 'section names',
			'.strtab' => 'symbol names',
			'.symtab' => 'symbol table',
			'.symtab_shndx' => 'special symbol table section index array',
			'.tbss' => 'uninitialized thread-local data',
			'.tdata'  => 'initialized thread-local data',
			'.text' => 'executable instructions',
		},
		'long' => {
			'.bss' => 'Historically stands for "Block Started by Symbol". This section holds uninitialized data that contribute to the program\'s memory image. By definition, the system initializes the data with zeros when the pgrogram begins to run. The section occupies no file space, as indicated by the section type, SHT_NOBITS.',
			'.comment' => 'This section holds version control information.',
			'.data'  => 'This section holds initialized data that contribute to the program\'s memory image.',
			'.data1' => 'This section holds initialized data that contribute to the program\'s memory image.',
			'.debug' => 'This section holds information for symbolic debugging. The contents are unspecified. All section names with the prefix .debug are reserved for future use in the ABI.',
			'.dynamic' => 'This section holds dynamic linking information. This section\'s attributes will include the SHF_ALLOC bit. Whether the SHF_WRITE bit is set is processor specific.',
			'.dynstr' => 'This section holds strings needed for dynamic linking, most commonly the strings that represent the names associated with symbol table entries.',
			'.dynsym' => 'This section holds the dynamic linking symbol table.',
			'.fini' => 'This section holds executable instructions that contribute to the process termination code. That is, when a program exits normally, the system arranges to execute the code in this section.',
			'.fini_array' => 'This section holds an array of function pointers that contributes to a single termination array for the executable or shared object containing the section.',
			'.got' => 'This section holds the global offset table.',
			'.hash' => 'This section holds a symbol hash table (following classic ELF).',
			'.init' => 'This section holds executable instructions that contribute to the process initialization code. When a program starts to run, the system arranges to execute the code in this section before calling the main program entry point (called "main" for C programs).',
			'.init_array' => 'This section an array of function pointers that contributes to a single initialization array for the executable or shared object containing the section.',
			'.interp' => 'This section holds the path name of a program interpreter. If the file has a loadable segment that includes relocation, the sections\' attributes will include the SHF_ALLOC bit; otherwise, that bit will be off.',
			'.line' => 'This section holds line number information for symbolic debugging, which describes the correspondence between the source program and the machine code. The contents are unspecified.',
			'.note' => 'Tis section holds special information that other programs will check for conformance, compatibility, etc.',
			'.plt' => 'This section holds the procedure linkage table.',
			'.preinit_array' => 'This section holds an array of function pointers that contributes to a single pre-initialization array for the executable or shared object containing the section.',
			'.rel' => 'This section holds relocation information that apply to section %s. If the file has a loadable segment that includes relocation, the sections\' attributes will include the SHF_ALLOC bit; otherwise, that bit will be off.',
			'.rodata'  => 'This section holds read-only data that typically contribute to a non-writable segment in the process image.',
			'.rodata1' => 'This section holds read-only data that typically contribute to a non-writable segment in the process image.',
			'.shstrtab' => 'This section holds section names. Its index is referenced in the file header by e_shstrndx.',
			'.strtab' => 'This section holds strings, most commonly the strings that represent the names associated with symbol table entries.',
			'.symtab' => 'This section holds a symbol table. If the file has a loadable segment that includes the symbol table, the section\'s attributes will include the SHF_ALLOC bit; otherwise, that bit will be off.',
			'.symtab_shndx' => 'This section holds a special symbol table section index array.',
			'.tbss' => 'This section holds uninitialized thread-local data that contribute to the program\'s memory image. By definition, the system initializes the data with zeros when the data is instantiated for each new execution flow. The section occupies no file space, as indicated by the section type, SHT_NOBITS. Implementations need not support thread-local storage.',
			'.tdata' => 'This section holds initialized thread-local data that contributes to the program\'s memory image. A copy of its contents is instantiated by the system for each new execution flow. Implementations need not support thread-local storage.',
			'.text' => 'This section holds the "text", or executable instructions, of a program.',
		}
	},
	# Additional Special Sections from LSB_5.0.0
	'lsb5-core-generic_additional_special_sections' => {
		'short' => {
			'.ctors' => 'constructor function pointers',
			'.data.rel.ro' => 'read-only relocatable initialized data',
			'.dtors' => 'destructor function pointers',
			'.eh_frame' => 'frame unwinding information',
			'.eh_frame_hdr' => 'frame unwinding additional information',
			'.gcc_except_table' => 'language specific data',
			'.gnu.version' => 'symbol version table',
			'.gnu.version_d' => 'version definitions',
			'.gnu.version_r' => 'version requirements',
			'.got.plt' => 'read-only relocatable global offset table',
			'.jcr' => 'Java class registration information',
			'.note.ABI-tag' => 'ABI details',
			'.stab' => 'debugging information',
			'.stabstr' => 'strings for .stab section',
		},
		'long' => {
			'.ctors' => 'This section contains a list of global constructor function pointers.',
			'.data.rel.ro' => 'This section holds initialized data that contribute to the program\'s memory image. This section may be made read-only after relocations have been applied.',
			'.dtors' => 'This section contains a list of global destructor function pointers.',
			'.eh_frame' => 'This section contains information necessary for frame unwinding during exception handling.',
			'.eh_frame_hdr' => 'This section contains a pointer to the .eh_frame section which is accessible to the runtime support code of a C++ application. This section may also contain a binary search table which may be used by the runtime support code to more efficiently access records in the .eh_frame section.',
			'.gcc_except_table' => 'This section holds Language Specific Data.',
			'.gnu.version' => 'This section contains the Symbol Version Table.',
			'.gnu.version_d' => 'This section contains the Version Definitions.',
			'.gnu.version_r' => 'This section contains the Version Requirements.',
			'.got.plt' => 'This section holds the read-only portion of the GLobal Offset Table. This section may be made read-only after relocations have been applied.',
			'.jcr' => 'This section contains information necessary for registering compiled Java classes. The contents are compiler-specific and used by compiler initialization functions.',
			'.note.ABI-tag' => 'Specify ABI details, e.g. the earliest compatible kernel version.',
			'.stab' => 'This section contains debugging information.',
			'.stabstr' => 'This section contains strings associated with the debugging infomation contained in the .stab section.',
		}
	},
	'lsb5-core-amd64_additional_special_sections' => {
		'short' => {
			'.rela.dyn' => 'relocation information for all but .plt',
			'.rela.plt' => 'relocation information for .plt',
		},
		'long' => {
			'.rela.dyn' => 'This section holds RELA type relocation information for all sections of a shared library except the PLT.',
			'.rela.plt' => 'This section holds RELA type relocation information for the PLT section of a shared library or dynamically linked application.',
		}
	},
	'gnu_special_sections' => {
		'short' => {
			'.gnu.hash' => 'symbol hash table, GNU-style',
			'.note.gnu.build-id' => 'unique bits identifying the file',
		},
		'long' => {
			'.gnu.hash' => 'This section holds a symbol hash table (following GNU ELF).',
			'.note.gnu.build-id' => 'According to the GNU linker (ld) man page, the contents of the note are unique bits identifying this linked file.',
		}
	},
	'st_name' => {
		'single_meaning' => 'this offset is used to locate the symbol name',
	},
	'st_value' => {
		'single_meaning' => 'associated value (absolute value or address)',
	},
	'st_size' => {
		'single_meaning' => 'associated size',
	},
	'st_info' => {
		'single_meaning' => 'type and binding attributes (st_type and st_bind)',
	},
	'st_bind' => {
		'name' => {
			0 => 'STB_LOCAL',
			1 => 'STB_GLOBAL',
			2 => 'STB_WEAK',
		},
		'meaning' => {
			0 => 'the symbol is not visible outside the object file',
			1 => 'the symbol is visible to all object files being combined',
			2 => 'the symbol is visible to all object files with lower precedence',
		},
		'ranges' => {
			'OS' => {
				'meaning' => 'operating system-specific semantics',
				'low_value' => 10,
				'low_name' => 'STB_LOOS',
				'high_value' => 12,
				'high_name' => 'STB_HIOS',
			},
			'PROC' => {
				'meaning' => 'processor-specific semantics',
				'low_value' => 13,
				'low_name' => 'STB_LOPROC',
				'high_value' => 15,
				'high_name' => 'STB_HIPROC',
			},
		},
	},
	'st_type' => {
		'name' => {
			0 => 'STT_NOTYPE',
			1 => 'STT_OBJECT',
			2 => 'STT_FUNC',
			3 => 'STT_SECTION',
			4 => 'STT_FILE',
			5 => 'STT_COMMON',
			6 => 'STT_TLS',
		},
		'meaning' => {
			0 => 'the symbol type is not specified',
			1 => 'the symbol is associated with a data object, such as a variable, an array, etc.',
			2 => 'the symbol is associated with a function or other executable code',
			3 => 'the symbol is associated with a section',
			4 => 'the symbol\'s name gives the name of the source file associated with the object file',
			5 => 'the symbol labels an uninitialized common block',
			6 => 'the symbol specifies a Thread-Local Storage entity',
		},
		'ranges' => {
			'OS' => {
				'meaning' => 'operating system-specific semantics',
				'low_value' => 10,
				'low_name' => 'STT_LOOS',
				'high_value' => 12,
				'high_name' => 'STT_HIOS',
			},
			'PROC' => {
				'meaning' => 'processor-specific semantics',
				'low_value' => 13,
				'low_name' => 'STT_LOPROC',
				'high_value' => 15,
				'high_name' => 'STT_HIPROC',
			},
		},
	},
	'st_other' => {
		'name' => {
			0 => 'STV_DEFAULT',
			1 => 'STV_INTERNAL',
			2 => 'STV_HIDDEN',
			3 => 'STV_PROTECTED',
		},
		'meaning' => {
			0 => 'respect the symbol\'s binding type',
			1 => 'visible in other components but not preemptable',
			2 => 'not visible to other components',
			3 => 'defined by processor supplements',
		},
	},
	'st_shndx' => {
		'name' => {
			0 => 'SHN_UNDEF',
			0xFFF1 => 'SHN_ABS',
			0xFFF2 => 'SHN_COMMON',
			0xFFFF => 'SHN_XINDEX',
		},
		'meaning' => {
			0 => colored('this symbol is to be linked from another file', 'bright_yellow'),
			0xFFF1 => 'the symbol has an absolute value that will not change because of relocation',
			0xFFF2 => 'the symbol labels a common block that has not yet been allocated',
			0xFFFF => 'an escape value which indicates that the symbol refers to a specific location within a section, but that the section header index for that section is too large to be represented directly in the symbol table entry',
		},
		'unexplained_value' => 'this is the index of the related section',
	},
	'd_tag' => {
		'name' => {
			0 => 'DT_NULL',
			1 => 'DT_NEEDED',
			2 => 'DT_PLTRELSZ',
			3 => 'DT_PLTGOT',
			4 => 'DT_HASH',
			5 => 'DT_STRTAB',
			6 => 'DT_SYMTAB',
			7 => 'DT_RELA',
			8 => 'DT_RELASZ',
			9 => 'DT_RELAENT',
			10 => 'DT_STRSZ',
			11 => 'DT_SYMENT',
			12 => 'DT_INIT',
			13 => 'DT_FINI',
			14 => 'DT_SONAME',
			15 => 'DT_RPATH',
			16 => 'DT_SYMBOLIC',
			17 => 'DT_REL',
			18 => 'DT_RELSZ',
			19 => 'DT_RELENT',
			20 => 'DT_PLTREL',
			21 => 'DT_DEBUG',
			22 => 'DT_TEXTREL',
			23 => 'DT_JMPREL',
			24 => 'DT_BIND_NOW',
			25 => 'DT_INIT_ARRAY',
			26 => 'DT_FINI_ARRAY',
			27 => 'DT_INIT_ARRAYSZ',
			28 => 'DT_FINI_ARRAYSZ',
			29 => 'DT_RUNPATH',
			30 => 'DT_FLAGS',
			32 => 'DT_PREINIT_ARRAY',
			33 => 'DT_PREINIT_ARRAYSZ',
			34 => 'DT_SYMTAB_SHNDX',
			0x6ffffef5 => 'DT_GNU_HASH',
			0x6ffffef6 => 'DT_TLSDESC_PLT',
			0x6ffffef7 => 'DT_TLSDESC_GOT',
			0x6ffffef8 => 'DT_GNU_CONFLICT',
			0x6ffffef9 => 'DT_GNU_LIBLIST',
			0x6ffffefa => 'DT_CONFIG',
			0x6ffffefb => 'DT_DEPAUDIT',
			0x6ffffefc => 'DT_AUDIT',
			0x6ffffefd => 'DT_PLTPAD',
			0x6ffffefe => 'DT_MOVETAB',
			0x6ffffeff => 'DT_SYMINFO',
			0x6ffffff0 => 'DT_VERSYM',
			0x6ffffff9 => 'DT_RELACOUNT',
			0x6ffffffa => 'DT_RELCOUNT',
			0x6ffffffb => 'DT_FLAGS_1',
			0x6ffffffc => 'DT_VERDEF',
			0x6ffffffd => 'DT_VERDEFNUM',
			0x6ffffffe => 'DT_VERNEED',
			0x6fffffff => 'DT_VERNEEDNUM',
		},
		'meaning' => {
			0 => 'marks the end of the _DYNAMIC array',
			1 => 'holds the string table offset of a null-terminated string, giving the name of a needed library',
			2 => 'holds the total size, in bytes, of the relocation entries associated with the procedure linkage table. If an entry of type DT_JMPREL is present, a DT_PLTRELSZ must accompany it. ',
			3 => 'holds an address associated with the procedure linkage table and/or the global offset table',
			4 => 'holds the address of the symbol hash table; this hash table refers to the symbol table referenced by the DT_SYMTAB element',
			5 => 'holds the address of the string table; symbol names, library names, and other strings reside in this table',
			6 => 'holds the address of the symbol table',
			7 => 'holds the address of a relocation table (with explicit addends)',
			8 => ' holds the total size, in bytes, of the DT_RELA relocation table',
			9 => 'holds the size, in bytes, of a DT_RELA relocation entry',
			10 => 'holds the size, in bytes, of the string table',
			11 => 'holds the size, in bytes, of a symbol table entry',
			12 => 'holds the address of the initialization function',
			13 => 'holds the address of the termination function',
			14 => 'holds the string table offset of a null-terminated string, giving the name of the shared object; the offset is an index into the table recorded in the DT_STRTAB entry',
			15 => 'holds the string table offset of a null-terminated string, which is actually a library search path; the offset is an index into the table recorded in the DT_STRTAB entry',
			16 => 'alters the dynamic linker\'s symbol resolution algorithm for references within the library',
			17 => 'holds the address of a relocation table (with implicit addends)',
			18 => 'holds the total size, in bytes, of the DT_REL relocation table',
			19 => 'holds the size, in bytes, of a DT_REL relocation entry',
			20 => 'specifies the type of relocation entry to which the procedure linkage table refers',
			21 => 'used for debugging',
			22 => 'one or more relocation entries might request modifications to a non-writable segment',
			23 => ' holds the address of relocation entries associated solely with the procedure linkage table',
			24 => 'instructs the dynamic linker to process all relocations for the object containing this entry before transferring control to the program',
			25 => 'holds the address of the array of pointers to initialization functions',
			26 => ' holds the address of the array of pointers to termination functions',
			27 => 'holds the size in bytes of the array of initialization functions pointed to by the DT_INIT_ARRAY entry',
			28 => 'holds the size in bytes of the array of termination functions pointed to by the DT_FINI_ARRAY entry',
			29 => 'holds the string table offset of a null-terminated string, which is actually a library search path; the offset is an index into the table recorded in the DT_STRTAB entry',
			30 => 'holds flag values specific to the object being loaded',
			32 => 'holds the address of the array of pointers to pre-initialization functions',
			33 => 'holds the size in bytes of the array of pre-initialization functions pointed to by the DT_PREINIT_ARRAY entry',
			34 => 'holds the address of the SHT_SYMTAB_SHNDX section associated with the dynamic symbol table referenced by the DT_SYMTAB element',
			0x6ffffe00 => '',
			0x6ffffef5 => 'holds the address of the GNU-style symbol hash table; this hash table refers to the symbol table referenced by the DT_SYMTAB element',
			0x6ffffef6 => 'location of the GOT entry used by the TLS descriptor resolver PLT entry',
			0x6ffffef7 => 'location of the PLT entry for TLS descriptor resolved calls',
			0x6ffffef8 => 'start of conflict section',
			0x6ffffef9 => 'library list',
			0x6ffffefa => 'configuration information',
			0x6ffffefb => 'dependency auditing',
			0x6ffffefc => 'object auditing',
			0x6ffffefd => 'PLT padding',
			0x6ffffefe => 'move table',
			0x6ffffeff => 'syminfo table',
			0x6ffffeff => 'holds the address of the Syminfo table',
			0x6ffffff0 => 'holds the address of the table provided by the .gnu.version section',
			0x6ffffff9 => 'holds the number of relative relocations in .rela.dyn',
			0x6ffffffa => 'all Elf32_Rel R_*_RELATIVE relocations have been placed into a single block and this entry specifies the number of entries in that block. This permits ld.so.1 to streamline the processing of RELATIVE relocations',
			0x6ffffffb => 'DF_1_* state flags',
			0x6ffffffc => 'holds the address of version definition table',
			0x6ffffffd => 'holds the number of version definitions',
			0x6ffffffe => 'holds the address of the table with needed versions',
			0x6fffffff => 'holds the number of needed versions',
		},
		'ranges' => {
			'OS' => {
				'meaning' => 'operating system-specific semantics',
				'low_value' => 0x6000000D,
				'low_name' => 'DT_LOOS',
				'high_value' => 0X6FFFF000,
				'high_name' => 'DT_HIOS',
			},
			'PROC' => {
				'meaning' => 'processor-specific semantics',
				'low_value' => 0x70000000,
				'low_name' => 'DT_LOPROC',
				'high_value' => 0X7FFFFFFF,
				'high_name' => 'DT_HIPROC',
			},
			'ADDRRNG' => {
				'meaning' => 'reserved for definition by an architecture specific part',
				'low_value' => 0X6FFFFE00,
				'low_name' => 'DT_ADDRRNGLO',
				'high_value' => 0X6FFFFEFF,
				'high_name' => 'DT_ADDRRNGHI',
			},
		},
	},
	'd_un' => {
		'single_meaning' => 'see d_tag',
	},
	'dt_flags' => {
		'is_flag' => 1,
		'name' => {
			1 => 'DF_ORIGIN',
			2 => 'DF_SYMBOLIC',
			4 => 'DF_TEXTREL',
			8 => 'DF_BIND_NOW',
			16 => 'DF_STATIC_TLS',
		},
		'meaning' => {
			1 => 'inform the dynamic linker that the object being loaded may make reference to the $ORIGIN substitution string',
			2 => 'instruct the dynamic linker to start its symbol search with the shared object instead of the executable file',
			4 =>  'inform the dynamic linker that one or more relocation entries might request modifications to a non-writable segment',
			8 => 'instruct the dynamic linker to process all relocations for the object containing this entry before transferring control to the program',
			16 => 'instruct the dynamic linker to reject attempts to load this file dynamically',
		},
	},
	'dt_flags_1' => {
		'is_flag' => 1,
		'name' => {
			0x00000001 => 'DF_1_NOW',
			0x00000002 => 'DF_1_GLOBAL',
			0x00000004 => 'DF_1_GROUP',
			0x00000008 => 'DF_1_NODELETE',
			0x00000010 => 'DF_1_LOADFLTR',
			0x00000020 => 'DF_1_INITFIRST',
			0x00000040 => 'DF_1_NOOPEN',
			0x00000080 => 'DF_1_ORIGIN',
			0x00000100 => 'DF_1_DIRECT',
			0x00000200 => 'DF_1_TRANS',
			0x00000400 => 'DF_1_INTERPOSE',
			0x00000800 => 'DF_1_NODEFLIB',
			0x00001000 => 'DF_1_NODUMP',
			0x00002000 => 'DF_1_CONFALT',
			0x00004000 => 'DF_1_ENDFILTEE',
			0x00008000 => 'DF_1_DISPRELDNE',
			0x00010000 => 'DF_1_DISPRELPND',
			0x00020000 => 'DF_1_NODIRECT',
			0x00040000 => 'DF_1_IGNMULDEF',
			0x00080000 => 'DF_1_NOKSYMS',
			0x00100000 => 'DF_1_NOHDR',
			0x00200000 => 'DF_1_EDITED',
			0x00400000 => 'DF_1_NORELOC',
			0x00800000 => 'DF_1_SYMINTPOSE',
			0x01000000 => 'DF_1_GLOBAUDIT',
			0x02000000 => 'DF_1_SINGLETON',
		},
		'meaning' => {
			0x00000001 => 'perform complete relocation processing',
			0x00000002 => 'set RTLD_GLOBAL for this object',
			0x00000004 => 'indicate object is a member of a group',
			0x00000008 => 'object cannot be deleted from a process',
			0x00000010 => 'ensure immediate loading of filtees',
			0x00000020 => 'object\'s initialization occurs first',
			0x00000040 => 'object cannot be used with dlopen()',
			0x00000080 => '$ORIGIN processing required',
			0x00000100 => 'direct bindings enabled',
			0x00000200 => 'meaning unknown / undefined',
			0x00000400 => 'object is an interposer',
			0x00000800 => 'ignore the default library search path',
			0x00001000 => 'object cannot be dumped with dldump()',
			0x00002000 => 'object is a configuration alternative',
			0x00004000 => 'filtee terminates filter\'s search',
			0x00008000 => 'displacement relocation has been carried out at build time',
			0x00010000 => 'displacement relocation pending (to be applied at run-time)',
			0x00020000 => 'object contains non-direct bindings',
			0x00040000 => 'internal use',
			0x00080000 => 'internal use',
			0x00100000 => 'internal use',
			0x00200000 => 'object has been modified since originally built',
			0x00400000 => 'internal use',
			0x00800000 => 'individual symbol interposers exist for this object',
			0x01000000 => 'establish global auditing',
			0x02000000 => 'singleton symbols are used',
		},
	},
	'vd_version' => {
		'name' => {
			0 => 'VER_DEF_NONE',
			1 => 'VER_DEF_CURRENT',
		},
		'meaning' => {
			0 => 'invalid version',
			1 => 'current version',
		},
	},
	'vd_flags' => {
		'is_flag' => 1,
		'name' => {
			1 => 'VER_FLG_BASE',
			2 => 'VER_FLG_WEAK',
		},
		'meaning' => {
			1 => 'version definition of the file',
			2 => 'weak version identifier',
		},
	},
	'vd_ndx' => {
		'single_meaning' => 'version index numeric value referenced in the SHT_GNU_versym section',
	},
	'vd_cnt' => {
		'single_meaning' => 'number of associated verdaux array entries',
	},
	'vd_hash' => {
		'single_meaning' => 'version name hash value (ELF hash function)',
	},
	'vd_aux' => {
		'single_meaning' => 'offset in bytes to a corresponding entry in an array of Verdaux structures',
	},
	'vd_next' => {
		'single_meaning' => 'offset to the next verdef entry, in bytes',
	},
	'vda_name' => {
		'single_meaning' => 'offset to the version or dependency name string in the section header, in bytes',
	},
	'vda_next' => {
		'single_meaning' => 'offset to the next verdaux entry, in bytes',
	},
	'vn_version' => {
		'name' => {
			0 => 'VER_NEED_NONE',
			1 => 'VER_NEED_CURRENT',
		},
		'meaning' => {
			0 => 'invalid version',
			1 => 'current version',
		},
	},
	'vn_cnt' => {
		'single_meaning' => 'number of associated vernaux array entries',
	},
	'vn_file' => {
		'single_meaning' => 'offset to the file name string in the section header, in bytes',
	},
	'vn_aux' => {
		'single_meaning' => 'offset in bytes to a corresponding entry in an array of Vernaux structures',
	},
	'vn_next' => {
		'single_meaning' => 'offset to the next verneed entry, in bytes',
	},
	'vna_hash' => {
		'single_meaning' => 'dependency name hash value (ELF hash function)',
	},
	'vna_flags' => {
		'is_flag' => 1,
		'name' => {
			2 => 'VER_FLG_WEAK',
			4 => 'VER_FLG_INFO',
		},
		'meaning' => {
			2 => 'weak version identifier',
			4 => 'this reference exists for informational purposes, and need not be validated at runtime',
		},
	},
	'vna_other' => {
		'single_meaning' => 'object file version identifier used in the .gnu.version symbol version array',
	},
	'vna_name' => {
		'single_meaning' => 'offset to the dependency name string in the section header, in bytes',
	},
	'vna_next' => {
		'single_meaning' => 'offset to the next vernaux entry, in bytes',
	},
	'namesz' => {
		'single_meaning' => 'size, in bytes, of the note name, i.e. a null-terminated character representation of the entry\'s owner or originator',
	},
	'descsz' => {
		'single_meaning' => 'size, in bytes, of the note descriptor (excluding the padding required to align the next note, if any)',
	},
	'type' => {
		'single_meaning' => 'interpretation of the descriptor -- not specified by the SysV ABI',
	},
	'r_offset' => {
		'single_meaning' => 'location at which to apply the relocation action',
	},
	'r_info' => {
		'single_meaning' => 'symbol index (r_sym) and relocation type (r_type)',
	},
	'r_sym' => {
		'single_meaning' => 'symbol table index with respect to which the relocation must be made',
	},
	'r_type' => {
		'single_meaning' => 'the meaning is processor-specific',
	},
	'r_type_EM_386' => {
		'name' => {
			0  => 'R_386_NONE',
			1  => 'R_386_32',
			2  => 'R_386_PC32',
			3  => 'R_386_GOT32',
			4  => 'R_386_PLT32',
			5  => 'R_386_COPY',
			6  => 'R_386_GLOB_DAT',
			7  => 'R_386_JMP_SLOT',
			8  => 'R_386_RELATIVE',
			9  => 'R_386_GOTOFF',
			10 => 'R_386_GOTPC',
		},
		'meaning' => {
			0  => relocationMeaning(R_FIELD_NONE,   'none'),
			1  => relocationMeaning(R_FIELD_WORD32, 'S + A'),
			2  => relocationMeaning(R_FIELD_WORD32, 'S + A - P'),
			3  => relocationMeaning(R_FIELD_WORD32, 'G + A - P',   'computes the distance from the base of the GOT to the symbol\'s GOT entry'),
			4  => relocationMeaning(R_FIELD_WORD32, 'L + A - P',   'computes the address of the symbol\'s PLT entry'),
			5  => relocationMeaning(R_FIELD_NONE,   'none',        'relocation type for dynamic linking: its offset refers to a location in a writable segment'),
			6  => relocationMeaning(R_FIELD_WORD32, 'S',           'used to set a GOT entry to the address of the specified symbol'),
			7  => relocationMeaning(R_FIELD_WORD32, 'S',           'relocation type for dynamic linking: its offset gives the location of a PLT entry'),
			8  => relocationMeaning(R_FIELD_WORD32, 'B + A',       'relocation type for dynamic linking: its offset gives a location within a shared object that contains a value representing a relative address'),
			9  => relocationMeaning(R_FIELD_WORD32, 'S + A - GOT', 'computes the difference between a symbol\'s value and the address of the GOT'),
			10 => relocationMeaning(R_FIELD_WORD32, 'GOT + A - P', 'resembles R_386_PC32 except it uses the address of teh global offset table in its calculation'),
		},
	},
	'r_type_EM_X86_64' => {
		'name' => {
			0  => 'R_X86_64_NONE',
			1  => 'R_X86_64_64',
			2  => 'R_X86_64_PC32',
			3  => 'R_X86_64_GOT32',
			4  => 'R_X86_64_PLT32',
			5  => 'R_X86_64_COPY',
			6  => 'R_X86_64_GLOB_DAT',
			7  => 'R_X86_64_JUMP_SLOT',
			8  => 'R_X86_64_RELATIVE',
			9  => 'R_X86_64_GOTPCREL',
			10 => 'R_X86_64_32',
			11 => 'R_X86_64_32S',
			12 => 'R_X86_64_16',
			13 => 'R_X86_64_PC16',
			14 => 'R_X86_64_8',
			15 => 'R_X86_64_PC8',
			16 => 'R_X86_64_DPTMOD64',
			17 => 'R_X86_64_DTPOFF64',
			18 => 'R_X86_64_TPOFF64',
			19 => 'R_X86_64_TLSGD',
			20 => 'R_X86_64_TLSLD',
			21 => 'R_X86_64_DTPOFF32',
			22 => 'R_X86_64_GOTTPOFF',
			23 => 'R_X86_64_TPOFF32',
			24 => 'R_X86_64_PC64',
			25 => 'R_X86_64_GOTOFF64',
			26 => 'R_X86_64_GOTPC32',
			27 => 'R_X86_64_GOT64',
			28 => 'R_X86_64_GOTPCREL64',
			29 => 'R_X86_64_GOTPC64',
			30 => 'R_X86_64_GOTPLT64',
			31 => 'R_X86_64_PLTOFF64',
			32 => 'R_X86_64_SIZE32',
			33 => 'R_X86_64_SIZE64',
		},
		'meaning' => {
			0  => relocationMeaning(R_FIELD_NONE,   'none'),
			1  => relocationMeaning(R_FIELD_WORD64, 'S + A'),
			2  => relocationMeaning(R_FIELD_WORD32, 'S + A - P'),
			3  => relocationMeaning(R_FIELD_WORD32, 'G + A',           'computes the distance from the base of the GOT to the symbol\'s GOT entry'),
			4  => relocationMeaning(R_FIELD_WORD32, 'L + A - P',       'computes the address of the symbol\'s PLT entry'),
			5  => relocationMeaning(R_FIELD_NONE,   'none',            'relocation type for dynamic linking: its offset refers to a location in a writable segment'),
			6  => relocationMeaning(R_FIELD_WORD64, 'S',               'used to set a GOT entry to the address of the specified symbol'),
			7  => relocationMeaning(R_FIELD_WORD64, 'S',               'relocation type for dynamic linking: its offset gives the location of a PLT entry'),
			8  => relocationMeaning(R_FIELD_WORD64, 'B + A',           'relocation type for dynamic linking: its offset gives a location within a shared object that contains a value representing a relative address'),
			9  => relocationMeaning(R_FIELD_WORD32, 'G + GOT + A - P', 'gives the difference between the location in the GOT where the symbol\'s address is given and the location where the relocation is applied'),
			10 => relocationMeaning(R_FIELD_WORD32, 'S + A',           'truncates the computed value to 32-bits'),
			11 => relocationMeaning(R_FIELD_WORD32, 'S + A',           'truncates the computed value to 32-bits'),
			12 => relocationMeaning(R_FIELD_WORD16, 'S + A',           'this relocation is not conformant to the AMD64 ABI'),
			13 => relocationMeaning(R_FIELD_WORD16, 'S + A - P',       'this relocation is not conformant to the AMD64 ABI'),
			14 => relocationMeaning(R_FIELD_WORD8,  'S + A - P',       'this relocation is not conformant to the AMD64 ABI'),
			15 => relocationMeaning(R_FIELD_WORD8,  'S + A - P',       'this relocation is not conformant to the AMD64 ABI'),
			16 => relocationMeaning(R_FIELD_WORD64, '',                'part of the Thread-Local Storage ABI extensions'),
			17 => relocationMeaning(R_FIELD_WORD64, '',                'part of the Thread-Local Storage ABI extensions'),
			18 => relocationMeaning(R_FIELD_WORD64, '',                'part of the Thread-Local Storage ABI extensions'),
			19 => relocationMeaning(R_FIELD_WORD32, '',                'part of the Thread-Local Storage ABI extensions'),
			20 => relocationMeaning(R_FIELD_WORD32, '',                'part of the Thread-Local Storage ABI extensions'),
			21 => relocationMeaning(R_FIELD_WORD32, '',                'part of the Thread-Local Storage ABI extensions'),
			22 => relocationMeaning(R_FIELD_WORD32, '',                'part of the Thread-Local Storage ABI extensions'),
			23 => relocationMeaning(R_FIELD_WORD32, '',                'part of the Thread-Local Storage ABI extensions'),
			24 => relocationMeaning(R_FIELD_WORD64, 'S + A - P'),
			25 => relocationMeaning(R_FIELD_WORD64, 'S + A - GOT'),
			26 => relocationMeaning(R_FIELD_WORD32, 'GOT + A - P'),
			27 => relocationMeaning(R_FIELD_WORD64, 'G + A',           'large model relocation type'),
			28 => relocationMeaning(R_FIELD_WORD64, 'G + GOT - P + A', 'large model relocation type'),
			29 => relocationMeaning(R_FIELD_WORD64, 'GOT - P + A',     'large model relocation type'),
			30 => relocationMeaning(R_FIELD_WORD64, 'G + A',           'large model relocation type'),
			31 => relocationMeaning(R_FIELD_WORD64, 'L - GOT + A',     'large model relocation type'),
			32 => relocationMeaning(R_FIELD_WORD32, 'Z + A'),
			33 => relocationMeaning(R_FIELD_WORD64, 'Z + A'),
		},
	},
	'r_addend' => {
		'single_meaning' => 'constant addend used to compute the value to be stored into the relocatable field',
	},
	'nbuckets' => {
		'single_meaning' => 'number of hash buckets',
	},
	'symndx' => {
		'single_meaning' => 'index of the first symbol in the dynamic symbol table that is to be accessible via the hash table',
	},
	'maskwords' => {
		'single_meaning' => 'number of ELFCLASS sized words in the Bloom filter portion of the hash table section',
	},
	'shift2' => {
		'single_meaning' => 'a shift count used by the Bloom filter',
	},
	'p_type' => {
		'name' => {
			0 => 'PT_NULL',
			1 => 'PT_LOAD',
			2 => 'PT_DYNAMIC',
			3 => 'PT_INTERP',
			4 => 'PT_NOTE',
			5 => 'PT_SHLIB',
			6 => 'PT_PHDR',
			7 => 'PT_TLS',
			# Additional segment types defined by the LSB.
			0x6474e550 => 'PT_GNU_EH_FRAME',
			0x6474e551 => 'PT_GNU_STACK',
			0x6474e552 => 'PT_GNU_RELRO',
		},
		'meaning' => {
			0 => 'unused / ignored entry',
			1 => 'loadable segment, described by p_filesz and p_memsz',
			2 => 'dynamic linking information',
			3 => 'location and size of a null-terminated path name to invoke as an interpreter',
			4 => 'location an size of auxiliary information',
			5 => 'reserved segment type / unspecified semantics',
			6 => 'location and size of the program header table itself',
			7 => 'Thread-Local Storage template',
			0x6474e550 => 'location and size of the exception handling information as defined by the .eh_frame_hdr section',
			0x6474e551 => 'specifies, through p_flags, whether the stack should be executable (default is yes)',
			0x6474e552 => 'location and size of a segment which may be made read-only after relocations have been applied',
		},
		'ranges' => {
			'OS' => {
				'meaning' => 'operating system-specific semantics',
				'low_value' => 0x60000000,
				'low_name' => 'PT_LOOS',
				'high_value' => 0X6FFFFFFF,
				'high_name' => 'PT_HIOS',
			},
			'PROC' => {
				'meaning' => 'processor-specific semantics',
				'low_value' => 0x70000000,
				'low_name' => 'PT_LOPROC',
				'high_value' => 0X7FFFFFFF,
				'high_name' => 'PT_HIPROC',
			},
		},
	},
	'p_flags' => {
		'is_flag' => 1,
		'name' => {
			1 => 'PF_X',
			2 => 'PF_W',
			4 => 'PF_R',
			0X0FF00000 => 'PF_MASKOS',
			0xf0000000 => 'PF_MASKPROC',
		},
		'meaning' => {
			1 => 'execute',
			2 => 'write',
			4 => 'read',
			0X0FF00000 => 'system-specific semantics',
			0xf0000000 => 'processor-specific semantics',
		},
		'_allowable' => {
			1 => 'read permission',
			2 => 'read and execute permissions',
			3 => 'read permission',
			4 => 'execute permission',
			6 => 'execute permission',
		},
	},
	'p_offset' => {
		'single_meaning' => 'offset from the beginning of the file at which the first byte of the segment resides',
	},
	'p_vaddr' => {
		'single_meaning' => 'virtual address at which the first byte of the segment resides in memory',
	},
	'p_paddr' => {
		'single_meaning' => 'segment\'s physical address, if relevant',
	},
	'p_filesz' => {
		'single_meaning' => 'number of bytes in the file image of the segment',
	},
	'p_memsz' => {
		'single_meaning' => 'number of bytes in the memory image of the segment',
	},
	'p_align' => {
		'single_meaning' => 'value to which the segments are aligned in memory and in the file',
	},
};

my $columns = 80;
$SIG{'WINCH'} = \&updateNumberOfColumns;
updateNumberOfColumns();

# Simple arguments parsing: require one and exactly one argument.
usage() if @ARGV != 1;

# That arguments is expected to be an existing, readable file.
our $elf_filepath = $ARGV[0];
unless (-f $elf_filepath && -r $elf_filepath) {
	exitWithMessage(120, sprintf('%s either does not exist or is not readable.', $elf_filepath));
}

coloralias('main_action', 'bright_yellow');
coloralias('error', 'red');
coloralias('index', 'bright_white');
coloralias('offset', 'yellow');
coloralias('value_name', 'bright_white');
coloralias('value', 'cyan');
coloralias('name', 'green');
coloralias('flag_name', 'green');
coloralias('flag_value', 'cyan');

my $default_class     = ELFCLASS64;
my $default_endianess = LITTLE_ENDIAN;

# Open the file
our $elf_fh;
our $opening = open($elf_fh, '<' . $elf_filepath);
if (!$opening) {
	exitWithMessage(115, sprintf('Unable to open %s: %s', $elf_filepath, $!));
}
binmode($elf_fh);
walkELF($elf_fh);
close($elf_fh);
exit(0);

sub updateNumberOfColumns {
	$columns = numberOfColumns();
}

sub numberOfColumns {
	{
		$SIG{__WARN__} = sub { };
		require "sys/ioctl.ph";
	}
	# Taken from perldoc -q "get the screen size".
	my $tty_fh;
	my $winsize;
	if (!open($tty_fh, '+</dev/tty')) {
		return 'notty';
	}
	if (!ioctl($tty_fh, &TIOCGWINSZ, $winsize)) {
		return 'notty';
	}
	my ($row, $col, $xpixel, $ypixel) = unpack('S4', $winsize);
	return $col;
}

sub say {
	my $indent = shift;
	$indent = '  'x$indent;
	my $string;
	for my $str (@_) {
		next if (!defined($str));
		$string = $str;
		$string =~ s/^/${indent}/mgs;
		print $string . "\n";
	}
}

sub sayf {
	my $indent = shift;
	my $string = shift;
	my @args = @_;
	say($indent, sprintf($string, @args));
}

sub announceOperation {
	my ($ind, $prefix, $index, $suffix) = @_;
	say(
		$ind,
		colored($prefix . ' #', 'underline') .
		colored($index, 'index underline') .
		colored($suffix, 'underline')
	);
}

sub exitWithMessage {
	my $rc = $_[0];
	my $message = $_[1];
	print "Error: " unless ($rc == 0);
	print $message . "\n";
	exit($rc);
}

sub usage {
	print "Usage: ${0} file\n";
	exit(125);
}

sub setDefaultEndianess {
	$default_endianess = shift;
}

sub setDefaultClass {
	$default_class = shift;
}

sub typeInfo {
	my $type = shift;
	my $class = shift;

	$class = $default_class if (!defined($class));

	if (!exists($elf_data_types->{$class})) {
		exitWithMessage(150, "tried to work with unknown ELF class (${class})");
	}

	if (!exists($elf_data_types->{$class}->{$type})) {
		exitWithMessage(148, "tried to work with unknown type (${type})");
	}

	return $elf_data_types->{$class}->{$type};
}

# Expected arguments:
#   - file descriptor
#   - type, e.g. 'Half', 'Word', 'Addr', etc.
#   - ELF Class, e.g. ELFCLASS32 or ELFCLASS64
#   - endianess: either LITTLE_ENDIAN or BIG_ENDIAN
sub readELFValue {
	my $fd = shift;
	my $type = shift;
	my $class = shift;
	my $endianess = shift;

	# Default values.
	$class = $default_class if (!defined($class));
	$endianess = $default_endianess if (!defined($endianess));

	my $type_info = typeInfo($type, $class);

	return readValue(
		$fd,
		$type_info->{'size'},
		$type_info->{'signed'},
		$endianess
	);
}

# Expected arguments:
#   - file descriptor
#   - amount of bytes to read and parse
#   - 0 for unsigned, 1 for signed
#   - endianess: either LITTLE_ENDIAN or BIG_ENDIAN
sub readValue {
	my $fd = shift;
	my $size = shift;
	my $signed = shift;
	my $endianess = shift;

	# Check arguments.
	if (!defined($signed) || ($signed != SIGNED && $signed != UNSIGNED)) {
		exitWithMessage(8, 'attempted to call readValue with invalid signedness');
	}
	if (!defined($endianess) || ($endianess != LITTLE_ENDIAN && $endianess != BIG_ENDIAN)) {
		exitWithMessage(8, 'attempted to call readValue with invalid endianess');
	}

	my $data;
	my $reading = read($fd, $data, $size);
	if (!defined($reading)) {
		exitWithMessage(6, sprintf("error when attempting to read %d bytes: %s.", $size, $!));
	}
	elsif ($reading == 0) {
		exitWithMessage(5, "reached end of file.");
	}
	elsif ($reading != $size) {
		exitWithMessage(4, sprintf("was asked to read %d bytes but could only read %d.", $size, $reading));
	}

	my $unpack_arg;
	if ($size == 1) {
		$unpack_arg = ($signed ? 'c' : 'C');
	}
	elsif ($size == 2 || $size == 4) {
		# n stands for "network" (i.e. big-endian)
		# v stands for "VAX" (i.e. little-endian)
		$unpack_arg = $endianess ? 'n' : 'v';
		# lowercase is 16 bits, uppercase is 32 bits
		$unpack_arg = uc($unpack_arg) if ($size == 4);
		$unpack_arg .= '!' if ($signed);
	}
	elsif ($size == 8) {
		$unpack_arg = ($signed ? 'q' : 'Q') . ($endianess ? '>' : '<');
	}
	return unpack($unpack_arg, $data);
	# Unpack cheat sheet:
	#   n  An unsigned short (16-bit) in "network" (big-endian) order.
	#   N  An unsigned long (32-bit) in "network" (big-endian) order.
	#   v  An unsigned short (16-bit) in "VAX" (little-endian) order.
	#   V  An unsigned long (32-bit) in "VAX" (little-endian) order
	#   n! A signed short (16-bit) in "network" (big-endian) order.
	#   N! A signed long (32-bit) in "network" (big-endian) order.
	#   v! A signed short (16-bit) in "VAX" (little-endian) order.
	#   V! A signed long (32-bit) in "VAX" (little-endian) order
	#   q> A signed quad (64-bit) value (big-endian).
	#   Q> An unsigned quad (64-bit) value (big-endian).
	#   q< A signed quad (64-bit) value (little-endian).
	#   Q< An unsigned quad (64-bit) value (little-endian).
}

sub formatValue {
	my $value = shift;
	my $type = shift;
	my $class = shift;

	my $type_info = typeInfo($type, $class);
	return sprintf($type_info->{'formatter'}, $value);
}

sub formatOffset {
	my $offset = shift;
	$offset = formatValue($offset, 'Off');
	return colored($offset, 'offset');
}

sub formatFlags {
	my @flags = sort(@_);
	my $flags_string = join('+', @flags) || '-';
	return $flags_string;
}

sub formatVersionDefinition {
	my $verdef = shift;

	my @verdef_strings;
	for (my $i = 0; $i < $verdef->{'vd_cnt'}; ++ $i) {
		push(@verdef_strings, $verdef->{'verdaux'}->{$i}->{'name'});
	}
	my $str = shift(@verdef_strings);
	if (scalar(@verdef_strings)) {
		$str .= sprintf(' (deps: %s)', join(', ', @verdef_strings));
	}
	return $str;
}

sub formatVersionRequirement {
	my $vernaux = shift;

	my $verneed_string = sprintf(
		'%s (%s)',
		$vernaux->{'name'},
		$vernaux->{'verneed'}->{'file'}
	);
	return $verneed_string;
}

sub walkELF {
	my $elf_fh = shift;

	my $elf_data = {};
	say(0, colored("Parsing file header", 'main_action'));
	walkELFHeaderMagicNumber($elf_fh, $elf_data);
	walkELFHeaderIdent($elf_fh, $elf_data);
	walkELFHeaderFields($elf_fh, $elf_data);
	walkELFSections($elf_fh, $elf_data);
	walkELFProgramHeader($elf_fh, $elf_data);
}

sub analyseMachineSpecificValue {
	my $table_name = shift;
	my $value = shift;
	my $machine = shift;

	if (defined($machine)) {
		# Does $machine look like a machine name?
		if ($machine !~ m#^EM_#) {
			# Seems not -- assume $machine is a e_machine value instead.
			$machine = $elf->{'e_machine'}->{'name'}->{$machine};
		}
		# Ensure $machine does look like a machine name now.
		if (defined($machine) && $machine =~ m#^EM_#) {
			my $machine_table_name = $table_name . '_' . $machine;
			if (exists($elf->{$machine_table_name})) {
				return analyseValue($machine_table_name, $value);
			}
		}
	}
	return analyseValue($table_name, $value);
}

sub analyseValue {
	my $table_name = shift;
	my $value = shift;

	return undef if (!exists($elf->{$table_name}));
	my $table= $elf->{$table_name};

	my $return = {};

	# Can we get a single, systematic meaning?
	if (exists($table->{'single_meaning'})) {
		$return->{'single_meaning'} = $table->{'single_meaning'};
	}

	# Are we dealing with a set of flags, by chance?
	if (exists($table->{'is_flag'}) && $table->{'is_flag'}) {
		$return->{'is_flag'} = 1;
		$return->{'name'} = {};
		$return->{'meaning'} = {};
		for my $mask (keys(%{$table->{'name'}})) {
			if (($value & $mask) == $mask) {
				$return->{'name'}->{$mask}    = $table->{'name'}->{$mask};
				$return->{'meaning'}->{$mask} = $table->{'meaning'}->{$mask};
			}
		}
		return $return;
	}

	# Can we get a value-specific name and/or a meaning?
	for my $info ('name', 'meaning') {
		if (exists($table->{$info})) {
			if (exists($table->{$info}->{$value})) {
				$return->{$info} = $table->{$info}->{$value};
			}
		}
	}
	return $return if (keys(%$return));

	# It seems we got neither a name nor a meaning.
	# => Try ranges now.
	if (exists($table->{'ranges'})) {
		for my $range (keys(%{$table->{'ranges'}})) {
			my $lo = $table->{'ranges'}->{$range}->{'low_value'};
			my $hi = $table->{'ranges'}->{$range}->{'high_value'};
			if ($value >= $lo && $value <= $hi) {
				return $table->{'ranges'}->{$range};
			}
		}
	}

	# Still there? Seriously? Well, is this fatal?
	if (exists($table->{'unexplained_value'})) {
		$return->{'unexplained_value'} = $table->{'unexplained_value'};
		return $return;
	}
	return undef;
}

sub analyseAndExplainValue {
	my $value = shift;
	my $table_name = shift;
	my $type = shift;
	my $class = shift;
	my $machine = shift;

	my $analysis = analyseMachineSpecificValue($table_name, $value, $machine);
	return undef if (!defined($analysis));

	my $str = sprintf(
		'%s is %s',
		colored($table_name, 'value_name'),
		colored(formatValue($value, $type, $class), 'value')
	);

	if (exists($analysis->{'single_meaning'})) {
		$str .= sprintf(' -- %s.', $analysis->{'single_meaning'});
	}
	elsif (exists($analysis->{'is_flag'})) {
		if (keys(%{$analysis->{'name'}})) {
			$str .= ", i.e. the following flags:";
			for my $mask (keys(%{$analysis->{'name'}})) {
				$str .= sprintf(
					"\n  - %s (%s): %s",
					colored($analysis->{'name'}->{$mask}, 'flag_name'),
					colored(formatValue($mask, $type, $class), 'flag_value'),
					$analysis->{'meaning'}->{$mask}
				);
			}
		}
		else {
			$str .= " (no flags)";
		}
	}
	elsif (exists($analysis->{'name'}) && exists($analysis->{'meaning'})) {
		$str .= sprintf(
			', i.e. %s: %s.',
			colored($analysis->{'name'}, 'name'),
			$analysis->{'meaning'}
		);
	}
	elsif (exists($analysis->{'low_value'})) {
		$str .= sprintf(
			', between %s (%s) and %s (%s): %s',
			$analysis->{'low_name'},
			formatValue($analysis->{'low_value'}, $type, $class),
			$analysis->{'high_name'},
			formatValue($analysis->{'high_value'}, $type, $class),
			$analysis->{'meaning'}
		);
	}
	elsif (exists($analysis->{'unexplained_value'})) {
		if ($analysis->{'unexplained_value'} eq 'fatal') {
			$str .= ", which is illegal, aborting.";
			exitWithMessage(100, $str);
		}
		elsif ($analysis->{'unexplained_value'} eq 'warning') {
			$str .= ", which is probably abnormal, ignoring.";
		}
		else {
			$str .= ", " . $analysis->{'unexplained_value'} . ".";
		}
	}
	return $str;
}

sub analyseSectionName {
	my $name = shift;
	my $return = {};
	my @specs = (
		'gnu_special_sections',
		'lsb5-core-amd64_additional_special_sections',
		'lsb5-core-generic_additional_special_sections',
		'elf_special_sections',
	);
	for my $desc_type ('short', 'long') {
		for my $sections_type (@specs) {
			my $known_sections = $elf->{$sections_type}->{$desc_type};
			if (exists($known_sections->{$name})) {
				$return->{$desc_type} = $known_sections->{$name};
				last;
			}
			elsif (exists($known_sections->{'.rel'})) {
				if ($name =~ m/^\.rela?\.(.+)$/) {
					$return->{$desc_type} = sprintf($known_sections->{'.rel'}, $1);
				}
			}
		}
	}
	return $return;
}

sub walkELFHeaderMagicNumber {
	my $elf_fh = shift;

	for (my $i = 0; $i < 4; ++ $i) {
		my $byte = sprintf('EI_MAG%d', $i);
		my $expected = $elf->{'e_ident'}->{'_magic'}->{$byte};
		my $value = readELFValue($elf_fh, 'char');

		my $msg = sprintf(
			'%s is %s (%s)',
			colored($byte, 'value_name'),
			colored(sprintf('0x%02X', $value), 'value'),
			colored(sprintf('%c',     $value), 'value')
		);

		if ($value == $expected) {
			say(2, $msg . ' as expected.');
		}
		else {
			$msg .= sprintf(
				' but %s (%s) was expected, aborting.',
				colored(sprintf('0x%02X', $expected), 'value'),
				colored(sprintf('%c',     $expected), 'value')
			);
			exitWithMessage(100, $msg);
		}
	}
}

sub walkELFHeaderIdent {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $data;

	my $ind = 2;
	for my $entry (@{$elf_structures->{'e_ident'}->{'ordered_entries'}}) {
		my $type = $elf_structures->{'e_ident'}->{$entry};
		my $value = $elf_data->{$entry} = readELFValue($elf_fh, $type);
		say($ind, analyseAndExplainValue($elf_data->{$entry}, $entry, $type));

		if ($entry eq 'EI_CLASS') {
			setDefaultClass($value);
		}
		elsif ($entry eq 'EI_DATA') {
			# Our constants are "0 and 1" vs "1 and 2" for the ELF format.
			setDefaultEndianess($value - 1);
		}
	}

	my $ei_nident = $elf->{'e_ident'}->{'_constants'}->{'EI_NIDENT'};
	sayf($ind, 'Seeking to offset EI_NIDENT (%d)', $ei_nident);
	seek($elf_fh, $ei_nident, Fcntl::SEEK_SET);
}

sub walkELFHeaderFields {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $data;

	my $ind = 1;
	for my $entry (@{$elf_structures->{'file_header'}->{'ordered_entries'}}) {
		my $type = $elf_structures->{'file_header'}->{$entry};
		my $value = $elf_data->{$entry} = readELFValue($elf_fh, $type);
		say($ind, analyseAndExplainValue($elf_data->{$entry}, $entry, $type));

		if ($entry eq 'e_entry') {
			say($ind + 1, 'Here, zero means there is no entry point.') if (!$value);
		}
		elsif ($entry eq 'e_phnum') {
			my $e_phentsize = $elf_data->{'e_phentsize'};
			sayf($ind + 1, 'conclusion: the program header table is %d x %d = %d bytes', $e_phentsize, $value, $e_phentsize * $value);
		}
		elsif ($entry eq 'e_shnum') {
			my $e_shentsize = $elf_data->{'e_shentsize'};
			sayf($ind + 1, 'conclusion: sections are indexed from 0 (this index is reserved though) to %d.', $value - 1);
			sayf($ind + 1, 'conclusion: the section header table is %d x %d = %d bytes', $e_shentsize, $value, $e_shentsize * $value);
		}
	}
}

sub walkELFSections {
	my $elf_fh = shift;
	my $elf_data = shift;

	say(0, colored('Parsing section headers', 'main_action'));
	$elf_data->{'sections'} = {};
	my $ind = 1;

	announceOperation($ind, 'Parsing section header', $elf_data->{'e_shstrndx'}, ' first as it holds section names:');
	walkELFSectionHeader($elf_fh, $elf_data, $elf_data->{'e_shstrndx'});

	announceOperation($ind, 'Not parsing section header', 0, ' as it is reserved.');
	say($ind + 1, "It must contain only zeroes.");

	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		next if ($i == $elf_data->{'e_shstrndx'});
		announceOperation($ind, 'Parsing section header', $i, ':');
		walkELFSectionHeader($elf_fh, $elf_data, $i);
	}

	# Display a summary of sections.
	say(0, colored('Summary of sections', 'main_action'));
	my $table_format = '%3s %-30s %-20s %-30s %-30s';
	sayf(1, $table_format, '#', 'Name', 'Type', 'Flags', 'Use');
	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		my $section = $elf_data->{'sections'}->{$i};
		sayf(
			1,
			$table_format,
			$i,
			colored($section->{'_name'}, 'name'),
			$section->{'_type'},
			formatFlags(values(%{$section->{'_flags'}})),
			analyseSectionName($section->{'_name'})->{'short'}
		);
	}

	say(0, colored('Parsing SHT_HASH sections', 'main_action'));
	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		my $section = $elf_data->{'sections'}->{$i};
		if ($section->{'_type'} eq 'SHT_HASH') {
			announceOperation($ind, 'Parsing section', $i, ' (' . colored($section->{'_name'}, 'name') . '):');
			walkHashSection($elf_fh, $elf_data, $i);
		}
	}

	say(0, colored('Parsing SHT_GNU_HASH sections', 'main_action'));
	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		my $section = $elf_data->{'sections'}->{$i};
		if ($section->{'_type'} eq 'SHT_GNU_HASH') {
			announceOperation($ind, 'Parsing section', $i, ' (' . colored($section->{'_name'}, 'name') . '):');
			walkGnuHashSection($elf_fh, $elf_data, $i);
		}
	}

	say(0, colored('Parsing SHT_DYNSYM sections', 'main_action'));
	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		my $section = $elf_data->{'sections'}->{$i};
		if ($section->{'_type'} eq 'SHT_DYNSYM') {
			announceOperation($ind, 'Parsing section', $i, ' (' . colored($section->{'_name'}, 'name') . '):');
			walkSymbolTable($elf_fh, $elf_data, $i);
			push(@{$elf_data->{'SHT_DYNSYM'}}, $i);
		}
	}

	say(0, colored('Parsing SHT_REL/SHT_RELA sections', 'main_action'));
	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		my $section = $elf_data->{'sections'}->{$i};
		if ($section->{'_type'} =~ '^SHT_RELA?$') {
			announceOperation($ind, 'Parsing section', $i, ' (' . colored($section->{'_name'}, 'name') . '):');
			walkRela($elf_fh, $elf_data, $i);
		}
	}

	say(0, colored('Parsing SHT_DYNAMIC sections', 'main_action'));
	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		my $section = $elf_data->{'sections'}->{$i};
		if ($section->{'_type'} eq 'SHT_DYNAMIC') {
			announceOperation($ind, 'Parsing section', $i, ' (' . colored($section->{'_name'}, 'name') . '):');
			walkDynamic($elf_fh, $elf_data, $i);
		}
	}

	say(0, colored('Parsing SHT_GNU_verdef sections', 'main_action'));
	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		my $section = $elf_data->{'sections'}->{$i};
		if ($section->{'_type'} eq 'SHT_GNU_verdef') {
			announceOperation($ind, 'Parsing section', $i, ' (' . colored($section->{'_name'}, 'name') . '):');
			walkVerdef($elf_fh, $elf_data, $i);
		}
	}

	say(0, colored('Parsing SHT_GNU_verneed sections', 'main_action'));
	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		my $section = $elf_data->{'sections'}->{$i};
		if ($section->{'_type'} eq 'SHT_GNU_verneed') {
			announceOperation($ind, 'Parsing section', $i, ' (' . colored($section->{'_name'}, 'name') . '):');
			walkVerneed($elf_fh, $elf_data, $i);
		}
	}

	say(0, colored('Parsing SHT_GNU_versym sections', 'main_action'));
	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		my $section = $elf_data->{'sections'}->{$i};
		if ($section->{'_type'} eq 'SHT_GNU_versym') {
			announceOperation($ind, 'Parsing section', $i, ' (' . colored($section->{'_name'}, 'name') . '):');
			walkVersym($elf_fh, $elf_data, $i);
		}
	}

	say(0, colored('Parsing SHT_NOTE sections', 'main_action'));
	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		my $section = $elf_data->{'sections'}->{$i};
		if ($section->{'_type'} =~ '^SHT_NOTE$') {
			announceOperation($ind, 'Parsing section', $i, ' (' . colored($section->{'_name'}, 'name') . '):');
			walkNote($elf_fh, $elf_data, $i);
		}
	}

	say(0, colored('Parsing known SHT_PROGBITS sections', 'main_action'));
	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		my $section = $elf_data->{'sections'}->{$i};
		if ($section->{'_type'} eq 'SHT_PROGBITS') {
			if ($section->{'_name'} =~ m/^\.interp/) {
				announceOperation($ind, 'Parsing section', $i, ' (' . colored($section->{'_name'}, 'name') . '):');
				walkProgBits($elf_fh, $elf_data, $i);
			}
		}
	}
}

sub walkELFSectionHeader {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;

	my $ind = 2;
	my $offset = sectionOffset($elf_data, $section_index);
	sayf(
		$ind,
		"Seeking to offset shoff + (%s x shentsize) = %s.",
		colored($section_index, 'index'),
		formatOffset($offset)
	);
	seek($elf_fh, $offset, Fcntl::SEEK_SET);

	my $arch = $elf_data->{'EI_CLASS'};
	my $sh_info = $elf_structures->{'section_header'}->{$arch};

	my $type, my $value, my $details;
	my $section = {};
	# For each entry of a section header...
	for my $entry (@{$sh_info->{'ordered_entries'}}) {
		# get its type, ...
		$type = $sh_info->{$entry};
		# read its value, ...
		$value = $section->{$entry} = readELFValue($elf_fh, $type);
		# and provide a first basic explanation.
		say($ind, analyseAndExplainValue($section->{$entry}, $entry, $type));

		# For some entries, complete with extra explanations or details.
		my $details = '';
		if ($entry eq 'sh_type') {
			# Store the human name of the type for later use.
			my $analysis = analyseValue('sh_type', $value);
			$section->{'_type'} = $analysis->{'name'} if (exists($analysis->{'name'}));
			if (exists($analysis->{'high_name'})) {
				$section->{'_type'} = $analysis->{'low_name'} . '-' . $analysis->{'high_name'};
			}
		}
		elsif ($entry eq 'sh_flags') {
			# Store the human names of the flags for later use.
			my $analysis = analyseValue('sh_flags', $value);
			$section->{'_flags'} = $analysis->{'name'} if (exists($analysis->{'name'}));
		}
		elsif ($entry eq 'sh_addr') {
			$details = "Here, zero means the section is not allocated to the memory image of the program." if (!$value);
		}
		elsif ($entry eq 'sh_size') {
			if ($section->{'sh_type'} == 8) { # 8 is SHT_NOBITS
				$details = sprintf(
					"Here, since this is a %s section, this is the amount of space\noccupied in memory, not in the file.",
					colored('SHT_NOBITS', 'name')
				);
			}
		}
		elsif ($entry eq 'sh_link') {
			if ($value) {
				# sh_link is non-zero -- what is the use of this link, considering the current section type?
				my $sh_type = $section->{'sh_type'};
				if (exists($elf->{'sh_link'}->{'_use'}->{$sh_type})) {
					$details = sprintf(
						"Here, since this is a %s section, section #%s should be \n%s",
						colored($elf->{'sh_type'}->{'name'}->{$sh_type}, 'name'),
						colored($section->{'sh_link'}, 'index'),
						$elf->{'sh_link'}->{'_use'}->{$sh_type}
					);
				}
			}
		}
		elsif ($entry eq 'sh_info') {
			if ($value) {
				# sh_info is non-zero -- what is the use of this info, considering the current section type?
				my $sh_type = $section->{'sh_type'};
				if (exists($elf->{'sh_info'}->{'_use'}->{$sh_type})) {
					$details = sprintf(
						"Here, since this is a %s section, this should represent \n%s.",
						colored($elf->{'sh_type'}->{'name'}->{$sh_type}, 'name'),
						$elf->{'sh_info'}->{'_use'}->{$sh_type}
					);
				}
			}
		}
		elsif ($entry eq 'sh_addralign') {
			$details = 'This value must be a power of two.';
			if (!isPowerOfTwo($section->{'sh_addralign'})) {
				$details = colored($details, 'error');
			}
		}
		say($ind + 1, $details) if (length($details));
	}

	# Push the resulting section into $elf_data so readStringFromSection() can
	# leverage it.
	$section->{'_index'} = $section_index;
	$elf_data->{'sections'}->{$section_index} = $section;

	my $name = displayStringFromSection($ind, $elf_fh, $elf_data, $elf_data->{'e_shstrndx'}, $section->{'sh_name'});
	if (defined($name) && length($name->{'string'})) {
		$section->{'_name'} = $name->{'string'};
		my $analysis = analyseSectionName($section->{'_name'});
		if (defined($analysis->{'long'})) {
			say($ind, colored('Expected use based on the name:', 'value_name'));
			say($ind + 1, $analysis->{'long'});
		}
	}
}

sub walkSymbolTable {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $ind = 2;

	my $section = $elf_data->{'sections'}->{$section_index};
	return if (!defined($section));

	# How many entries do we intend to read?
	my $count = $section->{'sh_size'} / $section->{'sh_entsize'};

	# Where should we read names from?
	my $strtab = displayStringTableForSection($ind, $elf_data, $section_index, 'Symbol names');

	my $arch = $elf_data->{'EI_CLASS'};
	my $st_info = $elf_structures->{'symbol_table'}->{$arch};

	# Iterate over symbol table entries.
	for (my $i = 0; $i < $count; ++ $i) {
		sayf($ind, '[%d/%d] Symbol at index #%s:', $i + 1, $count, $i);

		if (!$i) {
			say($ind + 1, 'Skipping as the symbol table entry for index 0 (STN_UNDEF) is reserved');
			next;
		}

		my $offset = $section->{'sh_offset'} + ($i * $section->{'sh_entsize'});
		sayf($ind + 1, 'Seeking to offset sh_offset + (%s x sh_entsize) = %s', colored($i, 'index'), formatOffset($offset));
		seek($elf_fh, $offset, Fcntl::SEEK_SET);

		# For each field of the current entry...
		for my $entry (@{$st_info->{'ordered_entries'}}) {
			# read its value, ...
			my $value = readELFValue($elf_fh, $st_info->{$entry});
			$section->{'_symbols'}->{$i}->{$entry} = $value;
			# and provide a first basic explanation.
			say($ind + 1, analyseAndExplainValue($value, $entry, $st_info->{$entry}));

			if ($entry eq 'st_name') {
				if (!$value) {
					sayf($ind + 2, 'This symbol table entry has no name.');
				}
				elsif ($strtab) {
					my $name = displayStringFromSection($ind + 2, $elf_fh, $elf_data, $strtab, $value);
					if (defined($name) && length($name->{'string'})) {
						$section->{'_symbols'}->{$i}->{'_name'} = $name->{'string'};
					}
				}
			}
			elsif ($entry eq 'st_info') {
				my $st_bind = ($value >> 4);
				my $st_type = ($value & 0xf);
				say($ind + 2, analyseAndExplainValue($st_bind, 'st_bind', $st_info->{$entry}));
				say($ind + 2, analyseAndExplainValue($st_type, 'st_type', $st_info->{$entry}));
			}
			elsif ($entry eq 'st_shndx') {
				sayf(
					$ind + 2,
					'Here, %s is the index of section %s.',
					colored($value, 'value'),
					colored($elf_data->{'sections'}->{$value}->{'_name'}, 'name')
				) unless (exists($elf->{'st_shndx'}->{'name'}->{$value}));
			}
			elsif ($entry eq 'st_value' && $value) {
				my $type_analysis = analyseValue('e_type', $elf_data->{'e_type'});
				# Refine analysis based on file type.
				if ($type_analysis->{'name'} =~ m/ET_EXEC|ET_DYN|ET_REL/) {
					# Assume ET_EXEC or ET_DYN first.
					my $extra_explanation1 = 'this file has type ' . colored($type_analysis->{'name'}, 'name');
					my $extra_explanation2 = 'the virtual address of this symbol, assuming the associated section was loaded in memory at virtual address ' . colored('sh_addr', 'value_name');

					# Overwrite or complete if ET_REL.
					if ($type_analysis->{'name'} eq 'ET_REL') {
						# Refine analysis based on st_shndx.
						if ($section->{'_symbols'}->{$i}->{'st_shndx'} == 0xFFF2) {
							$extra_explanation1 .= ' and since ' . colored('st_shndx', 'value_name') . ' is ' . colored('SHN_COMMON', 'name');
							$extra_explanation2 = 'alignment constraints for this symbol'
						}
						else {
							$extra_explanation2 = 'a section offset from the beginning of the section that ' . colored('st_shndx', 'value_name') . ' identifies';
						}
					}

					sayf(
						$ind + 2,
						'Here, since %s, %s holds %s.',
						$extra_explanation1,
						colored('st_value', 'value_name'),
						$extra_explanation2
					);
				}
			}
		}
		# If the symbol has a name, display how to look it up through hash sections.
		if (exists($section->{'_symbols'}->{$i}->{'_name'})) {
			displayHashDetailsForSymbol($ind + 1, $elf_fh, $elf_data, $section->{'_symbols'}->{$i}, $i);
		}
	}
}

sub walkHashSection {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $ind = 2;

	my $section = $elf_data->{'sections'}->{$section_index};
	return if (!defined($section));

	displayLinkedSection($ind, $elf_data, $section_index);

	my $offset = $section->{'sh_offset'};
	sayf($ind, 'Seeking to offset sh_offset = %s', formatOffset($offset));
	seek($elf_fh, $offset, Fcntl::SEEK_SET);

	# Analyse the structure of the hash section.
	my $nbucket = readELFValue($elf_fh, 'Word');
	my $nchain = readELFValue($elf_fh, 'Word');
	my $sizeof_word = $elf_data_types->{$default_class}->{'Word'}->{'size'};
	my $bucket_offset = $offset + (2 * $sizeof_word);
	my $chain_offset = $bucket_offset + ($nbucket * $sizeof_word);

	# Display these pieces of information.
	sayf($ind, 'This ELF hash section holds:');
	sayf(
		$ind,
		'  - %s bucket entries, i.e. %d bytes, from %s;',
		colored($nbucket, 'value'),
		$nbucket * $sizeof_word,
		formatOffset($bucket_offset)
	);
	sayf(
		$ind,
		'  - %s chain entries, i.e. %d bytes, from %s.',
		colored($nchain, 'value'),
		$nchain * $sizeof_word,
		formatOffset($chain_offset)
	);

	# Store these pieces of information for later use.
	$elf_data->{'elf_hash'}->{$section_index} = {
		'name' => $section->{'_name'},
		'nbucket' => $nbucket,
		'nchain' => $nchain,
		'bucket_offset' => $bucket_offset,
		'chain_offset' => $chain_offset,
	};

	# Optionally display non-zero bucket/chain entries.
	# This is not done by default because it is hardly relevant for anybody.
	if (exists($ENV{'ELFWALK_DISPLAY_ELF_HASH_BUCKET_ENTRIES'})) {
		sayf($ind, 'Non-zero bucket entries:');
		for (my $i = 0; $i < $nbucket; ++ $i) {
			my $value = readELFValue($elf_fh, 'Word');
			next unless ($value);
			sayf($ind + 1, '#%d: %s, i.e. %s', $i, colored(sprintf('0x%08x', $value), 'value'), colored(sprintf('%d', $value), 'value'));
		}
	}
	if (exists($ENV{'ELFWALK_DISPLAY_ELF_HASH_CHAIN_ENTRIES'})) {
		sayf($ind, 'Non-zero chain entries:');
		for (my $i = 0; $i < $nchain; ++ $i) {
			my $value = readELFValue($elf_fh, 'Word');
			next unless ($value);
			sayf($ind + 1, '#%d: %s, i.e. %s', $i, colored(sprintf('0x%08x', $value), 'value'), colored(sprintf('%d', $value), 'value'));
		}
	}
}

sub walkGnuHashSection {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $ind = 2;

	my $section = $elf_data->{'sections'}->{$section_index};
	return if (!defined($section));

	my $linked_section = displayLinkedSection($ind, $elf_data, $section_index);
	my $symbols_count = 0;
	if (defined($linked_section)) {
		$symbols_count = $linked_section->{'sh_size'} / $linked_section->{'sh_entsize'};
	}

	my $offset = $section->{'sh_offset'};
	sayf($ind, 'Seeking to offset sh_offset = %s', formatOffset($offset));
	seek($elf_fh, $offset, Fcntl::SEEK_SET);

	# Analyse the header of the hash section.
	sayf($ind, 'This GNU hash section holds:');
	sayf($ind, '  - a header, made of 4 32-bit words:');
	my $gnu_hash = {
		'_index' => $section_index,
		'name' => $section->{'_name'},
	};
	for my $entry (@{$elf_structures->{'gnu_hash'}->{'ordered_entries'}}) {
		my $type = $elf_structures->{'gnu_hash'}->{$entry};
		my $value = $gnu_hash->{$entry} = readELFValue($elf_fh, $type);
		say($ind + 2, analyseAndExplainValue($value, $entry, $type));

		if ($entry eq 'maskwords') {
			my $details = 'This value must be a power of two.';
			if (!isPowerOfTwo($value)) {
				sayf($ind + 1, colored($details, 'error'));
			}
		}
	}

	# Compute extra values related to the Bloom filter/mask.
	$gnu_hash->{'sizeof_maskword'} = $default_class * 4;
	$gnu_hash->{'mask_offset'} = $offset + (4 * typeInfo('Word')->{'size'});
	$gnu_hash->{'mask_size'} = $gnu_hash->{'maskwords'} * $gnu_hash->{'sizeof_maskword'};

	# Compute extra values related to the buckets.
	$gnu_hash->{'bucket_offset'} = $gnu_hash->{'mask_offset'} + $gnu_hash->{'mask_size'};
	$gnu_hash->{'bucket_size'} = $gnu_hash->{'nbuckets'} * 4;

	# Compute extra values related to the chains.
	$gnu_hash->{'chain_offset'} = $gnu_hash->{'bucket_offset'} + $gnu_hash->{'bucket_size'};
	$gnu_hash->{'chain_count'} = $symbols_count - $gnu_hash->{'symndx'};
	$gnu_hash->{'chain_size'} = $gnu_hash->{'nbuckets'} * 4;

	# Display these pieces of information.
	sayf(
		$ind,
		'  - a Bloom filter, %s %d-bit words long, i.e. %s bytes long, from %s; this filter is used to rapidly reject attempts to look up symbols that do not exist in the object',
		colored($gnu_hash->{'maskwords'}, 'value'),
		$default_class * 32,
		$gnu_hash->{'mask_size'},
		formatOffset($gnu_hash->{'mask_offset'})
	);
	sayf(
		$ind,
		'  - an array of %s 32-bit hash buckets, i.e. %d bytes, from %s.',
		colored($gnu_hash->{'nbuckets'}, 'value'),
		$gnu_hash->{'bucket_size'},
		formatOffset($gnu_hash->{'bucket_offset'})
	);
	sayf(
		$ind,
		'  - an array of %s (%d symbols in %s - %s) 32-bit hash chain values, one per dynamic symbol that is to be accessible from the hash table, i.e. %d bytes, from %s',
		colored($gnu_hash->{'chain_count'}, 'value'),
		$symbols_count,
		colored($linked_section->{'_name'}, 'name'),
		colored('symndx', 'value_name'),
		$gnu_hash->{'chain_size'},
		formatOffset($gnu_hash->{'chain_offset'})
	);

	# Store these pieces of information for later use.
	$elf_data->{'gnu_hash'}->{$section_index} = $gnu_hash;
}

sub walkRela {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $ind = 2;

	my $section = $elf_data->{'sections'}->{$section_index};
	return if (!defined($section));

	# How many entries do we intend to read?
	my $count = $section->{'sh_size'} / $section->{'sh_entsize'};

	# What kind of entries do we intend to read?
	my $arch = $elf_data->{'EI_CLASS'};
	my $struct = ($section->{'_type'} eq 'SHT_RELA') ? 'rela' : 'rel';
	my $rela_info = $elf_structures->{$struct}->{$arch};

	# Explain sh_link.
	my $dynsym_index = undef;
	my $linked_section = displayLinkedSection($ind, $elf_data, $section_index);
	if (defined($linked_section) && $linked_section->{'_type'} eq 'SHT_DYNSYM') {
		sayf($ind + 1, 'This is the associated symbol table.');
		$dynsym_index = $linked_section->{'_index'};
	}

	# Explain sh_info.
	my $target_section = getLinkedSection($elf_data, $section_index, 'sh_info');
	if (defined($target_section)) {
		sayf(
			$ind,
			'Relocations described in this section apply to section #%s: %s, of type %s.',
			colored($target_section->{'_index'}, 'index'),
			colored($target_section->{'_name'}, 'name'),
			colored($target_section->{'_type'}, 'name')
		);
	}

	# Iterate over relocation entries.
	for (my $i = 0; $i < $count; ++ $i) {
		sayf($ind, '[%d/%d] Relocation at index #%s:', $i + 1, $count, $i);

		my $offset = $section->{'sh_offset'} + ($i * $section->{'sh_entsize'});
		sayf($ind + 1, 'Seeking to offset sh_offset + (%s x sh_entsize) = %s', colored($i, 'index'), formatOffset($offset));
		seek($elf_fh, $offset, Fcntl::SEEK_SET);

		# For each field of the current entry...
		for my $entry (@{$rela_info->{'ordered_entries'}}) {
			# read its value, ...
			my $value = readELFValue($elf_fh, $rela_info->{$entry});
			$section->{'_relocations'}->{$i}->{$entry} = $value;
			# and provide a first basic explanation.
			say($ind + 1, analyseAndExplainValue($value, $entry, $rela_info->{$entry}));

			if ($entry eq 'r_info') {
				# Extract r_sym from r_info.
				my $r_sym = ($value >> ($arch == ELFCLASS32 ? 8 : 32));
				$section->{'_relocations'}->{$i}->{'r_sym'} = $r_sym;
				say($ind + 2, analyseAndExplainValue($r_sym, 'r_sym', $rela_info->{$entry}));

				if ($r_sym) {
					# Get the name of the associated symbol.
					my $symbol_name = symbolNameFromIndex($elf_data, $r_sym, $dynsym_index);
					$section->{'_relocations'}->{$i}->{'_symbol'} = $symbol_name;
					sayf($ind + 3, 'Here, %s is the index of symbol %s.', colored($r_sym, 'value'), colored($symbol_name, 'name'));
				}
				else {
					sayf($ind + 3, 'Here, the index is STN_UNDEF, so the relocation uses zero as the symbol value.');
				}

				# Extract r_type from r_info.
				my $r_type = ($arch == ELFCLASS32 ? ($value & 0xFF) : ($value & 0xFFFFFFFF));
				$section->{'_relocations'}->{$i}->{'r_type'} = $r_sym;
				say($ind + 2, analyseAndExplainValue($r_type, 'r_type', $rela_info->{$entry}, undef, $elf_data->{'e_machine'}));
			}
		}
	}
}

sub walkDynamic {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $ind = 2;

	my $section = $elf_data->{'sections'}->{$section_index};
	return if (!defined($section));

	# Where should we read names from?
	my $strtab = displayStringTableForSection($ind, $elf_data, $section_index, 'Library names');

	# What kind of entries do we intend to read?
	my $arch = $elf_data->{'EI_CLASS'};
	my $dyn_info = $elf_structures->{'dynamic'}->{$arch};

	my $offset = $section->{'sh_offset'};
	sayf($ind, 'Seeking to offset sh_offset = %s', formatOffset($offset));
	seek($elf_fh, $offset, Fcntl::SEEK_SET);

	my $dyn_section = $elf_data->{'dynamic'}->{$section_index} = {};
	# Read expected fields: namesz, descsz, type.
	my $i = 0;
	dynamic_array: while (1) {
		dynamic_entry: for my $entry (@{$dyn_info->{'ordered_entries'}}) {
			my $value = readELFValue($elf_fh, $dyn_info->{$entry});
			$dyn_section->{$i}->{$entry} = $value;
			say($ind, analyseAndExplainValue($value, $entry, $dyn_info->{$entry}));

			if ($entry eq 'd_un') {
				my $tag_value = $dyn_section->{$i}->{'d_tag'};
				if (!$tag_value) { # DT_NULL
					# An entry with a DT_NULL tag marks the end of the _DYNAMIC array.
					last dynamic_array;
				}
				elsif ($tag_value == 1 || $tag_value == 14 || $tag_value == 15 || $tag_value == 29) {
					# DT_NEEDED, DT_SONAME, DT_RPATH or DT_RUNPATH
					# "The offset is an index into the table recorded in the DT_STRTAB code."
					# ... except we are liable to read a DT_NEEDED entry before
					# stumbling upon the DT_STRTAB one.
					# As DT_STRTAB is supposed to point to a SHT_STRTAB section,
					# typically .dynstr, we can assume everything will be fine
					# if we rely on the linked strtab.
					if ($strtab) {
						displayStringFromSection($ind + 1, $elf_fh, $elf_data, $strtab, $value);
					}
				}
				elsif ($tag_value == 20) { # DT_PLTREL
					my $meaning;
					if ($value == 17) { # DT_REL
						$meaning = 'means DT_REL';
					}
					elsif ($value == 7) { # DT_RELA
						$meaning = 'means DT_RELA';
					}
					else {
						$meaning = 'should be either DT_REL or DT_RELA.'
					}
					$value = sprintf('%d', $value);
					$value = colored($value, 'value');
					sayf($ind + 1, 'Here, %s %s.', $value, $meaning);
				}
				elsif ($tag_value == 30) { # DT_FLAGS
					say($ind + 1, analyseAndExplainValue($value, 'dt_flags', $dyn_info->{'d_un'}));
				}
				elsif ($tag_value == 0x6FFFFFFB) { # DT_FLAGS_1
					say($ind + 1, analyseAndExplainValue($value, 'dt_flags_1', $dyn_info->{'d_un'}));
				}
			}
		}
		++ $i;
	}
}

sub walkVerdef {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $ind = 2;

	my $section = $elf_data->{'sections'}->{$section_index};
	return if (!defined($section));

	# How many entries do we intend to read?
	# Answer from the LSB: "The number of entries in this section shall be
	# contained in the DT_VERDEFNUM entry of the Dynamic Section .dynamic."
	my @dt_verdefnum_entries = dynamicEntries($elf_data, 0x6ffffffd); # DT_VERDEFNUM
	if (!scalar(@dt_verdefnum_entries)) {
		say($ind, colored('Unable to find the DT_VERDEFNUM entry in .dynamic, aborting the parsing of this section.', 'error'));
		return;
	}
	my $dt_verdefnum = shift(@dt_verdefnum_entries);
	sayf($ind, 'This section contains DT_VERDEFNUM = %d entries.', $dt_verdefnum);

	# Where should we read names from?
	my $strtab = displayStringTableForSection($ind, $elf_data, $section_index, 'Version strings and library names');

	my $offset = $section->{'sh_offset'};
	sayf($ind, 'Seeking to offset sh_offset = %s', formatOffset($offset));
	seek($elf_fh, $offset, Fcntl::SEEK_SET);

	for (my $i = 0; $i < $dt_verdefnum; ++ $i) {
		my $verdef = {};

		# Parse Elfxx_Verdef
		sayf($ind, '[%d/%d] Version definition entry #%d:', $i + 1, $dt_verdefnum, $i);
		for my $entry (@{$elf_structures->{'verdef'}->{'ordered_entries'}}) {
			my $type = $elf_structures->{'verdef'}->{$entry};
			my $value = readELFValue($elf_fh, $type);
			say($ind + 1, analyseAndExplainValue($value, $entry, $type));
			$verdef->{$entry} = $value;
		}

		if ($verdef->{'vd_cnt'} > 0) {
			for (my $j = 0; $j < $verdef->{'vd_cnt'}; ++ $j) {
				sayf($ind + 1, '[%d/%d] Version definition auxiliary entry #%d:', $j + 1, $verdef->{'vd_cnt'}, $j);
				for my $entry (@{$elf_structures->{'verdaux'}->{'ordered_entries'}}) {
					my $type = $elf_structures->{'verdaux'}->{$entry};
					my $value = readELFValue($elf_fh, $type);
					say($ind + 2, analyseAndExplainValue($value, $entry, $type));
					$verdef->{'verdaux'}->{$j}->{$entry} = $value;
					if ($entry eq 'vda_name' && $strtab) {
						my $name = displayStringFromSection($ind + 3, $elf_fh, $elf_data, $strtab, $value);
						if (defined($name) && length($name->{'string'})) {
							$verdef->{'verdaux'}->{$j}->{'name'} = $name->{'string'};
						}
					}
				}
			}
		}
		my $verdef_index = $verdef->{'vd_ndx'};
		$elf_data->{'verdef'}->{$section_index}->{$verdef_index} = $verdef;
	}
}

sub walkVerneed {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $ind = 2;

	my $section = $elf_data->{'sections'}->{$section_index};
	return if (!defined($section));

	# How many entries do we intend to read?
	# Answer from the LSB: "The number of entries in this section shall be
	# contained in the DT_VERNEEDNUM entry of the Dynamic Section .dynamic."
	my @dt_verneednum_entries = dynamicEntries($elf_data, 0x6fffffff); # DT_VERNEEDNUM
	if (!scalar(@dt_verneednum_entries)) {
		say($ind, colored('Unable to find the DT_VERNEEDNUM entry in .dynamic, aborting the parsing of this section.', 'error'));
		return;
	}
	my $dt_verneednum = shift(@dt_verneednum_entries);
	sayf($ind, 'This section contains DT_VERNEEDNUM = %d entries.', $dt_verneednum);

	# Where should we read names from?
	my $strtab = displayStringTableForSection($ind, $elf_data, $section_index, 'Version strings and library names');

	my $offset = $section->{'sh_offset'};
	sayf($ind, 'Seeking to offset sh_offset = %s', formatOffset($offset));
	seek($elf_fh, $offset, Fcntl::SEEK_SET);

	for (my $i = 0; $i < $dt_verneednum; ++ $i) {
		my $verneed = {};

		# Parse Elfxx_Verneed
		sayf($ind, '[%d/%d] Version requirements entry #%d:', $i + 1, $dt_verneednum, $i);
		for my $entry (@{$elf_structures->{'verneed'}->{'ordered_entries'}}) {
			my $type = $elf_structures->{'verneed'}->{$entry};
			my $value = readELFValue($elf_fh, $type);
			say($ind + 1, analyseAndExplainValue($value, $entry, $type));
			$verneed->{$entry} = $value;
			if ($entry eq 'vn_file') {
				my $name = displayStringFromSection($ind + 2, $elf_fh, $elf_data, $strtab, $value);
				if (defined($name) && length($name->{'string'})) {
					$verneed->{'file'} = $name->{'string'};
				}
			}
		}
		$elf_data->{'verneed'}->{$section_index}->{$i} = $verneed;

		if ($verneed->{'vn_cnt'} > 0) {
			for (my $j = 0; $j < $verneed->{'vn_cnt'}; ++ $j) {
				my $vernaux = {};
				sayf($ind + 1, '[%d/%d] Version requirements auxiliary entry #%d:', $j + 1, $verneed->{'vn_cnt'}, $j);
				for my $entry (@{$elf_structures->{'vernaux'}->{'ordered_entries'}}) {
					my $type = $elf_structures->{'vernaux'}->{$entry};
					my $value = readELFValue($elf_fh, $type);
					say($ind + 2, analyseAndExplainValue($value, $entry, $type));
					$vernaux->{$entry} = $value;
					if ($entry eq 'vna_name' && $strtab) {
						my $name = displayStringFromSection($ind + 3, $elf_fh, $elf_data, $strtab, $value);
						if (defined($name) && length($name->{'string'})) {
							$vernaux->{'name'} = $name->{'string'};
						}
					}
				}
				# Attach vernaux entries to their parent verneed entry.
				$verneed->{'vernaux'}->{$j} = $vernaux;
				# Keep a reference to the parent verneed entry.
				$vernaux->{'verneed'} = $verneed;
				# Store vernaux entries in $elf_data indexed by their vna_other
				# value as this is the equivalent of vd_ndx.
				my $vernaux_index = $vernaux->{'vna_other'};
				$elf_data->{'vernaux'}->{$section_index}->{$vernaux_index} = $vernaux;
			}
		}
	}
}

sub walkVersym {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $ind = 2;

	my $section = $elf_data->{'sections'}->{$section_index};
	return if (!defined($section));

	my $dynsym = displayLinkedSection($ind, $elf_data, $section_index);
	my $dynsym_index = defined($dynsym) ? $dynsym->{'_index'} : undef;

	# How many entries do we intend to read?
	my $count = $section->{'sh_size'} / $section->{'sh_entsize'};
	# Note: sh_entsize is expected to be 2 bytes (16 bits) as the LSB states
	# that "The .gnu.version section shall contain an array of elements of
	# type Elfxx_Half."

	my $offset = $section->{'sh_offset'};
	sayf($ind, 'Seeking to offset sh_offset = %s', formatOffset($offset));
	seek($elf_fh, $offset, Fcntl::SEEK_SET);

	# Iterate over entries of the Symbol Version Table.
	for (my $i = 0; $i < $count; ++ $i) {
		sayf($ind, '[%d/%d] symbol version entry at index #%s:', $i + 1, $count, $i);
		if (!$i) {
			readELFValue($elf_fh, 'Half');
			say($ind + 1, 'Skipping as the symbol table entry for index 0 (STN_UNDEF) is reserved');
			next;
		}

		my $symbol = symbolFromIndex($elf_data, $i, $dynsym_index);

		my $value = readELFValue($elf_fh, 'Half');
		my $printable_value = colored(formatValue($value, 'Half'), 'value');

		my $explanation = 'symbol ' . colored($symbol->{'_name'}, 'name');
		if (!$value) {
			$explanation .= ' is local, not available outside this ELF object';
		}
		elsif ($value == 1) {
			$explanation .= ' is defined in this object and is globally available';
		}
		else {
			# "Bit number 15 controls whether or not the object is hidden; if
			# this bit is set, the object cannot be used and the static linker
			# will ignore the symbol's presence in the object."
			if (($value & 0x8000) == 0x8000) {
				$explanation .= ' is marked as hidden (0x8000) and';
				# Remove bit #15 prior to the vernaux / verdef lookup.
				$value &= 0x7FFF;
			}

			# Check requirements (vernaux entries).
			my $vernaux;
			for my $i (keys(%{$elf_data->{'vernaux'}})) {
				if (exists($elf_data->{'vernaux'}->{$i}->{$value})) {
					$vernaux = $elf_data->{'vernaux'}->{$i}->{$value};
					$vernaux = formatVersionRequirement($vernaux);
					$vernaux = colored($vernaux, 'name');
					$explanation .= ' requires version ' . $vernaux;
					goto explained;
				}
			}

			# Otherwise, we must display its version _definition_.
			my $verdef;
			for my $i (keys(%{$elf_data->{'verdef'}})) {
				if (exists($elf_data->{'verdef'}->{$i}->{$value})) {
					$verdef = $elf_data->{'verdef'}->{$i}->{$value};
					$verdef = formatVersionDefinition($verdef);
					$verdef = colored($verdef, 'name');
					$explanation .= ' is provided for version ' . $verdef;
					goto explained;
				}
			}
		}
		explained:
		sayf($ind + 1, '%s: %s.', $printable_value, $explanation);
	}
}

sub walkNote {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $ind = 2;

	my $section = $elf_data->{'sections'}->{$section_index};
	return if (!defined($section));

	# What kind of entries do we intend to read?
	my $arch = $elf_data->{'EI_CLASS'};
	my $note_info = $elf_structures->{'note'}->{$arch};

	my $offset = $section->{'sh_offset'};
	sayf($ind, 'Seeking to offset sh_offset = %s', formatOffset($offset));
	seek($elf_fh, $offset, Fcntl::SEEK_SET);

	# Read expected fields: namesz, descsz, type.
	for my $entry (@{$note_info->{'ordered_entries'}}) {
		my $value = readELFValue($elf_fh, $note_info->{$entry});
		$elf_data->{'_notes'}->{$section_index}->{$entry} = $value;
		say($ind, analyseAndExplainValue($value, $entry, $note_info->{$entry}));
	}

	# Read the name.
	my $name_size = $elf_data->{'_notes'}->{$section_index}->{'namesz'};
	if ($name_size) {
		my $name = readNullTerminatedString($elf_fh, $name_size);
		sayf($ind, '%s is "%s".', colored('name', 'value_name'), colored($name, 'value'));
	}
	else {
		sayf($ind, '%s will not be read as %s is 0.', colored('name', 'value_name'), colored('namesz', 'value_name'));
	}

	# Cross potential padding: "Padding is present, if necessary, to ensure
	# 4-byte alignment for the descriptor. Such padding is not included in
	# namesz."
	my $cur_pos = my $new_pos = tell($elf_fh);
	++ $new_pos while ($new_pos % 4);
	if ($new_pos != $cur_pos) {
		sayf($ind, 'Seeking to offset %s to cross the padding', formatOffset($new_pos));
		seek($elf_fh, $new_pos, Fcntl::SEEK_SET);
	}

	# Read the descriptor.
	if ($section->{'_name'} eq '.note.ABI-tag') {
		sayf($ind, 'The note section being named %s, we know how to interpret the descriptor:', colored('.note.ABI-tag', 'name'));
		my $value = readELFValue($elf_fh, 'Word');
		sayf($ind + 1, '%s: this is expected to be 0, signifying a Linux executable.', colored(formatValue($value, 'Word'), 'value'));
		my $kv1 = readELFValue($elf_fh, 'Word');
		sayf($ind + 1, '%s: this is the earliest kernel version compatible with this file', colored(formatValue($kv1, 'Word'), 'value'));
		my $kv2 = readELFValue($elf_fh, 'Word');
		sayf($ind + 1, '%s: this is the earliest kernel major revision compatible with this file', colored(formatValue($kv2, 'Word'), 'value'));
		my $kv3 = readELFValue($elf_fh, 'Word');
		sayf($ind + 1, '%s: this is the earliest kernel minor revision compatible with this file', colored(formatValue($kv3, 'Word'), 'value'));
		$elf_data->{'_kernel'} = sprintf('%d.%d.%d', $kv1, $kv2, $kv3);
		sayf($ind, 'Basically, this note states this file requires at least Linux kernel %s.', colored($elf_data->{'_kernel'}, 'value'));
	}
	elsif ($section->{'_name'} eq '.note.gnu.build-id') {
		my $desc_size = $elf_data->{'_notes'}->{$section_index}->{'descsz'};
		sayf($ind, 'The note section being named %s, we know how to interpret the descriptor:', colored('.note.gnu.build-id', 'name'));
		sayf($ind + 1, 'This descriptor is expected to host a unique build ID, for debugging purposes.');
		# I took the same heuristic as "file", with a minimal amount of shame.
		my $id_type = ($desc_size == 16) ? 'a MD5 hash or a UUID' : 'a SHA1 hash';
		sayf($ind + 1, 'This descriptor being %d bytes, it is very likely to host %s.', $desc_size, $id_type);
		my $build_id = '';
		for (my $i = 0; $i < $desc_size; ++ $i) {
			my $byte = readELFValue($elf_fh, 'char');
			$build_id .=  sprintf('%02x', $byte);
		}
		sayf($ind + 1, 'The build ID of this file is: %s.', colored($build_id, 'value'));
	}
}

sub walkProgBits {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $ind = 2;

	my $section = $elf_data->{'sections'}->{$section_index};
	return if (!defined($section));

	my $offset = $section->{'sh_offset'};
	sayf($ind, 'Seeking to offset sh_offset = %s', formatOffset($offset));
	seek($elf_fh, $offset, Fcntl::SEEK_SET);

	if ($section->{'_name'} eq '.interp') {
		my $interp = readNullTerminatedString($elf_fh, $section->{'sh_size'});
		if (length($interp)) {
			$elf_data->{'.interp'} = $interp;
			sayf($ind, 'This ELF file shall be interpreted using %s.', colored($interp, 'value'));
		}
		else {
			sayf($ind, 'Error: unable to retrieve the path of the interpreter.');
		}
	}
}

sub walkELFProgramHeader {
	my $elf_fh = shift;
	my $elf_data = shift;

	say(0, colored('Parsing program header', 'main_action'));
	my $ind = 1;

	my $arch = $elf_data->{'EI_CLASS'};
	my $ph_info = $elf_structures->{'program_header'}->{$arch};

	for (my $i = 0; $i < $elf_data->{'e_phnum'}; ++ $i) {
		announceOperation($ind, 'Parsing segment', $i, ':');

		my $offset = $elf_data->{'e_phoff'} + ($i * $elf_data->{'e_phentsize'});
		sayf(
			$ind + 1,
			'Seeking to offset e_phoff + (%s x e_phentsize) = %s',
			colored($i, 'index'),
			formatOffset($offset)
		);
		seek($elf_fh, $offset, Fcntl::SEEK_SET);

		for my $entry (@{$ph_info->{'ordered_entries'}}) {
			my $value = readELFValue($elf_fh, $ph_info->{$entry});
			$elf_data->{'program_header'}->{$i}->{$entry} = $value;
			say($ind + 1, analyseAndExplainValue($value, $entry, $ph_info->{$entry}));

			if ($entry eq 'p_align') {
				my $comment;
				if ($value == 0 || $value == 1) {
					# Values 0 and 1 mean no alignment is required.
					sayf($ind + 2, 'Here, %s means no alignment is required.', colored($value, 'value'));
				}
				else {
					# Otherwise, p_align should be a positive, integral power
					# of 2,
					$comment = 'Here, ' . colored($value, 'value') . ' ';
					$comment .= isPowerOfTwo($value) ? 'is' : 'should be';
					$comment .= ' a power of two, as expected by the SysV ABI.';
					say($ind + 2, $comment);

					# ... and p_vaddr should equal p_offset, modulo p_align.
					my $p_vaddr  = $elf_data->{'program_header'}->{$i}->{'p_vaddr'};
					my $p_offset = $elf_data->{'program_header'}->{$i}->{'p_offset'};
					my $is_aligned = (($p_vaddr - $p_offset) % $value);
					$comment = $is_aligned ? 'should equal' : 'equals';
					sayf(
						$ind + 2,
						'Also, %s %s %s, modulo %s, as expected by the SysV ABI.',
						colored('p_vaddr', 'value_name'),
						$comment,
						colored('p_offset', 'value_name'),
						colored('p_align', 'value_name')
					);
				}
			}
			elsif ($entry eq 'p_flags') {
				# Focus on the lowest 3 bits.
				my $perm_flags = ($value & 0x7);
				# This hash associates perm sets with extra, allowable perms.
				my $allowable = $elf->{'p_flags'}->{'_allowable'};
				sayf(
					$ind + 2,
					'ABI-conforming systems may also provide %s.',
					$allowable->{$perm_flags}
				) if (exists($allowable->{$perm_flags}));
			}
			elsif ($entry eq 'p_filesz') {
				my $segment = $elf_data->{'program_header'}->{$i};
				if (getSectionsInSegment($elf_data, $i)) {
					say($ind + 2, 'This segment covers the following sections:');
					for my $i (@{$segment->{'_sections'}}) {
						my $section_name = $elf_data->{'sections'}->{$i}->{'_name'};
						sayf(
							$ind + 3,
							'- section #%s: %s',
							colored($i, 'index'),
							colored($section_name, 'name')
						);
					}
				}
			}
		}
	}
}

sub getSectionsInSegment {
	my $elf_data = shift;
	my $segment_index = shift;

	my $segment = $elf_data->{'program_header'}->{$segment_index};
	$segment->{'_sections'} = [];

	# Iterate over known sections...
	for (my $i = 1; $i < $elf_data->{'e_shnum'}; ++ $i) {
		my $section = $elf_data->{'sections'}->{$i};
		# to check whether they are included in the current segment.
		if (segmentIncludesSection($segment, $section)) {
			push(@{$segment->{'_sections'}}, $i);
		}
	}
	return scalar(@{$segment->{'_sections'}});
}

sub sectionEnd {
	my $section = shift;

	my $section_end = $section->{'sh_offset'};
	if ($section->{'_type'} ne 'SHT_NOBITS') {
		$section_end += $section->{'sh_size'};
	}
	return $section_end;
}

sub sectionOffset {
	my $elf_data = shift;
	my $section_index = shift;

	return undef if ($section_index < 0);
	return undef if ($section_index >= $elf_data->{'e_shnum'});
	return $elf_data->{'e_shoff'} + ($section_index * $elf_data->{'e_shentsize'});
}

sub segmentIncludesSection {
	my $segment = shift;
	my $section = shift;

	my $section_start = $section->{'sh_offset'};
	my $section_end = sectionEnd($section);

	my $segment_start = $segment->{'p_offset'};
	my $segment_end = $segment_start + $segment->{'p_filesz'};

	my $segment_includes_section = (
		$segment_start <= $section_start &&
		$section_end <= $segment_end
	);
	return $segment_includes_section;
}

sub symbolFromIndex {
	my $elf_data = shift;
	my $index = shift;
	my $dynsym_index = shift;

	for my $section_index (@{$elf_data->{'SHT_DYNSYM'}}) {
		if (defined($dynsym_index)) {
			next if ($section_index != $dynsym_index);
		}
		if (exists($elf_data->{'sections'}->{$section_index}->{'_symbols'}->{$index})) {
			return $elf_data->{'sections'}->{$section_index}->{'_symbols'}->{$index};
		}
	}
	return undef;
}

sub symbolNameFromIndex {
	my $elf_data = shift;
	my $index = shift;
	my $dynsym_index = shift;

	my $symbol = symbolFromIndex($elf_data, $index, $dynsym_index);
	return defined($symbol) ? $symbol->{'_name'} : undef;
}

sub dynamicEntries {
	my $elf_data = shift;
	my $entry_tag = shift;

	my @entries;
	for my $dynsec_index (keys(%{$elf_data->{'dynamic'}})) {
		my $dynsec = $elf_data->{'dynamic'}->{$dynsec_index};
		for my $i (keys(%{$dynsec})) {
			if ($dynsec->{$i}->{'d_tag'} == $entry_tag) {
				push(@entries, $dynsec->{$i}->{'d_un'});
			}
		}
	}
	return @entries;
}

sub displayHashDetailsForSymbol {
	my $ind = shift;
	my $elf_fh = shift;
	my $elf_data = shift;
	my $symbol = shift;
	my $symbol_index = shift;

	# Backup the position of the file handler as we are going to mess with it.
	my $pos = tell($elf_fh);

	my $sizeof_word = $elf_data_types->{$default_class}->{'Word'}->{'size'};

	if (exists($elf_data->{'elf_hash'})) {
		for my $hash_section_index (keys(%{$elf_data->{'elf_hash'}})) {
			my $hash_section = $elf_data->{'elf_hash'}->{$hash_section_index};
			sayf(
				$ind,
				'Path to reach that symbol index (i.e. #%d) from its name through hash section %s:',
				$symbol_index,
				colored($hash_section->{'name'}, 'name')
			);

			my $elf_hash = hashSymbolName($symbol->{'_name'});
			sayf($ind + 1, 'ELF hash: %s', colored(sprintf('0x%08x', $elf_hash), 'value'));

			my $bucket_entry_index = $elf_hash % $hash_section->{'nbucket'};
			my $bucket_entry_offset = $hash_section->{'bucket_offset'} + ($bucket_entry_index * $sizeof_word);
			seek($elf_fh, $bucket_entry_offset, Fcntl::SEEK_SET);
			my $bucket_entry_value = readELFValue($elf_fh, 'Word');

			sayf(
				$ind + 1,
				'index y = bucket[ELF hash %% nbucket] = bucket[%s %% %s] = bucket[%s] = %s',
				colored(sprintf('0x%08x', $elf_hash), 'value'),
				colored($hash_section->{'nbucket'}, 'value'),
				colored($bucket_entry_index, 'value'),
				colored($bucket_entry_value, 'value')
			);

			for (my $i = 0, my $index = $bucket_entry_value; $index != $symbol_index && $i < 100; ++ $i) {
				my $chain_entry_offset = $hash_section->{'chain_offset'} + ($index * $sizeof_word);
				seek($elf_fh, $chain_entry_offset, Fcntl::SEEK_SET);
				my $chain_entry_value = readELFValue($elf_fh, 'Word');
				sayf(
					$ind + 1,
					'chain[y] = chain[%s] = %s%s',
					colored($index, 'value'),
					colored($chain_entry_value, 'value'),
					($chain_entry_value == $symbol_index) ? ' -- symbol index reached' : ''
				);
				$index = $chain_entry_value;
			}
		}
	}

	if (exists($elf_data->{'gnu_hash'})) {
		for my $gnu_section_index (keys(%{$elf_data->{'gnu_hash'}})) {
			my $gnu_section = $elf_data->{'gnu_hash'}->{$gnu_section_index};
			sayf(
				$ind,
				'Path to reach that symbol index (i.e. #%d) from its name through GNU hash section %s:',
				$symbol_index,
				colored($gnu_section->{'name'}, 'name')
			);

			if ($symbol_index < $gnu_section->{'symndx'}) {
				sayf($ind + 1, 'This symbol is not meant to be accessible via the GNU hash section.');
				return;
			}

			my $gnu_hash = hashSymbolNameGNUStyle($symbol->{'_name'});
			sayf($ind + 1, 'GNU hash: %s', colored(sprintf('0x%08x', $gnu_hash), 'value'));
			my $bucket_entry_index = $gnu_hash % $gnu_section->{'nbuckets'};
			my $bucket_entry_offset = $gnu_section->{'bucket_offset'} + ($bucket_entry_index * $sizeof_word);
			seek($elf_fh, $bucket_entry_offset, Fcntl::SEEK_SET);
			my $bucket_entry_value = readELFValue($elf_fh, 'Word');
			sayf(
				$ind + 1,
				'index y = bucket[GNU hash %% nbucket] = bucket[%s %% %s] = bucket[%s] = %s',
				colored(sprintf('0x%08x', $gnu_hash), 'value'),
				colored($gnu_section->{'nbuckets'}, 'value'),
				colored($bucket_entry_index, 'value'),
				colored($bucket_entry_value, 'value')
			);
			if (!$bucket_entry_value) {
				sayf($ind + 1, 'Got empty hash chain, cannot reach symbol.');
			}

			for (my $i = 0, my $index = $bucket_entry_value; $i < 100; ++ $i, ++ $index) {
				my $chain_entry_offset = $gnu_section->{'chain_offset'} + (($index - $gnu_section->{'symndx'}) * $sizeof_word);
				seek($elf_fh, $chain_entry_offset, Fcntl::SEEK_SET);
				my $chain_entry_value = readELFValue($elf_fh, 'Word');
				sayf(
					$ind + 1,
					'chain[y - %s] = chain[%s - %s] = chain[%s] = %s',
					colored('symndx', 'value_name'),
					colored($index, 'value'),
					colored($gnu_section->{'symndx'}, 'value'),
					colored($index - $gnu_section->{'symndx'}, 'value'),
					colored(sprintf('0x%08x', $chain_entry_value), 'value'),
				);
				if (($gnu_hash & ~1) == ($chain_entry_value & ~1)) {
					my $conclusion = 'The first 31 bits match the GNU hash of the symbol we are looking for';
					# We do not need to compare names as we already know what
					# index we are looking for.
					if ($index != $symbol_index) {
						sayf($ind + 1, $conclusion . ', but this is not the expected symbol index: we must continue.');
					}
					else {
						sayf($ind + 1, $conclusion . ' -- symbol index reached');
						last;
					}
				}
				if ($chain_entry_value & 1) {
					sayf($ind + 1, 'Reached end of chain, unable to reach symbol index.');
					last;
				}
			}
		}
	}

	# Restore the position of the file handler as it was before we mess with it.
	seek($elf_fh, $pos, Fcntl::SEEK_SET);
}

sub getLinkedSection {
	my $elf_data = shift;
	my $section_index = shift;
	my $sh_attribute = shift || 'sh_link';

	my $section = $elf_data->{'sections'}->{$section_index};
	return undef if (!defined($section));

	my $linked_index = $section->{$sh_attribute};
	if ($linked_index && exists($elf_data->{'sections'}->{$linked_index})) {
		return $elf_data->{'sections'}->{$linked_index};
	}
	return undef;
}


sub displayLinkedSection {
	my $ind = shift;
	my $elf_data = shift;
	my $section_index = shift;

	my $linked_section = getLinkedSection($elf_data, $section_index);
	if (!$linked_section) {
		sayf($ind, 'This section is not associated to any other section.');
	}
	else {
		sayf(
			$ind,
			'This section is associated with section #%s: %s, of type %s.',
			colored($linked_section->{'_index'}, 'index'),
			colored($linked_section->{'_name'}, 'name'),
			colored($linked_section->{'_type'}, 'name')
		);
	}
	return $linked_section;
}

sub displayStringTableForSection {
	my $ind = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $content = shift || 'Strings';

	my $strtab = displayLinkedSection($ind, $elf_data, $section_index);
	if (defined($strtab)) {
		if ($strtab->{'_type'} eq 'SHT_STRTAB') {
			sayf(
				$ind + 1,
				'%s will be read from it, i.e. from offset %s.',
				$content,
				formatOffset($strtab->{'sh_offset'})
			);
			return $strtab->{'_index'};
		}
		else {
			sayf(
				$ind + 1,
				'%s will not be read from there though, as its type is not SHT_STRTAB.',
				$content
			);
		}
	}
	return undef;
}

sub displayStringFromSection {
	my $ind = shift;
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $rel_offset = shift;

	my $name = readStringFromSection($elf_fh, $elf_data, $section_index, $rel_offset);
	if (defined($name) && length($name->{'string'})) {
		sayf(
			$ind,
			'Fetched name at offset %s: %s',
			formatOffset($name->{'abs_offset'}),
			colored($name->{'string'}, 'name')
		);
	}
	else {
		say($ind, colored('Error: unable to retrieve the name.', 'error'));
	}
	return $name;
}

sub readStringFromSection {
	my $elf_fh = shift;
	my $elf_data = shift;
	my $section_index = shift;
	my $rel_offset = shift;

	my $section = $elf_data->{'sections'}->{$section_index};
	return if (!defined($section));

	my $abs_offset = $section->{'sh_offset'} + $rel_offset;

	# Compute a sensible maximum expected length.
	my $max = $section->{'sh_size'} - $rel_offset;
	# It does not make sense if we are asked to fetch data in a section at an
	# offset beyond the size of this section.
	return undef if ($max < 0);

	my $string = readStringAtOffset($elf_fh, $abs_offset, $max, 1);
	return {
		'string' => $string,
		'rel_offset' => $rel_offset,
		'abs_offset' => $abs_offset,
	};
}

sub readStringAtOffset {
	my $elf_fh = shift;
	my $offset = shift;
	my $max = shift;
	my $restore_pos = shift;

	my $pos;
	if (defined($restore_pos) && $restore_pos) {
		$pos = tell($elf_fh);
	}

	# Seek to that offset.
	seek($elf_fh, $offset, Fcntl::SEEK_SET);

	# Read data into a string until we stumble upon EOF or 0x00.
	my $string = readNullTerminatedString($elf_fh, $max);

	if (defined($restore_pos) && $restore_pos) {
		seek($elf_fh, $pos, Fcntl::SEEK_SET);
	}

	return $string;
}

sub readNullTerminatedString {
	my $elf_fh = shift;
	my $max = shift;

	# Read data into a string until we stumble upon EOF or 0x00.
	my $name;
	my $read_bytes;
	while (1) {
		$read_bytes = read($elf_fh, my $char, 1);
		last if ($read_bytes != 1 || $char eq "\x0");
		$name .= $char;
		last if (defined($max) && length($name) >= $max);
	}
	return $name;
}

sub isPowerOfTwo {
	my $value = shift;
	return $value && !($value & ($value - 1));
}

sub hashSymbolName {
	my $name = shift;

	my $h = 0;
	my $g;
	for (my $i = 0; $i < length($name); ++ $i) {
		$h = ($h << 4) + ord(substr($name, $i, 1));
		$h ^= $g >> 24 if ($g = $h & 0xf0000000);
		$h &= ~$g;
	}
	return $h;
}

sub hashSymbolNameGNUStyle {
	my $name = shift;

	use bigint;
	my $h = 5381;
	my $ch;
	for (my $i = 0; $i < length($name); ++ $i) {
		$h = ($h * 33) + ord(substr($name, $i, 1));
	}
	return $h & 0xffffffff;
}

# Relocations meanings were written based on the "calculation" column found in
# SysV ABI AMD64 supplement. This column uses a particular notation to describe
# relocations. That notation was copied "as is" in $elf.
# This procedure rewrites this notation to something expected to be more
# understandable (or at least less cryptic).
sub relocationMeaning {
	my $field = shift;
	my $calculation = shift;
	my $description = shift;

	$calculation =~ s,none,No relocation,;
	$calculation =~ s,GOT,GOT address,;
	$calculation =~ s,A,addend,;
	$calculation =~ s,B,base address,;
	$calculation =~ s,G,symbol GOT offset,;
	$calculation =~ s,L,symbol PLT place,;
	$calculation =~ s,P,r_offset place,;
	$calculation =~ s,S,symbol value,;
	$calculation =~ s,Z,symbol size,;

	my $final_description = '';
	if ($field > R_FIELD_NONE) {
		$final_description .= sprintf('for a %d-bit field: ', $field);
	}
	$final_description .= $calculation;
	if (defined($description) && length($description)) {
		$final_description .= '; ' . $description;
	}

	return $final_description;
}