【问题标题】:Using libunwind for implementing exceptions使用 libunwind 实现异常
【发布时间】:2020-02-01 06:48:01
【问题描述】:

在编译器上工作,需要一些帮助来理解和使用 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);
  }
}

好吧,这已经很混乱了,但是一些上下文可能有助于证明这些奇怪的选择是正确的。我想做的是在foo 中调用set_try 之后的任何点调用throw_exception,以便展开堆栈并将CPU 状态恢复到调用set_try 之后但之前有条件的。虽然这目前只是一个小型 C 程序,但我打算在编译器中使用这些函数的一般结构,以生成必要的函数调用(类似于在 C++ 中使用 g++ 完成异常的方式),这就是为什么我有label+goto 作为一种快速模仿我将要生成的程序集的方法。我试过使用 libunwind 的 setjmp 实现,但它不太适合我的用例。

我遇到的问题与展开调用堆栈后unw_resume 的恢复位置有关。无论如何,printf("This should never run\n") 似乎每次都会运行。我的理解是它应该将堆栈和 CPU 状态恢复到调用unw_getcontext 时存储的任何状态,并且我认为存储的状态是正确的,因为 IP 寄存器(或 PC 寄存器,因为这is x86_64) 在我调用set_trythrow_exception 时在光标中完全相同。我什至在调用 set_try 之后和条件之前多次跳入 gdb 来查看 PC 寄存器,并且每次都匹配打印输出。

我的问题是:

  • 我是不是误会unw_resume
  • 我需要修改 PC (UNW_REG_IP) 寄存器吗?
  • 还有其他地方(除了 nongnu.org 文档)我可以寻求有关 libunwind 的帮助吗?

提前致谢!

【问题讨论】:

  • “我遇到的问题与...有关”——您忘记描述实际问题。您已经(某种程度上)描述了您预期会发生什么,但实际上会发生什么?
  • 好的,我已经更新了问题。问题是每次我执行程序时都会运行 `printf("This line should never run\n"),即使它应该被跳过。

标签: c exception stack-unwinding libunwind


【解决方案1】:

首先是好消息:修复后,您的程序会产生(我认为预期的)输出:

./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 man page 中有明确说明。

    unw_resume 调用期间,违反此规则导致您的程序在我的机器上执行 SIGSEGV。

  2. unw_step更新了游标,但不更新上下文,在unw_resume中实际用于恢复机器状态的是后者。

    这可以在unw_resume documentation 中更清楚地说明。

要解决问题 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,您需要在您打算返回的框架中建立机器上下文

这需要将set_try 转换为宏或始终内联的函数,并去掉unw_step

【讨论】:

  • 太棒了!我会试一试,非常感谢!
猜你喜欢
  • 1970-01-01
  • 2010-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多