Working on a compiler, need some assistance understanding and working with libunwind. Here's what I have so far:
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
typedef void *data_t;
typedef struct exception_stack_st *exception_stack_t;
struct exception_stack_st {
unw_cursor_t catch_block;
exception_stack_t prev;
};
/* PROTOTYPES */
void foo(void);
void bar(void);
void set_try(void);
void clear_try(void);
bool check_exception(void);
void throw_exception(data_t);
void print_backtrace();
/* GLOBALS */
exception_stack_t exception_stack = NULL;
data_t curr_exception = NULL;
int main(void) {
foo();
}
void foo(void) {
printf("In foo\n");
set_try();
if(check_exception()) {
printf("EXCEPTION: %s\n", (char*)curr_exception);
goto CATCH;
}
bar();
printf("This should never run\n");
CATCH:
clear_try();
printf("Leaving foo\n");
}
void bar(void) {
printf("In bar\n");
throw_exception("Throwing an exception in bar");
printf("Leaving bar\n");
}
void set_try(void) {
unw_cursor_t cursor;
unw_context_t context;
unw_word_t ip, offp;
char buf[1024];
unw_getcontext(&context);
unw_init_local(&cursor, &context);
unw_step(&cursor);
unw_get_reg(&cursor, UNW_REG_IP, &ip);
unw_get_proc_name(&cursor, buf, 1024, &offp);
printf("%s+0x%lx IP %lx\n", buf, offp, ip);
exception_stack_t cb = malloc(sizeof(struct exception_stack_st));
cb->catch_block = cursor;
cb->prev = exception_stack;
exception_stack = cb;
}
void clear_try(void) {
if (exception_stack != NULL)
exception_stack = exception_stack->prev;
curr_exception = NULL;
}
void throw_exception(data_t exception) {
unw_word_t ip, offp;
char buf[1024];
curr_exception = exception;
unw_get_reg(&(exception_stack->catch_block), UNW_REG_IP, &ip);
unw_get_proc_name(&(exception_stack->catch_block), buf, 1024, &offp);
printf("%s+0x%lx IP %lx\n", buf, offp, ip);
unw_resume(&(exception_stack->catch_block));
printf("PANIC: unw_resume returned.\n");
exit(1);
}
bool check_exception(void) {
return curr_exception != NULL;
}
void print_backtrace() {
unw_cursor_t cursor;
unw_context_t context;
char buf[1024];
unw_getcontext(&context);
unw_init_local(&cursor, &context);
printf("BACKTRACE:\n");
while(unw_step(&cursor) > 0) {
unw_get_proc_name(&cursor, buf, 1024, NULL);
printf("%s\n", buf);
}
}
Alright, this is already pretty messy, but some context might help justify the weird choices. What I would like to do is call throw_exception
at any point down the call stack after calling set_try
in foo
in order to unwind the stack and restore the CPU state to right after the call to set_try
but before the conditional. While this is currently just a small C program, I'm intending on using the general structure of these functions in a compiler that will generate the function calls necessary (similar to how exceptions are done in C++ using g++), which is why I have the labels+goto as a way to quickly mimic the assembly I would be generating. I've tried using libunwind's setjmp implementation, but it doesn't quite fit my use case well enough.
The issue I'm having has to do with where unw_resume
resumes after unwinding the call stack. The printf("This should never run\n")
seems to run every time no matter what. My understanding is that it should restore the stack and CPU state to whatever was stored when the call to unw_getcontext
was made, and I think the state that gets stored is correct because the value of the IP register (or PC register, since this is x86_64) is exactly the same in the cursor when I call set_try
and throw_exception
. I've even jumped into gdb a number of times to look at the PC register right after the call to set_try and before the conditional, and it matched the printed output every time.
My questions are:
- Am I misunderstanding
unw_resume
? - Do I need to be modifying the PC (UNW_REG_IP) register?
- Is there somewhere (aside from nongnu.org docs) else I can look to for help with libunwind?
Thanks in advance!