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.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
346 lines
12 KiB
346 lines
12 KiB
/** @file |
|
|
|
$Id$ |
|
|
|
$Date$ |
|
$Author$ |
|
|
|
@copy © Marc Wä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;
|
|
|