stack traces with any exception class

Define THROW_EXCEPTION(E) which takes an exception class instance,
and uses it to construct an instance of a class which is a subclass
of E and ExceptionMixin.  The original instance is discarded, and
the newly constructed sub-class is thrown.  Equivalent to
"throw E;".

Define THROW_EXCEPTION2(ETYPE,MSG) which takes an exception class
type, and argument.  Directly constructs a ExceptionMixin sub-class
with the given message argument.  Equivalent to
"throw ETYPE(MSG);".

Define PRINT_EXCEPTION2(E, FP) If E is a instance of a sub-class of
ExceptionMixin then write information to FP (FILE*).

Define SHOW_EXCEPTION(E) If E is a instance of a sub-class of
ExceptionMixin then return a std::string with information.
This commit is contained in:
Michael Davidsaver
2011-03-02 16:31:08 -05:00
parent 9fd158df1f
commit 0c61ac0833
4 changed files with 280 additions and 161 deletions

View File

@@ -30,6 +30,7 @@ INC += status.h
LIBSRCS += CDRMonitor.cpp
LIBSRCS += byteBuffer.cpp
LIBSRCS += bitSet.cpp
LIBSRCS += epicsException.cpp
LIBSRCS += requester.cpp
LIBSRCS += serializeHelper.cpp
LIBSRCS += linkedListVoid.cpp

View File

@@ -0,0 +1,87 @@
/**
* Copyright - See the COPYRIGHT that is included with this distribution.
* EPICS pvDataCPP is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*/
#include "epicsException.h"
#include <sstream>
#include <cstdio>
#include <cstring>
namespace epics{namespace pvData{
void
ExceptionMixin::print(FILE *fp) const
{
fprintf(fp, "On line %d of %s\n",m_line,m_file);
#if defined(EXCEPT_USE_BACKTRACE)
if(m_depth>0) {
fflush(fp); // must flush before using raw handle
backtrace_symbols_fd(m_stack, m_depth, fileno(fp));
fprintf(fp, "To translate run 'addr2line -e execname 0xXXXXXXX ...'\n"
" Note: Must be compiled with debug symbols\n");
}
#endif
}
std::string
ExceptionMixin::show() const
{
std::ostringstream out;
out<<"On line "<<m_line<<" of "<<m_file<<"\n";
#if defined(EXCEPT_USE_BACKTRACE)
if (m_depth>0) {
char **symbols=backtrace_symbols(m_stack, m_depth);
for(int i=0; i<m_depth; i++) {
out<<symbols[i]<<"\n";
}
free(symbols);
}
#endif
return out.str();
}
const char*
BaseException::what() const throw()
{
try{
if (base_msg.size()==0) {
const char *base=std::logic_error::what();
std::string out, stack;
const ExceptionMixin *info=dynamic_cast<const ExceptionMixin*>(this);
if(info) {
stack=info->show();
}
out.reserve(strlen(base)+1+stack.size()+1);
out+=base;
out+="\n";
if(info) {
out+=stack;
out+="\n";
}
base_msg.swap(out);
}
return base_msg.c_str();
} catch(std::bad_alloc&) {
return "BaseException::what - Insufficient memory to construct message";
} catch(...) {
return "BaseException::what - Unknown error when constructing message";
}
}
}}

View File

@@ -10,193 +10,207 @@
* Author: Matej Sekoranja
*/
#include <cstdio>
/*
* Throwing exceptions w/ file+line# and, when possibly, a stack trace
*
* THROW_EXCEPTION1( std::bad_alloc );
*
* THROW_EXCEPTION2( std::logic_error, "my message" );
*
* THROW_EXCEPTION( mySpecialException("my message", 42, "hello", ...) );
*
* Catching exceptions
*
* catch(std::logic_error& e) {
* fprintf(stderr, "%s happened\n", e.what());
* PRINT_EXCEPTION2(e, stderr);
* cout<<SHOW_EXCEPTION(e);
* }
*
* If the exception was not thrown with the above THROW_EXCEPTION*
* the nothing will be printed.
*/
#ifndef EPICSEXCEPTION_H_
#define EPICSEXCEPTION_H_
#include <stdexcept>
#include <string>
#include <cstdio>
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <cxxabi.h>
// Users may redefine this for a large size if desired
#ifndef EXCEPT_DEPTH
# define EXCEPT_DEPTH 20
#endif
#if defined(__GLIBC__) /* and possibly some BSDs */
# include<execinfo.h>
# define EXCEPT_USE_BACKTRACE
#elif defined(_WIN32) && !defined(__MINGW__) && !defined(SKIP_DBGHELP)
# include <windows.h>
# include <dbghelp.h>
# define EXCEPT_USE_CAPTURE
#else
# define EXCEPT_USE_NONE
#endif
namespace epics { namespace pvData {
class BaseException :
public std::exception {
/* Stores file and line number given, and when possible the call stack
* at the point where it was constructed
*/
class ExceptionMixin {
const char *m_file;
int m_line;
#ifndef EXCEPT_USE_NONE
void *m_stack[EXCEPT_DEPTH];
int m_depth; // always <= EXCEPT_DEPTH
#endif
public:
BaseException(const char* message, const char* file, int line)
// allow the ctor to be inlined if possible
ExceptionMixin(const char* file, int line)
:m_file(file)
,m_line(line)
#if defined(EXCEPT_USE_BACKTRACE)
{
toString(m_what, message, file, line, 0);
m_depth=backtrace(m_stack,EXCEPT_DEPTH);
}
BaseException(const char* message, const char* file, int line, std::exception& cause)
#elif defined(EXCEPT_USE_CAPTURE)
{
toString(m_what, message, file, line, cause.what());
m_depth=CaptureStackBackTrace(0,EXCEPT_DEPTH.m_stack,0);
}
virtual ~BaseException() throw()
{
}
virtual const char* what() const throw() { return m_what.c_str(); }
private:
static inline void toString(std::string& str, const char* message, const char* file, int line, const char * cause) {
str.append(message);
str.append("\n\tat ");
str.append(file);
str.append(":");
char sline[10];
snprintf(sline, 10, "%d", line);
str.append(sline);
str.append("\n");
getStackTrace(&str);
if (cause)
str.append(cause);
}
/** Get stack trace, i.e. demangled backtrace of the caller. */
static inline void getStackTrace(std::string* trace, unsigned int skip_frames = 0, unsigned int max_frames = 63)
{
#ifdef DISABLE_STACK_TRACE
trace += "(stack trace disabled)";
#else
// storage array for stack trace address data
void* addrlist[max_frames+1];
// retrieve current stack addresses
int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));
if (addrlen == 0) {
trace->append("(stack trace not available)");
return;
}
// resolve addresses into strings containing "filename(function+address)",
// this array must be free()-ed
char** symbollist = backtrace_symbols(addrlist, addrlen);
// allocate string which will be filled with the demangled function name
size_t funcnamesize = 256;
char* funcname = (char*)malloc(funcnamesize);
// iterate over the returned symbol lines. skip the first, it is the
// address of this function.
for (int i = (1 + skip_frames); i < addrlen; i++)
{
char *module = 0, *fname = 0, *offset = 0;
#ifdef __APPLE__
int stage = 0;
for (char *p = symbollist[i]; *p; ++p)
{
// find spaces and separate
// 0 a.out 0x0000000100000bbc _Z11print_tracev + 22
switch (stage)
{
case 0: // skip frame index
if (*p == ' ') stage++;
break;
case 1: // skip spaces
if (*p != ' ') { module = p; stage++; }
break;
case 2: // module name
if (*p == ' ') { *p = '\0'; stage++; }
break;
case 3: // skip spaces
if (*p != ' ') stage++;
break;
case 4: // address
if (*p == ' ') { fname = p+1; stage++; }
break;
case 5: // function
if (*p == ' ') { *p = '\0'; stage++; }
break;
case 6: // "+ "
if (*p == '+') { p++; offset = p+1; };
break;
}
}
#else
// find parentheses and +address offset surrounding the mangled name:
// ./module(function+0x15c) [0x8048a6d]
module = symbollist[i];
for (char *p = symbollist[i]; *p; ++p)
{
if (*p == '(') {
// terminate module
*p = '\0';
fname = p+1;
}
else if (*p == '+') {
// terminate fname
*p = '\0';
offset = p+1;
}
else if (*p == ')' && offset) {
// terminate offset
*p = '\0';
break;
}
}
{}
#endif
if (fname && offset && offset && fname < offset)
{
void print(FILE *fp=stderr) const;
// mangled name is now in [begin_name, begin_offset) and caller
// offset in [begin_offset, end_offset). now apply
// __cxa_demangle():
int status;
char* ret = abi::__cxa_demangle(fname,
funcname, &funcnamesize, &status);
if (status == 0) {
trace->append("\t ");
*trace += module;
trace->append(": ");
*trace += ret; // use possibly realloc()-ed string
trace->append("+");
*trace += offset;
trace->append("\n");
}
else {
// demangling failed. Output function name as a C function with
// no arguments.
trace->append("\t ");
*trace += module;
trace->append(": ");
*trace += fname;
*trace += "()+";
*trace += offset;
trace->append("\n");
}
}
else
{
// couldn't parse the line? print the whole line.
trace->append("\t ");
*trace += symbollist[i];
trace->append("\n");
}
}
free(funcname);
free(symbollist);
#endif
}
private:
std::string m_what;
std::string show() const;
};
#ifndef THROW_EXCEPTION_COMPAT
#define THROW_BASE_EXCEPTION(msg) throw ::epics::pvData::BaseException(msg, __FILE__, __LINE__)
#define THROW_BASE_EXCEPTION_CAUSE(msg, cause) throw ::epics::pvData::BaseException(msg, __FILE__, __LINE__, cause)
namespace detail {
/* Combines user exception type with Mixin
*
* Takes advantage of the requirement that all exception classes
* must be copy constructable. Of course this also requires
* the and extra copy be constructed...
*/
template<typename E>
class ExceptionMixed : public E, public ExceptionMixin {
public:
// construct from copy of E
ExceptionMixed(const E& self,const char* file, int line)
:E(self), ExceptionMixin(file,line)
{}
// construct for E w/o arguments
ExceptionMixed(const char* file, int line)
:E(), ExceptionMixin(file,line)
{}
// construct for E one argument
template<typename A1>
ExceptionMixed(A1 arg1,const char* file, int line)
:E(arg1), ExceptionMixin(file,line)
{}
// construct for E two arguments
template<typename A1, typename A2>
ExceptionMixed(A1 arg1, A2 arg2,const char* file, int line)
:E(arg1,arg2), ExceptionMixin(file,line)
{}
};
// function template to deduce E from argument
template<typename E>
static inline
ExceptionMixed<E>
makeException(const E& self,const char* file, int line)
{
return ExceptionMixed<E>(self,file,line);
}
template<typename E>
static inline
std::string
showException(const E& ex)
{
ExceptionMixin *mx=dynamic_cast<ExceptionMixin*>(&ex);
if(!mx) return std::string();
return mx->show();
}
}
// Throw an exception of a mixed sub-class of the type of E
// The instance E is copied and discarded
#define THROW_EXCEPTION(E) \
do { \
throw ::epics::pvData::detail::makeException(E, __FILE__, __LINE__); \
} while(0)
// Throw an exception of a mixed sub-class of E, passing MSG as an argument
#define THROW_EXCEPTION1(TYPE) \
do { \
throw ::epics::pvData::detail::ExceptionMixed<TYPE>(__FILE__, __LINE__); \
}while(0)
// Throw an exception of a mixed sub-class of E, passing MSG as an argument
#define THROW_EXCEPTION2(TYPE,MSG) \
do { \
throw ::epics::pvData::detail::ExceptionMixed<TYPE>(MSG, __FILE__, __LINE__); \
}while(0)
#define PRINT_EXCEPTION2(EI, FP) \
do { \
ExceptionMixin *_em_p=dynamic_cast<ExceptionMixin*>(&EI); \
if (_em_p) {_em_p->print(FP);} \
}while(0)
#define PRINT_EXCEPTION(EI) PRINT_EXCEPTION2(EI,stderr)
#ifndef __GNUC__
# define SHOW_EXCEPTION(EI) ::epics::pvData::detail::showException(EI)
#else
# define SHOW_EXCEPTION(EI) \
({ ExceptionMixin *_mx=dynamic_cast<ExceptionMixin*>(&(EI)); \
_mx ? _mx->show() : std::string(); \
})
#endif
#else // THROW_EXCEPTION_COMPAT
/* For older compilers which have a problem with the above */
#define PRINT_EXCEPTION(EI) do{}while(0)
#define PRINT_EXCEPTION2(EI,FP) do{}while(0)
#define SHOW_EXCEPTION(EI) std::string()
#define THROW_EXCEPTION(E) do{throw (E);}while(0)
#define THROW_EXCEPTION1(E) do{throw (E)();}while(0)
#define THROW_EXCEPTION2(E,A) do{throw (E)(A);}while(0)
#endif // THROW_EXCEPTION_COMPAT
class BaseException : public std::logic_error {
public:
explicit BaseException(const std::string msg) : std::logic_error(msg) {}
virtual ~BaseException() throw(){};
virtual const char* what() const throw();
private:
mutable std::string base_msg;
};
#define THROW_BASE_EXCEPTION(msg) THROW_EXCEPTION2(::epics::pvData::BaseException, msg)
#define THROW_BASE_EXCEPTION_CAUSE(msg, cause) THROW_EXCEPTION2(::epics::pvData::BaseException, msg)
}
}

View File

@@ -63,6 +63,22 @@ void testBaseException(FILE *fp) {
fprintf(fp,"PASSED\n");
}
void testLogicException(FILE *fp) {
try {
THROW_EXCEPTION(std::logic_error("There is a logic_error"));
} catch (std::logic_error& be) {
fprintf(fp,"\n\n%s\n\n", be.what());
PRINT_EXCEPTION2(be, fp);
}
try {
THROW_EXCEPTION2(std::logic_error, "There is another logic_error");
} catch (std::logic_error& be) {
fprintf(fp,"\n\n%s\n\n", be.what());
fprintf(fp,"%s\n", SHOW_EXCEPTION(be).c_str());
}
}
int main(int argc,char *argv[])
{
FILE *fp=NULL;
@@ -71,6 +87,7 @@ int main(int argc,char *argv[])
if(!fp) fprintf(stderr,"Failed to open test output file\n");
}
if(!fp) fp=stdout;
testLogicException(fp);
testBaseException(fp);
return(0);
}