/** @file

    $Id$

    $Date$
    $Author$

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

*/
#ifndef __MRW_STACKTRACE_HPP__
#define __MRW_STACKTRACE_HPP__
#include <mrw/auto.hpp>
#include <vector>
#include <list>
#include <map>
#include <string>
#include <memory>
#include <sys/mman.h>
#include <bfd.h>

#if (__GNUC__==3 && __GNUC_MINOR__<4 || __GNUC__<3) && _REENTRANT && !_MT
#define _MT
#endif
#ifdef _MT
#include <boost/thread/recursive_mutex.hpp>
#endif

namespace mrw {

  /** @defgroup debug Debug Utilities */
  //@{
  
  /** @defgroup grpStackTrace Collect and Format a Stack Trace

      Somewhere in a program, there is a fatal error, e.g. an
      unexpected exception is thrown. How is it possible to debug the
      problem in such a case? Sometimes you can start a debugger and
      trace the execution of your program. But what if it occurs only
      once a week, or if you cannot set a breakpoint, because you
      don't know where the problem is located, or because only the
      1000th run of a method causes a problem, or what if the problem
      occurs only at your customers installation?

      One way to solve these problems is to do logging, or even
      function tracing, so you can narrow down the lines of code,
      where the problem occurs. But sometimes this is not enough,
      especially with exceptions. One of the worst things with
      exceptions is, you can catch an exception somewhere, but you
      don't know where it was thrown. Here it is very handy, to be
      able to write a stacktrace to a logging device.

      For logging, I recommend log4cxx on page:
       - http://logging.apache.org/log4cxx

      These classes are for collecting a stack trace and later for
      formatting with source code file name, line number and the
      method name.

      For collecting the stack trace (the addresses):
       - either the GNU gcc compiler is required
       - or the GNU glibc library function @c backtrace

      For extracting information from an address, the ELF library is required.

      @note For all features and full operation, this class requires:
       - either a GNU glibc bases system (LINUX), or the GNU gcc compiler
       - a system with ELF binaries (LINUX, Solaris, ...)
       - debug information, compile option @c -g
  */
  //@{

  /** @brief store and print a stack trace of the actual position in code
      @pre \#include <mrw/stacktrace.hpp>

      In the constructor, a stack trace is stored, but not yet
      evaluated. Therefore storing a stack trace is relatively
      fast. The evaluation is done when the stack trace is printed on
      a stream or converted to a string. "Evaluation" means, that the
      addresses are mapped to the correspoding symbols, the method
      names, sorce file names and line numbers are evaluated.

      @note Method StackTrace::createSymtable must be called exactely
      once, before evaluating the first stack trace. Best place is the
      first line of the @c main function.

      @note Debug information is required for compiling. You nee the
      compile option @c -g, or even better @c -ggdb3.

      @note The stack trace is known to work perfectly on Linux and
      Solaris both with GNU gcc compiler. But it should work with the
      GNU compiler on all systems, or wherever there is a glibc
      library.

      @note Symbol evaluation requires the ELF library and an ELF system.
      
      <h3>Technology</h3>

      On GNU glibc based systems (Linux), the stack trace is collected
      with GNU glibc's function @c backtrace(). On other systems
      (Solaris) it is collected using the GNU gcc's internal function @c
      __builtin_return_address(). With both functions, at most 50 steps
      back are collected.

      The evaluation is not done with the glibc library function @c
      backtrace_symbols(), because this function is unable to print
      the source file name and line number information. Instead of
      this, the executable binary is loaded into the memory and
      evaluated using the bdf library functions. For this the stack
      tracer needs to know how to find out which executable is
      running. It is possible to get this information automatically on
      Linux and Solaris. On other systems, I don't have this
      information, but you can either tell me, and I integrate support
      for your system (when I have time to do it), or provide the
      executable file name as an argument to @c
      mrw::StackTrace::createSymtable().

      <h3>Draw Backs</h3>

      Unfortunately it is not possible to extract the source file name
      and line number information if the executable was not compiled
      with debug option @c -g.

      @note If file and line is wrong, but the function name is
      correct, try to build without optimizations. For GNU compiler,
      this means no option @c -O or @c -O0 and enable the debug
      information with @c -g and don't inline functions with compiler
      option @c -fno-inline. To build this project, you may enter:

      @code
        CXXFLAGS="-g -fno-inline" ./configure && make clean check
      @endcode

      @todo Should I add support for alternative symbol evaluation using @c
      backtrace_symbols? I think rather not...?
  */
  class StackTrace {
      friend class StackTraceTest;
    public:
      //............................................................... typedefs
      /// container for the adresses
      typedef std::vector<void*> AddressTrace;
      /// binary file with offset (for shared libraries, 0 for executables)
      typedef std::list< std::pair<std::string, void*> > BinFiles;
      /// structure to store all evaluated information
      struct CodePos {
          CodePos(void const*const a, const std::string& fu,
                  const std::string& fi, unsigned int l):
              address(a), function(fu), file(fi), line(l) {
          }
          void const*const address; ///< the address pointer
          std::string function;     ///< function/method name
          std::string file;         ///< code file name
          unsigned int line;        ///< code line number
      };
      //................................................................ methods
      /// the constructor stores the actual stack trace
      StackTrace() throw(std::bad_exception);
      /// depending on how we got the stack trace, we may have to free memory
      ~StackTrace() throw();
      /// evaluates the symbol table and returns the formatted stack trace
      operator std::string() const throw(std::bad_exception);
      /** @return list of raw stack addresses */
      operator const AddressTrace&() const throw(std::bad_exception) {
        return _trace;
      }
      /// evaluate the stack trace and print it to a stream
      const StackTrace& print(std::ostream& os) const throw(std::bad_exception);
      /// evaluates and returns all information from a raw address
      /** @classmutex _mutex */
      static CodePos translate(void* addr) throw(std::bad_exception);
    
      /** @brief read the symbol table from the executable file or a
          shared library

          On Solaris and Linux, the executable is automatically
          detected through the @c /proc file system. Only on Linux the
          shared libraries are detected through @c /proc/self/maps. So
          you may leave the parameter empty on these systems.

          @param fname The file name of the executable or a shared
          library. On Linux and Solaris, this can be evaluated
          automatically, so the parameter is optional.

          @param offs Offset of the address space. It is 0 for
          executables, but must be given for shared libraries. Use @c
          ldd on the executable to find out the offset.

          @return @c true in case of success. If @c false is returned,
          the symbol table was not read and the evaluation cannot be
          done. Printing then only prints the raw addresses, without
          file, line nmber information and method names.

          @note createSymtable must be executed at least once before a
          stack trace is printed the very first time. For storing a
          stack trace (that means for the creation of a
          mrw::StackTrace object) a call to this method is not yet
          needed.

          @note In general, call only one of both createSymtable
          methods! But instead of callin the other method once, you
          may also call this one several times.

          @note If this method is called more than once for the same
          file, the symbols are created only the first time, so you
          don't loose too much time.
          
          @classmutex _mutex
       */
      static bool createSymtable(const std::string& fname="", void* offs=0)
        throw(std::bad_exception);

      /** @brief get the error text if @ref createSymtable returns false

          In case @ref createSymtable is not successful, you can use
          this method to retrieve the error text of the exception that
          was caught in @ref createSymtable.

          @return error text */
      static const std::string& error() throw() {
        return _error;
      }
    
      /** @brief read the symbol table from a list of an executable file and
                 shared libraries

          @param files The list of file names that must contain the
          executable plus all shared libraries that should be included
          in stack traces. Every file name has an offset address as
          second parameter. This offset must be given for shared
          libraries and can be retrieved using the program @c ldd.

          @return @c true in case of success. If @c false is returned,
          the symbol table was not completely read and the evaluation
          cannot be done. Printing then only prints the raw addresses,
          without file, line nmber information and method names.

          @note This method must be executed once before a stack trace
          is printed the very first time. For storing a stack trace
          (that means for the creation of a mrw::StackTrace object) a
          call to this method is not yet needed.

          @note In general, call only one of both createSymtable methods!

          @note This method calls the other one for all files in
          parameter @c files.
          
          @classmutex _mutex
      */
      static bool createSymtable(const BinFiles& files)
        throw(std::bad_exception);
    
    private:
      //............................................................... typedefs
      typedef std::map<bfd_vma, std::pair<bfd_vma, asection*> > Translator;
      static int bfdClose(bfd*) throw();
      typedef mrw::AutoResource<bfd*, int(*)(bfd*), StackTrace::bfdClose, int>
        AutoBfd;
      friend class mrw::AutoResource
          <bfd*, int(*)(bfd*), StackTrace::bfdClose, int>;
      //.............................................................. variables
      AddressTrace _trace;
      static std::map<std::string, Translator> _dic;
      static std::map<Translator::key_type, std::string> _addrs;
      static std::map<std::string, AutoBfd> _bfd;
      static std::map<std::string, mrw::AutoPtrAry<asymbol*> > _syms;
#ifdef _MT
      static boost::recursive_mutex _mutex;
#endif
      static std::string _error;
      
      //................................................................ methods
      static BinFiles filename() throw(std::bad_exception);
      static void buildSectionMap(bfd*, asection*, void*)
        throw(std::bad_exception);
  };

  //@}
  //@}
}

/** @addtogroup grpStackTrace */
//@{

/// evaluate a stack trace and shift it on a stream
inline std::ostream& operator<<(std::ostream& os, const mrw::StackTrace& st)
  throw(std::bad_exception) {
  return os<<(std::string)st;
}

//@}

#endif
