Forgot to do '-a' on the last commit 'cause git is silly like that...

This commit is contained in:
2011-07-27 22:34:53 -04:00
parent a7b038ff52
commit 26605a3220
5 changed files with 93 additions and 129 deletions

View File

@@ -1,11 +1,9 @@
CC=gcc CC=gcc
LD=gcc LD=gcc
EXECOBJ= EXECOBJ=
OBJECTS=exclib.o exception_test.o backtrace.o OBJECTS=exclib.o exception_test.o
LIBS= LIBS=
ifeq "$(OS)" "win32" CFLAGS=
LIBS=-lbfd -lintl -liberty -imagehlp
endif
all: exception_test all: exception_test

View File

@@ -25,7 +25,6 @@ int main(void)
{ {
int x = 2; int x = 2;
exclib_register_signals();
printf("Stack frames are %d bytes in size\n\n", sizeof(struct exc_status)); printf("Stack frames are %d bytes in size\n\n", sizeof(struct exc_status));
TRY { TRY {
@@ -50,8 +49,7 @@ int main(void)
printf("Using THROW_NONZERO\n"); printf("Using THROW_NONZERO\n");
THROW_NONZERO(strcmp("a", "b"), 0, "String Compare Exception"); THROW_NONZERO(strcmp("a", "b"), 0, "String Compare Exception");
} FINALLY { } FINALLY {
printf("In finally clause.\n"); exclib_print_exception_stack("In finally clause", __FILE__, (char *)__func__, __LINE__);
exclib_print_exception_stack(__FILE__, (char *)__func__, __LINE__);
} ETRY; } ETRY;
TRY { TRY {

View File

@@ -2,17 +2,30 @@
int main(void) int main(void)
{ {
exclib_register_signals(); EXCLIB_TRACE("No stack to print?");
TRY { TRY {
exclib_print_exception_stack(__FILE__, (char *)__func__, __LINE__); EXCLIB_TRACE("Inside TRY");
THROW(2, NULL);
} CATCH(2) {
exclib_print_exception_stack(__FILE__, (char *)__func__, __LINE__);
THROW(3, NULL); THROW(3, NULL);
} CATCH(3) { } CATCH(3) {
EXCLIB_TRACE("Caught 3");
THROW(5, NULL);
} CATCH(5) {
EXCLIB_TRACE("Caught 5");
TRY {
THROW(6, NULL);
} CATCH (6) {
EXCLIB_TRACE("Caught 6");
THROW(7, NULL);
} ETRY;
} CATCH(7) {
EXCLIB_TRACE("Caught 7");
} FINALLY { } FINALLY {
exclib_print_exception_stack(__FILE__, (char *)__func__, __LINE__); EXCLIB_TRACE("In finally clause");
return 0;
} ETRY; } ETRY;
EXCLIB_TRACE("Exiting program");
return 0;
} }

150
exclib.c
View File

@@ -2,7 +2,6 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <setjmp.h> #include <setjmp.h>
#include <signal.h>
#include <unistd.h> #include <unistd.h>
#include <sys/types.h> #include <sys/types.h>
#include <string.h> #include <string.h>
@@ -18,38 +17,60 @@ struct exc_name_data __exclib_exc_names[EXC_PREDEFINED_EXCEPTIONS] = {
{EXC_OUTOFBOUNDS, "Array Index Out of Bounds", SIGTERM} {EXC_OUTOFBOUNDS, "Array Index Out of Bounds", SIGTERM}
}; };
void exclib_print_exception_stack(char *file, char *func, int line) void exclib_print_exception_stack(char *mbuf, char *file, char *func, int line)
{ {
char buf[256]; char buf[256];
char flagbuf[256]; char flagbuf[256];
char *excname = NULL;
struct exc_status *cur = EXC_STATUS_LIST; struct exc_status *cur = EXC_STATUS_LIST;
int idx = 0; int idx = 0;
fprintf(stderr, fprintf(stderr,
"EXCLIB: %s:%d:%s: %s\n",
file,
line,
func,
mbuf);
/*fprintf(stderr,
"%s:%d:%s: Contents of the Exception Stack at this time:\n", "%s:%d:%s: Contents of the Exception Stack at this time:\n",
file, file,
line, line,
func); func);*/
if ( ! cur ) if ( ! cur )
fprintf(stderr, "#0: No exception stack\n"); fprintf(stderr, "EXCLIB: #0: No exception stack\n");
while ( cur != NULL ) { while ( cur != NULL ) {
memset((char *)&buf, 0, 256); memset((char *)&buf, 0, 256);
memset((char *)&flagbuf, 0, 256); memset((char *)&flagbuf, 0, 256);
if ( cur->catching == 1) if ( cur->catching == 1) {
strcat((char *)&flagbuf, "catching "); if ( strlen((char *)&flagbuf) > 0 )
if ( cur->caught == 1) strcat((char *)&flagbuf, ",");
strcat((char *)&flagbuf, "caught "); strcat((char *)&flagbuf, "catching");
if ( cur->tried == 1) }
strcat((char *)&flagbuf, "tried "); if ( cur->caught == 1) {
if ( strlen((char *)&flagbuf) > 0 )
strcat((char *)&flagbuf, ",");
strcat((char *)&flagbuf, "caught");
}
if ( cur->tried == 1) {
if ( strlen((char *)&flagbuf) > 0 )
strcat((char *)&flagbuf, ",");
strcat((char *)&flagbuf, "tried");
}
if ( cur->name )
excname = cur->name;
else
excname = "NULL";
sprintf((char *)&buf, sprintf((char *)&buf,
"#%d[0x%x] %s:%d:%s: %s (%d) ( %s): %s\n", "EXCLIB: #%d[0x%x] %s:%d:%s:%s:%d:%s: %s\n",
idx, idx,
cur, cur,
cur->file, cur->file,
cur->line, cur->line,
cur->function, cur->function,
cur->name, excname,
cur->value, cur->value,
(char *)&flagbuf, (char *)&flagbuf,
cur->description); cur->description);
@@ -59,72 +80,6 @@ void exclib_print_exception_stack(char *file, char *func, int line)
} }
} }
void exclib_print_stacktrace(char *msg, char *file, char *func, int line)
{
size_t bt_size;
void *bt_frames[EXC_BT_FRAMES];
char **strings;
size_t i;
if (!msg)
msg = "Unknown error occured";
fprintf(stderr,
"%s:%d:%s: %s\n",
file,
line,
func,
msg);
bt_size = backtrace(bt_frames, EXC_BT_FRAMES);
strings = backtrace_symbols (bt_frames, bt_size);
for (i = 0; i < bt_size; i++)
fprintf(stderr, "#%d: %s\n", i, strings[i]);
free (strings);
}
void exclib_print_exc_stacktrace (char *msg)
{
char **strings;
size_t i;
if ( !msg )
msg = "Unhandled Exception";
fprintf(stderr,
"%s:%d:%s: %s '%s' (%d) : %s\n",
EXC_STATUS_LIST->file,
EXC_STATUS_LIST->line,
EXC_STATUS_LIST->function,
msg,
EXC_STATUS_LIST->name,
EXC_STATUS_LIST->value,
EXC_STATUS_LIST->description);
strings = backtrace_symbols (EXC_STATUS_LIST->bt_frames, EXC_STATUS_LIST->bt_size);
for (i = 0; i < EXC_STATUS_LIST->bt_size; i++) {
fprintf(stderr, "#%d: %s\n", i, strings[i]);
}
free (strings);
}
void __exclib_sigsegv_handler(int signal)
{
exclib_print_stacktrace("SIGSEGV - Segmentation Fault.", __FILE__, (char *)__func__, __LINE__);
#ifdef DEBUG_LOOPONSEGFAULT
fprintf(stderr, "Entering infinite loop; please connect a debugger to this process or kill it.\n");
fflush(stderr);
while ( 1 ) { };
#endif
exit(signal);
}
void exclib_register_signals(void)
{
signal(SIGSEGV, &__exclib_sigsegv_handler);
}
void exclib_bulk_name_exceptions(struct exc_name_data *exclib_exc_names, int size) void exclib_bulk_name_exceptions(struct exc_name_data *exclib_exc_names, int size)
{ {
struct exc_name_data *ptr; struct exc_name_data *ptr;
@@ -171,25 +126,30 @@ void exclib_prep_throw(int value, char *msg, char *file, char *func, int line)
if ( EXC_STATUS_LIST && EXC_STATUS_LIST->tried == 1) { if ( EXC_STATUS_LIST && EXC_STATUS_LIST->tried == 1) {
sprintf((char *)&stbuf, "Tried to THROW %d but couldn't create new exception frame", value); sprintf((char *)&stbuf, "Tried to THROW %d but couldn't create new exception frame", value);
if ( EXC_STATUS_LIST->catching == 1 && EXC_STATUS_LIST->prev ) { if ( EXC_STATUS_LIST->catching == 1 && EXC_STATUS_LIST->prev ) {
if ( exclib_new_exc_frame(EXC_STATUS_LIST, file, func, line) ) if ( exclib_new_exc_frame(EXC_STATUS_LIST, file, func, line) ) {
exclib_print_stacktrace((char *)&stbuf, file, func, line); exclib_print_exception_stack((char *)&stbuf, file, func, line);
exit(value);
}
memcpy(EXC_STATUS_LIST->buf, EXC_STATUS_LIST->prev->buf, sizeof(jmp_buf)); memcpy(EXC_STATUS_LIST->buf, EXC_STATUS_LIST->prev->buf, sizeof(jmp_buf));
} else if ( EXC_STATUS_LIST->catching && (EXC_STATUS_LIST->prev == NULL) ) { } /*else if ( EXC_STATUS_LIST->catching == 1 && (EXC_STATUS_LIST->prev == NULL) ) {
if ( exclib_new_exc_frame(&__exc_statuses[__exc_curidx++], file, func, line) ) if ( exclib_new_exc_frame(&__exc_statuses[__exc_curidx++], file, func, line) ) {
exclib_print_stacktrace((char *)&stbuf, file, func, line); exclib_print_exception_stack((char *)&stbuf, file, func, line);
exit(value);
}
EXCLIB_TRACE("Condition 2");
exclib_clear_exc_frame(); exclib_clear_exc_frame();
__exc_curidx--; __exc_curidx--;
} else { } */ else {
if ( exclib_new_exc_frame(EXC_STATUS_LIST, file, func, line) ) if ( exclib_new_exc_frame(EXC_STATUS_LIST, file, func, line) )
exclib_print_stacktrace((char *)&stbuf, file, func, line); exclib_print_exception_stack((char *)&stbuf, file, func, line);
} }
EXC_STATUS_LIST->value = value; EXC_STATUS_LIST->value = value;
EXC_STATUS_LIST->name = __exc_names[value]; EXC_STATUS_LIST->name = __exc_names[value];
EXC_STATUS_LIST->description = msg; EXC_STATUS_LIST->description = msg;
} else { } else {
sprintf((char *)&stbuf, "Tried to THROW Exception %d but had no exception context. (Called outside of TRY block, or thrown while TRY was setting up?)", value); sprintf((char *)&stbuf, "Tried to THROW Exception %d but had no exception context. (Called outside of TRY block, or thrown while TRY was setting up?)", value);
exclib_print_stacktrace("", file, func, line); exclib_print_exception_stack((char *)&stbuf, file, func, line);
kill(getpid(), __exc_signals[value]); exit(__exc_signals[value]);
} }
} }
@@ -216,7 +176,6 @@ int exclib_new_exc_frame(struct exc_status *es, char *file, char *function, int
es->caught = 0; es->caught = 0;
es->tried = 0; es->tried = 0;
es->catching = 0; es->catching = 0;
es->bt_size = backtrace(es->bt_frames, EXC_BT_FRAMES);
EXC_STATUS_LIST = es; EXC_STATUS_LIST = es;
return 0; return 0;
} }
@@ -225,27 +184,24 @@ int exclib_clear_exc_frame()
{ {
struct exc_status *es = EXC_STATUS_LIST; struct exc_status *es = EXC_STATUS_LIST;
if ( !es ) { if ( !es ) {
exclib_print_stacktrace("exclib_clear_exc_frame was called but there were no exception frames to clear!", exclib_print_exception_stack("exclib_clear_exc_frame was called but there were no exception frames to clear!",
__FILE__, (char *)__func__, __LINE__); __FILE__, (char *)__func__, __LINE__);
kill(getpid(), SIGTERM); exit(1);
} }
if ( es->prev ) if ( es->prev )
es->prev->next = NULL; es->prev->next = NULL;
if ( !es->caught ) { if ( es->value && !es->caught ) {
// exception was unhandled - do we have anywhere else to go? // thrown exception was unhandled - do we have anywhere else to go?
if ( !es->prev ) { if ( !es->prev ) {
// No frame above us to propagate this into, stacktrace and kill ourselves // No frame above us to propagate this into, stacktrace and kill ourselves
// we do kill here instead of just exit() to try and let any custom signal exclib_print_exception_stack("Uncaught exception", __FILE__, (char*)__func__, __LINE__);
// handlers clean things up for us exit(es->value);
exclib_print_exc_stacktrace(NULL);
kill(getpid(), __exc_signals[es->value]);
//kill(getpid(), SIGKILL); //kill(getpid(), SIGKILL);
} else if ( es->tried && es->prev && (es->catching == 0)) { } else if ( es->tried && es->prev && (es->catching == 0)) {
// copy this exception up into the upper frame and siglongjmp back to that // copy this exception up into the upper frame and siglongjmp back to that
EXC_STATUS_LIST = es->prev; EXC_STATUS_LIST = es->prev;
EXC_STATUS_LIST->caught = 0; EXC_STATUS_LIST->caught = 0;
memcpy(&EXC_STATUS_LIST->bt_frames, &es->bt_frames, (sizeof(void *)*EXC_BT_FRAMES)); memcpy(&EXC_STATUS_LIST->bt_frames, &es->bt_frames, (sizeof(void *)*EXC_BT_FRAMES));
EXC_STATUS_LIST->bt_size = es->bt_size;
EXC_STATUS_LIST->file = es->file; EXC_STATUS_LIST->file = es->file;
EXC_STATUS_LIST->function = es->function; EXC_STATUS_LIST->function = es->function;
EXC_STATUS_LIST->line = es->line; EXC_STATUS_LIST->line = es->line;

View File

@@ -26,12 +26,12 @@
* 9- Each member of the exception stack is currently ~700 bytes, so beware of making EXC_BT_FRAMES too large (the default, 50, is already unimaginably deep and adds ~35kB to your memory usage automatically). Don't be afraid to make EXC_BT_FRAMES smaller; most programs won't need more than 10 or 15, because this only tracks where TRY/THROW have been used, and each TRY/THROW pair use only one entry. Stacktraces aren't stored here, so this doesn't limit the possible size of a stacktrace. * 9- Each member of the exception stack is currently ~700 bytes, so beware of making EXC_BT_FRAMES too large (the default, 50, is already unimaginably deep and adds ~35kB to your memory usage automatically). Don't be afraid to make EXC_BT_FRAMES smaller; most programs won't need more than 10 or 15, because this only tracks where TRY/THROW have been used, and each TRY/THROW pair use only one entry. Stacktraces aren't stored here, so this doesn't limit the possible size of a stacktrace.
* 10- Because this library allows you to name your exceptions, you will automatically use an additional (1 * EXC_MAX_EXCEPTIONS) bytes of memory for the array of character pointers to store linkage to your exception strings, not counting whatever memory is used up by the actual string table for your strings. By default, EXC_MAX_EXCEPTIONS is set to 65535. You should probably leave it there, since you have no way of knowing how many exceptions a dependent library might use (and remember, compiler array bounds checking may not always save you here)... The 64k memory hit is, in this day and age, a pretty small price to pay for the verbosity provided. * 10- Because this library allows you to name your exceptions, you will automatically use an additional (1 * EXC_MAX_EXCEPTIONS) bytes of memory for the array of character pointers to store linkage to your exception strings, not counting whatever memory is used up by the actual string table for your strings. By default, EXC_MAX_EXCEPTIONS is set to 65535. You should probably leave it there, since you have no way of knowing how many exceptions a dependent library might use (and remember, compiler array bounds checking may not always save you here)... The 64k memory hit is, in this day and age, a pretty small price to pay for the verbosity provided.
* 11- You get an additional (sizeof(int) * EXC_MAX_EXCEPTIONS) in memory usage from the table of signals to match exceptions. When an uncaught exception rises to the top, it generates a signal action. By default, that signal is SIGKILL, but you can override it in the call to exclib_name_exception. * 11- You get an additional (sizeof(int) * EXC_MAX_EXCEPTIONS) in memory usage from the table of signals to match exceptions. When an uncaught exception rises to the top, it generates a signal action. By default, that signal is SIGKILL, but you can override it in the call to exclib_name_exception.
* 12- The underlying mechanism behind this is sigsetjmp / siglongjmp, two "arcane and slightly dangerous" functions. Essentially this is used to treat your *entire* codebase as something we can GOTO between when there's an exception. I don't think it will, but if this does funny things to your code, I'm sorry. * 12- The underlying mechanism behind this is setjmp / longjmp, two "arcane and slightly dangerous" functions. Essentially this is used to treat your *entire* codebase as something we can GOTO between when there's an exception. I don't think it will, but if this does funny things to your code, I'm sorry.
* 13- You should limit your involvement with this library to the all-capital #defines. The functions themselves are undocumented and may change without warning, and they were never meant for human consumption anyways. * 13- You should limit your involvement with this library to the all-capital #defines. The functions themselves are undocumented and may change without warning, and they were never meant for human consumption anyways.
* 14- There are exceptions to every rule, and the exception to #11 is that you can safely call "exclib_print_stacktrace", and that you MUST call "exclib_name_exception" if you want your exceptions to have pretty names in tracebacks, and not just numbers. * 14- There are exceptions to every rule, and the exception to #11 is that you can safely call "exclib_print_stacktrace", and that you MUST call "exclib_name_exception" if you want your exceptions to have pretty names in tracebacks, and not just numbers.
* 15- I haven't tried it, but this should be safe for C++ as well. Beware of mangled symbols in tracebacks. (C++ already has exception handling, you shouldn't need this there, I'm just saying it should work.) * 15- I haven't tried it, but this should be safe for C++ as well. Beware of mangled symbols in tracebacks. (C++ already has exception handling, you shouldn't need this there, I'm just saying it should work.)
* 16- It is impossible to THROW(0). A compile time error will occur complaining about duplicate case value. If by some miracle you do get it to compile, you will enter an infinite loop, as there is no exit path for this. * 16- It is impossible to THROW(0). A compile time error will occur complaining about duplicate case value. If by some miracle you do get it to compile, you will enter an infinite loop, as there is no exit path for this.
* 17- These are done as defines for a reason; at the time of TRY/THROW, a lot of stack information is saved to show the user (via traceback) where the exception handling occured. And besides, sigsetjmp/siglongjmp won't work if we use functions for this instead of defines, because the call stack will be different. * 17- These are done as defines for a reason; at the time of TRY/THROW, a lot of stack information is saved to show the user (via traceback) where the exception handling occured. And besides, setjmp/longjmp won't work if we use functions for this instead of defines, because the call stack will be different.
* *
* Model usage: * Model usage:
* TRY { * TRY {
@@ -102,10 +102,10 @@
#define TRY \ #define TRY \
if (__exc_curidx >= EXC_BT_FRAMES) \ if (__exc_curidx >= EXC_BT_FRAMES) \
exclib_print_stacktrace("No available exception stack context", __FILE__, (char *)__func__, __LINE__); \ exclib_print_exception_stack("No available exception stack context", __FILE__, (char *)__func__, __LINE__); \
if ( exclib_new_exc_frame(&__exc_statuses[__exc_curidx++], __FILE__, (char *)__func__, __LINE__) != 0) \ if ( exclib_new_exc_frame(&__exc_statuses[__exc_curidx++], __FILE__, (char *)__func__, __LINE__) != 0) \
exclib_print_stacktrace("Tried to TRY but couldn't create new exception frame", __FILE__, (char *)__func__, __LINE__); \ exclib_print_exception_stack("Tried to TRY but couldn't create new exception frame", __FILE__, (char *)__func__, __LINE__); \
EXC_STATUS_LIST->value = sigsetjmp(EXC_STATUS_LIST->buf, 1); \ EXC_STATUS_LIST->value = setjmp(EXC_STATUS_LIST->buf); \
EXC_STATUS_LIST->tried = 1;\ EXC_STATUS_LIST->tried = 1;\
switch( EXC_STATUS_LIST->value ) { \ switch( EXC_STATUS_LIST->value ) { \
case 0: case 0:
@@ -139,8 +139,9 @@ if ( EXC_STATUS_LIST->caught ) {
#define THROW_NONZERO(x, y, z) int rc = (x); if ( rc != 0 ) THROW(rc + y, z); #define THROW_NONZERO(x, y, z) int rc = (x); if ( rc != 0 ) THROW(rc + y, z);
#define THROW_ZERO(x, y, z) if ( (x) == 0 ) THROW(y, z); #define THROW_ZERO(x, y, z) if ( (x) == 0 ) THROW(y, z);
#define THROW(x, y) exclib_prep_throw(x, y, __FILE__, (char *)__func__, __LINE__); if ( EXC_STATUS_LIST ) { siglongjmp(EXC_STATUS_LIST->buf, x); } else { exclib_print_stacktrace("Unhandled Exception "#x"", __FILE__, (char *)__func__, __LINE__); }; #define THROW(x, y) exclib_prep_throw(x, y, __FILE__, (char *)__func__, __LINE__); if ( EXC_STATUS_LIST ) { longjmp(EXC_STATUS_LIST->buf, x); } else { exclib_print_exception_stack("Unhandled Exception "#x"", __FILE__, (char *)__func__, __LINE__); };
#define EXCLIB_TRACE(x) exclib_print_exception_stack(x, __FILE__, (char *)__func__, __LINE__)
struct exc_name_data { struct exc_name_data {
int exc; int exc;
@@ -183,9 +184,7 @@ extern void exclib_register_signals(void);
extern void exclib_prep_throw(int value, char *msg, char *file, char *func, int line); extern void exclib_prep_throw(int value, char *msg, char *file, char *func, int line);
extern void exclib_name_exception(int value, char *name, int signal); extern void exclib_name_exception(int value, char *name, int signal);
extern void exclib_bulk_name_exceptions(struct exc_name_data *exclib_exc_names, int size); extern void exclib_bulk_name_exceptions(struct exc_name_data *exclib_exc_names, int size);
extern void exclib_print_stacktrace(char *msg, char *file, char *func, int line); extern void exclib_print_exception_stack(char *mbuf, char *file, char *func, int line);
extern void exclib_print_exc_stacktrace(char *msg);
extern void exclib_print_exception_stack(char *file, char *func, int line);
extern int exclib_new_exc_frame(struct exc_status *es, char *file, char *function, int line); extern int exclib_new_exc_frame(struct exc_status *es, char *file, char *function, int line);
extern int exclib_clear_exc_frame(); extern int exclib_clear_exc_frame();