/**
 * @file  readelf.c
 * @brief Display information about a 64-bit Elf binary or object.
 *
 * Implementation of a `readelf` utility.
 *
 * I've tried to get the output here as close to the binutils format
 * as possible, so it should be compatible with tools that try to
 * parse that. Most of the same arguments should also be supported.
 *
 * This is a rewrite of an earlier version.
 *
 * @copyright
 * This file is part of ToaruOS and is released under the terms
 * of the NCSA / University of Illinois License - see LICENSE.md
 * Copyright (C) 2020-2021 K. Lange
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <getopt.h>
#include <kernel/elf.h>

#define SHOW_FILE_HEADER      0x0001
#define SHOW_SECTION_HEADERS  0x0002
#define SHOW_PROGRAM_HEADERS  0x0004
#define SHOW_SYMBOLS          0x0008
#define SHOW_DYNAMIC          0x0010
#define SHOW_RELOCATIONS      0x0020

static const char * elf_classToStr(unsigned char ei_class) {
	static char buf[64];
	switch (ei_class) {
		case ELFCLASS32: return "ELF32";
		case ELFCLASS64: return "ELF64";
		default:
			sprintf(buf, "unknown (%d)", ei_class);
			return buf;
	}
}

static const char * elf_dataToStr(unsigned char ei_data) {
	static char buf[64];
	switch (ei_data) {
		case ELFDATA2LSB: return "2's complement, little endian";
		case ELFDATA2MSB: return "2's complement, big endian";
		default:
			sprintf(buf, "unknown (%d)", ei_data);
			return buf;
	}
}

static char * elf_versionToStr(unsigned char ei_version) {
	static char buf[64];
	switch (ei_version) {
		case 1: return "1 (current)";
		default:
			sprintf(buf, "unknown (%d)", ei_version);
			return buf;
	}
}

static char * elf_osabiToStr(unsigned char ei_osabi) {
	static char buf[64];
	switch (ei_osabi) {
		case 0: return "UNIX - System V";
		case 1: return "HP-UX";
		case 255: return "Standalone";
		default:
			sprintf(buf, "unknown (%d)", ei_osabi);
			return buf;
	}
}

static char * elf_typeToStr(Elf64_Half type) {
	static char buf[64];
	switch (type) {
		case ET_NONE: return "NONE (No file type)";
		case ET_REL:  return "REL (Relocatable object file)";
		case ET_EXEC: return "EXEC (Executable file)";
		case ET_DYN:  return "DYN (Shared object file)";
		case ET_CORE: return "CORE (Core file)";
		default:
			sprintf(buf, "unknown (%d)", type);
			return buf;
	}
}

static char * elf_machineToStr(Elf64_Half machine) {
	static char buf[64];
	switch (machine) {
		case EM_X86_64: return "Advanced Micro Devices X86-64";
		default:
			sprintf(buf, "unknown (%d)", machine);
			return buf;
	}
}

static char * sectionHeaderTypeToStr(Elf64_Word type) {
	static char buf[64];
	switch (type) {
		case SHT_NULL: return "NULL";
		case SHT_PROGBITS: return "PROGBITS";
		case SHT_SYMTAB: return "SYMTAB";
		case SHT_STRTAB: return "STRTAB";
		case SHT_RELA: return "RELA";
		case SHT_HASH: return "HASH";
		case SHT_DYNAMIC: return "DYNAMIC";
		case SHT_NOTE: return "NOTE";
		case SHT_NOBITS: return "NOBITS";
		case SHT_REL: return "REL";
		case SHT_SHLIB: return "SHLIB";
		case SHT_DYNSYM: return "DYNSYM";

		case 0xE: return "INIT_ARRAY";
		case 0xF: return "FINI_ARRAY";
		case 0x6ffffff6: return "GNU_HASH";
		case 0x6ffffffe: return "VERNEED";
		case 0x6fffffff: return "VERSYM";
		default:
			sprintf(buf, "(%x)", type);
			return buf;
	}
}

static char * programHeaderTypeToStr(Elf64_Word type) {
	static char buf[64];
	switch (type) {
		case PT_NULL: return "NULL";
		case PT_LOAD: return "LOAD";
		case PT_DYNAMIC: return "DYNAMIC";
		case PT_INTERP: return "INTERP";
		case PT_NOTE: return "NOTE";
		case PT_PHDR: return "PHDR";
		case PT_GNU_EH_FRAME: return "GNU_EH_FRAME";

		case 0x6474e553: return "GNU_PROPERTY";
		case 0x6474e551: return "GNU_STACK";
		case 0x6474e552: return "GNU_RELRO";

		default:
			sprintf(buf, "(%x)", type);
			return buf;
	}
}

static char * programHeaderFlagsToStr(Elf64_Word flags) {
	static char buf[10];

	snprintf(buf, 10, "%c%c%c   ",
		(flags & PF_R) ? 'R' : ' ',
		(flags & PF_W) ? 'W' : ' ',
		(flags & PF_X) ? 'E' : ' '); /* yes, E, not X... */

	return buf;
}

static char * dynamicTagToStr(Elf64_Dyn * dynEntry, char * dynstr) {
	static char buf[1024];
	static char extra[500];
	char * name = NULL;
	sprintf(extra, "0x%lx", dynEntry->d_un.d_val);

	switch (dynEntry->d_tag) {
		case DT_NULL:
			name = "(NULL)";
			break;
		case DT_NEEDED:
			name = "(NEEDED)";
			sprintf(extra, "[shared lib = %s]", dynstr + dynEntry->d_un.d_val);
			break;
		case DT_PLTRELSZ:
			name = "(PLTRELSZ)";
			break;
		case DT_PLTGOT:
			name = "(PLTGOT)";
			break;
		case DT_HASH:
			name = "(HASH)";
			break;
		case DT_STRTAB:
			name = "(STRTAB)";
			break;
		case DT_SYMTAB:
			name = "(SYMTAB)";
			break;
		case DT_RELA:
			name = "(RELA)";
			break;
		case DT_RELASZ:
			name = "(RELASZ)";
			break;
		case DT_RELAENT:
			name = "(RELAENT)";
			break;
		case DT_STRSZ:
			name = "(STRSZ)";
			sprintf(extra, "%ld (bytes)", dynEntry->d_un.d_val);
			break;
		case DT_SYMENT:
			name = "(SYMENT)";
			sprintf(extra, "%ld (bytes)", dynEntry->d_un.d_val);
			break;
		case DT_INIT:
			name = "(INIT)";
			break;
		case DT_FINI:
			name = "(FINI)";
			break;
		case DT_SONAME:
			name = "(SONAME)";
			break;
		case DT_RPATH:
			name = "(RPATH)";
			break;
		case DT_SYMBOLIC:
			name = "(SYMBOLIC)";
			break;
		case DT_REL:
			name = "(REL)";
			break;
		case DT_RELSZ:
			name = "(RELSZ)";
			sprintf(extra, "%ld (bytes)", dynEntry->d_un.d_val);
			break;
		case DT_RELENT:
			name = "(RELENT)";
			break;
		case DT_PLTREL:
			name = "(PLTREL)";
			sprintf(extra, "%s",
				dynEntry->d_un.d_val == DT_REL ? "REL" : "RELA");
			break;
		case DT_DEBUG:
			name = "(DEBUG)";
			break;
		case DT_TEXTREL:
			name = "(TEXTREL)";
			break;
		case DT_JMPREL:
			name = "(JMPREL)";
			break;
		case DT_BIND_NOW:
			name = "(BIND_NOW)";
			break;
		case DT_INIT_ARRAY:
			name = "(INIT_ARRAY)";
			break;
		case DT_FINI_ARRAY:
			name = "(FINI_ARRAY)";
			break;
		case DT_INIT_ARRAYSZ:
			name = "(INIT_ARRAYSZ)";
			sprintf(extra, "%ld (bytes)", dynEntry->d_un.d_val);
			break;
		case DT_FINI_ARRAYSZ:
			name = "(FINI_ARRASZ)";
			sprintf(extra, "%ld (bytes)", dynEntry->d_un.d_val);
			break;
		case 0x1E:
			name = "(FLAGS)";
			break;
		case 0x6ffffef5:
			name = "(GNU_HASH)";
			break;
		case 0x6ffffffb:
			name = "(FLAGS_1)";
			break;
		case 0x6ffffffe:
			name = "(VERNEED)";
			break;
		case 0x6fffffff:
			name = "(VERNEEDNUM)";
			sprintf(extra, "%ld", dynEntry->d_un.d_val);
			break;
		case 0x6ffffff0:
			name = "(VERSYM)";
			break;
		case 0x6ffffff9:
			name = "(RELACOUNT)";
			sprintf(extra, "%ld", dynEntry->d_un.d_val);
			break;
		default:
			name = "(unknown)";
			break;
	}

	sprintf(buf,"%-15s %s", name, extra);
	return buf;
}

static char * relocationInfoToStr(Elf64_Xword info) {
#define CASE(o) case o: return #o;
	switch (info) {
		CASE(R_X86_64_NONE)
		CASE(R_X86_64_64)
		CASE(R_X86_64_PC32)
		CASE(R_X86_64_GOT32)
		CASE(R_X86_64_PLT32)
		CASE(R_X86_64_COPY)
		CASE(R_X86_64_GLOB_DAT)
		CASE(R_X86_64_JUMP_SLOT)
		CASE(R_X86_64_RELATIVE)
		CASE(R_X86_64_GOTPCREL)
		CASE(R_X86_64_32)
		CASE(R_X86_64_32S)
		CASE(R_X86_64_DTPMOD64)
		CASE(R_X86_64_DTPOFF64)
		CASE(R_X86_64_TPOFF64)
		CASE(R_X86_64_TLSGD)
		CASE(R_X86_64_TLSLD)
		CASE(R_X86_64_DTPOFF32)
		CASE(R_X86_64_GOTTPOFF)
		CASE(R_X86_64_TPOFF32)
		CASE(R_X86_64_PC64)
		CASE(R_X86_64_GOTOFF64)
		CASE(R_X86_64_GOTPC32)
		CASE(R_X86_64_GOT64)
		CASE(R_X86_64_GOTPCREL64)
		CASE(R_X86_64_GOTPC64)
		CASE(R_X86_64_GOTPLT64)
		CASE(R_X86_64_PLTOFF64)
		CASE(R_X86_64_SIZE32)
		CASE(R_X86_64_SIZE64)
		CASE(R_X86_64_GOTPC32_TLSDESC)
		CASE(R_X86_64_TLSDESC_CALL)
		CASE(R_X86_64_TLSDESC)
		CASE(R_X86_64_IRELATIVE)
		default:
			return "unknown";
	}
#undef CASE
}

static int sizeOfRelocationValue(int type) {
	switch (type) {
		case R_X86_64_TLSDESC:
			return 16;
		case R_X86_64_64:
		case R_X86_64_GLOB_DAT:
		case R_X86_64_JUMP_SLOT:
		case R_X86_64_RELATIVE:
		case R_X86_64_DTPMOD64:
		case R_X86_64_DTPOFF64:
		case R_X86_64_TPOFF64:
		case R_X86_64_PC64:
		case R_X86_64_GOTOFF64:
		case R_X86_64_GOT64:
		case R_X86_64_GOTPCREL64:
		case R_X86_64_GOTPC64:
		case R_X86_64_GOTPLT64:
		case R_X86_64_PLTOFF64:
		case R_X86_64_SIZE64:
		case R_X86_64_IRELATIVE:
			return 8;
		case R_X86_64_PC32:
		case R_X86_64_GOT32:
		case R_X86_64_PLT32:
		case R_X86_64_GOTPCREL:
		case R_X86_64_32:
		case R_X86_64_32S:
		case R_X86_64_TLSGD:
		case R_X86_64_TLSLD:
		case R_X86_64_DTPOFF32:
		case R_X86_64_GOTTPOFF:
		case R_X86_64_TPOFF32:
		case R_X86_64_GOTPC32:
		case R_X86_64_SIZE32:
		case R_X86_64_GOTPC32_TLSDESC:
			return 4;
		case R_X86_64_16:
		case R_X86_64_PC16:
			return 2;
		case R_X86_64_8:
		case R_X86_64_PC8:
			return 1;
		case R_X86_64_NONE:
		case R_X86_64_COPY:
		case R_X86_64_TLSDESC_CALL:
		default:
			return 0;
	}
}

static char * symbolTypeToStr(int type) {
	static char buf[10];
	switch (type) {
		case STT_NOTYPE:  return "NOTYPE";
		case STT_OBJECT:  return "OBJECT";
		case STT_FUNC:    return "FUNC";
		case STT_SECTION: return "SECTION";
		case STT_FILE:    return "FILE";
		default:
			sprintf(buf, "%x", type);
			return buf;
	}
}

static char * symbolBindToStr(int bind) {
	static char buf[10];
	switch (bind) {
		case STB_LOCAL:  return "LOCAL";
		case STB_GLOBAL: return "GLOBAL";
		case STB_WEAK:   return "WEAK";
		default:
			sprintf(buf, "%x", bind);
			return buf;
	}
}

static int usage(char * argv[]) {
	fprintf(stderr,
		"Usage: %s <option(s)> elf-file(s)\n"
		" Displays information about ELF object files in a GNU binutils-compatible way.\n"
		" Supported options:\n"
		"  -a --all             Equivalent to -h -l -S -s -d -r\n"
		"  -h --file-header     Display the ELF file header\n"
		"  -l --program-headers Display the program headers\n"
		"  -S --section-headers Display the section headers\n"
		"  -e --headers         Equivalent to -h -l -S\n"
		"  -s --syms            Display symbol table\n"
		"  -d --dynamic         Display dynamic section\n"
		"  -r --relocs          Display relocations\n"
		"  -H --help            Show this help text\n"
		" Aliases:\n"
		"  --segments   Same as --file-header\n"
		"  --sections   Same as --section-headers\n"
		"  --symbols    Same as --syms\n"
		, argv[0]);
	return 1;
}

int main(int argc, char * argv[]) {

	static struct option long_opts[] = {
		{"all",             no_argument, 0, 'a'},
		{"file-header",     no_argument, 0, 'h'},
		{"program-headers", no_argument, 0, 'l'},
		{"section-headers", no_argument, 0, 'S'},
		{"headers",         no_argument, 0, 'e'},
		{"syms",            no_argument, 0, 's'},
		{"dynamic",         no_argument, 0, 'd'},
		{"relocs",          no_argument, 0, 'r'},
		{"help",            no_argument, 0, 'H'},

		{"segments",        no_argument, 0, 'l'}, /* Alias for --program-headers */
		{"sections",        no_argument, 0, 'S'}, /* Alias for --section-headers */
		{"symbols",         no_argument, 0, 's'}, /* Alias for --syms */
		{0,0,0,0}
	};

	int show_bits = 0;
	int index, c;

	while ((c = getopt_long(argc, argv, "ahlSesdrH", long_opts, &index)) != -1) {
		if (!c) {
			if (long_opts[index].flag == 0) {
				c = long_opts[index].val;
			}
		}
		switch (c) {
			case 'H':
				return usage(argv);
			case 'a':
				show_bits |= SHOW_FILE_HEADER | SHOW_SECTION_HEADERS | SHOW_PROGRAM_HEADERS | SHOW_SYMBOLS | SHOW_DYNAMIC | SHOW_RELOCATIONS;
				break;
			case 'd':
				show_bits |= SHOW_DYNAMIC;
				break;
			case 'h':
				show_bits |= SHOW_FILE_HEADER;
				break;
			case 'l':
				show_bits |= SHOW_PROGRAM_HEADERS;
				break;
			case 'S':
				show_bits |= SHOW_SECTION_HEADERS;
				break;
			case 'e':
				show_bits |= SHOW_FILE_HEADER | SHOW_PROGRAM_HEADERS | SHOW_SECTION_HEADERS;
				break;
			case 's':
				show_bits |= SHOW_SYMBOLS;
				break;
			case 'r':
				show_bits |= SHOW_RELOCATIONS;
				break;
			default:
				fprintf(stderr, "Unrecognized option: %c\n", c);
				break;
		}
	}

	if (optind >= argc || !show_bits) {
		return usage(argv);
	}

	int out = 0;
	int print_names = 0;

	if (optind + 1 < argc) {
		print_names = 1;
	}

	for (; optind < argc; optind++) {
		FILE * f = fopen(argv[optind],"r");

		if (!f) {
			fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno));
			out = 1;
			continue;
		}

		if (print_names) {
			printf("\nFile: %s\n", argv[optind]);
		}

		/**
		 * Validate header.
		 */
		Elf64_Header header;
		fread(&header, sizeof(Elf64_Header), 1, f);

		if (memcmp("\x7F" "ELF",&header,4)) {
			fprintf(stderr, "%s: %s: not an elf\n", argv[0], argv[optind]);
			out = 1;
			continue;
		}

		if (show_bits & SHOW_FILE_HEADER) {
			printf("ELF Header:\n");
			printf("  Magic:   %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
				header.e_ident[0],  header.e_ident[1],  header.e_ident[2],  header.e_ident[3],
				header.e_ident[4],  header.e_ident[5],  header.e_ident[6],  header.e_ident[7],
				header.e_ident[8],  header.e_ident[9],  header.e_ident[10], header.e_ident[11],
				header.e_ident[12], header.e_ident[13], header.e_ident[14], header.e_ident[15]);
			printf("  Class:                             %s\n", elf_classToStr(header.e_ident[EI_CLASS]));
			printf("  Data:                              %s\n", elf_dataToStr(header.e_ident[EI_DATA]));
			printf("  Version:                           %s\n", elf_versionToStr(header.e_ident[EI_VERSION]));
			printf("  OS/ABI:                            %s\n", elf_osabiToStr(header.e_ident[EI_OSABI]));
			printf("  ABI Version:                       %u\n", header.e_ident[EI_ABIVERSION]);
		}

		if (header.e_ident[EI_CLASS] != ELFCLASS64) {
			continue;
		}

		if (show_bits & SHOW_FILE_HEADER) {
			/* Byte-order dependent from here on out... */
			printf("  Type:                              %s\n", elf_typeToStr(header.e_type));
			printf("  Machine:                           %s\n", elf_machineToStr(header.e_machine));
			printf("  Version:                           0x%x\n", header.e_version);
			printf("  Entry point address:               0x%lx\n", header.e_entry);
			printf("  Start of program headers:          %lu (bytes into file)\n", header.e_phoff);
			printf("  Start of section headers:          %lu (bytes into file)\n", header.e_shoff);
			printf("  Flags:                             0x%x\n", header.e_flags);
			printf("  Size of this header:               %u (bytes)\n", header.e_ehsize);
			printf("  Size of program headers:           %u (bytes)\n", header.e_phentsize);
			printf("  Number of program headers:         %u\n", header.e_phnum);
			printf("  Size of section headers:           %u (bytes)\n", header.e_shentsize);
			printf("  Number of section headers:         %u\n", header.e_shnum);
			printf("  Section header string table index: %u\n", header.e_shstrndx);
		}

		/* Get the section header string table */
		Elf64_Shdr shstr_hdr;
		fseek(f, header.e_shoff + header.e_shentsize * header.e_shstrndx, SEEK_SET);
		fread(&shstr_hdr, sizeof(Elf64_Shdr), 1, f);

		char * stringTable = malloc(shstr_hdr.sh_size);
		fseek(f, shstr_hdr.sh_offset, SEEK_SET);
		fread(stringTable, shstr_hdr.sh_size, 1, f);

		/**
		 * Section Headers
		 */
		if (show_bits & SHOW_SECTION_HEADERS) {
			printf("\nSection Headers:\n");
			printf("  [Nr] Name              Type             Address           Offset\n");
			printf("       Size              EntSize          Flags  Link  Info  Align\n");
			for (unsigned int i = 0; i < header.e_shnum; ++i) {
				fseek(f, header.e_shoff + header.e_shentsize * i, SEEK_SET);
				Elf64_Shdr sectionHeader;
				fread(&sectionHeader, sizeof(Elf64_Shdr), 1, f);

				printf("  [%2d] %-17.17s %-16.16s %016lx  %08lx\n",
					i, stringTable + sectionHeader.sh_name, sectionHeaderTypeToStr(sectionHeader.sh_type),
					sectionHeader.sh_addr, sectionHeader.sh_offset);
				printf("       %016lx  %016lx %4ld %6d %5d %5ld\n",
					sectionHeader.sh_size, sectionHeader.sh_entsize, sectionHeader.sh_flags,
					sectionHeader.sh_link, sectionHeader.sh_info, sectionHeader.sh_addralign);
			}
		}

		/**
		 * Program Headers
		 */
		if (show_bits & SHOW_PROGRAM_HEADERS && header.e_phoff && header.e_phnum) {
			printf("\nProgram Headers:\n");
			printf("  Type           Offset             VirtAddr           PhysAddr\n");
			printf("                 FileSiz            MemSiz              Flags  Align\n");
			for (unsigned int i = 0; i < header.e_phnum; ++i) {
				fseek(f, header.e_phoff + header.e_phentsize * i, SEEK_SET);
				Elf64_Phdr programHeader;
				fread(&programHeader, sizeof(Elf64_Phdr), 1, f);

				printf("  %-14.14s 0x%016lx 0x%016lx 0x%016lx\n",
					programHeaderTypeToStr(programHeader.p_type),
					programHeader.p_offset, programHeader.p_vaddr, programHeader.p_paddr);
				printf("                 0x%016lx 0x%016lx  %s 0x%lx\n",
					programHeader.p_filesz, programHeader.p_memsz,
					programHeaderFlagsToStr(programHeader.p_flags), programHeader.p_align);

				if (programHeader.p_type == PT_INTERP) {
					/* Read interpreter string */
					char * tmp = malloc(programHeader.p_filesz);
					fseek(f, programHeader.p_offset, SEEK_SET);
					fread(tmp, programHeader.p_filesz, 1, f);
					printf("    [Requesting program interpreter: %.*s]\n",
						(int)programHeader.p_filesz, tmp);
					free(tmp);
				}
			}
		}

		/* TODO Section to segment mapping? */

		/**
		 * Dump section information.
		 */
		for (unsigned int i = 0; i < header.e_shnum; ++i) {
			fseek(f, header.e_shoff + header.e_shentsize * i, SEEK_SET);
			Elf64_Shdr sectionHeader;
			fread(&sectionHeader, sizeof(Elf64_Shdr), 1, f);

			/* I think there should only be one of these... */
			switch (sectionHeader.sh_type) {
				case SHT_DYNAMIC:
					if (show_bits & SHOW_DYNAMIC) {
						printf("\nDynamic section at offset 0x%lx contains (up to) %ld entries:\n",
							sectionHeader.sh_offset, sectionHeader.sh_size / sectionHeader.sh_entsize);
						printf("  Tag        Type                         Name/Value\n");

						/* Read the linked string table */
						Elf64_Shdr dynstr;
						fseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_link, SEEK_SET);
						fread(&dynstr, sizeof(Elf64_Shdr), 1, f);
						char * dynStr = malloc(dynstr.sh_size);
						fseek(f, dynstr.sh_offset, SEEK_SET);
						fread(dynStr, dynstr.sh_size, 1, f);

						char * dynTable = malloc(sectionHeader.sh_size);
						fseek(f, sectionHeader.sh_offset, SEEK_SET);
						fread(dynTable, sectionHeader.sh_size, 1, f);

						for (unsigned int i = 0; i < sectionHeader.sh_size / sectionHeader.sh_entsize; i++) {
							Elf64_Dyn * dynEntry = (Elf64_Dyn *)(dynTable + sectionHeader.sh_entsize * i);

							printf(" 0x%016lx %s\n",
								dynEntry->d_tag,
								dynamicTagToStr(dynEntry, dynStr));

							if (dynEntry->d_tag == DT_NULL) break;
						}

						free(dynStr);
						free(dynTable);
					}
					break;
				case SHT_RELA:
					if (show_bits & SHOW_RELOCATIONS) {
						printf("\nRelocation section '%s' at offset 0x%lx contains %ld entries.\n",
							stringTable + sectionHeader.sh_name, sectionHeader.sh_offset,
							sectionHeader.sh_size / sizeof(Elf64_Rela));
						printf("  Offset          Info           Type           Sym. Value    Sym. Name + Addend\n");

						/* Section this relocation is in */
						Elf64_Shdr shdr_this;
						fseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_info, SEEK_SET);
						fread(&shdr_this, sizeof(Elf64_Shdr), 1, f);

						/* Symbol table link */
						Elf64_Shdr shdr_symtab;
						fseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_link, SEEK_SET);
						fread(&shdr_symtab, sizeof(Elf64_Shdr), 1, f);
						Elf64_Sym * symtab = malloc(shdr_symtab.sh_size);
						fseek(f, shdr_symtab.sh_offset, SEEK_SET);
						fread(symtab, shdr_symtab.sh_size, 1, f);

						/* Symbol table's string table link */
						Elf64_Shdr shdr_strtab;
						fseek(f, header.e_shoff + header.e_shentsize * shdr_symtab.sh_link, SEEK_SET);
						fread(&shdr_strtab, sizeof(Elf64_Shdr), 1, f);
						char * strtab = malloc(shdr_strtab.sh_size);
						fseek(f, shdr_strtab.sh_offset, SEEK_SET);
						fread(strtab, shdr_strtab.sh_size, 1, f);

						/* Load relocations from file */
						Elf64_Rela * relocations = malloc(sectionHeader.sh_size);
						fseek(f, sectionHeader.sh_offset, SEEK_SET);
						fread((void*)relocations, sectionHeader.sh_size, 1, f);

						for (unsigned int i = 0; i < sectionHeader.sh_size / sizeof(Elf64_Rela); ++i) {
							Elf64_Shdr shdr;
							Elf64_Sym * this = &symtab[ELF64_R_SYM(relocations[i].r_info)];
							char * symName;

							/* Get symbol name for this relocation */
							if ((this->st_info & 0xF) == STT_SECTION) {
								fseek(f, header.e_shoff + header.e_shentsize * this->st_shndx, SEEK_SET);
								fread(&shdr, sizeof(Elf64_Shdr), 1, f);
								symName = stringTable + shdr.sh_name;
							} else { 
								symName = strtab + this->st_name;
							}

							/* Get the value currently in the section data */
							Elf64_Xword value = 0;
							int valueSize = sizeOfRelocationValue(ELF64_R_TYPE(relocations[i].r_info));
							fseek(f, shdr_this.sh_offset + relocations[i].r_offset, SEEK_SET);
							switch (valueSize) {
								case 8:
									{
										uint64_t val;
										fread(&val, valueSize, 1, f);
										value = val;
										break;
									}
								case 4:
									{
										uint32_t val;
										fread(&val, valueSize, 1, f);
										value = val;
										break;
									}
								case 2:
									{
										uint16_t val;
										fread(&val, valueSize, 1, f);
										value = val;
										break;
									}
								case 1:
									{
										uint8_t val;
										fread(&val, valueSize, 1, f);
										value = val;
										break;
									}
								default:
									break;
							}

							printf("%012lx  %012lx %-15.15s %016lx %s + %lx\n",
								relocations[i].r_offset, relocations[i].r_info,
								relocationInfoToStr(ELF64_R_TYPE(relocations[i].r_info)),
								value,
								symName,
								relocations[i].r_addend);
						}

						free(relocations);
						free(strtab);
						free(symtab);
					}
					break;
				case SHT_DYNSYM:
				case SHT_SYMTAB:
					if (show_bits & SHOW_SYMBOLS) {
						printf("\nSymbol table '%s' contains %ld entries.\n",
							stringTable + sectionHeader.sh_name,
							sectionHeader.sh_size / sizeof(Elf64_Sym));
						printf("   Num:    Value          Size Type    Bind   Vis      Ndx Name\n");

						Elf64_Sym * symtab = malloc(sectionHeader.sh_size);
						fseek(f, sectionHeader.sh_offset, SEEK_SET);
						fread(symtab, sectionHeader.sh_size, 1, f);

						Elf64_Shdr shdr_strtab;
						fseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_link, SEEK_SET);
						fread(&shdr_strtab, sizeof(Elf64_Shdr), 1, f);
						char * strtab = malloc(shdr_strtab.sh_size);
						fseek(f, shdr_strtab.sh_offset, SEEK_SET);
						fread(strtab, shdr_strtab.sh_size, 1, f);

						for (unsigned int i = 0; i < sectionHeader.sh_size / sizeof(Elf64_Sym); ++i) {
							printf("%6u: %016lx %6lu %-7.7s %-6.6s %-4d %6d %s\n",
								i, symtab[i].st_value, symtab[i].st_size,
								symbolTypeToStr(symtab[i].st_info & 0xF),
								symbolBindToStr(symtab[i].st_info >> 4),
								symtab[i].st_other,
								symtab[i].st_shndx,
								strtab + symtab[i].st_name);
						}

						free(strtab);
						free(symtab);
					}
					break;
				default:
					break;

			}
		}
	}

	return out;
}
