/** @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
1000 th 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