diff options
Diffstat (limited to 'lib/debugger/src/dbg_icmd.erl')
-rw-r--r-- | lib/debugger/src/dbg_icmd.erl | 482 |
1 files changed, 482 insertions, 0 deletions
diff --git a/lib/debugger/src/dbg_icmd.erl b/lib/debugger/src/dbg_icmd.erl new file mode 100644 index 0000000000..7ccb9793a3 --- /dev/null +++ b/lib/debugger/src/dbg_icmd.erl @@ -0,0 +1,482 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(dbg_icmd). + +%% Internal command receiver/handler +-export([cmd/3]). + +%% User control of process execution and settings +-export([step/1, next/1, continue/1, finish/1, skip/1, timeout/1, + stop/1]). +-export([eval/2]). +-export([set/3, get/3]). +-export([handle_msg/4]). + +%% Library functions for attached process handling +-export([tell_attached/1]). + +%% get_binding/2 +-export([get_binding/2]). + +-include("dbg_ieval.hrl"). + +%%==================================================================== +%% Internal command receiver/handler +%%==================================================================== + +%%-------------------------------------------------------------------- +%% cmd(Expr, Bs, Ieval) -> {skip, Bs} | Bs +%% This function is called from dbg_ieval before evaluating any +%% expression to give the user the chance to inspect variables etc. +%% get(next_break) => break | running +%% | Le +%% specifies if the process should break. +%%-------------------------------------------------------------------- + +%% Common Test adaptation +cmd({call_remote,0,ct_line,line,_As}, Bs, _Ieval) -> + Bs; + +cmd(Expr, Bs, Ieval) -> + cmd(Expr, Bs, get(next_break), Ieval). + +%% Evaluation should break +cmd(Expr, Bs, break, Ieval) -> + break(Expr, Bs, Ieval); +%% Evaluation should continue, unless there is a breakpoint at +%% the current line +cmd(Expr, Bs, running, #ieval{level=Le,module=M}=Ieval) -> + Line = element(2, Expr), + case break_p(M, Line, Le, Bs) of + true -> + put(next_break, break), + break(Expr, Bs, Ieval); + false -> + handle_cmd(Bs, running, Ieval) + end; +%% Evaluation should continue for now (until we've returned to +%% call level Next) +cmd(Expr, Bs, Next, #ieval{level=Le}=Ieval) when is_integer(Next), + Next<Le -> + Line = element(2, Expr), + handle_cmd(Bs, Next, Ieval#ieval{line=Line}); +%% Evaluation has returned to call level Next, break +cmd(Expr, Bs, Next, #ieval{level=Le}=Ieval) when is_integer(Next), + Next>=Le -> + put(next_break, break), + break(Expr, Bs, Ieval). + +%% break_p(Mod, Line, Le, Bs) -> true | false +%% Checks if there is a breakpoint at Line in Mod. +%% As a side effect, disables or deletes breakpoint as specified +break_p(Mod, Line, Le, Bs) -> + case lists:keysearch({Mod, Line}, 1, get(breakpoints)) of + {value, {_Point, [active, Action, _, Cond]}} -> + case get(user_eval) of + [{Line, Le}|_] -> false; + _ -> + Bool = case Cond of + null -> true; + {CM, CN} -> + try apply(CM, CN, [Bs]) of + true -> true; + false -> false; + _Term -> false + catch + _C:_R -> false + end + end, + if + Bool -> + case Action of + enable -> ignore; + disable -> + dbg_iserver:cast(get(int), + {break_option, + {Mod, Line}, + status, + inactive}); + delete -> + dbg_iserver:cast(get(int), + {delete_break, + {Mod, Line}}) + end; + true -> ignore + end, + Bool + end; + _Other -> % {value, {_Point, [inactive|_]}} | false + false + end. + +%% Called whenever evaluation enters break mode, informs attached +%% process and dbg_iserver +break(Expr, Bs, #ieval{level=Le,module=M}=Ieval) -> + Line = element(2, Expr), + dbg_iserver:cast(get(int), {set_status,self(),break,{M,Line}}), + tell_attached({break_at,M,Line,Le}), + handle_cmd(Bs, break, Ieval#ieval{line=Line}). + +%%-------------------------------------------------------------------- +%% handle_cmd(Bs0, Status, Ieval) -> Bs1 | {skip, Bs1} +%% Status = break | running | Le +%% In break mode, loop waiting for user commands (and handle other +%% messages meanwhile). +%% In other modes, handle other messages, if any. +%%-------------------------------------------------------------------- +handle_cmd(Bs, break, #ieval{level=Le}=Ieval) -> + receive + {user, {cmd, Cmd}} -> + dbg_iserver:cast(get(int), {set_status,self(),running,{}}), + tell_attached(running), + case Cmd of + step -> Bs; + next -> put(next_break, Le), Bs; + continue -> put(next_break, running), Bs; + finish -> put(next_break, Le-1), Bs; + skip -> {skip, Bs} + end; + {user, {eval, Cmd}} -> + Bs1 = eval_nonrestricted(Cmd, Bs, Ieval), + handle_cmd(Bs1, break, Ieval); + Msg -> + dbg_ieval:check_exit_msg(Msg, Bs, Ieval), + handle_msg(Msg, break, Bs, Ieval), + handle_cmd(Bs, break, Ieval) + end; +handle_cmd(Bs, Status, Ieval) -> + receive + Msg -> + dbg_ieval:check_exit_msg(Msg, Bs, Ieval), + handle_msg(Msg, Status, Bs, Ieval), + handle_cmd(Bs, Status, Ieval) + after 0 -> + Bs + end. + +%%==================================================================== +%% User control of process execution and settings +%%==================================================================== + +step(Meta) -> Meta ! {user, {cmd, step}}. +next(Meta) -> Meta ! {user, {cmd, next}}. +continue(Meta) -> Meta ! {user, {cmd, continue}}. +finish(Meta) -> Meta ! {user, {cmd, finish}}. +skip(Meta) -> Meta ! {user, {cmd, skip}}. + +timeout(Meta) -> Meta ! {user, timeout}. + +stop(Meta) -> Meta ! {user, {cmd, stop}}. + +eval(Meta, {Mod, Cmd}) -> + eval(Meta, {Mod, Cmd, nostack}); +eval(Meta, {Mod, Cmd, SP}) -> + Cmd2 = case lists:reverse(Cmd) of % Commands must end with ".\n" + [10,$.|_] -> Cmd; + [10|T] -> lists:reverse([10,$.|T]); + [$.|T] -> lists:reverse([10,$.|T]); + T -> lists:reverse([10,$.|T]) + end, + Meta ! {user, {eval, {self(), Mod, Cmd2, SP}}}. + +%% Tag Args +%% --- ---- +%% trace Bool +%% stack_trace Flag +set(Meta, Tag, Args) -> + Meta ! {user, {set, Tag, Args}}. + +%% Tag Args Reply +%% --- ---- ----- +%% bindings nostack | SP [{Var,Val}] (Var=atom(), Val=term()) +%% stack_frame {up|down, SP} [{Le,Where,Bs}] | top | bottom +%% (Where = {Mod,Li} +%% messages null [Msg] (Msg=term()) +%% backtrace all | N [{Le,MFA}] (MFA={M,F,Args}|{Fun,Args}) +get(Meta, Tag, Args) -> + Meta ! {user, {get, Tag, self(), Args}}, + receive + {Meta, Tag, Reply} -> Reply + end. + +%%-------------------------------------------------------------------- +%% handle_msg({int, Msg} | {user, Msg}, Status, Bs, Ieval) +%% Status = idle | exit_at | wait_at | wait_after_at +%% | break | running | Le | {Le,MFA} +%%-------------------------------------------------------------------- +handle_msg({int, Msg}, Status, Bs, Ieval) -> + handle_int_msg(Msg, Status, Bs, Ieval); +handle_msg({user, Msg}, Status, Bs, Ieval) -> + handle_user_msg(Msg, Status, Bs, Ieval); +handle_msg(Msg, Status, Bs, Ieval) -> + io:format("***WARNING*** Unexp msg ~p, info ~p~n", + [Msg,{Status,Bs,Ieval}]). + +%% handle_int_msg(Msg, Status, Bs, Ieval) +%% Msg = {attached, AttPid} | {detached, AttPid} +%% | {old_code, Mod} +%% | {new_break, Break} | {delete_break, Break} +%% | {break_options, {Break, Options}} +%% | no_break | {no_break, Mod} +%% | stop (only when Status==exit_at, means AttPid has terminated) +%% Interpreter internal messages (from dbg_iserver) +handle_int_msg({attached, AttPid}, Status, _Bs, + #ieval{level=Le,module=M,line=Line}) -> + + %% Update process dictionary + put(attached, AttPid), + put(next_break, break), + + %% Tell attached process in which module evalution is located + if + Le==1 -> + tell_attached({attached, undefined, -1, get(trace)}); + true -> + tell_attached({attached, M, Line, get(trace)}), + + %% Give info about status and call level as well + %% In this case, Status can not be exit_at + Msg = case Status of + idle -> {func_at,M,Line,Le}; + break -> {break_at,M,Line,Le}; + wait_at -> {wait_at,M,Line,Le}; + wait_after_at -> {wait_after_at,M,Line,Le}; + _ -> running % running | Le | {Le,MFA} + end, + tell_attached(Msg) + end; +handle_int_msg(detached, _Status, _Bs, _Ieval) -> + %% Update process dictionary + put(attached, undefined), + put(next_break, running), + put(trace, false); % no need for tracing if there is no AttPid +handle_int_msg({old_code,Mod}, Status, Bs, + #ieval{level=Le,module=M}=Ieval) -> + if + Status==idle, Le==1 -> + erase([Mod|db]), + put(cache, []); + true -> + case dbg_ieval:in_use_p(Mod, M) of + true -> + %% A call to Mod is on the stack (or might be), + %% so we must terminate. + exit(get(self), kill), + dbg_ieval:exception(exit, old_code, Bs, Ieval); + false -> + erase([Mod|db]), + put(cache, []) + end + end; +handle_int_msg({new_break, Break}, _Status, _Bs, _Ieval) -> + put(breakpoints, [Break | get(breakpoints)]); +handle_int_msg({delete_break, Point}, _Status, _Bs, _Ieval) -> + put(breakpoints, lists:keydelete(Point, 1, get(breakpoints))); +handle_int_msg({break_options, Break}, _Status, _Bs, _Ieval) -> + {Point, _Options} = Break, + put(breakpoints, lists:keyreplace(Point,1,get(breakpoints), Break)); +handle_int_msg(no_break, _Status, _Bs, _Ieval) -> + put(breakpoints, []); +handle_int_msg({no_break,M}, _Status, _Bs, _Ieval) -> + put(breakpoints, [ML || {Mod,_L}=ML <- get(breakpoints), Mod=/=M]); +handle_int_msg(stop, exit_at, _Bs, _Ieval) -> + erlang:exit(normal). + +%% handle_user_msg(Msg, Status, Bs, Ieval) +%% Msg = {cmd, Cmd}, Cmd = step | next | continue | finish| skip| stop +%% | timeout +%% | {eval, {Pid, Mod, Str, SP}} +%% | {set, Tag, Args} | {get, Tag, Pid, Args} +%% Messages from the attached process +%% Msg = {cmd, Cmd}, Cmd /= stop, can only be received in break mode, +%% handled in handle_cmd/3 +%% Msg = timeout is handled when needed (when evaluating receive..after) +%% in dbg_ieval:do_receive/5 when Status==wait_after_at +%% For all other Status, it should be ignored +handle_user_msg({cmd, stop}, Status, _Bs, _Ieval) -> + case lists:member(Status, [running, wait_at, wait_after_at]) of + true -> + put(next_break, break); + false when is_integer(Status); is_tuple(Status) -> + put(next_break, break); + false -> % idle | exit_at (| break) + ignore + end; +handle_user_msg({cmd, continue}, Status, _Bs, _Ieval) -> + %% Allow leaving break mode when waiting in a receive + case lists:member(Status, [wait_at, wait_after_at]) of + true -> + put(next_break, running); + false -> + ignore + end; +handle_user_msg({cmd, _Cmd}, _Status, _Bs, _Ieval) -> + ignore; +handle_user_msg(timeout, _Status, _Bs, _Ieval) -> + ignore; +handle_user_msg({eval,Cmd}, wait_at, Bs, _Ieval) -> + eval_restricted(Cmd, Bs); +handle_user_msg({eval,Cmd}, wait_after_at, Bs, _Ieval) -> + eval_restricted(Cmd, Bs); +handle_user_msg({set,trace,Bool}, _Status, _Bs, _Ieval) -> + put(trace, Bool), + tell_attached({trace, Bool}); +handle_user_msg({set,stack_trace,Flag}, _Status, _Bs, _Ieval) -> + set_stack_trace(Flag); +handle_user_msg({get,bindings,From,SP}, _Status, Bs, _Ieval) -> + reply(From, bindings, bindings(Bs, SP)); +handle_user_msg({get,stack_frame,From,{Dir,SP}}, _Status, _Bs,_Ieval) -> + reply(From, stack_frame, dbg_ieval:stack_frame(Dir, SP)); +handle_user_msg({get,messages,From,_}, _Status, _Bs, _Ieval) -> + reply(From, messages, messages()); +handle_user_msg({get,backtrace,From,N}, _Status, _Bs, _Ieval) -> + reply(From, backtrace, dbg_ieval:backtrace(N)). + +set_stack_trace(true) -> + set_stack_trace(all); +set_stack_trace(Flag) -> + if + Flag==false -> + put(stack, []); + Flag==no_tail; Flag==all -> + ignore + end, + put(trace_stack, Flag), + tell_attached({stack_trace, Flag}). + +reply(From, Tag, Reply) -> + From ! {self(), Tag, Reply}. + +bindings(Bs, nostack) -> + Bs; +bindings(Bs, SP) -> + case dbg_ieval:stack_level() of + Le when SP>Le -> + Bs; + _ -> + dbg_ieval:bindings(SP) + end. + +messages() -> + {messages, Msgs} = erlang:process_info(get(self), messages), + Msgs. + + +%%==================================================================== +%% Evaluating expressions within process context +%%==================================================================== + +eval_restricted({From,_Mod,Cmd,SP}, Bs) -> + case catch parse_cmd(Cmd, 1) of + {'EXIT', _Reason} -> + From ! {self(), {eval_rsp, 'Parse error'}}; + [{var,_,Var}] -> + Bs2 = bindings(Bs, SP), + Res = case get_binding(Var, Bs2) of + {value, Value} -> Value; + unbound -> unbound + end, + From ! {self(), {eval_rsp, Res}}; + _Forms -> + Rsp = 'Only possible to inspect variables', + From ! {self(), {eval_rsp, Rsp}} + end. + +eval_nonrestricted({From,Mod,Cmd,SP}, Bs, #ieval{level=Le}) when SP<Le-> + %% Evaluate in stack + eval_restricted({From, Mod, Cmd, SP}, Bs), + Bs; +eval_nonrestricted({From, _Mod, Cmd, _SP}, Bs, + #ieval{level=Le,module=M,line=Line}=Ieval) -> + case catch parse_cmd(Cmd, Line) of + {'EXIT', _Reason} -> + From ! {self(), {eval_rsp, 'Parse error'}}, + Bs; + Forms -> + mark_running(Line, Le), + {Res, Bs2} = + lists:foldl(fun(Expr, {_Res, Bs0}) -> + eval_nonrestricted_1(Expr,Bs0,Ieval) + end, + {null, Bs}, + Forms), + mark_break(M, Line, Le), + From ! {self(), {eval_rsp, Res}}, + Bs2 + end. + +eval_nonrestricted_1({match,_,{var,_,Var},Expr}, Bs, Ieval) -> + {value,Res,Bs2} = + dbg_ieval:eval_expr(Expr, Bs, Ieval#ieval{last_call=false}), + Bs3 = case lists:keysearch(Var, 1, Bs) of + {value, {Var,_Value}} -> + lists:keyreplace(Var, 1, Bs2, {Var,Res}); + false -> [{Var,Res} | Bs2] + end, + {Res,Bs3}; +eval_nonrestricted_1({var,_,Var}, Bs, _Ieval) -> + Res = case lists:keysearch(Var, 1, Bs) of + {value, {Var, Value}} -> Value; + false -> unbound + end, + {Res,Bs}; +eval_nonrestricted_1(Expr, Bs, Ieval) -> + {value,Res,Bs2} = + dbg_ieval:eval_expr(Expr, Bs, Ieval#ieval{last_call=false}), + {Res,Bs2}. + +mark_running(LineNo, Le) -> + put(next_break, running), + put(user_eval, [{LineNo, Le} | get(user_eval)]), + dbg_iserver:cast(get(int), {set_status, self(), running, {}}), + tell_attached(running). + +mark_break(Cm, LineNo, Le) -> + put(next_break, break), + put(user_eval, tl(get(user_eval))), + tell_attached({break_at, Cm, LineNo, Le}), + dbg_iserver:cast(get(int), {set_status,self(),break,{Cm,LineNo}}). + +parse_cmd(Cmd, LineNo) -> + {ok,Tokens,_} = erl_scan:string(Cmd, LineNo), + {ok,Forms} = erl_parse:parse_exprs(Tokens), + Forms. + + +%%==================================================================== +%% Library functions for attached process handling +%%==================================================================== + +tell_attached(Msg) -> + case get(attached) of + undefined -> ignore; + AttPid -> + AttPid ! {self(), Msg} + end. + + +%%==================================================================== +%% get_binding/2 +%%==================================================================== + +get_binding(Var, Bs) -> + case lists:keysearch(Var, 1, Bs) of + {value, {Var, Value}} -> {value, Value}; + false -> unbound + end. |