C++ Library containing a lot of needful things: Stack Trace, Command Line Parser, Resource Handling, Configuration Files, Unix Command Execution, Directories, Regular Expressions, Tokenizer, Function Trace, Standard Extensions.
292 lines
12 KiB
292 lines
12 KiB
/** @file |
|
|
|
$Id$ |
|
|
|
$Date$ |
|
$Author$ |
|
|
|
@copy © Marc Wä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); |
|
/// 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::AutoPtr<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
|
|
|