aboutsummaryrefslogtreecommitdiffstats
path: root/lib/debugger/src/dbg_icmd.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/debugger/src/dbg_icmd.erl')
-rw-r--r--lib/debugger/src/dbg_icmd.erl482
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.