原文地址:https://monoinfinito.wordpress.com/2013/05/16/c-exceptions-under-the-hood-18-getting-the-right-stack-frame/
作者:nicolasbrailo
我们最新的personality函数知道它是否可以处理一个异常(假设每个try块仅有一条catch语句,并假设没有使用继承),不过要使得这个知识有用,我们首先需要检查我们可以处理的异常是否匹配抛出的异常。我们试着做一下。
当然,我们首先需要知道异常类型。要这样做,在调用__cxa_throw时,我们需要保存异常类型(这是ABI给予我们设置所有自定义数据的机会):
备注:你可以从我的github repo下载完整的源代码。
1 2 3 4 5 6 7 8 9 10 11 12 | void __cxa_throw(void* thrown_exception, std::type_info *tinfo, void (*dest)(void*)) { __cxa_exception *header = ((__cxa_exception *) thrown_exception - 1); // We need to save the type info in the exception header _Unwind_ will // receive, otherwise we won't be able to know it when unwinding header->exceptionType = tinfo; _Unwind_RaiseException(&header->unwindHeader); } |
现在我们可以在personality函数里读异常类型,很容易检查是否异常类型是否匹配(异常名是C++字符串,因此执行一次==足够了):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // Get the type of the exception we can handle const void* catch_type_info = lsda.types_table_start[ -1 * type_index ]; const std::type_info *catch_ti = (const std::type_info *) catch_type_info; // Get the type of the original exception being thrown __cxa_exception* exception_header = (__cxa_exception*)(unwind_exception+1) - 1; std::type_info *org_ex_type = exception_header->exceptionType; printf("%s thrown, catch handles %s\n", org_ex_type->name(), catch_ti->name()); // Check if the exception being thrown is of the same type // than the exception we can handle if (org_ex_type->name() != catch_ti->name()) continue; |
这里有新修改的完整源代码。当然如果我们加入这,将有问题(你能看出来吗)。如果在两阶段里抛出异常,并且我们说在第一阶段我们能处理它,那么我们不能在第二阶段再说我们不想要它。我不知道_Unwind_是否根据任何文档处理这个情形,但这最有可能要求未定义的行为,因此只是说我们将处理一切是不足够的。
因为我们给予了personality函数知道着陆垫是否可以处理要抛出异常的能力,我们一直欺骗_Unwind_我们可以处理的异常;即使在我们的ABI 9上我们说处理所有的异常,真相是我们不知道我们是否能够处理它。这很容易修改,我们可以像这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | _Unwind_Reason_Code __gxx_personality_v0 (...) { printf("Personality function, searching for handler\n"); // ... foreach (call site entry in lsda) { if (call site entry.not_good()) continue; // We found a landing pad for this exception; resume execution // If we are on search phase, tell _Unwind_ we can handle this one if (actions & _UA_SEARCH_PHASE) return _URC_HANDLER_FOUND; // If we are not on search phase then we are on _UA_CLEANUP_PHASE /* set everything so the landing pad can run */ return _URC_INSTALL_CONTEXT; } return _URC_CONTINUE_UNWIND; } |
同样,在我的github repo里获取项目的源代码。
好了,如果运行带有这个修改的personality函数我们会得到什么?失败了,这就是我们得到的!记得我们的抛出函数吗?这个应该捕捉我们的异常:
1 2 3 4 5 6 7 8 9 10 11 | void catchit() { try { try_but_dont_catch(); } catch(Fake_Exception&) { printf("Caught a Fake_Exception!\n"); } catch(Exception&) { printf("Caught an Exception!\n"); } printf("catchit handled the exception\n"); } |
不幸,我们的personality函数仅检查着陆垫可以处理的第一个类型。如果我们删除Fake_Exception块再尝试,我们得到另一位故事:最终,成功了!我们的personality函数现在可以在正确的帧里选择正确的catch,只要没有带有多个catch的try块。
下一次我们将进一步改进它。