Использование libunwind для реализации исключений - PullRequest
1 голос
/ 01 февраля 2020

Работая над компилятором, нужна помощь в понимании и работе с libunwind. Вот что у меня есть:

#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);
  }
}

Хорошо, это уже довольно грязно, но некоторый контекст может помочь оправдать странный выбор. То, что я хотел бы сделать, это вызвать throw_exception в любой точке стека вызовов после вызова set_try в foo, чтобы размотать стек и восстановить состояние процессора сразу после вызова set_try, но до условна. Хотя в настоящее время это всего лишь небольшая программа C, я намерен использовать общую структуру этих функций в компиляторе, который будет генерировать необходимые вызовы функций (аналогично тому, как выполняются исключения в C ++ с использованием g ++), поэтому У меня есть ярлыки + goto как способ быстро скопировать c сборку, которую я буду генерировать. Я пытался использовать реализацию setjmp в libunwind, но она недостаточно хорошо подходит для моего варианта использования.

Проблема, с которой я столкнулся, связана с тем, где unw_resume возобновляется после разматывания стека вызовов. printf("This should never run\n"), кажется, работает каждый раз, несмотря ни на что. Насколько я понимаю, он должен восстановить состояние стека и процессора до того, что было сохранено при вызове unw_getcontext, и я думаю, что сохраняемое состояние является правильным, поскольку значение регистра IP (или регистра P C) , поскольку это x86_64) точно так же в курсоре, когда я вызываю set_try и throw_exception. Я даже несколько раз заходил в gdb, чтобы посмотреть на регистр P C сразу после вызова set_try и перед условным, и он соответствовал выводу на печать каждый раз.

Мои вопросы:

  • Не понимаю ли я unw_resume?
  • Нужно ли изменять регистр P C (UNW_REG_IP)?
  • Есть ли где-то (кроме nongnu.org docs) еще можно обратиться за помощью по libunwind?

Заранее спасибо!

1 Ответ

0 голосов
/ 03 февраля 2020

Сначала хорошие новости: после исправлений ваша программа выдаст (что я предполагаю ожидаемый) вывод:

./a.out
In foo
foo+0x31  IP 557615f273a1
In bar
foo+0x31  IP 557615f273a1
foo+0x31  IP 557615f273a1
EXCEPTION: Throwing an exception in bar
Leaving foo

Теперь плохие новости: есть несколько отдельных проблем с вашей исходной программой:

  1. Контекст (состояние машины), который использовался для инициализации курсора, должен оставаться действительным в течение времени, в течение которого используется курсор. Это явно прописано в справочной странице unw_init_local .

    Нарушение этого правила привело к тому, что ваша программа SIGSEGV на моем компьютере во время вызова unw_resume.

  2. unw_step обновляет курсор , но не контекст, и именно последний фактически используется для восстановления состояния компьютера в unw_resume.

    Это может быть Более понятное в документации unw_resume .

Чтобы исправить проблему 1, просто переместите unw_context_t в exception_stack_st, например, так:

struct exception_stack_st {
  unw_context_t context;
  unw_cursor_t cursor;
  exception_stack_t prev;
};

И инициализировать их вместе:

  exception_stack_t cb = malloc(sizeof(*cb));
  unw_getcontext(&cb->context);
  unw_init_local(&cb->cursor, &cb->context);

Чтобы решить проблему 2, вам нужно установить sh контекст машины в кадре, в который вы намереваетесь вернуться .

Для этого требуется либо превратить set_try в макрос, либо всегда встроенную функцию и избавиться от unw_step.

...