/* Copyright (C) 2007-2010 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
*/

/*
 * Contains implementations of classes defined for a variety of DWARF objects.
 */

#include "stdio.h"
#include "dwarf_die.h"
#include "dwarf_cu.h"
#include "dwarf_utils.h"
#include "elf_file.h"

DIEObject::~DIEObject() {
  /* Delete all children of this object. */
  DIEObject* to_del = last_child();
  while (to_del != NULL) {
    DIEObject* next = to_del->prev_sibling();
    delete to_del;
    to_del = next;
  }
}

ElfFile* DIEObject::elf_file() const {
  return parent_cu()->elf_file();
}

Dwarf_Tag DIEObject::get_tag() const {
  Dwarf_Tag tag;
  return advance(NULL, &tag) != NULL ? tag : 0;
}

const char* DIEObject::get_name() const {
  DIEAttrib die_attr;
  /* Start with the obvious. */
  if (get_attrib(DW_AT_name, &die_attr)) {
    return die_attr.value()->str;
  }

  /* Lets see if there is a reference to the abstract origin, or specification,
   * and use its name as the name for this DIE. */
  if (get_attrib(DW_AT_abstract_origin, &die_attr) ||
      get_attrib(DW_AT_specification, &die_attr)) {
    DIEObject* org_die_obj =
        parent_cu()->get_referenced_die_object(die_attr.value()->u32);
    if (org_die_obj != NULL) {
      return org_die_obj->get_name();
    }
  }

  /* Lets see if there is a reference to the type DIE, and use
   * its name as the name for this DIE. */
  if (get_attrib(DW_AT_type, &die_attr)) {
    DIEObject* org_die_obj =
        parent_cu()->get_referenced_die_object(die_attr.value()->u32);
    if (org_die_obj != NULL) {
      return org_die_obj->get_name();
    }
  }

  /* Can't figure the name for this DIE. */
  return NULL;
}

bool DIEObject::get_attrib(Dwarf_At at_id, DIEAttrib* attr) const {
  const Dwarf_Abbr_AT* at_abbr;

  /* Advance to DIE attributes. */
  const Elf_Byte* die_attr = advance(&at_abbr, NULL);
  if (die_attr == NULL) {
    _set_errno(EINVAL);
    return false;
  }

  /* Loop through all DIE attributes, looking for the one that's being
   * requested. */
  while (!at_abbr->is_separator()) {
    at_abbr = at_abbr->process(&attr->at_, &attr->form_);
    die_attr = parent_cu()->process_attrib(die_attr, attr->form_, &attr->value_);
    if (at_id == attr->at()) {
      return true;
    }
  }

  _set_errno(EINVAL);

  return false;
}

DIEObject* DIEObject::get_leaf_for_address(Elf_Xword address) {
  const bool contains = parent_cu()->is_CU_address_64() ?
                            contains_address<Elf_Xword>(address) :
                            contains_address<Elf_Word>(address);
  if (!contains && !is_cu_die()) {
    /* For CU DIEs address range may be zero size, even though its child DIEs
     * occupie some address space. So, if CU DIE's address range doesn't
     * contain the given address, we still want to go and check the children.
     */
    _set_errno(EINVAL);
    return NULL;
  }

  /* This DIE contains given address (or may contain it, if this is a CU DIE).
   * Lets iterate through child DIEs to find the leaf (last DIE) that contains
   * this address. */
  DIEObject* child = last_child();
  while (child != NULL) {
    DIEObject* leaf = child->get_leaf_for_address(address);
    if (leaf != NULL) {
      return leaf;
    }
    child = child->prev_sibling();
  }
  /* No child DIE contains this address. This DIE is the leaf. */
  return contains || !is_cu_die() ? this : NULL;
}

template <typename AddrType>
bool DIEObject::contains_address(Elf_Xword address) {
  DIEAttrib die_ranges;
  /* DIE can contain either list of ranges (f.i. DIEs that represent a routine
   * that is inlined in multiple places will contain list of address ranges
   * where that routine is inlined), or a pair "low PC, and high PC" describing
   * contiguos address space where routine has been placed by compiler. */
  if (get_attrib(DW_AT_ranges, &die_ranges)) {
    /* Iterate through this DIE's ranges list, looking for the one that
     * contains the given address. */
    AddrType low;
    AddrType high;
    Elf_Word range_off = die_ranges.value()->u32;
    while (elf_file()->get_range(range_off, &low, &high) &&
           (low != 0 || high != 0)) {
      if (address >= low && address < high) {
        return true;
      }
      range_off += sizeof(AddrType) * 2;
    }
    return false;
  } else {
    /* This DIE doesn't have ranges. Lets see if it has low_pc and high_pc
     * attributes. */
    DIEAttrib low_pc;
    DIEAttrib high_pc;
    if (!get_attrib(DW_AT_low_pc, &low_pc) ||
        !get_attrib(DW_AT_high_pc, &high_pc) ||
        address < low_pc.value()->u64 ||
        address >= high_pc.value()->u64) {
      return false;
    }
    return true;
  }
}

DIEObject* DIEObject::find_die_object(const Dwarf_DIE* die_to_find) {
  if (die_to_find == die()) {
    return this;
  }

  /* First we will iterate through the list of children, since chances to
   * find requested DIE decrease as we go deeper into DIE tree. */
  DIEObject* iter = last_child();
  while (iter != NULL) {
    if (iter->die() == die_to_find) {
      return iter;
    }
    iter = iter->prev_sibling();
  };

  /* DIE has not been found among the children. Lets go deeper now. */
  iter = last_child();
  while (iter != NULL) {
    DIEObject* ret = iter->find_die_object(die_to_find);
    if (ret != NULL) {
      return ret;
    }
    iter = iter->prev_sibling();
  }

  _set_errno(EINVAL);
  return NULL;
}

void DIEObject::dump(bool only_this) const {
  const Dwarf_Abbr_AT*  at_abbr;
  Dwarf_Tag             tag;

  const Elf_Byte* die_attr = advance(&at_abbr, &tag);
  if (die_attr != NULL) {
    printf("\n********** DIE[%p(%04X)] %s: %s **********\n",
           die_, parent_cu()->get_die_reference(die_), dwarf_tag_name(tag),
           get_name());

    /* Dump this DIE attributes. */
    while (!at_abbr->is_separator()) {
      DIEAttrib attr;
      at_abbr = at_abbr->process(&attr.at_, &attr.form_);
      die_attr = parent_cu()->process_attrib(die_attr, attr.form(), &attr.value_);
      dump_attrib(attr.at(), attr.form(), attr.value());
      if (attr.at() == DW_AT_ranges) {
        /* Dump all ranges for this DIE. */
        Elf_Word off = attr.value()->u32;
        if (parent_cu()->is_CU_address_64()) {
          Elf_Xword low, high;
          while (elf_file()->get_range<Elf_Xword>(off, &low, &high) &&
                 (low != 0 || high != 0)) {
            printf("                                %08" FMT_I64 "X - %08" FMT_I64 "X\n",
                   (unsigned long long)low, (unsigned long long)high);
            off += 16;
          }
        } else {
          Elf_Word low, high;
          while (elf_file()->get_range<Elf_Word>(off, &low, &high) &&
                 (low != 0 || high != 0)) {
            printf("                                %08X - %08X\n",
                   low, high);
            off += 8;
          }
        }
      }
    }
  }

  if (only_this) {
    if (parent_die_ != NULL && !parent_die_->is_cu_die()) {
      printf("\n-----------> CHILD OF:\n");
      parent_die_->dump(true);
    }
  } else {
    /* Dump this DIE's children. */
    if (last_child() != NULL) {
        last_child()->dump(false);
    }

    /* Dump this DIE's siblings. */
    if (prev_sibling() != NULL) {
      prev_sibling()->dump(false);
    }
  }
}

const Elf_Byte* DIEObject::advance(const Dwarf_Abbr_AT** at_abbr,
                                   Dwarf_Tag* tag) const {
  Dwarf_AbbrNum abbr_num;
  Dwarf_Tag     die_tag;

  const Elf_Byte* die_attr = die()->process(&abbr_num);
  const Dwarf_Abbr_DIE* abbr = parent_cu()->get_die_abbr(abbr_num);
  if (abbr == NULL) {
    return NULL;
  }

  const Dwarf_Abbr_AT* attrib_abbr = abbr->process(NULL, &die_tag);
  if (at_abbr != NULL) {
    *at_abbr = attrib_abbr;
  }
  if (tag != NULL) {
    *tag = die_tag;
  }
  return die_attr;
}