/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.compatibility.common.util;

import com.google.errorprone.annotations.CanIgnoreReturnValue;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A poor man's implementation of the readelf command. This program is designed to parse ELF
 * (Executable and Linkable Format) files.
 */
// ToDo: consolidate with com.android.compatibility.common.util
public class ReadElf implements AutoCloseable {
    /** The magic values for the ELF identification. */
    private static final byte[] ELFMAG = {
        (byte) 0x7F, (byte) 'E', (byte) 'L', (byte) 'F',
    };

    private static final int EI_NIDENT = 16;

    private static final int EI_CLASS = 4;
    private static final int EI_DATA = 5;

    public static final int ET_DYN = 3;
    public static final int EM_386 = 3;
    public static final int EM_ARM = 40;
    public static final int EM_X86_64 = 62;
    // http://en.wikipedia.org/wiki/Qualcomm_Hexagon
    public static final int EM_QDSP6 = 164;
    public static final int EM_AARCH64 = 183;
    public static final int EM_RISCV = 243;

    public static final String ARCH_ARM = "arm";
    public static final String ARCH_X86 = "x86";
    public static final String ARCH_RISCV = "riscv";
    public static final String ARCH_UNKNOWN = "unknown";
    private static final String RODATA = ".rodata";

    private static final int ELFCLASS32 = 1;
    private static final int ELFCLASS64 = 2;

    private static final int ELFDATA2LSB = 1;
    private static final int ELFDATA2MSB = 2;

    private static final int EV_CURRENT = 1;

    private static final long PT_LOAD = 1;

    // https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
    private static final int SHT_PROGBITS = 1;
    private static final int SHT_SYMTAB = 2;
    private static final int SHT_STRTAB = 3;
    private static final int SHT_DYNAMIC = 6;
    private static final int SHT_DYNSYM = 11;
    private static final int SHT_GNU_VERDEF = 0x6ffffffd;
    private static final int SHT_GNU_VERNEED = 0x6ffffffe;
    private static final int SHT_GNU_VERSYM = 0x6fffffff;

    public static class Symbol {
        public static final int STB_LOCAL = 0;
        public static final int STB_GLOBAL = 1;
        public static final int STB_WEAK = 2;
        public static final int STB_LOPROC = 13;
        public static final int STB_HIPROC = 15;

        public static final int STT_NOTYPE = 0;
        public static final int STT_OBJECT = 1;
        public static final int STT_FUNC = 2;
        public static final int STT_SECTION = 3;
        public static final int STT_FILE = 4;
        public static final int STT_COMMON = 5;
        public static final int STT_TLS = 6;

        public static final int SHN_UNDEF = 0;
        public static final int SHN_ABS = 0xfff1;

        public final String name;
        public final int bind;
        public final int type;
        public final int shndx;
        public final long value;
        public final long size;
        public final int other;

        public VerNeed mVerNeed;
        public VerDef mVerDef;

        Symbol(String name, int st_info, int st_shndx, long st_value, long st_size, int st_other) {
            this.name = name;
            this.bind = (st_info >> 4) & 0x0F;
            this.type = st_info & 0x0F;
            this.shndx = st_shndx;
            this.value = st_value;
            this.size = st_size;
            this.other = st_other;
        }

        @Override
        public String toString() {
            return String.format(
                    "%s, %s, %s, %s, %s, %s",
                    name,
                    toBind(),
                    toType(),
                    toShndx(),
                    getExternalLibFileName(),
                    getExternalLibName());
        }

        public String toBind() {
            switch (bind) {
                case STB_LOCAL:
                    return "LOCAL";
                case STB_GLOBAL:
                    return "GLOBAL";
                case STB_WEAK:
                    return "WEAK";
            }
            return "STB_??? (" + bind + ")";
        }

        public String toType() {
            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";
                case STT_COMMON:
                    return "COMMON";
                case STT_TLS:
                    return "TLS";
            }
            return "STT_??? (" + type + ")";
        }

        public String toShndx() {
            switch (shndx) {
                case SHN_ABS:
                    return "ABS";
                case SHN_UNDEF:
                    return "UND";
            }
            return String.valueOf(shndx);
        }

        // if a symbol is not define locally
        public boolean isGlobalUnd() {
            return (bind != STB_LOCAL && shndx == SHN_UNDEF);
        }

        // if a symbol is extern
        public boolean isExtern() {
            return (bind != STB_LOCAL && shndx != SHN_UNDEF);
        }

        public String getExternalLibFileName() {
            if (mVerNeed != null) {
                return mVerNeed.vn_file_name;
            }
            return null;
        }

        public String getExternalLibName() {
            if (mVerNeed != null) {
                return mVerNeed.vn_vernaux[0].vna_lib_name;
            }
            return null;
        }

        public int getExternalLibVer() {
            if (mVerNeed != null) {
                return mVerNeed.vn_vernaux[0].vna_other;
            }
            return -1;
        }

        public String getVerDefLibName() {
            if (mVerDef != null) {
                return mVerDef.vd_verdaux[0].vda_lib_name;
            }
            return null;
        }

        public int getVerDefVersion() {
            if (mVerDef != null) {
                return mVerDef.vd_version;
            }
            return -1;
        }
    }

    public static class SecHeader {
        public final long sh_name;
        public final long sh_type;
        public final long sh_flags;
        public final long sh_addr;
        public final long sh_offset;
        public final long sh_size;
        public final long sh_link;
        public final long sh_info;
        public final long sh_addralign;
        public final long sh_entsize;

        SecHeader(
                long name,
                long type,
                long flags,
                long addr,
                long offset,
                long size,
                long link,
                long info,
                long addralign,
                long entsize) {
            this.sh_name = name;
            this.sh_type = type;
            this.sh_flags = flags;
            this.sh_addr = addr;
            this.sh_offset = offset;
            this.sh_size = size;
            this.sh_link = link;
            this.sh_info = info;
            this.sh_addralign = addralign;
            this.sh_entsize = entsize;
        }

        @Override
        public String toString() {
            return String.format(
                    "%d, %d, %d, %d, %d, %d, %d, %d, %d, %d",
                    this.sh_name,
                    this.sh_type,
                    this.sh_flags,
                    this.sh_addr,
                    this.sh_offset,
                    this.sh_size,
                    this.sh_link,
                    this.sh_info,
                    this.sh_addralign,
                    this.sh_entsize);
        }
    }

    public static class VerNeed {
        public final int vn_version;
        public final int vn_cnt;
        public final long vn_file;
        public final long vn_aux;
        public final long vn_next;
        public String vn_file_name;
        public VerNAux[] vn_vernaux;

        VerNeed(String file_name, String lib_name, int ndx) {
            this.vn_file_name = file_name.toLowerCase();
            this.vn_vernaux = new VerNAux[1];
            this.vn_vernaux[0] = new VerNAux(lib_name, ndx);

            this.vn_version = 0;
            this.vn_cnt = 0;
            this.vn_file = 0;
            this.vn_aux = 0;
            this.vn_next = 0;
        }

        VerNeed(int ver, int cnt, long file, long aux, long next) {
            this.vn_version = ver;
            this.vn_cnt = cnt;
            this.vn_file = file;
            this.vn_aux = aux;
            this.vn_next = next;
        }

        @Override
        public String toString() {
            String vernauxStr = "";
            for (int i = 0; i < this.vn_cnt; i++) {
                vernauxStr += String.format("    %s\n", this.vn_vernaux[i].toString());
            }
            return String.format(
                    "%s, %d, %d, %d, %d, %d \n%s",
                    this.vn_file_name,
                    this.vn_version,
                    this.vn_cnt,
                    this.vn_file,
                    this.vn_aux,
                    this.vn_next,
                    vernauxStr);
        }
    }

    public static class VerNAux {
        public final long vna_hash;
        public final int vna_flags;
        public final int vna_other;
        public final long vna_name;
        public final long vna_next;
        public String vna_lib_name;

        VerNAux(String lib_name, int ndx) {
            this.vna_lib_name = lib_name;

            this.vna_hash = 0;
            this.vna_flags = 0;
            this.vna_other = ndx;
            this.vna_name = 0;
            this.vna_next = 0;
        }

        VerNAux(long hash, int flags, int other, long name, long next) {
            this.vna_hash = hash;
            this.vna_flags = flags;
            this.vna_other = other;
            this.vna_name = name;
            this.vna_next = next;
        }

        @Override
        public String toString() {
            return String.format(
                    "%s, %d, %d, %d, %d, %d",
                    this.vna_lib_name,
                    this.vna_hash,
                    this.vna_flags,
                    this.vna_other,
                    this.vna_name,
                    this.vna_next);
        }
    }

    public static class VerDef {
        public final int vd_version;
        public final int vd_flags;
        public final int vd_ndx;
        public final int vd_cnt;
        public final long vd_hash;
        public final long vd_aux;
        public final long vd_next;
        public VerDAux[] vd_verdaux;

        VerDef(String lib_name) {
            this.vd_verdaux = new VerDAux[1];
            this.vd_verdaux[0] = new VerDAux(lib_name);

            this.vd_version = 0;
            this.vd_flags = 0;
            this.vd_ndx = 0;
            this.vd_cnt = 0;
            this.vd_hash = 0;
            this.vd_aux = 0;
            this.vd_next = 0;
        }

        VerDef(int ver, int flags, int ndx, int cnt, long hash, long aux, long next) {
            this.vd_version = ver;
            this.vd_flags = flags;
            this.vd_ndx = ndx;
            this.vd_cnt = cnt;
            this.vd_hash = hash;
            this.vd_aux = aux;
            this.vd_next = next;
        }

        @Override
        public String toString() {
            String vStr = "";
            for (int i = 0; i < this.vd_cnt; i++) {
                vStr += String.format("    %s\n", this.vd_verdaux[i].toString());
            }
            return String.format(
                    "%s, %d, %d, %d, %d, %d \n%s",
                    this.vd_verdaux[0].vda_lib_name,
                    this.vd_version,
                    this.vd_flags,
                    this.vd_ndx,
                    this.vd_cnt,
                    this.vd_hash,
                    vStr);
        }
    }

    public static class VerDAux {
        public final long vda_name;
        public final long vda_next;
        public String vda_lib_name;

        VerDAux(String lib_name) {
            this.vda_lib_name = lib_name.toLowerCase();

            this.vda_name = 0;
            this.vda_next = 0;
        }

        VerDAux(long name, long next) {
            this.vda_name = name;
            this.vda_next = next;
        }

        @Override
        public String toString() {
            return String.format("%s, %d, %d", this.vda_lib_name, this.vda_name, this.vda_next);
        }
    }

    // Dynamic Section Entry
    public static class DynamicEntry {
        private static final int DT_NEEDED = 1;
        public final long mTag;
        public final long mValue;

        DynamicEntry(long tag, long value) {
            mTag = tag;
            mValue = value;
        }

        public boolean isNeeded() {
            if (mTag == DT_NEEDED) {
                return true;
            } else {
                // System.err.println(String.format("Not Needed: %d, %d", mTag, mValue));
                return false;
            }
        }

        public long getValue() {
            return mValue;
        }

        @Override
        public String toString() {
            return String.format("%d, %d", this.mTag, this.mValue);
        }
    }

    private final String mPath;
    private final RandomAccessFile mFile;
    private final byte[] mBuffer = new byte[512];
    private int mEndian;
    private boolean mIsDynamic;
    private boolean mIsPIE;
    private int mType;
    private int mAddrSize;
    private int mMachine;

    /** Symbol Table offset */
    private long mSymTabOffset;

    /** Symbol Table size */
    private long mSymTabSize;

    /** Symbol entry count */
    private int mSymEntCnt;

    /** Dynamic Symbol Table offset */
    private long mDynSymOffset;

    /** Dynamic Symbol Table size */
    private long mDynSymSize;

    /** Dynamic entry count */
    private int mDynSymEntCnt;

    /** Section Header String Table offset */
    private long mShStrTabOffset;

    /** Section Header String Table size */
    private long mShStrTabSize;

    /** String Table offset */
    private long mStrTabOffset;

    /** String Table size */
    private long mStrTabSize;

    /** Dynamic String Table offset */
    private long mDynStrOffset;

    /** Dynamic String Table size */
    private long mDynStrSize;

    /** Dynamic Table offset */
    private long mDynamicTabOffset;

    /** Dynamic Table size */
    private long mDynamicTabSize;

    /** Version Symbols Table offset */
    private long mVerSymTabOffset;

    /** Version Symbols Table size */
    private long mVerSymTabSize;

    /** Version Needs Table offset */
    private long mVerNeedTabOffset;

    /** Version Definition Table size */
    private long mVerNeedTabSize;

    private int mVerNeedEntryCnt;

    /** Version Definition Table offset */
    private long mVerDefTabOffset;

    /** Version Needs Table size */
    private long mVerDefTabSize;

    private int mVerDefEntryCnt;

    /** Symbol Table symbol names */
    private Map<String, Symbol> mSymbols;

    /** Symbol Table symbol array */
    private Symbol[] mSymArr;

    /** Dynamic Symbol Table symbol names */
    private Map<String, Symbol> mDynamicSymbols;

    /** Dynamic Symbol Table symbol array */
    private Symbol[] mDynSymArr;

    /** Version Symbols Table */
    private int[] mVerSym;

    /** Version Needed Table */
    private VerNeed[] mVerNeedArr;

    /** Version Definition Table */
    private VerDef[] mVerDefArr;

    /** Dynamic Table */
    private List<DynamicEntry> mDynamicArr;

    /** Rodata offset */
    private boolean mHasRodata;

    /** Rodata offset */
    private long mRodataOffset;

    /** Rodata size */
    private int mRodataSize;

    /** Rodata String List */
    private List<String> mRoStrings;

    /** Rodata byte[] */
    private byte[] mRoData;

    public static ReadElf read(File file) throws IOException {
        return new ReadElf(file);
    }

    public static void main(String[] args) throws IOException {
        for (String arg : args) {
            ReadElf elf = ReadElf.read(new File(arg));
            elf.getDynamicSymbol("x");
            elf.getSymbol("x");

            Symbol[] symArr;
            System.out.println("===Symbol===");
            symArr = elf.getSymArr();
            for (int i = 0; i < symArr.length; i++) {
                System.out.println(String.format("%8x: %s", i, symArr[i].toString()));
            }
            System.out.println("===Dynamic Symbol===");
            symArr = elf.getDynSymArr();
            for (int i = 0; i < symArr.length; i++) {
                if (elf.mVerNeedEntryCnt > 0) {
                    System.out.println(
                            String.format(
                                    "%8x: %s, %s, %s - %d",
                                    i,
                                    symArr[i].toString(),
                                    symArr[i].getExternalLibName(),
                                    symArr[i].getExternalLibFileName(),
                                    symArr[i].getExternalLibVer()));
                } else {
                    System.out.println(
                            String.format(
                                    "%8x: %s, %s - %d",
                                    i,
                                    symArr[i].toString(),
                                    symArr[i].getVerDefLibName(),
                                    symArr[i].getVerDefVersion()));
                }
            }

            System.out.println("===Dynamic Dependencies===");
            for (String DynDepEntry : elf.getDynamicDependencies()) {
                System.out.println(DynDepEntry);
            }

            System.out.println("===Strings in Read Only(.rodata) section===");
            for (String roStr : elf.getRoStrings()) {
                System.out.println(roStr);
            }

            elf.close();
        }
    }

    public static boolean isElf(File file) {
        try {
            if (file.length() < EI_NIDENT) {
                throw new IllegalArgumentException(
                        "Too small to be an ELF file: " + file.getCanonicalPath());
            }

            RandomAccessFile raFile = new RandomAccessFile(file, "r");
            byte[] buffer = new byte[512];
            raFile.seek(0);
            raFile.readFully(buffer, 0, EI_NIDENT);
            if (buffer[0] != ELFMAG[0]
                    || buffer[1] != ELFMAG[1]
                    || buffer[2] != ELFMAG[2]
                    || buffer[3] != ELFMAG[3]) {
                throw new IllegalArgumentException("Invalid ELF file: " + file.getCanonicalPath());
            }
            raFile.close();
            ;
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public int getBits() {
        if (mMachine == EM_386 || mMachine == EM_ARM || mMachine == EM_QDSP6) {
            return 32;
        } else if (mMachine == EM_AARCH64 || mMachine == EM_X86_64 || mMachine == EM_RISCV) {
            return 64;
        } else {
            return -1;
        }
    }

    public String getArchitecture() {
        if (mMachine == EM_ARM || mMachine == EM_AARCH64) {
            return ARCH_ARM;
        } else if (mMachine == EM_386 || mMachine == EM_X86_64) {
            return ARCH_X86;
        } else if (mMachine == EM_RISCV) {
            return ARCH_RISCV;
        } else {
            return ARCH_UNKNOWN;
        }
    }

    public Map<String, Symbol> getSymbols() throws IOException {
        if (mSymbols == null) {
            getSymbol("");
        }
        return mSymbols;
    }

    public Symbol[] getSymArr() throws IOException {
        if (mSymArr == null) {
            getSymbol("");
        }
        return mSymArr;
    }

    public Map<String, Symbol> getDynamicSymbols() throws IOException {
        if (mDynamicSymbols == null) {
            getDynamicSymbol("");
        }
        return mDynamicSymbols;
    }

    public Symbol[] getDynSymArr() throws IOException {
        if (mDynSymArr == null) {
            getDynamicSymbol("");
        }
        return mDynSymArr;
    }

    public boolean isDynamic() {
        return mIsDynamic;
    }

    public int getType() {
        return mType;
    }

    public boolean isPIE() {
        return mIsPIE;
    }

    private ReadElf(File file) throws IOException {
        mHasRodata = false;
        mRoData = null;
        mPath = file.getPath();
        mFile = new RandomAccessFile(file, "r");

        if (mFile.length() < EI_NIDENT) {
            throw new IllegalArgumentException("Too small to be an ELF file: " + file);
        }

        readHeader();
    }

    @Override
    public void close() {
        try {
            mFile.close();
        } catch (IOException ignored) {
        }
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            close();
        } finally {
            super.finalize();
        }
    }

    private void readHeader() throws IOException {
        mFile.seek(0);
        mFile.readFully(mBuffer, 0, EI_NIDENT);

        if (mBuffer[0] != ELFMAG[0]
                || mBuffer[1] != ELFMAG[1]
                || mBuffer[2] != ELFMAG[2]
                || mBuffer[3] != ELFMAG[3]) {
            throw new IllegalArgumentException("Invalid ELF file: " + mPath);
        }

        int elfClass = mBuffer[EI_CLASS];
        if (elfClass == ELFCLASS32) {
            mAddrSize = 4;
        } else if (elfClass == ELFCLASS64) {
            mAddrSize = 8;
        } else {
            throw new IOException("Invalid ELF EI_CLASS: " + elfClass + ": " + mPath);
        }

        mEndian = mBuffer[EI_DATA];
        if (mEndian == ELFDATA2LSB) {
        } else if (mEndian == ELFDATA2MSB) {
            throw new IOException("Unsupported ELFDATA2MSB file: " + mPath);
        } else {
            throw new IOException("Invalid ELF EI_DATA: " + mEndian + ": " + mPath);
        }

        mType = readHalf();

        int e_machine = readHalf();
        if (e_machine != EM_386
                && e_machine != EM_X86_64
                && e_machine != EM_AARCH64
                && e_machine != EM_ARM
                && e_machine != EM_RISCV
                && e_machine != EM_QDSP6) {
            throw new IOException("Invalid ELF e_machine: " + e_machine + ": " + mPath);
        }

        // AbiTest relies on us rejecting any unsupported combinations.
        if ((e_machine == EM_386 && elfClass != ELFCLASS32)
                || (e_machine == EM_X86_64 && elfClass != ELFCLASS64)
                || (e_machine == EM_AARCH64 && elfClass != ELFCLASS64)
                || (e_machine == EM_ARM && elfClass != ELFCLASS32)
                || (e_machine == EM_QDSP6 && elfClass != ELFCLASS32)) {
            throw new IOException(
                    "Invalid e_machine/EI_CLASS ELF combination: "
                            + e_machine
                            + "/"
                            + elfClass
                            + ": "
                            + mPath);
        }

        mMachine = e_machine;
        long e_version = readWord();
        if (e_version != EV_CURRENT) {
            throw new IOException("Invalid e_version: " + e_version + ": " + mPath);
        }

        long e_entry = readAddr();

        long ph_off = readOff();
        long sh_off = readOff();

        long e_flags = readWord();
        int e_ehsize = readHalf();
        int e_phentsize = readHalf();
        int e_phnum = readHalf();
        int e_shentsize = readHalf();
        int e_shnum = readHalf();
        int e_shstrndx = readHalf();

        readSectionHeaders(sh_off, e_shnum, e_shentsize, e_shstrndx);
        readProgramHeaders(ph_off, e_phnum, e_phentsize);
    }

    private void readSectionHeaders(long sh_off, int e_shnum, int e_shentsize, int e_shstrndx)
            throws IOException {
        // Read the Section Header String Table offset first.
        {
            mFile.seek(sh_off + e_shstrndx * e_shentsize);

            long sh_name = readWord();
            long sh_type = readWord();
            long sh_flags = readX(mAddrSize);
            long sh_addr = readAddr();
            long sh_offset = readOff();
            long sh_size = readX(mAddrSize);
            // ...

            if (sh_type == SHT_STRTAB) {
                mShStrTabOffset = sh_offset;
                mShStrTabSize = sh_size;
            }
        }

        for (int i = 0; i < e_shnum; ++i) {
            // Don't bother to re-read the Section Header StrTab.
            if (i == e_shstrndx) {
                continue;
            }

            mFile.seek(sh_off + i * e_shentsize);

            long sh_name = readWord();
            long sh_type = readWord();
            long sh_flags = readX(mAddrSize);
            long sh_addr = readAddr();
            long sh_offset = readOff();
            long sh_size = readX(mAddrSize);
            long sh_link = readWord();
            long sh_info = readWord();
            long sh_addralign = readX(mAddrSize);
            ;
            long sh_entsize = readX(mAddrSize);
            ;

            if (sh_type == SHT_SYMTAB || sh_type == SHT_DYNSYM) {
                final String symTabName = readShStrTabEntry(sh_name);
                if (".symtab".equals(symTabName)) {
                    mSymTabOffset = sh_offset;
                    mSymTabSize = sh_size;
                    mSymEntCnt = (int) (sh_size / sh_entsize);
                } else if (".dynsym".equals(symTabName)) {
                    mDynSymOffset = sh_offset;
                    mDynSymSize = sh_size;
                    mDynSymEntCnt = (int) (sh_size / sh_entsize);
                }
                System.out.println(
                        String.format(
                                "%s, %d, %d, %d, %d, %d",
                                symTabName, sh_offset, sh_size, sh_link, sh_info, sh_entsize));
            } else if (sh_type == SHT_STRTAB) {
                final String strTabName = readShStrTabEntry(sh_name);
                if (".strtab".equals(strTabName)) {
                    mStrTabOffset = sh_offset;
                    mStrTabSize = sh_size;
                    System.out.println(
                            String.format(
                                    "%s, %d, %d, %d, %d",
                                    strTabName, sh_offset, sh_size, sh_link, sh_info));
                } else if (".dynstr".equals(strTabName)) {
                    mDynStrOffset = sh_offset;
                    mDynStrSize = sh_size;
                    System.out.println(
                            String.format(
                                    "%s, %d, %d, %d, %d",
                                    strTabName, sh_offset, sh_size, sh_link, sh_info));
                }
            } else if (sh_type == SHT_DYNAMIC) {
                mIsDynamic = true;
                final String strTabName = readShStrTabEntry(sh_name);
                mDynamicTabOffset = sh_offset;
                mDynamicTabSize = sh_size;
                System.out.println(
                        String.format(
                                "%s, %d, %d, %d, %d",
                                strTabName, sh_offset, sh_size, sh_link, sh_info));
            } else if (sh_type == SHT_GNU_VERSYM) {
                final String strTabName = readShStrTabEntry(sh_name);
                if (".gnu.version".equals(strTabName)) {
                    mVerSymTabOffset = sh_offset;
                    mVerSymTabSize = sh_size;
                }
                System.out.println(
                        String.format(
                                "%s, %d, %d, %d, %d",
                                strTabName, sh_offset, sh_size, sh_link, sh_info));
            } else if (sh_type == SHT_GNU_VERNEED) {
                final String strTabName = readShStrTabEntry(sh_name);
                if (".gnu.version_r".equals(strTabName)) {
                    mVerNeedTabOffset = sh_offset;
                    mVerNeedTabSize = sh_size;
                    mVerNeedEntryCnt = (int) sh_info;
                }
                System.out.println(
                        String.format(
                                "%s, %d, %d, %d, %d",
                                strTabName, sh_offset, sh_size, sh_link, sh_info));
            } else if (sh_type == SHT_GNU_VERDEF) {
                final String strTabName = readShStrTabEntry(sh_name);
                if (".gnu.version_d".equals(strTabName)) {
                    mVerDefTabOffset = sh_offset;
                    mVerDefTabSize = sh_size;
                    mVerDefEntryCnt = (int) sh_info;
                }
                System.out.println(
                        String.format(
                                "%s, %d, %d, %d, %d",
                                strTabName, sh_offset, sh_size, sh_link, sh_info));
            } else if (sh_type == SHT_PROGBITS) {
                final String strTabName = readShStrTabEntry(sh_name);
                if (".rodata".equals(strTabName)) {
                    mHasRodata = true;
                    mRodataOffset = sh_offset;
                    mRodataSize = (int) sh_size;
                }
                System.out.println(
                        String.format(
                                "%s, %d, %d, %d, %d",
                                strTabName, sh_offset, sh_size, sh_link, sh_info));
            }
        }
    }

    private void readProgramHeaders(long ph_off, int e_phnum, int e_phentsize) throws IOException {
        for (int i = 0; i < e_phnum; ++i) {
            mFile.seek(ph_off + i * e_phentsize);

            long p_type = readWord();
            if (p_type == PT_LOAD) {
                if (mAddrSize == 8) {
                    // Only in Elf64_phdr; in Elf32_phdr p_flags is at the end.
                    long p_flags = readWord();
                }
                long p_offset = readOff();
                long p_vaddr = readAddr();
                // ...

                if (p_vaddr == 0) {
                    mIsPIE = true;
                }
            }
        }
    }

    private HashMap<String, Symbol> readSymbolTable(
            Symbol[] symArr,
            boolean isDynSym,
            long symStrOffset,
            long symStrSize,
            long tableOffset,
            long tableSize)
            throws IOException {
        HashMap<String, Symbol> result = new HashMap<String, Symbol>();
        mFile.seek(tableOffset);
        int i = 0;
        while (mFile.getFilePointer() < tableOffset + tableSize) {
            long st_name = readWord();
            int st_info;
            int st_shndx;
            long st_value;
            long st_size;
            int st_other;
            if (mAddrSize == 8) {
                st_info = readByte();
                st_other = readByte();
                st_shndx = readHalf();
                st_value = readAddr();
                st_size = readX(mAddrSize);
            } else {
                st_value = readAddr();
                st_size = readWord();
                st_info = readByte();
                st_other = readByte();
                st_shndx = readHalf();
            }

            String symName;
            if (st_name == 0) {
                symName = "";
            } else {
                symName = readStrTabEntry(symStrOffset, symStrSize, st_name);
            }

            Symbol sym = new Symbol(symName, st_info, st_shndx, st_value, st_size, st_other);
            if (!symName.equals("")) {
                result.put(symName, sym);
            }
            if (isDynSym) {
                if (mVerNeedEntryCnt > 0) {
                    if (sym.type == Symbol.STT_NOTYPE) {
                        sym.mVerNeed = mVerNeedArr[0];
                    } else {
                        sym.mVerNeed = getVerNeed(mVerSym[i]);
                    }
                } else if (mVerDefEntryCnt > 0) {
                    sym.mVerDef = mVerDefArr[mVerSym[i]];
                }
            }
            symArr[i] = sym;
            i++;
        }
        System.out.println(
                String.format(
                        "Info readSymbolTable: %s, isDynSym %b, symbol# %d",
                        mPath, isDynSym, symArr.length));
        return result;
    }

    private String readShStrTabEntry(long strOffset) throws IOException {
        if (mShStrTabOffset == 0 || strOffset < 0 || strOffset >= mShStrTabSize) {
            return null;
        }
        return readString(mShStrTabOffset + strOffset);
    }

    private String readStrTabEntry(long tableOffset, long tableSize, long strOffset)
            throws IOException {
        if (tableOffset == 0 || strOffset < 0 || strOffset >= tableSize) {
            return null;
        }
        return readString(tableOffset + strOffset);
    }

    private String readDynStrTabEntry(long strOffset) throws IOException {
        if (mDynStrOffset == 0 || strOffset < 0 || strOffset >= mDynStrSize) {
            return null;
        }
        return readString(mDynStrOffset + strOffset);
    }

    private int[] getVerSym() throws IOException {
        if (mVerSym == null) {
            mFile.seek(mVerSymTabOffset);
            int cnt = (int) mVerSymTabSize / 2;
            mVerSym = new int[cnt];
            for (int i = 0; i < cnt; i++) {
                mVerSym[i] = readHalf();
                //System.out.println(String.format("%d, %d", i, mVerSym[i]));
            }
        }
        return mVerSym;
    }

    public VerNeed getVerNeed(int ndx) throws IOException {
        // vna_other Contains version index unique for the file which is used in the version symbol table.
        if (ndx < 2) {
            return this.mVerNeedArr[ndx];
        }

        for (int i = 2; i < this.mVerNeedEntryCnt + 2; i++) {
            for (int j = 0; j < this.mVerNeedArr[i].vn_cnt; j++) {
                if (this.mVerNeedArr[i].vn_vernaux[j].vna_other == ndx) {
                    return this.mVerNeedArr[i];
                }
            }
        }
        System.out.println(String.format("no VerNeed found: %d", ndx));
        return null;
    }

    private VerNeed[] getVerNeedArr() throws IOException {
        if (mVerNeedArr == null) {
            mVerNeedArr = new VerNeed[mVerNeedEntryCnt + 2];

            // SHT_GNU_versym 0: local
            mVerNeedArr[0] = new VerNeed("*local*", "*local*", 0);
            // HT_GNU_versym 1: global
            mVerNeedArr[1] = new VerNeed("*global*", "*global*", 1);

            long idx = mVerNeedTabOffset;
            for (int i = 2; i < mVerNeedEntryCnt + 2; i++) {
                mFile.seek(idx);
                mVerNeedArr[i] =
                        new VerNeed(readHalf(), readHalf(), readWord(), readWord(), readWord());
                mVerNeedArr[i].vn_file_name = readDynStrTabEntry(mVerNeedArr[i].vn_file).toLowerCase();

                mVerNeedArr[i].vn_vernaux = new VerNAux[mVerNeedArr[i].vn_cnt];
                long idxAux = idx + mVerNeedArr[i].vn_aux;
                for (int j = 0; j < mVerNeedArr[i].vn_cnt; j++) {
                    mFile.seek(idxAux);
                    mVerNeedArr[i].vn_vernaux[j] =
                            new VerNAux(readWord(), readHalf(), readHalf(), readWord(), readWord());
                    mVerNeedArr[i].vn_vernaux[j].vna_lib_name =
                            readDynStrTabEntry(mVerNeedArr[i].vn_vernaux[j].vna_name);
                    idxAux += mVerNeedArr[i].vn_vernaux[j].vna_next;
                }
                idx += mVerNeedArr[i].vn_next;
                System.out.println(mVerNeedArr[i]);
            }
        }

        return mVerNeedArr;
    }

    private VerDef[] getVerDef() throws IOException {
        if (mVerDefArr == null) {
            mVerDefArr = new VerDef[mVerDefEntryCnt + 2];

            // SHT_GNU_versym 0: local
            mVerDefArr[0] = new VerDef("*local*");
            // HT_GNU_versym 1: global
            mVerDefArr[1] = new VerDef("*global*");

            long idx = mVerDefTabOffset;
            for (int i = 2; i < mVerDefEntryCnt + 2; i++) {
                mFile.seek(idx);
                mVerDefArr[i] =
                        new VerDef(
                                readHalf(),
                                readHalf(),
                                readHalf(),
                                readHalf(),
                                readWord(),
                                readWord(),
                                readWord());

                mVerDefArr[i].vd_verdaux = new VerDAux[mVerDefArr[i].vd_cnt];
                long idxAux = idx + mVerDefArr[i].vd_aux;
                for (int j = 0; j < mVerDefArr[i].vd_cnt; j++) {
                    mFile.seek(idxAux);
                    mVerDefArr[i].vd_verdaux[j] = new VerDAux(readWord(), readWord());
                    mVerDefArr[i].vd_verdaux[j].vda_lib_name =
                            readDynStrTabEntry(mVerDefArr[i].vd_verdaux[j].vda_name).toLowerCase();
                    idxAux += mVerDefArr[i].vd_verdaux[j].vda_next;
                }
                idx += mVerDefArr[i].vd_next;
                System.out.println(mVerDefArr[i]);
            }
        }
        return mVerDefArr;
    }

    private int readHalf() throws IOException {
        return (int) readX(2);
    }

    private long readWord() throws IOException {
        return readX(4);
    }

    private long readOff() throws IOException {
        return readX(mAddrSize);
    }

    private long readAddr() throws IOException {
        return readX(mAddrSize);
    }

    private long readX(int byteCount) throws IOException {
        mFile.readFully(mBuffer, 0, byteCount);

        int answer = 0;
        if (mEndian == ELFDATA2LSB) {
            for (int i = byteCount - 1; i >= 0; i--) {
                answer = (answer << 8) | (mBuffer[i] & 0xff);
            }
        } else {
            final int N = byteCount - 1;
            for (int i = 0; i <= N; ++i) {
                answer = (answer << 8) | (mBuffer[i] & 0xff);
            }
        }

        return answer;
    }

    private String readString(long offset) throws IOException {
        long originalOffset = mFile.getFilePointer();
        mFile.seek(offset);
        mFile.readFully(mBuffer, 0, (int) Math.min(mBuffer.length, mFile.length() - offset));
        mFile.seek(originalOffset);

        for (int i = 0; i < mBuffer.length; ++i) {
            if (mBuffer[i] == 0) {
                return new String(mBuffer, 0, i);
            }
        }

        return null;
    }

    private int readByte() throws IOException {
        return mFile.read() & 0xff;
    }

    /** Gets the symbol by name. */
    @CanIgnoreReturnValue
    public Symbol getSymbol(String name) {
        if (mSymbols == null) {
            try {
                mSymArr = new Symbol[mSymEntCnt];
                mSymbols =
                        readSymbolTable(
                                mSymArr,
                                false,
                                mStrTabOffset,
                                mStrTabSize,
                                mSymTabOffset,
                                mSymTabSize);
            } catch (IOException e) {
                return null;
            }
        }
        return mSymbols.get(name);
    }

    /** Gets a dynamic symbol by name. */
    @CanIgnoreReturnValue
    public Symbol getDynamicSymbol(String name) throws IOException {
        if (mDynamicSymbols == null) {
            try {
                int[] verSmyArr = this.getVerSym();
                VerNeed[] verNeedArr = this.getVerNeedArr();
                VerDef[] verDefArr = this.getVerDef();
                mDynSymArr = new Symbol[mDynSymEntCnt];
                mDynamicSymbols =
                        readSymbolTable(
                                mDynSymArr,
                                true,
                                mDynStrOffset,
                                mDynStrSize,
                                mDynSymOffset,
                                mDynSymSize);
            } catch (IOException e) {
                return null;
            }
        }
        return mDynamicSymbols.get(name);
    }

    // Get Dynamic Linking Dependency List
    public List<String> getDynamicDependencies() throws IOException {
        List<String> result = new ArrayList<>();
        for (DynamicEntry entry : getDynamicList()) {
            if (entry.isNeeded()) {
                result.add(readDynStr(entry.getValue()));
            }
        }
        return result;
    }

    private List<DynamicEntry> getDynamicList() throws IOException {
        if (mDynamicArr == null) {
            int entryNo = 0;
            mDynamicArr = new ArrayList<>();
            mFile.seek(mDynamicTabOffset);
            System.out.println(
                    String.format(
                            "mDynamicTabOffset 0x%x, mDynamicTabSize %d",
                            mDynamicTabOffset, mDynamicTabSize));
            while (true) {
                long tag = readX(mAddrSize);
                long value = readX(mAddrSize);
                // System.out.println(String.format("%d: 0x%x, %d", entryNo, tag, value));
                mDynamicArr.add(new DynamicEntry(tag, value));
                if (tag == 0) {
                    break;
                }
                entryNo++;
            }
        }
        return mDynamicArr;
    }

    private String readDynStr(long strOffset) throws IOException {
        int offset = (int) (strOffset & 0xFFFFFFFF);
        if (mDynStrOffset == 0 || offset < 0 || offset >= mDynStrSize) {
            System.err.println(
                    String.format(
                            "err mDynStrOffset: %d,  mDynStrSize: %d, offset: %d",
                            mDynStrOffset, mDynStrSize, offset));
            return String.format("%d", offset);
        }
        return readString(mDynStrOffset + offset);
    }

    /**
     * Gets a list of string from .rodata section
     *
     * @return a String list .rodata section
     */
    public List<String> getRoStrings() throws IOException {
        if (mRoStrings == null) {
            mRoStrings = new ArrayList<>();
            byte[] byteArr = getRoData();
            if (byteArr != null) {
                int strOffset = 0;
                for (int i = 0; i < mRodataSize; i++) {
                    if (byteArr[i] == 0) {
                        // skip null string
                        if (i != strOffset) {
                            String str = new String(byteArr, strOffset, i - strOffset);
                            mRoStrings.add(str);
                        }
                        strOffset = i + 1;
                    }
                }
            }
        }
        return mRoStrings;
    }

    /**
     * Gets .rodata section
     *
     * @return byte [] of .rodata or null if there is none
     */
    public byte[] getRoData() throws IOException {
        if (mHasRodata && mRoData == null) {
            mRoData = new byte[mRodataSize];
            mFile.seek(mRodataOffset);
            mFile.readFully(mRoData);
        }

        return mRoData;
    }
}
