/** @file

    $Id$

    $Date$
    $Author$

    @copy &copy; Marc W&auml;ckerlin
    @license LGPL, see file <a href="license.html">COPYING</a>

*/
#include <mrw/stacktrace.hpp>
#include <mrw/exec.hpp>
#include <mrw/string.hpp>
#include <mrw/list.hpp>
#include <mrw/stdext.hpp>
#ifndef NO_LTDL
# include <mrw/dynamiclibrary.hpp>
#endif
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <assert.h>
#include <math.h>
#include <fstream>
#include <algorithm>
#include <list>
#if defined(__solaris__)
#include <sys/old_procfs.h>
#endif
#if defined (__GLIBC__)
#include <execinfo.h>
#endif
#include <bfd.h>
#include <iomanip>
#if HAVE_DEMANGLE_H
/* the defines work around wrong definitions in libiberty.h */
#define HAVE_DECL_BASENAME 1
#define HAVE_DECL_FFS 1
#define HAVE_DECL_ASPRINTF 1
#define HAVE_DECL_VASPRINTF 1
#define HAVE_DECL_SNPRINTF 1
#define HAVE_DECL_VSNPRINTF 1
#define HAVE_DECL_STRVERSCMP 1
# include <demangle.h>
#else
# define DMGL_PARAMS	 (1 << 0)	/* Include function args */
# define DMGL_ANSI	 (1 << 1)	/* Include const, volatile, etc */
  extern "C" {
    extern char * cplus_demangle(const char *, int);
  }
#endif

#if (__GNUC__==3 && __GNUC_MINOR__<4 || __GNUC__<3) && _REENTRANT && !_MT
#define _MT
#endif

namespace mrw {
  //----------------------------------------------------------------------------
  static std::string demangle(bfd* abfd, const char* name) {
    if (bfd_get_symbol_leading_char(abfd) == name[0]) ++name;
    
    /* This is a hack for better error reporting on XCOFF, PowerPC64-ELF
       or the MS PE format.  These formats have a number of leading '.'s
       on at least some symbols, so we remove all dots to avoid
       confusing the demangler.  */
    const char* p(name);
    while (p && *p == '.') ++p;
#   ifdef __solaris__
      char res[1024];
      if (cplus_demangle(p, res, 1024)) return name;
#   else
      Auto<char*>::Free res(cplus_demangle(p, DMGL_ANSI | DMGL_PARAMS));
#   endif
    if (res) {
      /* Now put back any stripped dots.  */
      if (p==name) return static_cast<const char*>(res);
      std::string add_dots('.', p-name);
      return add_dots+=static_cast<const char*>(res);
    }
    return name;
  }
}

//----------------------------------------------------------------------------
mrw::StackTrace::StackTrace() throw(std::bad_exception) {
  // maximum trace level is limited here to 50, see below why
#   if defined(__GLIBC__)
  {
    const int TRACE_LEVEL(50);
    void* ba[TRACE_LEVEL];
    for (int n(backtrace(ba, TRACE_LEVEL)), i(0); i<n; ++i)
      _trace.push_back(ba[i]);
  }
#   elif defined(__GNUG__)
  {
#     define push(i)                                                    \
    (__builtin_return_address(i) ?                                      \
     (_trace.push_back(__builtin_return_address(i)), true) : false)
    push(0) && push(1) && push(2) && push(3) && push(4) && push(5) &&
      push(6) && push(7) && push(8) && push(9) && push(10) && push(11) &&
      push(12) && push(13) && push(14) && push(15) && push(16) && push(17)
      && push(18) && push(19) && push(20) && push(21) && push(22) &&
      push(23) && push(24) && push(25) && push(26) && push(27) && push(28)
      && push(29) && push(30) && push(31) && push(32) && push(33) &&
      push(34) && push(35) && push(36) && push(37) && push(38) && push(39)
      && push(40) && push(41) && push(42) && push(43) && push(44) &&
      push(45) && push(46) && push(47) && push(48) && push(49);
#     undef push
  }
#   else
#     warning "You need GNU gcc or GNU glibc to be able to use mrw::StackTrace"
#   endif
}

//----------------------------------------------------------------------------
mrw::StackTrace::operator std::string() const throw(std::bad_exception) {
  std::stringstream s;
  bool first(true);
  unsigned int fusz(0), adsz(0); //lisz(0), fisz(0)
  std::list<CodePos> l;
  for (AddressTrace::const_reverse_iterator it(_trace.rbegin());
       it!=_trace.rend(); ++it, first=false) {
    CodePos c(translate(*it));
    if (((std::stringstream&)(std::stringstream()<<c.address)).str().size()
        > adsz)
      adsz =
        ((std::stringstream&)(std::stringstream()<<c.address)).str().size();
    if (c.function.size() > fusz) fusz = c.function.size();
    l.push_back(c);
  }
  for (std::list<CodePos>::iterator it(l.begin()); it!=l.end(); ++it)
    s<<'['<<std::setw(adsz)<<it->address<<"] "
     <<it->function<<std::setw(fusz-it->function.size()+1)<<' '
     <<it->file<<':'<<it->line<<std::endl;
  return s.str();
}

//----------------------------------------------------------------------------
const mrw::StackTrace& mrw::StackTrace::print(std::ostream& os) const
  throw(std::bad_exception) {
  os<<(std::string)*this;
  return *this;
}

//----------------------------------------------------------------------------
mrw::StackTrace::CodePos mrw::StackTrace::translate(void* addr)
  throw(std::bad_exception) {
#ifdef _MT
  boost::recursive_mutex::scoped_lock lock(_mutex);
#endif
  static const CodePos UNKNOWN(addr, "????", "????", 0);
  assert(sizeof(bfd_vma)>=sizeof(void*));
  bfd_vma vma_addr(reinterpret_cast<bfd_vma>(addr));
  std::map<Translator::key_type, std::string>::iterator
    it(_addrs.lower_bound(vma_addr));
  if (it==_addrs.begin() || (--it)->first > vma_addr ||
      _dic.find(it->second)==_dic.end() ||
      _dic[it->second][it->first].first <= vma_addr)
    return UNKNOWN;
  static const char* file(0);
  static const char* function(0);
  unsigned int line;
  if (!bfd_find_nearest_line
      (const_cast<bfd*>(static_cast<const bfd*>(_bfd[it->second])),
       _dic[it->second][it->first].second,
       _syms[it->second],
       vma_addr - it->first,
       &file, &function, &line))
    return UNKNOWN;
  return CodePos(addr, mrw::demangle(_bfd[it->second], function),
                 file?file:"????", line);
}

//----------------------------------------------------------------------------
bool mrw::StackTrace::createSymtable(const std::string& fname, void* offs)
    throw(std::bad_exception) {
#ifdef _MT
  boost::recursive_mutex::scoped_lock lock(_mutex);
#endif
  if (_dic.find(fname)!=_dic.end()) return true; // already loaded
  try {
#if NO_LTDL
    static bfd*(*bfd_openr)(const char*, const char*) =
      ::bfd_openr;
    static bfd_boolean(*bfd_check_format)(bfd*, bfd_format) =
      ::bfd_check_format;
    static bfd_boolean(*bfd_check_format_matches)(bfd*, bfd_format, char***) =
      ::bfd_check_format_matches;
    static void(*bfd_map_over_sections)
      (bfd*, void(*)(bfd*, asection*, void*), void*) =
      ::bfd_map_over_sections;
#else
    static DynamicLibrary lib("libbfd");
    static bfd*(*bfd_openr)(const char*, const char*) =
      (bfd*(*)(const char*, const char*))lib.symbol("bfd_openr");
    static bfd_boolean(*bfd_check_format)(bfd*, bfd_format) =
      (bfd_boolean(*)(bfd*, bfd_format))lib.symbol("bfd_check_format");
    static bfd_boolean(*bfd_check_format_matches)(bfd*, bfd_format, char***) =
      (bfd_boolean(*)(bfd*, bfd_format, char***))
      lib.symbol("bfd_check_format_matches");
    static void(*bfd_map_over_sections)
      (bfd*, void(*)(bfd*, asection*, void*), void*) =
      (void(*)(bfd*, void(*)(bfd*, asection*, void*), void*))
      lib.symbol("bfd_map_over_sections");
#endif
    if (fname=="") return createSymtable(filename());
    AutoBfd abfd((*bfd_openr)(fname.c_str(), 0));
    long memsz(-1);
    Auto<char**>::Free m(0);
    if (!abfd || (*bfd_check_format)(abfd, bfd_archive) ||
        !(*bfd_check_format_matches)(abfd, bfd_object, &(m.getClean())) ||
        !(bfd_get_file_flags(const_cast<bfd*>(static_cast<const bfd*>(abfd)))
          &HAS_SYMS) ||
        (memsz=bfd_get_symtab_upper_bound
         (const_cast<bfd*>(static_cast<const bfd*>(abfd))))<0) {
      _error = "cannot map bfd symbols - 'bfd_get_file_flags' failed "
        "for file: \""+fname+'"';
      return false;
    }
    mrw::AutoPtr<asymbol*> syms(new asymbol*[memsz]);
    if (bfd_canonicalize_symtab(const_cast<bfd*>(static_cast<const bfd*>(abfd)),
                                syms)<0) {
      _error = "cannot map bfd symbols - 'bfd_canonicalize_symtab' failed"
        "for file: \""+fname+'"';
      return false;
    }
    _bfd[fname] = abfd;
    _syms[fname] = syms;
    _dic[fname];
    std::pair<std::string, void*> fileoffs = std::make_pair(fname, offs);
    (*bfd_map_over_sections)(_bfd[fname], buildSectionMap, &fileoffs);
    return true;
  } catch (const std::exception& x) {
    _error = std::string("exception received: \"")+x.what()+"\", "
      "while processing file: \""+fname+'"';
    return false; // shared library for bfd not found or similar
  }
}

//----------------------------------------------------------------------------
bool mrw::StackTrace::createSymtable(const mrw::StackTrace::BinFiles& files)
  throw(std::bad_exception) {
#ifdef _MT
  boost::recursive_mutex::scoped_lock lock(_mutex);
#endif
  bool success(true);
  for (BinFiles::const_iterator it(files.begin());
       it!=files.end(); ++it) {
    if (!createSymtable(it->first, it->second)) success=false;
  }
  return success;
}

//----------------------------------------------------------------------------
int mrw::StackTrace::bfdClose(bfd* abfd) throw() {
  try {
#if NO_LTDL
    static int(*bfd_close)(bfd*) = ::bfd_close;
#else
    static DynamicLibrary lib("");
    static int(*bfd_close)(bfd*) = (int(*)(bfd*))lib.symbol("bfd_close");
#endif
    return (*bfd_close)(abfd);
  } catch (...) {
    return -1; // dynamic library loading problems
  }
}

//----------------------------------------------------------------------------
mrw::StackTrace::BinFiles mrw::StackTrace::filename()
  throw(std::bad_exception) {
  mrw::StackTrace::BinFiles res;
# if defined(__solaris__)
  {
    std::string s;
    s<<"/proc/"<<getpid();
    AutoFile fd(open(s.c_str(), O_RDONLY));
    prpsinfo_t status;
    if (fd==-1 || ioctl(fd, PIOCPSINFO, &status)==-1) return res;
    s = status.pr_psargs;
    return res<<BinFiles::value_type(s.substr(0, s.find(' ')), 0);
  }
# elif defined(__linux__)
  {
    res<<BinFiles::value_type("/proc/self/exe", 0);
    std::ifstream is("/proc/self/maps");
    std::string s;
    std::string range, perm, x1, x2, size, lib;
    while (getline(is, s)) try {
      range = perm = x1 = x2 = size = lib = "????";
      s>>range>>perm>>x1>>x2>>size>>lib;
      range.resize(range.find_first_not_of("0123456789abcdefABCDEF"));
      void* addr(0);
      range>>addr;
      // added check: no names in brackets: [lib], because there are
      // [heap], [stack] and [vdso] that cannot be loaded
      // question: should only files with ending '.so' be loaded?
      if (lib.size() && lib[0]!='[' && lib[lib.size()-1]!=']' && addr>0)
        res<<BinFiles::value_type(lib, addr);
    } catch (...) {} // ignore non matching lines
    return res;
  }
# else
  {
#     warning "Don't know how to get executable file name in your system!"
#     warning "Impossible to get function names in stack trace!"
#     warning "Give the path to the executable to StackTrace::createSymtable!"
    return res; // empty
  }
# endif 
}

//----------------------------------------------------------------------------
void mrw::StackTrace::buildSectionMap(bfd* abfd, asection* section,
                                      void* fileoffs)
  throw(std::bad_exception) {
  if (!abfd || !section || !fileoffs) return;
  if (!(bfd_get_section_flags(abfd, section)&SEC_ALLOC)) return;
  std::pair<std::string, void*> fileoffset =
    *(std::pair<std::string, void*>*)fileoffs;
  bfd_vma vma(bfd_get_section_vma(abfd, section)+
              reinterpret_cast<bfd_vma>(fileoffset.second));
#ifdef bfd_get_section_size
  bfd_size_type sz(bfd_get_section_size(section));
#elif defined bfd_get_section_size_before_reloc
  bfd_size_type sz(bfd_get_section_size_before_reloc(section));
#else
  bfd_size_type sz(section->size);
#endif
  _dic[fileoffset.first][vma] = Translator::mapped_type(vma+sz, section);
  _addrs[vma] = fileoffset.first;
}

//----------------------------------------------------------------------------
std::map<std::string, mrw::StackTrace::Translator> mrw::StackTrace::_dic;
std::map<mrw::StackTrace::Translator::key_type, std::string>
  mrw::StackTrace::_addrs;
std::map<std::string, mrw::StackTrace::AutoBfd> mrw::StackTrace::_bfd;
std::map<std::string, mrw::AutoPtr<asymbol*> > mrw::StackTrace::_syms;
#ifdef _MT
boost::recursive_mutex mrw::StackTrace::_mutex;
#endif
std::string mrw::StackTrace::_error;
