/*
  Copyright (C) 2010-2011 David Anderson.  

  This program is free software; you can redistribute it and/or modify it
  under the terms of version 2 of the GNU General Public License as
  published by the Free Software Foundation.

  This program is distributed in the hope that it would be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write the Free Software Foundation, Inc., 51
  Franklin Street - Fifth Floor, Boston MA 02110-1301, USA.

*/

// createirepfrombinary.cc

// Reads an object and inserts its dwarf data into 
// an object intended to hold all the dwarf data.

#include "config.h"
#include <unistd.h> 
#include <stdlib.h> // for exit
#include <iostream>
#include <string>
#include <list>
#include <vector>
#include <string.h> // For memset etc
#include <sys/stat.h> //open
#include <fcntl.h> //open
#include "elf.h"
#include "gelf.h"
#include "strtabdata.h"
#include "dwarf.h"
#include "libdwarf.h"
#include "irepresentation.h"
#include "createirepfrombinary.h"
#include "general.h" // For IToHex()

using std::string;
using std::cout;
using std::cerr;
using std::endl;
using std::vector;
using std::list;

static void readFrameDataFromBinary(Dwarf_Debug dbg, IRepresentation & irep);
static void readMacroDataFromBinary(Dwarf_Debug dbg, IRepresentation & irep);
static void readCUDataFromBinary(Dwarf_Debug dbg, IRepresentation & irep);

class DbgInAutoCloser {
public:
    DbgInAutoCloser(Dwarf_Debug dbg,int fd): dbg_(dbg),fd_(fd) {};
    ~DbgInAutoCloser() { 
        Dwarf_Error err = 0;
        dwarf_finish(dbg_,&err);
        close(fd_);
    };
private:
    Dwarf_Debug dbg_;
    int fd_;
};
 

void
createIrepFromBinary(const std::string &infile,
   IRepresentation & irep)
{
    Dwarf_Debug dbg = 0;
    Dwarf_Error err;
    int fd = open(infile.c_str(),O_RDONLY, 0);
    if(fd < 0 ) {
        cerr << "Unable to open " << infile <<
            " for reading." << endl;
        exit(1);
    }
    // All reader error handling is via the err argument.
    int res = dwarf_init(fd,DW_DLC_READ,
        0,
        0,
        &dbg,
        &err);
    if(res != DW_DLV_OK) {
        close(fd);
        cerr << "Error init-ing " << infile <<
            " for reading." << endl;
        exit(1);
    }

    DbgInAutoCloser closer(dbg,fd);

    readFrameDataFromBinary(dbg,irep);
    readCUDataFromBinary(dbg,irep);
    readMacroDataFromBinary(dbg,irep);

    return;
}

static void 
readFrameDataFromBinary(Dwarf_Debug dbg, IRepresentation & irep)
{
    Dwarf_Error err = 0;
    Dwarf_Cie * cie_data = 0;
    Dwarf_Signed  cie_count = 0;
    Dwarf_Fde * fde_data = 0;
    Dwarf_Signed  fde_count = 0;
    int res = dwarf_get_fde_list(dbg,
        &cie_data,
        &cie_count,
        &fde_data,
        &fde_count,
        &err);
    if(res == DW_DLV_NO_ENTRY) {
        // No frame data.
        return;
    }
    if(res == DW_DLV_ERROR) {
        cerr << "Error reading frame data " << endl;
        exit(1);
    }

    for(Dwarf_Signed i =0; i < cie_count; ++i) {
        Dwarf_Unsigned bytes_in_cie = 0;
        Dwarf_Small    version = 0;
        char *         augmentation = 0;
        Dwarf_Unsigned code_alignment_factor = 0;
        Dwarf_Signed   data_alignment_factor = 0;
        Dwarf_Half     return_address_register_rule = 0;
        Dwarf_Ptr      initial_instructions = 0;
        Dwarf_Unsigned initial_instructions_length = 0;
        res = dwarf_get_cie_info(cie_data[i], &bytes_in_cie,
            &version,&augmentation, &code_alignment_factor,
            &data_alignment_factor,&return_address_register_rule,
            &initial_instructions,&initial_instructions_length,
            &err);
        if(res != DW_DLV_OK) {
            cerr << "Error reading frame data cie index " << i << endl;
            exit(1);
        }
        // Index in cie_data must match index in ciedata_, here
        // correct by construction.
        IRCie cie(bytes_in_cie,version,augmentation,code_alignment_factor,
            data_alignment_factor,return_address_register_rule,
            initial_instructions,initial_instructions_length);
        irep.framedata().insert_cie(cie);
    } 
    for(Dwarf_Signed i =0; i < fde_count; ++i) {
        Dwarf_Addr low_pc = 0;
        Dwarf_Unsigned    func_length = 0;
        Dwarf_Ptr         fde_bytes = 0;
        Dwarf_Unsigned    fde_byte_length = 0;
        Dwarf_Off   cie_offset = 0;
        Dwarf_Signed     cie_index = 0;
        Dwarf_Off      fde_offset = 0;
        res = dwarf_get_fde_range(fde_data[i], &low_pc,
            &func_length,&fde_bytes, &fde_byte_length,
            &cie_offset,&cie_index,
            &fde_offset,
            &err);
        if(res != DW_DLV_OK) {
            cerr << "Error reading frame data fde index " << i << endl;
            exit(1);
        }
        IRFde fde(low_pc,func_length,fde_bytes,fde_byte_length,
            cie_offset, cie_index,fde_offset);
        Dwarf_Ptr instr_in = 0;
        Dwarf_Unsigned instr_len = 0;
        res = dwarf_get_fde_instr_bytes(fde_data[i], 
            &instr_in, &instr_len, &err);
        if(res != DW_DLV_OK) {
            cerr << "Error reading frame data fde instructions " << i << endl;
            exit(1);
        }
        fde.get_fde_instrs_into_ir(instr_in,instr_len);
        irep.framedata().insert_fde(fde);
    } 
    dwarf_fde_cie_list_dealloc(dbg,cie_data,cie_count,
        fde_data,fde_count);
}


static void
readCUMacroDataFromBinary(Dwarf_Debug dbg, IRepresentation & irep,
    Dwarf_Unsigned macrodataoffset,IRCUdata &cu)
{
    // Arbitrary, but way too high to be real!
    // Probably should be lower.
    Dwarf_Unsigned maxcount = 1000000000;
    Dwarf_Error error;
    Dwarf_Signed mcount=0;
    Dwarf_Macro_Details *md = 0;
    int res = dwarf_get_macro_details(dbg,macrodataoffset,
        maxcount, &mcount, &md, &error);
    if(res == DW_DLV_OK) {
        IRMacro &macrodata = irep.macrodata();
        vector<IRMacroRecord> mvec = macrodata.getMacroVec();
        mvec.reserve(mcount);
        Dwarf_Unsigned dieoffset = cu.baseDie().getGlobalOffset();
        Dwarf_Macro_Details *mdp = md;
        for(int i = 0; i < mcount; ++i, mdp++ ) {
            IRMacroRecord i(dieoffset,mdp->dmd_offset,
                mdp->dmd_type, mdp->dmd_lineno,
                mdp->dmd_fileindex, mdp->dmd_macro?mdp->dmd_macro:"");
            mvec.push_back(i);
        }
    }    
    dwarf_dealloc(dbg, md, DW_DLA_STRING);
}

void
get_basic_attr_data_one_attr(Dwarf_Debug dbg,
    Dwarf_Attribute attr,IRCUdata &cudata,IRAttr & irattr)
{
    Dwarf_Error error;
    Dwarf_Half attrnum = 0;
    Dwarf_Half dirform = 0;
    Dwarf_Half indirform = 0;
    int res = dwarf_whatattr(attr,&attrnum,&error);
    if(res != DW_DLV_OK) {
        cerr << "Unable to get attr number " << endl;
        exit(1);
    }
    res = dwarf_whatform(attr,&dirform,&error);
    if(res != DW_DLV_OK) {
        cerr << "Unable to get attr form " << endl;
        exit(1);
    }

    res = dwarf_whatform_direct(attr,&indirform,&error);
    if(res != DW_DLV_OK) {
        cerr << "Unable to get attr direct form " << endl;
        exit(1);
    }
    irattr.setBaseData(attrnum,dirform,indirform);
    enum Dwarf_Form_Class cl = dwarf_get_form_class(
        cudata.getVersionStamp(), attrnum,   
        cudata.getOffsetSize(), dirform);
    irattr.setFormClass(cl);
    if (cl == DW_FORM_CLASS_UNKNOWN) {
        cerr << "Unable to figure out form class. ver " << 
            cudata.getVersionStamp() <<
            " attrnum " << attrnum <<
            " offsetsize " << cudata.getOffsetSize() <<
            " formnum " <<  dirform << endl;
        return;
    }
    irattr.setFormData(formFactory(dbg,attr,cudata,irattr));
}
void 
get_basic_die_data(Dwarf_Debug dbg,Dwarf_Die indie,IRDie &irdie)
{
    Dwarf_Half tagval = 0;
    Dwarf_Error error = 0;
    int res = dwarf_tag(indie,&tagval,&error);
    if(res != DW_DLV_OK) {
        cerr << "Unable to get die tag "<< endl;
        exit(1);
    }
    Dwarf_Off goff = 0;
    res = dwarf_dieoffset(indie,&goff,&error);
    if(res != DW_DLV_OK) {
        cerr << "Unable to get die offset "<< endl;
        exit(1);
    }
    Dwarf_Off localoff = 0;
    res = dwarf_die_CU_offset(indie,&localoff,&error);
    if(res != DW_DLV_OK) {
        cerr << "Unable to get cu die offset "<< endl;
        exit(1);
    }
    irdie.setBaseData(tagval,goff,localoff);
}


static void
get_attrs_of_die(Dwarf_Die in_die,IRDie &irdie,
    IRCUdata &data, IRepresentation &irep,
    Dwarf_Debug dbg)
{
    Dwarf_Error error = 0;
    Dwarf_Attribute *atlist = 0;
    Dwarf_Signed atcnt = 0;
    std::list<IRAttr> &attrlist = irdie.getAttributes();
    int res = dwarf_attrlist(in_die, &atlist,&atcnt,&error);
    if(res == DW_DLV_NO_ENTRY) {
        return;
    }
    if(res == DW_DLV_ERROR) {
        cerr << "dwarf_attrlist failed " << endl;
        exit(1);
    }
    for (Dwarf_Signed i = 0; i < atcnt; ++i) {
        Dwarf_Attribute attr = atlist[i];
        IRAttr irattr;
        get_basic_attr_data_one_attr(dbg,attr,data,irattr);
        attrlist.push_back(irattr);

    }
    dwarf_dealloc(dbg,atlist, DW_DLA_LIST);
}

// Invariant: IRDie and IRCUdata  is in the irep tree,
// not local record references to local scopes.
static void
get_children_of_die(Dwarf_Die in_die,IRDie&irdie, 
    IRCUdata &ircudata,
    IRepresentation &irep,
    Dwarf_Debug dbg)
{
    Dwarf_Die curchilddie = 0;
    Dwarf_Error error = 0;
    int res = dwarf_child(in_die,&curchilddie,&error);
    if(res == DW_DLV_NO_ENTRY) {
        return;
    }
    if(res == DW_DLV_ERROR) {
        cerr << "dwarf_child failed " << endl;
        exit(1);
    }
    //std::list<IRDie> & childlist =  irdie.getChildren();
    int childcount = 0;
    for(;;) {
        IRDie child;
        get_basic_die_data(dbg,curchilddie,child);
        get_attrs_of_die(curchilddie,child,ircudata,irep,dbg);
        irdie.addChild(child);
        IRDie &lastchild = irdie.lastChild();

        get_children_of_die(curchilddie,lastchild,ircudata,irep,dbg);
        ++childcount;

        Dwarf_Die tchild = 0;
        res = dwarf_siblingof(dbg,curchilddie,&tchild,&error);
        if(res == DW_DLV_NO_ENTRY) {
            break;
        }
        if(res == DW_DLV_ERROR) {
            cerr << "dwarf_siblingof failed " << endl;
            exit(1);
        }
        dwarf_dealloc(dbg,curchilddie,DW_DLA_DIE);
        curchilddie = tchild;
    }
    dwarf_dealloc(dbg,curchilddie,DW_DLA_DIE);
}

static void
get_linedata_of_cu_die(Dwarf_Die in_die,IRDie&irdie, 
    IRCUdata &ircudata,
    IRepresentation &irep,
    Dwarf_Debug dbg)
{
    Dwarf_Error error = 0;
    char **srcfiles = 0;
    Dwarf_Signed srccount = 0;
    IRCULineData&culinesdata =  ircudata.getCULines();
    int res = dwarf_srcfiles(in_die,&srcfiles, &srccount,&error);
    if(res == DW_DLV_ERROR) {
        cerr << "dwarf_srcfiles failed " << endl;
        exit(1);
    } else if (res == DW_DLV_NO_ENTRY) {
        // No data.
        return;
    }

    std::vector<IRCUSrcfile> &srcs = culinesdata.get_cu_srcfiles();
    for (Dwarf_Signed i = 0; i < srccount; ++i) {
        IRCUSrcfile S(srcfiles[i]);
        srcs.push_back(S);
        /* use srcfiles[i] */
        dwarf_dealloc(dbg, srcfiles[i], DW_DLA_STRING);
    }
    dwarf_dealloc(dbg, srcfiles, DW_DLA_LIST);

    Dwarf_Line * linebuf = 0;
    Dwarf_Signed linecnt = 0;
    int res2 = dwarf_srclines(in_die,&linebuf,&linecnt, &error);
    if(res == DW_DLV_ERROR) {
        cerr << "dwarf_srclines failed " << endl;
        exit(1);
    } else if (res == DW_DLV_NO_ENTRY) {
        // No data.
        cerr << "dwarf_srclines failed NO_ENTRY, crazy "
            "since srcfiles worked" << endl;
        exit(1);
    }

    std::vector<IRCULine> &lines = culinesdata.get_cu_lines();
    for(Dwarf_Signed j = 0; j < linecnt ; ++j ) {
        Dwarf_Line li = linebuf[j];
        int lres;

        Dwarf_Addr address = 0;
        lres = dwarf_lineaddr(li,&address,&error);
        if (lres != DW_DLV_OK) {
            cerr << "dwarf_lineaddr failed. " << endl;
            exit(1);
        }
        Dwarf_Bool is_addr_set = 0;
        lres = dwarf_line_is_addr_set(li,&is_addr_set,&error);
        if (lres != DW_DLV_OK) {
            cerr << "dwarf_line_is_addr_set failed. " << endl;
            exit(1);
        }
        Dwarf_Unsigned fileno = 0;
        lres = dwarf_line_srcfileno(li,&fileno,&error);
        if (lres != DW_DLV_OK) {
            cerr << "dwarf_srcfileno failed. " << endl;
            exit(1);
        }
        Dwarf_Unsigned lineno = 0;
        lres = dwarf_lineno(li,&lineno,&error);
        if (lres != DW_DLV_OK) {
            cerr << "dwarf_lineno failed. " << endl;
            exit(1);
        }
        Dwarf_Unsigned lineoff = 0;
        lres = dwarf_lineoff_b(li,&lineoff,&error);
        if (lres != DW_DLV_OK) {
            cerr << "dwarf_lineoff failed. " << endl;
            exit(1);
        }
        char *linesrctmp = 0;
        lres = dwarf_linesrc(li,&linesrctmp,&error);
        if (lres != DW_DLV_OK) {
            cerr << "dwarf_linesrc failed. " << endl;
            exit(1);
        }
        // libdwarf is trying to generate a full path,
        // the string here is that generated data, not
        // simply the 'file' path represented by the
        // file number (fileno).
        std::string linesrc(linesrctmp);

      
        Dwarf_Bool is_stmt = 0;
        lres = dwarf_linebeginstatement(li,&is_stmt,&error);
        if (lres != DW_DLV_OK) {
            cerr << "dwarf_linebeginstatement failed. " << endl;
            exit(1);
        }

        Dwarf_Bool basic_block = 0;
        lres = dwarf_lineblock(li,&basic_block,&error);
        if (lres != DW_DLV_OK) {
            cerr << "dwarf_lineblock failed. " << endl;
            exit(1);
        }

        Dwarf_Bool end_sequence = 0;
        lres = dwarf_lineendsequence(li,&end_sequence,&error);
        if (lres != DW_DLV_OK) {
            cerr << "dwarf_lineendsequence failed. " << endl;
            exit(1);
        }

        Dwarf_Bool prologue_end = 0;
        Dwarf_Bool epilogue_begin = 0;
        Dwarf_Unsigned isa = 0;
        Dwarf_Unsigned discriminator = 0;
        lres = dwarf_prologue_end_etc(li,&prologue_end,
            &epilogue_begin,&isa,&discriminator,&error);
        if (lres != DW_DLV_OK) {
            cerr << "dwarf_prologue_end_etc failed. " << endl;
            exit(1);
        }

        IRCULine L(address,
            is_addr_set,
            fileno,
            lineno,
            lineoff,
            linesrc,
            is_stmt,
            basic_block,
            end_sequence,
            prologue_end,epilogue_begin,
            isa,discriminator);
        lines.push_back(L);
    }
    dwarf_srclines_dealloc(dbg, linebuf, linecnt);
}

static bool 
getToplevelOffsetAttr(Dwarf_Die cu_die,Dwarf_Half attrnumber,
    Dwarf_Unsigned &offset_out)
{
    Dwarf_Error error = 0;
    Dwarf_Off offset = 0;
    Dwarf_Attribute attr = 0;
    int res = dwarf_attr(cu_die,attrnumber,&attr, &error);
    bool foundit = false;
    Dwarf_Off sectoff = 0;
    if(res == DW_DLV_OK) {
        Dwarf_Signed sval = 0;
        Dwarf_Unsigned uval = 0;
        res = dwarf_global_formref(attr,&offset,&error);
        if(res == DW_DLV_OK) {
            foundit = true;
            offset_out = offset;
        }
    }
    return foundit;
}   

// We record the .debug_info info for each CU found
// To start with we restrict attention to very few DIEs and
// attributes,  but intend to get all eventually.
static void 
readCUDataFromBinary(Dwarf_Debug dbg, IRepresentation & irep)
{
    Dwarf_Error error;
    int cu_number = 0;
    std::list<IRCUdata> &culist = irep.infodata().getCUData();

    for(;;++cu_number) {
        Dwarf_Unsigned cu_header_length = 0;
        Dwarf_Half version_stamp = 0;
        Dwarf_Unsigned abbrev_offset = 0;
        Dwarf_Half address_size = 0;
        Dwarf_Unsigned next_cu_header = 0;
        Dwarf_Half offset_size = 0;
        Dwarf_Half extension_size = 0;
        Dwarf_Die no_die = 0;
        Dwarf_Die cu_die = 0;
        int res = DW_DLV_ERROR;
        res = dwarf_next_cu_header_b(dbg,&cu_header_length,
            &version_stamp, &abbrev_offset, &address_size,
            &offset_size, &extension_size,
            &next_cu_header, &error);
        if(res == DW_DLV_ERROR) {
            cerr <<"Error in dwarf_next_cu_header"<< endl;
            exit(1);
        }
        if(res == DW_DLV_NO_ENTRY) {
            /* Done. */
            return;
        }
        IRCUdata cudata(cu_header_length,version_stamp,
            abbrev_offset,address_size, offset_size,
            extension_size,next_cu_header);

        //  The CU will have a single sibling (well, it is
        //  not exactly a sibling, but close enough), a cu_die. 
        res = dwarf_siblingof(dbg,no_die,&cu_die,&error);
        if(res == DW_DLV_ERROR) {
            cerr <<"Error in dwarf_siblingof on CU die "<< endl;
            exit(1);
        }
        if(res == DW_DLV_NO_ENTRY) {
            /* Impossible case. */
            cerr <<"no Entry! in dwarf_siblingof on CU die "<< endl;
            exit(1);
        }
        Dwarf_Off macrooffset = 0;
        bool foundmsect = getToplevelOffsetAttr(cu_die,DW_AT_macro_info,
            macrooffset);
        Dwarf_Off linesoffset = 0;
        bool foundlines = getToplevelOffsetAttr(cu_die,DW_AT_stmt_list,
            linesoffset);
        Dwarf_Off dieoff = 0;
        res = dwarf_dieoffset(cu_die,&dieoff,&error);
        if(res != DW_DLV_OK) {
            cerr << "Unable to get cu die offset for macro infomation "<< endl;
            exit(1);
        }
        if(foundmsect) {
            cudata.setMacroData(macrooffset,dieoff);
        }
        if(foundlines) {
            cudata.setLineData(linesoffset,dieoff);
        }
        culist.push_back(cudata);
        IRCUdata & treecu = irep.infodata().lastCU();
        IRDie &cuirdie = treecu.baseDie();
        get_basic_die_data(dbg,cu_die,cuirdie);
        get_attrs_of_die(cu_die,cuirdie,treecu,irep,dbg);
        get_children_of_die(cu_die,cuirdie,treecu,irep,dbg);
        get_linedata_of_cu_die(cu_die,cuirdie,treecu,irep,dbg);
        dwarf_dealloc(dbg,cu_die,DW_DLA_DIE);
    }
    // If we want pointers from child to parent now is the time
    // we can construct them.
}

// We read thru the CU headers and the CU die to find
// the macro info for each CU (if any).
// We record the CU macro info for each CU found (using
// the value of the DW_AT_macro_info attribute, if any).
static void 
readMacroDataFromBinary(Dwarf_Debug dbg, IRepresentation & irep)
{

    list<IRCUdata>  &cudata = irep.infodata().getCUData();
    list<IRCUdata>::iterator it = cudata.begin();
    int ct = 0;
    for( ; it != cudata.end(); ++it,++ct) {
        Dwarf_Unsigned macrooffset = 0;
        Dwarf_Unsigned cudieoffset = 0;
        bool foundmsect = it->hasMacroData(&macrooffset,&cudieoffset);
        if(foundmsect) {
            readCUMacroDataFromBinary(dbg, irep, macrooffset,*it);
        }
    }
}

