1> catch 1+2. 3 2> catch 1+a. {'EXIT',{badarith,[...]}} 3> catch throw(hello). hello
-module(test).
-compile(export_all).
t() ->
catch erlang:now().
|
{function, t, 0, 2}.
{label,1}.
{line,[{location,"test.erl",4}]}.
{func_info,{atom,test},{atom,t},0}.
{label,2}.
{allocate,1,0}.
{'catch',{y,0},{f,3}}.
{line,[{location,"test.erl",5}]}.
{call_ext,0,{extfunc,erlang,now,0}}.
{label,3}.
{catch_end,{y,0}}.
{deallocate,1}.
return.
|
1> c(test).
{ok,test}
2> erts_debug:df(test).
ok
|
04B55938: i_func_info_IaaI 0 test t 0
04B5594C: allocate_tt 1 0
04B55954: catch_yf y(0) f(0000871B)
04B55960: call_bif_e erlang:now/0
04B55968: catch_end_y y(0)
04B55970: deallocate_return_Q 1
|
// beam_emu.c OpCase(catch_yf): c_p->catches++; // catches数量加1 yb(Arg(0)) = Arg(1); // 把catch指针地址存入进程栈,即f(0000871B) Next(2); // 执行下一条指令
// beam_emu.c OpCase(catch_end_y): { c_p->catches--; // 进程 catches数减1 make_blank(yb(Arg(0))); // 将catch立即数的值置NIL,数据将会丢掉 if (is_non_value(r(0))) { // 如果异常出现 if (x(1) == am_throw) { // 如果是 throw(Term),返回 Term r(0) = x(2); } else { if (x(1) == am_error) { // 如果是 error(Term), 再带上当前堆栈的信息 SWAPOUT; x(2) = add_stacktrace(c_p, x(2), x(3)); SWAPIN; } /* only x(2) is included in the rootset here */ if (E - HTOP < 3 || c_p->mbuf) { /* Force GC in case add_stacktrace() * created heap fragments */ // 检查进程堆空间不足,执行gc避免出现堆外数据 SWAPOUT; PROCESS_MAIN_CHK_LOCKS(c_p); FCALLS -= erts_garbage_collect(c_p, 3, reg+2, 1); ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p); PROCESS_MAIN_CHK_LOCKS(c_p); SWAPIN; } r(0) = TUPLE2(HTOP, am_EXIT, x(2)); HTOP += 3; } } CHECK_TERM(r(0)); Next(1); // 执行下一条指令 }
//beam_hot.h OpCase(deallocate_return_Q): { DeallocateReturn(Arg(0));//释放分配的栈空间,返回上一个CP指令地址(注:CP是返回地址指针) }DeallocateReturn实际是个宏,代码如下:
#define DeallocateReturn(Deallocate) \ do { \ int words_to_pop = (Deallocate); \ SET_I((BeamInstr *) cp_val(*E)); \ // 解析当前栈的指令地址,即获取上一个CP指令地址 E = ADD_BYTE_OFFSET(E, words_to_pop); \ CHECK_TERM(r(0)); \ Goto(*I); \//执行的指令 } while (0)
// erl_term.h #define make_catch(x) (((x) << _TAG_IMMED2_SIZE) | _TAG_IMMED2_CATCH) // 转成catch立即树 #define is_catch(x) (((x) & _TAG_IMMED2_MASK) == _TAG_IMMED2_CATCH) // 是否catch立即数这两个是 catch立即数的生成和判定,后面的代码会提到这两个宏的使用。
// beam_load.c 加载beam过程,有删节 static void final_touch(LoaderState* stp) { int i; int on_load = stp->on_load; unsigned catches; Uint index; BeamInstr* code = stp->code; Module* modp; /* * 申请catch索引,填补catch_yf指令 * 前面的f(0000871B)就在这里产生的,指向了beam_catches结构数据 * 因为一个catch立即数放不了整个beam_catches数据,就只放了指针 */ index = stp->catches; catches = BEAM_CATCHES_NIL; while (index != 0) { //遍历所有的catch_yf指令 BeamInstr next = code[index]; code[index] = BeamOpCode(op_catch_yf); // 指向catch_yf指令的opcode地址 // 获取 catch_end 指令地址,构造beam_catches结构数据 catches = beam_catches_cons((BeamInstr *)code[index+2], catches); code[index+2] = make_catch(catches); // 将beam_catches索引位置转成 catch立即数 index = next; } modp = erts_put_module(stp->module); modp->curr.catches = catches; /* * .... */ }再来看下什么时候会执行到 这里的代码。
// 执行匿名函数 OpCase(i_apply_fun): { BeamInstr *next; SWAPOUT; next = apply_fun(c_p, r(0), x(1), reg); SWAPIN; if (next != NULL) { r(0) = reg[0]; SET_CP(c_p, I+1); SET_I(next); Dispatchfun(); } goto find_func_info; // 遇到错误走这里 } // 数学运算错误,或检查错误就会走这里 lb_Cl_error: { if (Arg(0) != 0) { // 如果带了 label地址,就执行 jump指令 OpCase(jump_f): { // 这里就是 jump实现代码 jump_f: SET_I((BeamInstr *) Arg(0)); Goto(*I); } } ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue)); goto find_func_info; // 遇到错误走这里 } // 等待消息超时 OpCase(i_wait_error): { c_p->freason = EXC_TIMEOUT_VALUE; goto find_func_info; // 遇到错误走这里 }好了,再看下 find_func_info 究竟是什么神通?
/* Fall through here */ find_func_info: { reg[0] = r(0); SWAPOUT; I = handle_error(c_p, I, reg, NULL); // 获取异常错误指令地址 goto post_error_handling; } post_error_handling: if (I == 0) { // 等待下次调度 erl_exit(),抛出异常中断 goto do_schedule; } else { r(0) = reg[0]; ASSERT(!is_value(r(0))); if (c_p->mbuf) { // 存在堆外消息数据,执行gc erts_garbage_collect(c_p, 0, reg+1, 3); } SWAPIN; Goto(*I); // 执行指令 } }然后,简单看下 handle_error函数。
// erl_emu.c VM处理异常函数 static BeamInstr* handle_error(Process* c_p, BeamInstr* pc, Eterm* reg, BifFunction bf) { Eterm* hp; Eterm Value = c_p->fvalue; Eterm Args = am_true; c_p->i = pc; /* In case we call erl_exit(). */ ASSERT(c_p->freason != TRAP); /* Should have been handled earlier. */ /* * Check if we have an arglist for the top level call. If so, this * is encoded in Value, so we have to dig out the real Value as well * as the Arglist. */ if (c_p->freason & EXF_ARGLIST) { Eterm* tp; ASSERT(is_tuple(Value)); tp = tuple_val(Value); Value = tp[1]; Args = tp[2]; } /* * Save the stack trace info if the EXF_SAVETRACE flag is set. The * main reason for doing this separately is to allow throws to later * become promoted to errors without losing the original stack * trace, even if they have passed through one or more catch and * rethrow. It also makes the creation of symbolic stack traces much * more modular. */ if (c_p->freason & EXF_SAVETRACE) { save_stacktrace(c_p, pc, reg, bf, Args); } /* * Throws that are not caught are turned into 'nocatch' errors */ if ((c_p->freason & EXF_THROWN) && (c_p->catches <= 0) ) { hp = HAlloc(c_p, 3); Value = TUPLE2(hp, am_nocatch, Value); c_p->freason = EXC_ERROR; } /* Get the fully expanded error term */ Value = expand_error_value(c_p, c_p->freason, Value); /* Save final error term and stabilize the exception flags so no further expansion is done. */ c_p->fvalue = Value; c_p->freason = PRIMARY_EXCEPTION(c_p->freason); /* Find a handler or die */ if ((c_p->catches > 0 || IS_TRACED_FL(c_p, F_EXCEPTION_TRACE)) && !(c_p->freason & EXF_PANIC)) { BeamInstr *new_pc; /* The Beam handler code (catch_end or try_end) checks reg[0] for THE_NON_VALUE to see if the previous code finished abnormally. If so, reg[1], reg[2] and reg[3] should hold the exception class, term and trace, respectively. (If the handler is just a trap to native code, these registers will be ignored.) */ reg[0] = THE_NON_VALUE; reg[1] = exception_tag[GET_EXC_CLASS(c_p->freason)]; reg[2] = Value; reg[3] = c_p->ftrace; if ((new_pc = next_catch(c_p, reg))) { // 从进程栈上找到最近的 catch c_p->cp = 0; /* To avoid keeping stale references. */ return new_pc; // 返回 catch end 指令地址 } if (c_p->catches > 0) erl_exit(1, "Catch not found"); } ERTS_SMP_UNREQ_PROC_MAIN_LOCK(c_p); terminate_proc(c_p, Value); ERTS_SMP_REQ_PROC_MAIN_LOCK(c_p); return NULL; // 返回0,就是执行 erl_exit() }
t() ->
try
do_something(),
t()
catch
_:_ -> ok
end.
|
hibernate(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
erlang:hibernate(?MODULE, wake_up, [M, F, A]).
wake_up(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
try
apply(M, F, A)
catch
_Class:Reason -> exit(Reason)
end.
|