// g++ -Wall -D__SOLARIS__ -g -I /home/public/freeware/include -L /home/public/freeware/lib -I . stacktrace.cxx -lbfd -liberty #ifndef __MRW_STACKTRACE_HPP__ #define __MRW_STACKTRACE_HPP__ #include #include #include #include #include #include #include #ifdef __REENTRANT #warning "mrw::StackTrace is not thread safe yet!" #warning "It should work, but is at least untested..." #endif namespace mrw { /** @defgroup StackTrace 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 - it must be linked with @c -libery and @c -lbfd @subsection sttech Technology 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(). @subsection stdrawbacks Draw Backs 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. But what's worse, it is not possible to ger symbolic information from libraries linked to the executable. Perhaps it could be possible, if I'd add a possibility to read and evaluate these libraries, but that's for a future release. @todo Add support to read debugging information from libraries that are linked to the executable. @todo Add support for alternative symbol evaluation using @c backtrace_symbols. */ //@{ /** @brief store and print a stack trace of the actual position in code @pre #include 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 This class requires libbfd an libiberty. Debug information is required for compiling. You nee the compile option @c -g, or even better @c -ggdb3. To link, you need @c -lmrw, @c -lbfd and @c -liberty. @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. */ class StackTrace { public: //............................................................... typedefs typedef std::vector AddressTrace; ///< container for the adresses /// structure to store all evaluated information struct CodePos { CodePos(void* a, std::string fn, std::string fi, unsigned int l) throw(std::bad_exception): address(a), function(fn), file(fi), line(l) { } void* 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); /// 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 static CodePos translate(void* addr) throw(std::bad_exception); /** @brief read the symbol table from the executable file @param std::string The file name of the executable. On Linux and Solaris, this can be evaluated automatically, so the parameter is optional. @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 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 If this method is called more than once, the symbols are created only the first time, so you don't loose too much time. */ static bool createSymtable(std::string = "") throw(std::bad_exception); private: //............................................................... typedefs typedef std::map > Translator; //.............................................................. variables AddressTrace _trace; static std::auto_ptr _dic; static std::vector _addrs; static AutoBfd _bfd; static std::auto_ptr _syms; //................................................................ methods static std::string filename() throw(std::bad_exception); static void buildSectionMap(bfd*, asection*, void*) throw(std::bad_exception); }; /// evaluate a stack trace and shift it on a stream inline std::ostream& operator<<(std::ostream& os, const StackTrace& st) throw(std::bad_exception) { return os<<(std::string)st; } //@} } #endif