/* ###
 * IP: GHIDRA
 * NOTE: Excluded from Build.  Used for development only in support of console mode - Links to GNU BFD library which is GPL 3
 *
 * 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.
 */
#include "loadimage_bfd.hh"

namespace ghidra {

int4 LoadImageBfd::bfdinit = 0;	// Global initialization variable

LoadImageBfd::LoadImageBfd(const string &f,const string &t) : LoadImage(f)

{
  target = t;

  if (bfdinit == 0) {
    bfdinit = 1;
    bfd_init();
  }
  thebfd = (bfd *)0;
  spaceid = (AddrSpace *)0;
  symbol_table = (asymbol **)0;

  bufsize = 512;		// Default buffer size
  bufoffset = ~((uintb)0);
  buffer = new uint1[ bufsize ];
}

LoadImageBfd::~LoadImageBfd(void)

{
  if (symbol_table != (asymbol **)0)
    delete [] symbol_table;
  if (thebfd != (bfd *) 0)
    close();
  delete [] buffer;
}

string LoadImageBfd::getArchType(void) const

{
  string type;
  string targ;
  type = bfd_printable_name(thebfd);
  type += ':';
  targ = thebfd->xvec->name;
  type += targ;
  return type;
}

void LoadImageBfd::adjustVma(long adjust)

{
  asection *s;
  adjust = AddrSpace::addressToByte(adjust,spaceid->getWordSize());
  for(s=thebfd->sections;s!=(asection *)NULL;s = s->next) {
    s->vma += adjust;
    s->lma += adjust;
  }
}

void LoadImageBfd::open(void)

{
  if (thebfd != (bfd *)0) throw LowlevelError("BFD library did not initialize");
  thebfd = bfd_openr(filename.c_str(),target.c_str());
  if (thebfd == (bfd *)0) {
    string errmsg="Unable to open image file: ";
    errmsg += filename;
    throw LowlevelError(errmsg);
  }
  if (!bfd_check_format( thebfd, bfd_object)) {
    string errmsg="File: ";
    errmsg += filename;
    errmsg += " : not in recognized object file format";
    throw LowlevelError(errmsg);
  }
}

void LoadImageBfd::close(void)

{
  bfd_close(thebfd);
  thebfd = (bfd *)0;
}

asection *LoadImageBfd::findSection(uintb offset,uintb &secsize) const

{ // Return section containing offset, or closest greater section
  asection *p;
  uintb start,stop;

  for(p = thebfd->sections; p != (asection *)NULL; p = p->next) {
    start = p->vma;
    secsize = (p->size!=0) ? p->size : p->rawsize;
    stop = start + secsize;
    if ((offset>=start)&&(offset<stop))
      return p;
  }
  asection *champ = (asection *)0;
  for(p = thebfd->sections; p != (asection *)NULL; p = p->next) {
    if (p->vma > offset) {
      if (champ == (asection *)0)
	champ = p;
      else if (p->vma < champ->vma)
	champ = p;
    }
  }
  return champ;
}

void LoadImageBfd::loadFill(uint1 *ptr,int4 size,const Address &addr)

{
  asection *p;
  uintb secsize;
  uintb curaddr,offset;
  bfd_size_type readsize;
  int4 cursize;

  if (addr.getSpace() != spaceid)
    throw DataUnavailError("Trying to get loadimage bytes from space: "+addr.getSpace()->getName());
  curaddr = addr.getOffset();
  if ((curaddr>=bufoffset)&&(curaddr+size<bufoffset+bufsize)) {	// Requested bytes were previously buffered
    uint1 *bufptr = buffer + (curaddr-bufoffset);
    memcpy(ptr,bufptr,size);
    return;
  }
  bufoffset = curaddr;		// Load buffer with bytes from new address
  offset = 0;
  cursize = bufsize;		// Read an entire buffer

  while(cursize>0) {
    p = findSection(curaddr,secsize);
    if (p == (asection *)0) {
      if (offset==0)		// Initial address not mapped
	break;
      memset(buffer+offset,0,cursize); // Fill out the rest of the buffer with 0
      memcpy(ptr,buffer,size);
      return;
    }
    if (p->vma > curaddr) {	// No section matches
      if (offset==0)		// Initial address not mapped
	break;
      readsize = p->vma - curaddr;
      if (readsize > cursize)
	readsize = cursize;
      memset(buffer+offset,0,readsize); // Fill in with zeroes to next section
    }
    else {
      readsize = cursize;
      if (curaddr+readsize>p->vma+secsize)	// Adjust to biggest possible read
	readsize = (bfd_size_type)(p->vma+secsize-curaddr);
      bfd_get_section_contents(thebfd,p,buffer+offset,(file_ptr)(curaddr-p->vma),readsize);
    }
    offset += readsize;
    cursize -= readsize;
    curaddr += readsize;
  }
  if (cursize > 0) {
    ostringstream errmsg;
    errmsg << "Unable to load " << dec << cursize << " bytes at " << addr.getShortcut();
    addr.printRaw(errmsg);
    throw DataUnavailError(errmsg.str());
  }
  memcpy(ptr,buffer,size);	// Copy requested bytes from the buffer
}

void LoadImageBfd::advanceToNextSymbol(void) const

{
  while(cursymbol < number_of_symbols) {
    const asymbol *a = symbol_table[cursymbol];
    if ((a->flags & BSF_FUNCTION)!=0) {
      if (a->name != (const char *)0)
	return;
    }
    cursymbol += 1;
  }
}

void LoadImageBfd::openSymbols(void) const

{
  long storage_needed;
  cursymbol = 0;
  if (symbol_table != (asymbol **)0) {
    advanceToNextSymbol();
    return;
  }

  if (!(bfd_get_file_flags(thebfd) & HAS_SYMS)) { // There are no symbols
    number_of_symbols = 0;
    return;
  }

  storage_needed = bfd_get_symtab_upper_bound(thebfd);
  if (storage_needed <= 0) {
    number_of_symbols = 0;
    return;
  }

  symbol_table = (asymbol **) new uint1[storage_needed]; // Storage needed in bytes
  number_of_symbols = bfd_canonicalize_symtab(thebfd,symbol_table);
  if (number_of_symbols <= 0) {
    delete [] symbol_table;
    symbol_table = (asymbol **)0;
    number_of_symbols = 0;
    return;
  }
  advanceToNextSymbol();
  //  sort(symbol_table,symbol_table+number_of_symbols,compare_symbols);
}

bool LoadImageBfd::getNextSymbol(LoadImageFunc &record) const

{ // Get record for next symbol if it exists, otherwise return false
  if (cursymbol >= number_of_symbols) return false;

  const asymbol *a = symbol_table[cursymbol];
  cursymbol += 1;
  advanceToNextSymbol();
  record.name = a->name;
  uintb val = bfd_asymbol_value(a);
  record.address = Address(spaceid,val);
  return true;
}

void LoadImageBfd::openSectionInfo(void) const

{
  secinfoptr = thebfd->sections;
}

void LoadImageBfd::closeSectionInfo(void) const

{
  secinfoptr = (asection *)0;
}

bool LoadImageBfd::getNextSection(LoadImageSection &record) const

{
  if (secinfoptr == (asection *)0)
    return false;
  
  record.address = Address(spaceid,secinfoptr->vma);
  record.size = (secinfoptr->size!=0) ? secinfoptr->size : secinfoptr->rawsize;
  record.flags = 0;
  if ((secinfoptr->flags & SEC_ALLOC)==0)
    record.flags |= LoadImageSection::unalloc;
  if ((secinfoptr->flags & SEC_LOAD)==0)
    record.flags |= LoadImageSection::noload;
  if ((secinfoptr->flags & SEC_READONLY)!=0)
    record.flags |= LoadImageSection::readonly;
  if ((secinfoptr->flags & SEC_CODE)!=0)
    record.flags |= LoadImageSection::code;
  if ((secinfoptr->flags & SEC_DATA)!=0)
    record.flags |= LoadImageSection::data;
  secinfoptr = secinfoptr->next;
  return (secinfoptr != (asection *)0);
}

void LoadImageBfd::closeSymbols(void) const

{
  if (symbol_table != (asymbol **)0)
    delete [] symbol_table;
  symbol_table = (asymbol **)0;
  number_of_symbols = 0;
  cursymbol = 0;
}

void LoadImageBfd::getReadonly(RangeList &list) const

{ // List all ranges that are read only
  uintb start,stop,secsize;
  asection *p;

  for(p = thebfd->sections; p != (asection *)NULL; p = p->next) {
    if ((p->flags & SEC_READONLY)!=0) {
      start = p->vma;
      secsize = (p->size!=0) ? p->size : p->rawsize;
      if (secsize == 0) continue;
      stop = start + secsize - 1;
      list.insertRange(spaceid,start,stop);
    }
  }
}

} // End namespace ghidra
