diff options
Diffstat (limited to 'lib/debugger/src')
42 files changed, 16429 insertions, 0 deletions
diff --git a/lib/debugger/src/Makefile b/lib/debugger/src/Makefile new file mode 100644 index 0000000000..8551fe887d --- /dev/null +++ b/lib/debugger/src/Makefile @@ -0,0 +1,136 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1997-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% +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(DEBUGGER_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/debugger-$(VSN) + +# ---------------------------------------------------- +# Common Macros +# ---------------------------------------------------- + +MODULES= \ + debugger \ + i \ + int \ + dbg_debugged \ + dbg_icmd \ + dbg_idb \ + dbg_ieval \ + dbg_iload \ + dbg_iserver \ + dbg_ui_break \ + dbg_ui_break_win \ + dbg_ui_edit \ + dbg_ui_edit_win \ + dbg_ui_filedialog_win \ + dbg_ui_interpret \ + dbg_ui_mon \ + dbg_ui_mon_win \ + dbg_ui_settings \ + dbg_ui_trace \ + dbg_ui_trace_win \ + dbg_ui_view \ + dbg_ui_win \ + dbg_ui_winman \ + dbg_wx_break \ + dbg_wx_break_win \ + dbg_wx_code \ + dbg_wx_filedialog_win \ + dbg_wx_interpret \ + dbg_wx_mon \ + dbg_wx_mon_win \ + dbg_wx_settings \ + dbg_wx_src_view \ + dbg_wx_trace \ + dbg_wx_trace_win \ + dbg_wx_view \ + dbg_wx_win \ + dbg_wx_winman + + +HRL_FILES= + +INTERNAL_HRL_FILES= dbg_ieval.hrl + +ERL_FILES= $(MODULES:%=%.erl) + +TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) + +#TOOLBOX_FILES= debugger.tool debugger.gif +#TARGET_TOOLBOX_FILES= $(EBIN)/debugger.tool $(EBIN)/debugger.gif + +APP_FILE = debugger.app +APPUP_FILE = debugger.appup + +APP_SRC = $(APP_FILE).src +APPUP_SRC = $(APPUP_FILE).src + +APP_TARGET = $(EBIN)/$(APP_FILE) +APPUP_TARGET = $(EBIN)/$(APPUP_FILE) + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += +warn_obsolete_guard + + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(TARGET_FILES) $(TARGET_TOOLBOX_FILES) + +clean: + rm -f $(TARGET_FILES) $(TARGET_TOOLBOX_FILES) + rm -f errs core *~ + +$(APP_TARGET): $(APP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +docs: + +# ---------------------------------------------------- +# Special Targets +# ---------------------------------------------------- + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DATA) $(ERL_FILES) $(INTERNAL_HRL_FILES) $(TOOLBOX_FILES) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(TARGET_TOOLBOX_FILES) $(RELSYSDIR)/ebin + +release_docs_spec: diff --git a/lib/debugger/src/dbg_debugged.erl b/lib/debugger/src/dbg_debugged.erl new file mode 100644 index 0000000000..b56ebef14a --- /dev/null +++ b/lib/debugger/src/dbg_debugged.erl @@ -0,0 +1,118 @@ +%% +%% %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_debugged). + +%% External exports +-export([eval/3]). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% eval(Mod, Func, Args) -> Value +%% Main entry point from external (non-interpreted) code. +%% Called via the error handler. +%%-------------------------------------------------------------------- +eval(Mod, Func, Args) -> + SaveStacktrace = erlang:get_stacktrace(), + Meta = dbg_ieval:eval(Mod, Func, Args), + Mref = erlang:monitor(process, Meta), + msg_loop(Meta, Mref, SaveStacktrace). + +%%==================================================================== +%% Internal functions +%%==================================================================== + +msg_loop(Meta, Mref, SaveStacktrace) -> + receive + + %% Evaluated function has returned a value + {sys, Meta, {ready, Val}} -> + demonitor(Mref), + + %% Restore original stacktrace and return the value + try erlang:raise(throw, stack, SaveStacktrace) + catch + throw:stack -> + case Val of + {dbg_apply,M,F,A} -> + apply(M, F, A); + _ -> + Val + end + end; + + %% Evaluated function raised an (uncaught) exception + {sys, Meta, {exception,{Class,Reason,Stacktrace}}} -> + demonitor(Mref), + + %% ...raise the same exception + erlang:error(erlang:raise(Class, Reason, Stacktrace), + [Class,Reason,Stacktrace]); + + %% Meta is evaluating a receive, must be done within context + %% of real (=this) process + {sys, Meta, {'receive',Msg}} -> + receive Msg -> Meta ! {self(), rec_acked} end, + msg_loop(Meta, Mref, SaveStacktrace); + + %% Meta needs something evaluated within context of real process + {sys, Meta, {command, Command, Stacktrace}} -> + Reply = handle_command(Command, Stacktrace), + Meta ! {sys, self(), Reply}, + msg_loop(Meta, Mref, SaveStacktrace); + + %% Meta has terminated + %% Must be due to int:stop() (or -heaven forbid- a debugger bug) + {'DOWN', Mref, _, _, Reason} -> + + %% Restore original stacktrace and return a dummy value + try erlang:raise(throw, stack, SaveStacktrace) + catch + throw:stack -> + {interpreter_terminated, Reason} + end + end. + +handle_command(Command, Stacktrace) -> + try reply(Command) + catch Class:Reason -> + Stacktrace2 = stacktrace_f(erlang:get_stacktrace()), + {exception, {Class,Reason,Stacktrace2++Stacktrace}} + end. + +reply({apply,M,F,As}) -> + {value, erlang:apply(M,F,As)}; +reply({eval,Expr,Bs}) -> + erl_eval:expr(Expr, Bs). % {value, Value, Bs2} + +%% Demonitor and delete message from inbox +%% +demonitor(Mref) -> + erlang:demonitor(Mref), + receive {'DOWN',Mref,_,_,_} -> ok + after 0 -> ok + end. + +%% Fix stacktrace - keep all above call to this module. +%% +stacktrace_f([]) -> []; +stacktrace_f([{?MODULE,_,_}|_]) -> []; +stacktrace_f([F|S]) -> [F|stacktrace_f(S)]. 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. diff --git a/lib/debugger/src/dbg_idb.erl b/lib/debugger/src/dbg_idb.erl new file mode 100644 index 0000000000..50ed2bb7ce --- /dev/null +++ b/lib/debugger/src/dbg_idb.erl @@ -0,0 +1,54 @@ +%% +%% %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_idb). + +%% External exports +-export([insert/3, lookup/2, match_object/2]). + +%%==================================================================== +%% External exports +%%==================================================================== + +insert(DbRef, Key, Value) -> + case DbRef of + {Node, ModDb} -> + rpc:block_call(Node, ets, insert, [ModDb, {Key, Value}]); + ModDb -> + ets:insert(ModDb, {Key, Value}) + end. + +lookup(DbRef, Key) -> + Res = case DbRef of + {Node, ModDb} -> + rpc:block_call(Node, ets, lookup, [ModDb, Key]); + ModDb -> + ets:lookup(ModDb, Key) + end, + case Res of + [{Key, Value}] -> {ok, Value}; + _ -> not_found + end. + +match_object(DbRef, Key) -> + case DbRef of + {Node, ModDb} -> + rpc:block_call(Node, ets, match_object, [ModDb, Key]); + ModDb -> + ets:match_object(ModDb, Key) + end. diff --git a/lib/debugger/src/dbg_ieval.erl b/lib/debugger/src/dbg_ieval.erl new file mode 100644 index 0000000000..47d4ecaaf8 --- /dev/null +++ b/lib/debugger/src/dbg_ieval.erl @@ -0,0 +1,1733 @@ +%% +%% %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_ieval). + +-export([eval/3,exit_info/5]). +-export([eval_expr/3]). +-export([check_exit_msg/3,exception/4,in_use_p/2]). +-export([stack_level/0, bindings/1, stack_frame/2, backtrace/1]). + +-include("dbg_ieval.hrl"). + +-import(lists, [foldl/3,flatmap/2]). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% eval(Mod, Func, Args) -> Meta +%% Mod = Func = atom() +%% Args = [term()] +%% MFA = {Mod,Func,Args} | {Mod,Func,Arity} | {Fun,Args} +%% Arity = integer() +%% Meta = pid() +%% Entry point from debugged process (dbg_debugged). +%% Immediately returns the pid for the meta process. +%% The evaluated value will later be sent as a message to +%% the calling process. +%%-------------------------------------------------------------------- +eval(Mod, Func, Args) -> + Debugged = self(), + Int = dbg_iserver:find(), + case dbg_iserver:call(Int, {get_meta,Debugged}) of + {ok,Meta} -> + Meta ! {re_entry, Debugged, {eval,{Mod,Func,Args}}}, + Meta; + {error,not_interpreted} -> + spawn(fun() -> + meta(Int, Debugged, Mod, Func, Args) + end) + end. + +%%-------------------------------------------------------------------- +%% exit_info(Int, AttPid, OrigPid, Reason, ExitInfo) +%% Int = AttPid = OrigPid = pid() +%% Reason = term() +%% ExitInfo = {{Mod,Line}, Bs, Stack} | {} +%% Meta process started when attaching to a terminated process. +%% Spawned (by dbg_iserver) in response to user request. +%%-------------------------------------------------------------------- +exit_info(Int, AttPid, OrigPid, Reason, ExitInfo) -> + put(int, Int), + put(attached, AttPid), + put(breakpoints, dbg_iserver:call(Int, all_breaks)), + put(self, OrigPid), + put(exit_info, ExitInfo), + + case ExitInfo of + {{Mod,Line},Bs,S} -> + Stack = binary_to_term(S), + put(stack, Stack), + Le = stack_level(Stack), + dbg_icmd:tell_attached({exit_at, {Mod, Line}, Reason, Le}), + exit_loop(OrigPid, Reason, Bs,#ieval{module=Mod,line=Line}); + {} -> + put(stack, []), + dbg_icmd:tell_attached({exit_at, null, Reason, 1}), + exit_loop(OrigPid, Reason, erl_eval:new_bindings(),#ieval{}) + end. + +%%-------------------------------------------------------------------- +%% eval_expr(Expr, Bs, Ieval) -> {value, Value, Bs} +%% +%% Evalute a shell expression in the real process. +%% Called (dbg_icmd) in response to a user request. +%%-------------------------------------------------------------------- +eval_expr(Expr, Bs, Ieval) -> + + %% Save current exit info + ExitInfo = get(exit_info), + Stacktrace = get(stacktrace), + + %% Emulate a surrounding catch + try debugged_cmd({eval,Expr,Bs}, Bs, Ieval) + catch + Class:Reason -> + Result = case Class of + throw -> Reason; + _ -> {'EXIT', Reason} + end, + + %% Reset exit info + put(exit_info, ExitInfo), + put(stacktrace, Stacktrace), + + {value, Result, Bs} + end. + +%%-------------------------------------------------------------------- +%% check_exit_msg(Msg, Bs, Ieval) +%% Msg = term() +%% Check if Msg is an 'EXIT' msg from the iserver or a 'DOWN' msg +%% from the debugged process. If so exit with correct reason. +%%-------------------------------------------------------------------- +check_exit_msg({'EXIT', Int, Reason}, _Bs, #ieval{level=Le}) -> + %% This *must* be interpreter which has terminated, + %% we are not linked to anyone else + if + Le==1 -> + exit(Reason); + Le>1 -> + exit({Int, Reason}) + end; +check_exit_msg({'DOWN',_,_,_,Reason}, Bs, + #ieval{level=Le, module=Mod, line=Li}) -> + %% This *must* be Debugged which has terminated, + %% we are not monitoring anyone else + + %% Inform Int about current position, bindings and stack + ExitInfo = + case get(exit_info) of + + %% Debugged has been terminated by someone + %% - really the position, bindings and stack are of no + %% importance in this case + %% If we don't save them, however, post-mortem analysis + %% of the process isn't possible + undefined when Le==1 -> % died outside interpreted code + {}; + undefined when Le>1 -> + StackBin = term_to_binary(get(stack)), + {{Mod, Li}, Bs, StackBin}; + + %% Debugged has terminated due to an exception + ExitInfo0 -> + ExitInfo0 + end, + dbg_iserver:cast(get(int), {set_exit_info,self(),ExitInfo}), + + if + Le==1 -> + exit(Reason); + Le>1 -> + exit({get(self), Reason}) + end; +check_exit_msg(_Msg, _Bs, _Ieval) -> + ignore. + +%%-------------------------------------------------------------------- +%% exception(Class, Reason, Bs, Ieval) +%% Class = error | exit | throw +%% Reason = term() +%% Bs = bindings() +%% Ieval = #ieval{} +%% Store information about where in the code the error is located +%% and then raise the exception. +%%-------------------------------------------------------------------- +exception(Class, Reason, Bs, Ieval) -> + exception(Class, Reason, fix_stacktrace(1), Bs, Ieval). + +exception(Class, Reason, Stacktrace, Bs, #ieval{module=M, line=Line}) -> + ExitInfo = {{M,Line}, Bs, term_to_binary(get(stack))}, + put(exit_info, ExitInfo), + put(stacktrace, Stacktrace), + erlang:Class(Reason). + +%%-------------------------------------------------------------------- +%% in_use_p(Mod, Cm) -> boolean() +%% Mod = Cm = atom() +%% Returns true if Mod is found on the stack, otherwise false. +%%-------------------------------------------------------------------- +in_use_p(Mod, Mod) -> true; +in_use_p(Mod, _Cm) -> + case get(trace_stack) of + false -> true; + _ -> % all | no_tail + lists:any(fun({_,{M,_,_,_}}) when M =:= Mod -> true; + (_) -> false + end, + get(stack)) + end. + +%%==================================================================== +%% Internal functions +%%==================================================================== + +%%--Loops------------------------------------------------------------- + +%% Entry point for first-time initialization of meta process +meta(Int, Debugged, M, F, As) -> + process_flag(trap_exit, true), + erlang:monitor(process, Debugged), + + %% Inform dbg_iserver, get the initial status in return + Pargs = case {M, F} of + %% If it's a fun we're evaluating, show a text + %% representation of the fun and its arguments, + %% not dbg_ieval:eval_fun(...) + {dbg_ieval, eval_fun} -> + {Mx, Fx} = lists:last(As), + {Mx, Fx, lists:nth(2, As)}; + _ -> + {M, F, As} + end, + Status = dbg_iserver:call(Int, {new_process,Debugged,self(),Pargs}), + + %% Initiate process dictionary + put(int, Int), % pid() dbg_iserver + put(attached, undefined),% pid() attached process + put(breakpoints, dbg_iserver:call(Int, all_breaks)), + put(cache, []), + put(next_break, Status), % break | running (other values later) + put(self, Debugged), % pid() interpreted process + put(stack, []), + put(stacktrace, []), + put(trace_stack, dbg_iserver:call(Int, get_stack_trace)), + put(trace, false), % bool() Trace on/off + put(user_eval, []), + + + %% Send the result of the meta process + Ieval = #ieval{}, + Debugged ! {sys, self(), eval_mfa(Debugged,M,F,As,Ieval)}, + + dbg_iserver:cast(Int, {set_status, self(), idle, {}}), + dbg_icmd:tell_attached(idle), + + meta_loop(Debugged, erl_eval:new_bindings(), Ieval). + +debugged_cmd(Cmd, Bs, Ieval) -> + Debugged = get(self), + Stacktrace = fix_stacktrace(2), + Debugged ! {sys, self(), {command,Cmd,Stacktrace}}, + meta_loop(Debugged, Bs, Ieval). + +meta_loop(Debugged, Bs, #ieval{level=Le} = Ieval) -> + receive + + %% The following messages can only be received when Meta is + %% waiting for Debugged to evaluate non-interpreted code + %% or a Bif. Le>1 + {sys, Debugged, {value,Val}} -> + {value, Val, Bs}; + {sys, Debugged, {value,Val,Bs2}} -> + {value, Val, Bs2}; + {sys, Debugged, {exception,{Class,Reason,Stacktrace}}} -> + case get(exit_info) of + + %% Error occured outside interpreted code + undefined -> + exception(Class,Reason,Stacktrace,Bs,Ieval); + + %% Error must have occured within a re-entry to + %% interpreted code, simply raise the exception + _ -> + erlang:Class(Reason) + end; + + %% Re-entry to Meta from non-interpreted code + {re_entry, Debugged, {eval,{M,F,As}}} when Le==1 -> + %% Reset process dictionary + %% This is really only necessary if the process left + %% interpreted code at a call level > 1 + put(stack, []), + put(stacktrace, []), + put(exit_info, undefined), + + dbg_iserver:cast(get(int), {set_status,self(),running,{}}), + dbg_icmd:tell_attached(running), + + %% Tell attached process(es) to update source code. + dbg_icmd:tell_attached({re_entry,M,F}), + + %% Send the result of the meta process + Debugged ! {sys,self(),eval_mfa(Debugged,M,F,As,Ieval)}, + + dbg_iserver:cast(get(int), {set_status,self(),idle,{}}), + dbg_icmd:tell_attached(idle), + meta_loop(Debugged, Bs, Ieval); + + %% Evaluation in Debugged results in call to interpreted + %% function (probably? a fun) + {re_entry, Debugged, {eval,{M,F,As}}} when Le>1 -> + Ieval2 = Ieval#ieval{module=undefined, line=-1}, + Debugged ! {sys,self(),eval_mfa(Debugged,M,F,As,Ieval2)}, + meta_loop(Debugged, Bs, Ieval); + + Msg -> + check_exit_msg(Msg, Bs, Ieval), + dbg_icmd:handle_msg(Msg, idle, Bs, Ieval), + meta_loop(Debugged, Bs, Ieval) + end. + +exit_loop(OrigPid, Reason, Bs, Ieval) -> + receive + Msg -> + check_exit_msg(Msg, Bs, Ieval), + dbg_icmd:handle_msg(Msg, exit_at, Bs, Ieval), + exit_loop(OrigPid, Reason, Bs, Ieval) + end. + +%%--Stack emulation--------------------------------------------------- + +%% We keep track of a call stack that is used for +%% 1) saving stack frames that can be inspected from an Attached +%% Process GUI (using dbg_icmd:get(Meta, stack_frame, {Dir, SP}) +%% 2) generate an approximation of regular stacktrace -- sent to +%% Debugged when it should raise an exception or evaluate a +%% function (since it might possible raise an exception) +%% +%% Stack = [Entry] +%% Entry = {Le, {MFA, Where, Bs}} +%% Le = int() % current call level +%% MFA = {M,F,Args} % called function (or fun) +%% | {Fun,Args} % +%% Where = {M,Li} % from where (module+line) function is called +%% Bs = bindings() % current variable bindings +%% +%% How to push depends on the "Stack Trace" option (value saved in +%% process dictionary item 'trace_stack'). +%% all - everything is pushed +%% no_tail - tail recursive push +%% false - nothing is pushed +%% Whenever a function returns, the corresponding call frame is popped. + +push(MFA, Bs, #ieval{level=Le,module=Cm,line=Li,last_call=Lc}) -> + Entry = {Le, {MFA, {Cm,Li}, Bs}}, + case get(trace_stack) of + false -> ignore; + no_tail when Lc -> + case get(stack) of + [] -> put(stack, [Entry]); + [_Entry|Entries] -> put(stack, [Entry|Entries]) + end; + _ -> % all | no_tail when Lc==false + put(stack, [Entry|get(stack)]) + end. + +pop() -> + case get(trace_stack) of + false -> ignore; + _ -> % all � no_tail + case get(stack) of + [_Entry|Entries] -> + put(stack, Entries); + [] -> + ignore + end + end. + +pop(Le) -> + case get(trace_stack) of + false -> ignore; + _ -> % all | no_tail + put(stack, pop(Le, get(stack))) + end. + +pop(Level, [{Le, _}|Stack]) when Level=<Le -> + pop(Level, Stack); +pop(_Level, Stack) -> + Stack. + + +%% stack_level() -> Le +%% stack_level(Stack) -> Le +%% Top call level +stack_level() -> + stack_level(get(stack)). + +stack_level([]) -> 1; +stack_level([{Le,_}|_]) -> Le. + +%% fix_stacktrace(Start) -> Stacktrace +%% Start = 1|2 +%% Stacktrace = [{M,F,Args|Arity} | {Fun,Args}] +%% Convert internal stack format to imitation of regular stacktrace. +%% Max three elements, no repeated (recursive) calls to the same +%% function and convert argument lists to arity for all but topmost +%% entry (and funs). +%% 'Start' indicates where at get(stack) to start. This somewhat ugly +%% solution is because fix_stacktrace has two uses: 1) to imitate +%% the stacktrace in the case of an exception in the interpreted code, +%% in which case the current call (top of the stack = first of the list) +%% should be included, and 2) to send a current stacktrace to Debugged +%% when evaluation passes into non-interpreted code, in which case +%% the current call should NOT be included (as it is Debugged which +%% will make the actual function call). +fix_stacktrace(Start) -> + case fix_stacktrace2(sublist(get(stack), Start, 3)) of + [] -> + []; + [H|T] -> + [H|args2arity(T)] + end. + +sublist([], _Start, _Length) -> + []; % workaround, lists:sublist([],2,3) fails +sublist(L, Start, Length) -> + lists:sublist(L, Start, Length). + +fix_stacktrace2([{_,{{M,F,As1},_,_}}, {_,{{M,F,As2},_,_}}|_]) + when length(As1)==length(As2) -> + [{M,F,As1}]; +fix_stacktrace2([{_,{{Fun,As1},_,_}}, {_,{{Fun,As2},_,_}}|_]) + when length(As1)==length(As2) -> + [{Fun,As1}]; +fix_stacktrace2([{_,{MFA,_,_}}|Entries]) -> + [MFA|fix_stacktrace2(Entries)]; +fix_stacktrace2([]) -> + []. + +args2arity([{M,F,As}|Entries]) when is_list(As) -> + [{M,F,length(As)}|args2arity(Entries)]; +args2arity([Entry|Entries]) -> + [Entry|args2arity(Entries)]; +args2arity([]) -> + []. + +%% bindings(SP) -> Bs +%% SP = Le % stack pointer +%% Return the bindings for the specified call level +bindings(SP) -> + bindings(SP, get(stack)). + +bindings(SP, [{SP,{_MFA,_Wh,Bs}}|_]) -> + Bs; +bindings(SP, [_Entry|Entries]) -> + bindings(SP, Entries); +bindings(_SP, []) -> + erl_eval:new_bindings(). + +%% stack_frame(Dir, SP) -> {Le, Where, Bs} | top | bottom +%% Dir = up | down +%% Where = {Cm, Li} +%% Cm = Module | undefined % module +%% Li = int() | -1 % line number +%% Bs = bindings() +%% Return stack frame info one step up/down from given stack pointer +%% up = to lower call levels +%% down = to higher call levels +stack_frame(up, SP) -> + stack_frame(SP, up, get(stack)); +stack_frame(down, SP) -> + stack_frame(SP, down, lists:reverse(get(stack))). + +stack_frame(SP, up, [{Le, {_MFA,Where,Bs}}|_]) when Le<SP -> + {Le, Where, Bs}; +stack_frame(SP, down, [{Le, {_MFA,Where,Bs}}|_]) when Le>SP -> + {Le, Where, Bs}; +stack_frame(SP, Dir, [{SP, _}|Stack]) -> + case Stack of + [{Le, {_MFA,Where,Bs}}|_] -> + {Le, Where, Bs}; + [] when Dir==up -> + top; + [] when Dir==down -> + bottom + end; +stack_frame(SP, Dir, [_Entry|Stack]) -> + stack_frame(SP, Dir, Stack). + +%% backtrace(HowMany) -> Backtrace +%% HowMany = all | int() +%% Backtrace = {Le, MFA} +%% Return all/the last N called functions, in reversed call order +backtrace(HowMany) -> + Stack = case HowMany of + all -> get(stack); + N -> lists:sublist(get(stack), N) + end, + [{Le, MFA} || {Le,{MFA,_Wh,_Bs}} <- Stack]. + +%%--Trace function---------------------------------------------------- + +%%-------------------------------------------------------------------- +%% trace(What, Args) +%% What = send | receivex | received | call | return | bif +%% Args depends on What, see the code. +%%-------------------------------------------------------------------- +trace(What, Args) -> + trace(What, Args, get(trace)). + +trace(return, {_Le,{dbg_apply,_,_,_}}, _Bool) -> + ignore; +trace(What, Args, true) -> + Str = case What of + send -> + {To,Msg} = Args, + io_lib:format("==> ~w : ~p~n", [To, Msg]); + receivex -> + {Le, TimeoutP} = Args, + Tail = case TimeoutP of + true -> "with timeout~n"; + false -> "~n" + end, + io_lib:format(" (~w) receive " ++ Tail, [Le]); + + received when Args==null -> + io_lib:format("~n", []); + received -> % Args=Msg + io_lib:format("~n<== ~p~n", [Args]); + + call -> + {Called, {Le,Li,M,F,As}} = Args, + case Called of + extern -> + io_lib:format("++ (~w) <~w> ~w:~w~s~n", + [Le,Li,M,F,format_args(As)]); + local -> + io_lib:format("++ (~w) <~w> ~w~s~n", + [Le,Li,F,format_args(As)]) + end; + call_fun -> + {Le,Li,F,As} = Args, + io_lib:format("++ (~w) <~w> ~w~s~n", + [Le, Li, F, format_args(As)]); + return -> + {Le,Val} = Args, + io_lib:format("-- (~w) ~p~n", [Le, Val]); + + + bif -> + {Le,Li,M,F,As} = Args, + io_lib:format("++ (~w) <~w> ~w:~w~s~n", + [Le, Li, M, F, format_args(As)]) + end, + dbg_icmd:tell_attached({trace_output, Str}); +trace(_What, _Args, false) -> + ignore. + +format_args(As) when is_list(As) -> + [$(,format_args1(As),$)]; +format_args(A) -> + [$/,io_lib:format("~p", [A])]. + +format_args1([A]) -> + [io_lib:format("~p", [A])]; +format_args1([A|As]) -> + [io_lib:format("~p", [A]),$,|format_args1(As)]; +format_args1([]) -> + []. + +%%--Other useful functions-------------------------------------------- + +%% Mimic catch behaviour +catch_value(error, Reason) -> + {'EXIT',{Reason,get(stacktrace)}}; +catch_value(exit, Reason) -> + {'EXIT',Reason}; +catch_value(throw, Reason) -> + Reason. + +%%--Code interpretation----------------------------------------------- + +%%-------------------------------------------------------------------- +%% Top level function of meta evaluator. +%% Return message to be replied to the target process. +%%-------------------------------------------------------------------- +eval_mfa(Debugged, M, F, As, Ieval) -> + Int = get(int), + Bs = erl_eval:new_bindings(), + try eval_function(M,F,As,Bs,extern,Ieval#ieval{last_call=true}) of + {value, Val, _Bs} -> + {ready, Val} + catch + exit:{Debugged, Reason} -> + exit(Reason); + exit:{Int, Reason} -> + exit(Reason); + Class:Reason -> + {exception, {Class, Reason, get(stacktrace)}} + end. + +eval_function(Mod, Fun, As0, Bs0, _Called, Ieval) when is_function(Fun); + Mod==?MODULE, + Fun==eval_fun -> + #ieval{level=Le, line=Li, last_call=Lc} = Ieval, + case lambda(Fun, As0) of + {Cs,Module,Name,As,Bs} -> + push({Module,Name,As}, Bs0, Ieval), + trace(call_fun, {Le,Li,Name,As}), + {value, Val, _Bs} = + fnk_clauses(Cs, Module, Name, As, Bs, + Ieval#ieval{level=Le+1}), + pop(), + trace(return, {Le,Val}), + {value, Val, Bs0}; + + not_interpreted when Lc -> % We are leaving interpreted code + trace(call_fun, {Le,Li,Fun,As0}), + {value, {dbg_apply,erlang,apply,[Fun,As0]}, Bs0}; + not_interpreted -> + push({Fun,As0}, Bs0, Ieval), + trace(call_fun, {Le,Li,Fun,As0}), + {value, Val, _Bs} = + debugged_cmd({apply,erlang,apply,[Fun,As0]},Bs0, + Ieval#ieval{level=Le+1}), + pop(), + trace(return, {Le,Val}), + {value, Val, Bs0}; + + {error,Reason} -> + %% It's ok not to push anything in this case, the error + %% reason contains information about the culprit + %% ({badarity,{{Mod,Name},As}}) + exception(error, Reason, Bs0, Ieval) + end; + +%% Common Test adaptation +eval_function(ct_line, line, As, Bs, extern, #ieval{level=Le}=Ieval) -> + debugged_cmd({apply,ct_line,line,As}, Bs, Ieval#ieval{level=Le+1}), + {value, ignore, Bs}; + +eval_function(Mod, Name, As0, Bs0, Called, Ieval) -> + #ieval{level=Le, line=Li, last_call=Lc} = Ieval, + + push({Mod,Name,As0}, Bs0, Ieval), + trace(call, {Called, {Le,Li,Mod,Name,As0}}), + + case get_function(Mod, Name, As0, Called) of + Cs when is_list(Cs) -> + {value, Val, _Bs} = + fnk_clauses(Cs, Mod, Name, As0, erl_eval:new_bindings(), + Ieval#ieval{level=Le+1}), + pop(), + trace(return, {Le,Val}), + {value, Val, Bs0}; + + not_interpreted when Lc -> % We are leaving interpreted code + {value, {dbg_apply,Mod,Name,As0}, Bs0}; + not_interpreted -> + {value, Val, _Bs} = + debugged_cmd({apply,Mod,Name,As0}, Bs0, + Ieval#ieval{level=Le+1}), + pop(), + trace(return, {Le,Val}), + {value, Val, Bs0}; + + undef -> + exception(error, undef, Bs0, Ieval) + end. + +lambda(eval_fun, [Cs,As,Bs,{Mod,Name}=F]) -> + %% Fun defined in interpreted code, called from outside + if + length(element(3,hd(Cs))) == length(As) -> + db_ref(Mod), %% Adds ref between module and process + {Cs,Mod,Name,As,Bs}; + true -> + {error,{badarity,{F,As}}} + end; +lambda(Fun, As) when is_function(Fun) -> + %% Fun called from within interpreted code... + case erlang:fun_info(Fun, module) of + + %% ... and the fun was defined in interpreted code + {module, ?MODULE} -> + {env, [{Mod,Name},Bs,Cs]} = erlang:fun_info(Fun, env), + {arity, Arity} = erlang:fun_info(Fun, arity), + if + length(As) == Arity -> + db_ref(Mod), %% Adds ref between module and process + {Cs,Mod,Name,As,Bs}; + true -> + {error,{badarity,{Fun,As}}} + end; + + %% ... and the fun was defined outside interpreted code + _ -> + not_interpreted + end. + +get_function(Mod, Name, Args, local) -> + Arity = length(Args), + Key = {Mod,Name,Arity}, + case cached(Key) of + false -> + DbRef = db_ref(Mod), + case dbg_idb:match_object(DbRef, {{Mod,Name,Arity,'_'},'_'}) of + [{{Mod,Name,Arity,Exp},Clauses}] -> + cache(Key, {Exp,Clauses}), + Clauses; + _ -> undef + end; + {_Exp,Cs} -> Cs + end; +get_function(Mod, Name, Args, extern) -> + Arity = length(Args), + Key = {Mod,Name,Arity}, + case cached(Key) of + false -> + case db_ref(Mod) of + not_found -> not_interpreted; + DbRef -> + case dbg_idb:lookup(DbRef, {Mod,Name,Arity,true}) of + {ok,Data} -> + cache(Key, {true,Data}), + Data; + not_found -> + case dbg_idb:lookup(DbRef, module) of + {ok,_} -> undef; + not_found -> not_interpreted + end + end + end; + {true,Cs} -> Cs; + {false,_} -> undef + end. + +db_ref(Mod) -> + case get([Mod|db]) of + undefined -> + case dbg_iserver:call(get(int), + {get_module_db, Mod, get(self)}) of + not_found -> + not_found; + ModDb -> + Node = node(get(int)), + DbRef = if + Node/=node() -> {Node,ModDb}; + true -> ModDb + end, + put([Mod|db], DbRef), + DbRef + end; + DbRef -> + DbRef + end. + + +cache(Key, Data) -> + put(cache, lists:sublist([{Key,Data}|get(cache)], 5)). + +cached(Key) -> + case lists:keysearch(Key, 1, get(cache)) of + {value,{Key,Data}} -> Data; + false -> false + end. + +%% Try to find a matching function clause +%% #ieval.level is set, the other fields must be set in this function +fnk_clauses([{clause,Line,Pars,Gs,Body}|Cs], M, F, As, Bs0, Ieval) -> + case head_match(Pars, As, [], Bs0) of + {match,Bs1} -> + Bs = add_bindings(Bs1, Bs0), + case guard(Gs, Bs) of + true -> + seq(Body, Bs, + Ieval#ieval{line=Line, + module=M,function=F,arguments=As}); + false -> + fnk_clauses(Cs, M, F, As, Bs0, Ieval) + end; + nomatch -> + fnk_clauses(Cs, M, F, As, Bs0, Ieval) + end; +fnk_clauses([], _M, _F, _As, Bs, Ieval) -> + exception(error, function_clause, Bs, Ieval). + +seq([E], Bs0, Ieval) -> + case dbg_icmd:cmd(E, Bs0, Ieval) of + {skip,Bs} -> + {value,skipped,Bs}; + Bs -> + expr(E, Bs, Ieval) + end; +seq([E|Es], Bs0, Ieval) -> + case dbg_icmd:cmd(E, Bs0, Ieval) of + {skip,Bs} -> + seq(Es, Bs, Ieval); + Bs1 -> + {value,_,Bs} = expr(E, Bs1, Ieval#ieval{last_call=false}), + seq(Es, Bs, Ieval) + end; +seq([], Bs, _) -> + {value,true,Bs}. + +%% Variable +expr({var,Line,V}, Bs, Ieval) -> + case binding(V, Bs) of + {value,Val} -> + {value,Val,Bs}; + unbound -> + exception(error, {unbound,V}, Bs, Ieval#ieval{line=Line}) + end; + +expr({value,_,Val}, Bs, _Ieval) -> + {value,Val,Bs}; +expr({value,Val}, Bs, _Ieval) -> % Special case straight values + {value,Val,Bs}; + +%% List +expr({cons,Line,H0,T0}, Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + Ieval1 = Ieval#ieval{last_call=false}, + {value,H,Bs1} = expr(H0,Bs0,Ieval1), + {value,T,Bs2} = expr(T0,Bs0,Ieval1), + {value,[H|T],merge_bindings(Bs2, Bs1, Ieval)}; + +%% Tuple +expr({tuple,Line,Es0}, Bs0, Ieval) -> + {Vs,Bs} = eval_list(Es0, Bs0, Ieval#ieval{line=Line}), + {value,list_to_tuple(Vs),Bs}; + +%% A block of statements +expr({block,Line,Es},Bs,Ieval) -> + seq(Es, Bs, Ieval#ieval{line=Line}); + +%% Catch statement +expr({'catch',Line,Expr}, Bs0, Ieval) -> + try expr(Expr, Bs0, Ieval#ieval{line=Line, last_call=false}) + catch + Class:Reason -> + %% Exception caught, reset exit info + put(exit_info, undefined), + pop(Ieval#ieval.level), + Value = catch_value(Class, Reason), + trace(return, {Ieval#ieval.level,Value}), + {value, Value, Bs0} + end; + +%% Try-catch statement +expr({'try',Line,Es,CaseCs,CatchCs,[]}, Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + try seq(Es, Bs0, Ieval#ieval{last_call=false}) of + {value,Val,Bs} = Value -> + case CaseCs of + [] -> Value; + _ -> + case_clauses(Val, CaseCs, Bs, try_clause, Ieval) + end + catch + Class:Reason when CatchCs=/=[] -> + catch_clauses({Class,Reason,[]}, CatchCs, Bs0, Ieval) + end; +expr({'try',Line,Es,CaseCs,CatchCs,As}, Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + try seq(Es, Bs0, Ieval#ieval{last_call=false}) of + {value,Val,Bs} = Value -> + case CaseCs of + [] -> Value; + _ -> + case_clauses(Val, CaseCs, Bs, try_clause, Ieval) + end + catch + Class:Reason when CatchCs =/= [] -> + catch_clauses({Class,Reason,[]}, CatchCs, Bs0, Ieval) + after + seq(As, Bs0, Ieval#ieval{last_call=false}) + end; + +%% Case statement +expr({'case',Line,E,Cs}, Bs0, Ieval) -> + {value,Val,Bs} = + expr(E, Bs0, Ieval#ieval{line=Line, last_call=false}), + case_clauses(Val, Cs, Bs, case_clause, Ieval#ieval{line=Line}); + +%% If statement +expr({'if',Line,Cs}, Bs, Ieval) -> + if_clauses(Cs, Bs, Ieval#ieval{line=Line}); + +%% Andalso/orelse +expr({'andalso',Line,E1,E2}, Bs, Ieval) -> + case expr(E1, Bs, Ieval#ieval{line=Line, last_call=false}) of + {value,false,_}=Res -> + Res; + {value,true,_} -> + expr(E2, Bs, Ieval#ieval{line=Line, last_call=false}); + {value,Val,Bs} -> + exception(error, {badarg,Val}, Bs, Ieval) + end; +expr({'orelse',Line,E1,E2}, Bs, Ieval) -> + case expr(E1, Bs, Ieval#ieval{line=Line, last_call=false}) of + {value,true,_}=Res -> + Res; + {value,false,_} -> + expr(E2, Bs, Ieval#ieval{line=Line, last_call=false}); + {value,Val,_} -> + exception(error, {badarg,Val}, Bs, Ieval) + end; + +%% Matching expression +expr({match,Line,Lhs,Rhs0}, Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {value,Rhs,Bs1} = expr(Rhs0, Bs0, Ieval#ieval{last_call=false}), + case match(Lhs, Rhs, Bs1) of + {match,Bs} -> + {value,Rhs,Bs}; + nomatch -> + exception(error, {badmatch,Rhs}, Bs1, Ieval) + end; + +%% Construct a fun +expr({make_fun,Line,Name,Cs}, Bs, #ieval{module=Module}=Ieval) -> + Arity = length(element(3,hd(Cs))), + Info = {Module,Name}, + Fun = + case Arity of + 0 -> fun() -> eval_fun(Cs, [], Bs, Info) end; + 1 -> fun(A) -> eval_fun(Cs, [A], Bs,Info) end; + 2 -> fun(A,B) -> eval_fun(Cs, [A,B], Bs,Info) end; + 3 -> fun(A,B,C) -> eval_fun(Cs, [A,B,C], Bs,Info) end; + 4 -> fun(A,B,C,D) -> eval_fun(Cs, [A,B,C,D], Bs,Info) end; + 5 -> fun(A,B,C,D,E) -> eval_fun(Cs, [A,B,C,D,E], Bs,Info) end; + 6 -> fun(A,B,C,D,E,F) -> eval_fun(Cs, [A,B,C,D,E,F], Bs,Info) end; + 7 -> fun(A,B,C,D,E,F,G) -> + eval_fun(Cs, [A,B,C,D,E,F,G], Bs,Info) end; + 8 -> fun(A,B,C,D,E,F,G,H) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H], Bs,Info) end; + 9 -> fun(A,B,C,D,E,F,G,H,I) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H,I], Bs,Info) end; + 10 -> fun(A,B,C,D,E,F,G,H,I,J) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J], Bs,Info) end; + 11 -> fun(A,B,C,D,E,F,G,H,I,J,K) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K], Bs,Info) end; + 12 -> fun(A,B,C,D,E,F,G,H,I,J,K,L) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L], Bs,Info) end; + 13 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M], Bs,Info) end; + 14 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N], Bs,Info) end; + 15 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O], Bs,Info) end; + 16 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P], Bs,Info) end; + 17 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q], Bs,Info) end; + 18 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R], Bs,Info) end; + 19 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S],Bs,Info) end; + 20 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T) -> + eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T],Bs,Info) end; + _Other -> + exception(error, {'argument_limit',{'fun',Cs}}, Bs, + Ieval#ieval{line=Line}) + end, + {value,Fun,Bs}; + +%% Common test adaptation +expr({call_remote,0,ct_line,line,As0}, Bs0, Ieval0) -> + {As,_Bs} = eval_list(As0, Bs0, Ieval0), + eval_function(ct_line, line, As, Bs0, extern, Ieval0); + +%% Local function call +expr({local_call,Line,F,As0}, Bs0, #ieval{module=M} = Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {As,Bs} = eval_list(As0, Bs0, Ieval), + eval_function(M, F, As, Bs, local, Ieval); + +%% Remote function call +expr({call_remote,Line,M,F,As0}, Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {As,Bs} = eval_list(As0, Bs0, Ieval), + eval_function(M, F, As, Bs, extern, Ieval); + +%% Emulated semantics of some BIFs +expr({dbg,Line,self,[]}, Bs, #ieval{level=Le}) -> + trace(bif, {Le,Line,erlang,self,[]}), + Self = get(self), + trace(return, {Le,Self}), + {value,Self,Bs}; +expr({dbg,Line,get_stacktrace,[]}, Bs, #ieval{level=Le}) -> + trace(bif, {Le,Line,erlang,get_stacktrace,[]}), + Stacktrace = get(stacktrace), + trace(return, {Le,Stacktrace}), + {value,Stacktrace,Bs}; +expr({dbg,Line,throw,As0}, Bs0, #ieval{level=Le}=Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {[Term],Bs} = eval_list(As0, Bs0, Ieval), + trace(bif, {Le,Line,erlang,throw,[Term]}), + exception(throw, Term, Bs, Ieval); +expr({dbg,Line,error,As0}, Bs0, #ieval{level=Le}=Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {[Term],Bs} = eval_list(As0, Bs0, Ieval), + trace(bif, {Le,Line,erlang,error,[Term]}), + exception(error, Term, Bs, Ieval); +expr({dbg,Line,fault,As0}, Bs0, #ieval{level=Le}=Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {[Term],Bs} = eval_list(As0, Bs0, Ieval), + trace(bif, {Le,Line,erlang,fault,[Term]}), + exception(fault, Term, Bs, Ieval); +expr({dbg,Line,exit,As0}, Bs0, #ieval{level=Le}=Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {[Term],Bs} = eval_list(As0, Bs0, Ieval), + trace(bif, {Le,Line,erlang,exit,[Term]}), + exception(exit, Term, Bs, Ieval); + +%% Call to "safe" BIF, ie a BIF that can be executed in Meta process +expr({safe_bif,Line,M,F,As0}, Bs0, #ieval{level=Le}=Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {As,Bs} = eval_list(As0, Bs0, Ieval), + trace(bif, {Le,Line,M,F,As}), + push({M,F,As}, Bs0, Ieval), + {_,Value,_} = Res = safe_bif(M, F, As, Bs, Ieval), + trace(return, {Le,Value}), + pop(), + Res; + +%% Call to a BIF that must be evaluated in the correct process +expr({bif,Line,M,F,As0}, Bs0, #ieval{level=Le}=Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {As,Bs} = eval_list(As0, Bs0, Ieval), + trace(bif, {Le,Line,M,F,As}), + push({M,F,As}, Bs0, Ieval), + {_,Value,_} = + Res = debugged_cmd({apply,M,F,As}, Bs, Ieval#ieval{level=Le+1}), + trace(return, {Le,Value}), + pop(), + Res; + +%% Call to a BIF that spawns a new process +expr({spawn_bif,Line,M,F,As0}, Bs0, #ieval{level=Le}=Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {As,Bs} = eval_list(As0, Bs0, Ieval), + trace(bif, {Le,Line,M,F,As}), + push({M,F,As}, Bs0, Ieval), + Res = debugged_cmd({apply,M,F,As}, Bs,Ieval#ieval{level=Le+1}), + trace(return, {Le,Res}), + pop(), + Res; + +%% Call to an operation +expr({op,Line,Op,As0}, Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {As,Bs} = eval_list(As0, Bs0, Ieval), + try apply(erlang,Op,As) of + Value -> + {value,Value,Bs} + catch + Class:Reason -> + exception(Class, Reason, Bs, Ieval) + end; + +%% apply/2 (fun) +expr({apply_fun,Line,Fun0,As0}, Bs0, #ieval{level=Le}=Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + FunValue = case expr(Fun0, Bs0, Ieval) of + {value,{dbg_apply,Mx,Fx,Asx},Bsx} -> + debugged_cmd({apply,Mx,Fx,Asx}, + Bsx, Ieval#ieval{level=Le+1}); + OtherFunValue -> + OtherFunValue + end, + case FunValue of + {value,Fun,Bs1} when is_function(Fun) -> + {As,Bs} = eval_list(As0, Bs1, Ieval), + eval_function(undefined, Fun, As, Bs, extern, Ieval); + {value,{M,F},Bs1} when is_atom(M), is_atom(F) -> + {As,Bs} = eval_list(As0, Bs1, Ieval), + eval_function(M, F, As, Bs, extern, Ieval); + {value,BadFun,Bs1} -> + exception(error, {badfun,BadFun}, Bs1, Ieval) + end; + +%% apply/3 +expr({apply,Line,As0}, Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {[M,F,As],Bs} = eval_list(As0, Bs0, Ieval), + eval_function(M, F, As, Bs, extern, Ieval); + +%% Mod:module_info/0,1 +expr({module_info_0,_,Mod}, Bs, _Ieval) -> + {value,[{compile,module_info(Mod,compile)}, + {attributes,module_info(Mod,attributes)}, + {imports,module_info(Mod,imports)}, + {exports,module_info(Mod,exports)}],Bs}; +expr({module_info_1,Line,Mod,[As0]}, Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {value,What,Bs} = expr(As0, Bs0, Ieval), + {value,module_info(Mod, What),Bs}; + +%% Receive statement +expr({'receive',Line,Cs}, Bs0, #ieval{level=Le}=Ieval) -> + trace(receivex, {Le,false}), + eval_receive(get(self), Cs, Bs0, Ieval#ieval{line=Line}); + +%% Receive..after statement +expr({'receive',Line,Cs,To,ToExprs}, Bs0, #ieval{level=Le}=Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {value,ToVal,ToBs} = expr(To, Bs0, Ieval#ieval{last_call=false}), + trace(receivex, {Le,true}), + check_timeoutvalue(ToVal, ToBs, To, Ieval), + {Stamp,_} = statistics(wall_clock), + eval_receive(get(self), Cs, ToVal, ToExprs, ToBs, Bs0, + 0, Stamp, Ieval); + +%% Send (!) +expr({send,Line,To0,Msg0}, Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + Ieval1 = Ieval#ieval{last_call=false}, + {value,To,Bs1} = expr(To0, Bs0, Ieval1), + {value,Msg,Bs2} = expr(Msg0, Bs0, Ieval1), + Bs = merge_bindings(Bs2, Bs1, Ieval), + eval_send(To, Msg, Bs, Ieval); + +%% Binary +expr({bin,Line,Fs}, Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + eval_bits:expr_grp(Fs, Bs0, + fun (E, B) -> expr(E, B, Ieval) end, + [], + false); + +%% List comprehension +expr({lc,_Line,E,Qs}, Bs, Ieval) -> + eval_lc(E, Qs, Bs, Ieval); +expr({bc,_Line,E,Qs}, Bs, Ieval) -> + eval_bc(E, Qs, Bs, Ieval); + +%% Brutal exit on unknown expressions/clauses/values/etc. +expr(E, _Bs, _Ieval) -> + erlang:error({'NYI',E}). + +%% Interpreted fun() called from uninterpreted module, recurse +eval_fun(Cs, As, Bs, Info) -> + dbg_debugged:eval(?MODULE, eval_fun, [Cs,As,Bs,Info]). + +%% eval_lc(Expr,[Qualifier],Bindings,IevalState) -> +%% {value,Value,Bindings}. +%% This is evaluating list comprehensions "straight out of the book". +%% Copied from rv's implementation in erl_eval. +eval_lc(E, Qs, Bs, Ieval) -> + {value,eval_lc1(E, Qs, Bs, Ieval),Bs}. + +eval_lc1(E, [{generate,Line,P,L0}|Qs], Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {value,L1,Bs1} = expr(L0, Bs0, Ieval#ieval{last_call=false}), + flatmap(fun (V) -> + case catch match1(P, V, [], Bs0) of + {match,Bsn} -> + Bs2 = add_bindings(Bsn, Bs1), + eval_lc1(E, Qs, Bs2, Ieval); + nomatch -> [] + end end,L1); +eval_lc1(E, [{b_generate,Line,P,L0}|Qs], Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {value,Bin,_} = expr(L0, Bs0, Ieval#ieval{last_call=false}), + CompFun = fun(NewBs) -> eval_lc1(E, Qs, NewBs, Ieval) end, + eval_b_generate(Bin, P, Bs0, CompFun); +eval_lc1(E, [{guard,Q}|Qs], Bs0, Ieval) -> + case guard(Q, Bs0) of + true -> eval_lc1(E, Qs, Bs0, Ieval); + false -> [] + end; +eval_lc1(E, [Q|Qs], Bs0, Ieval) -> + case expr(Q, Bs0, Ieval#ieval{last_call=false}) of + {value,true,Bs} -> eval_lc1(E, Qs, Bs, Ieval); + _ -> [] + end; +eval_lc1(E, [], Bs, Ieval) -> + {value,V,_} = expr(E, Bs, Ieval#ieval{last_call=false}), + [V]. + +%% eval_bc(Expr,[Qualifier],Bindings,IevalState) -> +%% {value,Value,Bindings}. +%% This is evaluating list comprehensions "straight out of the book". +%% Copied from rv's implementation in erl_eval. +eval_bc(E, Qs, Bs, Ieval) -> + Val = erlang:list_to_bitstring(eval_bc1(E, Qs, Bs, Ieval)), + {value,Val,Bs}. + +eval_bc1(E, [{generate,Line,P,L0}|Qs], Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {value,L1,Bs1} = expr(L0, Bs0, Ieval#ieval{last_call=false}), + flatmap(fun (V) -> + case catch match1(P, V, [], Bs0) of + {match,Bsn} -> + Bs2 = add_bindings(Bsn, Bs1), + eval_bc1(E, Qs, Bs2, Ieval); + nomatch -> [] + end end, L1); +eval_bc1(E, [{b_generate,Line,P,L0}|Qs], Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {value,Bin,_} = expr(L0, Bs0, Ieval#ieval{last_call=false}), + CompFun = fun(NewBs) -> eval_bc1(E, Qs, NewBs, Ieval) end, + eval_b_generate(Bin, P, Bs0, CompFun); +eval_bc1(E, [{guard,Q}|Qs], Bs0, Ieval) -> + case guard(Q, Bs0) of + true -> eval_bc1(E, Qs, Bs0, Ieval); + false -> [] + end; +eval_bc1(E, [Q|Qs], Bs0, Ieval) -> + case expr(Q, Bs0, Ieval#ieval{last_call=false}) of + {value,true,Bs} -> eval_bc1(E, Qs, Bs, Ieval); + _ -> [] + end; +eval_bc1(E, [], Bs, Ieval) -> + {value,V,_} = expr(E, Bs, Ieval#ieval{last_call=false}), + [V]. + +eval_b_generate(<<_/bitstring>>=Bin, P, Bs0, CompFun) -> + Mfun = fun(L, R, Bs) -> match1(L, R, Bs, Bs0) end, + Efun = fun(Exp, Bs) -> expr(Exp, Bs, #ieval{}) end, + case eval_bits:bin_gen(P, Bin, erl_eval:new_bindings(), Bs0, Mfun, Efun) of + {match,Rest,Bs1} -> + Bs2 = add_bindings(Bs1, Bs0), + CompFun(Bs2) ++ eval_b_generate(Rest, P, Bs0, CompFun); + {nomatch,Rest} -> + eval_b_generate(Rest, P, Bs0, CompFun); + done -> + [] + end. + +module_info(Mod, module) -> Mod; +module_info(_Mod, compile) -> []; +module_info(Mod, attributes) -> + {ok, Attr} = dbg_iserver:call(get(int), {lookup, Mod, attributes}), + Attr; +module_info(_Mod, imports) -> []; +module_info(Mod, exports) -> + {ok, Exp} = dbg_iserver:call(get(int), {lookup, Mod, exports}), + Exp; +module_info(_Mod, functions) -> []. + +safe_bif(M, F, As, Bs, Ieval) -> + try apply(M, F, As) of + Value -> + {value,Value,Bs} + catch + Class:Reason -> + exception(Class, Reason, Bs, Ieval) + end. + +eval_send(To, Msg, Bs, Ieval) -> + try To ! Msg of + Msg -> + trace(send, {To,Msg}), + {value,Msg,Bs} + catch + Class:Reason -> + exception(Class, Reason, Bs, Ieval) + end. + +%% Start tracing of messages before fetching current messages in +%% the queue to make sure that no messages are lost. +eval_receive(Debugged, Cs, Bs0, + #ieval{module=M,line=Line,level=Le}=Ieval) -> + %% To avoid private message passing protocol between META + %% and interpreted process. + erlang:trace(Debugged,true,['receive']), + {_,Msgs} = erlang:process_info(Debugged,messages), + case receive_clauses(Cs, Bs0, Msgs) of + nomatch -> + dbg_iserver:cast(get(int), {set_status, self(),waiting,{}}), + dbg_icmd:tell_attached({wait_at,M,Line,Le}), + eval_receive1(Debugged, Cs, Bs0, Ieval); + {eval,B,Bs,Msg} -> + rec_mess(Debugged, Msg, Bs, Ieval), + seq(B, Bs, Ieval) + end. + +eval_receive1(Debugged, Cs, Bs0, Ieval) -> + Msgs = do_receive(Debugged, Bs0, Ieval), + case receive_clauses(Cs, Bs0, Msgs) of + nomatch -> + eval_receive1(Debugged, Cs, Bs0, Ieval); + {eval,B,Bs,Msg} -> + rec_mess(Debugged, Msg, Bs0, Ieval), + dbg_iserver:cast(get(int), {set_status, self(),running,{}}), + dbg_icmd:tell_attached(running), + seq(B, Bs, Ieval) + end. + +check_timeoutvalue(ToVal,_,_,_) when is_integer(ToVal), ToVal>=0 -> true; +check_timeoutvalue(infinity,_,_,_) -> true; +check_timeoutvalue(_ToVal, ToBs, To, Ieval) -> + Line = element(2, To), + exception(error, timeout_value, ToBs, Ieval#ieval{line=Line}). + +eval_receive(Debugged, Cs, 0, ToExprs, ToBs, Bs0, 0, _Stamp, Ieval) -> + {_,Msgs} = erlang:process_info(Debugged,messages), + case receive_clauses(Cs, Bs0, Msgs) of + nomatch -> + trace(received,null), + seq(ToExprs, ToBs, Ieval); + {eval,B,Bs,Msg} -> + rec_mess_no_trace(Debugged, Msg, Bs0, Ieval), + seq(B, Bs, Ieval) + end; +eval_receive(Debugged, Cs, ToVal, ToExprs, ToBs, Bs0, + 0, Stamp, #ieval{module=M,line=Line,level=Le}=Ieval)-> + erlang:trace(Debugged,true,['receive']), + {_,Msgs} = erlang:process_info(Debugged,messages), + case receive_clauses(Cs, Bs0, Msgs) of + nomatch -> + {Stamp1,Time1} = newtime(Stamp,ToVal), + dbg_iserver:cast(get(int), {set_status, self(),waiting,{}}), + dbg_icmd:tell_attached({wait_after_at,M,Line,Le}), + eval_receive(Debugged, Cs, Time1, ToExprs, ToBs, Bs0, + infinity,Stamp1, Ieval); + {eval,B,Bs,Msg} -> + rec_mess(Debugged, Msg, Bs0, Ieval), + seq(B, Bs, Ieval) + end; +eval_receive(Debugged, Cs, ToVal, ToExprs, ToBs, Bs0, + _, Stamp, Ieval) -> + case do_receive(Debugged, ToVal, Stamp, Bs0, Ieval) of + timeout -> + trace(received,null), + rec_mess(Debugged), + dbg_iserver:cast(get(int), {set_status, self(),running,{}}), + dbg_icmd:tell_attached(running), + seq(ToExprs, ToBs, Ieval); + Msgs -> + case receive_clauses(Cs, Bs0, Msgs) of + nomatch -> + {Stamp1,Time1} = newtime(Stamp,ToVal), + eval_receive(Debugged, Cs, Time1, ToExprs, ToBs, + Bs0, infinity,Stamp1, Ieval); + {eval,B,Bs,Msg} -> + rec_mess(Debugged, Msg, Bs0, Ieval), + dbg_iserver:cast(get(int), + {set_status, self(), running, {}}), + dbg_icmd:tell_attached(running), + seq(B, Bs, Ieval) + end + end. + +do_receive(Debugged, Bs, Ieval) -> + receive + {trace,Debugged,'receive',Msg} -> + [Msg]; + Msg -> + check_exit_msg(Msg, Bs, Ieval), + dbg_icmd:handle_msg(Msg, wait_at, Bs, Ieval), + do_receive(Debugged, Bs, Ieval) + end. + +do_receive(Debugged, Time, Stamp, Bs, Ieval) -> + receive + {trace,Debugged,'receive',Msg} -> + [Msg]; + {user, timeout} -> + timeout; + Msg -> + check_exit_msg(Msg, Bs, Ieval), + dbg_icmd:handle_msg(Msg, wait_after_at, Bs, Ieval), + {Stamp1,Time1} = newtime(Stamp,Time), + do_receive(Debugged, Time1, Stamp1, Bs, Ieval) + after Time -> + timeout + end. + +newtime(Stamp,infinity) -> + {Stamp,infinity}; +newtime(Stamp,Time) -> + {Stamp1,_} = statistics(wall_clock), + case Time - (Stamp1 - Stamp) of + NewTime when NewTime > 0 -> + {Stamp1,NewTime}; + _ -> + {Stamp1,0} + end. + +rec_mess(Debugged, Msg, Bs, Ieval) -> + erlang:trace(Debugged, false, ['receive']), + flush_traces(Debugged), + Debugged ! {sys,self(),{'receive',Msg}}, + rec_ack(Debugged, Bs, Ieval). + +rec_mess(Debugged) -> + erlang:trace(Debugged, false, ['receive']), + flush_traces(Debugged). + +rec_mess_no_trace(Debugged, Msg, Bs, Ieval) -> + Debugged ! {sys,self(),{'receive',Msg}}, + rec_ack(Debugged, Bs, Ieval). + +rec_ack(Debugged, Bs, Ieval) -> + receive + {Debugged,rec_acked} -> + true; + Msg -> + check_exit_msg(Msg, Bs, Ieval), + io:format("***WARNING*** Unexp msg ~p, ieval ~p~n", + [Msg, Ieval]) + end. + +flush_traces(Debugged) -> + receive + {trace,Debugged,'receive',_} -> + flush_traces(Debugged) + after 0 -> + true + end. + +%% eval_list(ExpressionList, Bindings, Ieval) +%% Evaluate a list of expressions "in parallel" at the same level. +eval_list(Es, Bs, Ieval) -> + eval_list(Es, [], Bs, Bs, Ieval). + +eval_list([E|Es], Vs, BsOrig, Bs0, Ieval) -> + {value,V,Bs1} = expr(E, BsOrig, Ieval#ieval{last_call=false}), + eval_list(Es, [V|Vs], BsOrig, merge_bindings(Bs1,Bs0,Ieval), Ieval); +eval_list([], Vs, _, Bs, _Ieval) -> + {lists:reverse(Vs,[]),Bs}. + +%% if_clauses(Clauses, Bindings, Ieval) +if_clauses([{clause,_,[],G,B}|Cs], Bs, Ieval) -> + case guard(G, Bs) of + true -> + seq(B, Bs, Ieval); + false -> + if_clauses(Cs, Bs, Ieval) + end; +if_clauses([], Bs, Ieval) -> + exception(error, if_clause, Bs, Ieval). + +%% case_clauses(Value, Clauses, Bindings, Error, Ieval) +%% Error = try_clause � case_clause +case_clauses(Val, [{clause,_,[P],G,B}|Cs], Bs0, Error, Ieval) -> + case match(P, Val, Bs0) of + {match,Bs} -> + case guard(G, Bs) of + true -> + seq(B, Bs, Ieval); + false -> + case_clauses(Val, Cs, Bs0, Error, Ieval) + end; + nomatch -> + case_clauses(Val, Cs, Bs0, Error, Ieval) + end; +case_clauses(Val,[], Bs, Error, Ieval) -> + exception(error, {Error,Val}, Bs, Ieval). + +%% catch_clauses(Exception, Clauses, Bindings, Ieval) +%% Exception = {Class,Reason,[]} +catch_clauses(Exception, [{clause,_,[P],G,B}|CatchCs], Bs0, Ieval) -> + case match(P, Exception, Bs0) of + {match,Bs} -> + case guard(G, Bs) of + true -> + %% Exception caught, reset exit info + put(exit_info, undefined), + pop(Ieval#ieval.level), + seq(B, Bs, Ieval); + false -> + catch_clauses(Exception, CatchCs, Bs0, Ieval) + end; + nomatch -> + catch_clauses(Exception, CatchCs, Bs0, Ieval) + end; +catch_clauses({Class,Reason,[]}, [], _Bs, _Ieval) -> + erlang:Class(Reason). + +receive_clauses(Cs, Bs0, [Msg|Msgs]) -> + case rec_clauses(Cs, Bs0, Msg) of + nomatch -> + receive_clauses(Cs, Bs0, Msgs); + {eval,B,Bs} -> + {eval,B,Bs,Msg} + end; +receive_clauses(_, _, []) -> + nomatch. + +rec_clauses([{clause,_,Pars,G,B}|Cs], Bs0, Msg) -> + case rec_match(Pars, Msg, Bs0) of + {match,Bs} -> + case guard(G, Bs) of + true -> + trace(received, Msg), + {eval,B,Bs}; + false -> + rec_clauses(Cs, Bs0, Msg) + end; + nomatch -> + rec_clauses(Cs, Bs0, Msg) + end; +rec_clauses([], _, _) -> + nomatch. + +%% guard(GuardTests,Bindings) +%% Evaluate a list of guards. +guard([], _) -> true; +guard(Gs, Bs) -> or_guard(Gs, Bs). + +or_guard([G|Gs], Bs) -> + %% Short-circuit OR. + case and_guard(G, Bs) of + true -> true; + false -> or_guard(Gs, Bs) + end; +or_guard([], _) -> false. + +and_guard([G|Gs], Bs) -> + %% Short-circuit AND. + case catch guard_expr(G, Bs) of + {value,true} -> and_guard(Gs, Bs); + _ -> false + end; +and_guard([],_) -> true. + +guard_exprs([A0|As0], Bs) -> + {value,A} = guard_expr(A0, Bs), + {values,As} = guard_exprs(As0, Bs), + {values,[A|As]}; +guard_exprs([], _) -> + {values,[]}. + +guard_expr({'andalso',_,E1,E2}, Bs) -> + case guard_expr(E1, Bs) of + {value,false}=Res -> Res; + {value,true} -> + case guard_expr(E2, Bs) of + {value,Bool}=Res when is_boolean(Bool) -> Res + end + end; +guard_expr({'orelse',_,E1,E2}, Bs) -> + case guard_expr(E1, Bs) of + {value,true}=Res -> Res; + {value,false} -> + case guard_expr(E2, Bs) of + {value,Bool}=Res when is_boolean(Bool) -> Res + end + end; +guard_expr({dbg,_,self,[]}, _) -> + {value,get(self)}; +guard_expr({safe_bif,_,erlang,'not',As0}, Bs) -> + {values,As} = guard_exprs(As0, Bs), + {value,apply(erlang, 'not', As)}; +guard_expr({safe_bif,_,Mod,Func,As0}, Bs) -> + {values,As} = guard_exprs(As0, Bs), + {value,apply(Mod, Func, As)}; +guard_expr({var,_,V}, Bs) -> + {value,_} = binding(V, Bs); +guard_expr({value,_,Val}, _Bs) -> + {value,Val}; +guard_expr({cons,_,H0,T0}, Bs) -> + {value,H} = guard_expr(H0, Bs), + {value,T} = guard_expr(T0, Bs), + {value,[H|T]}; +guard_expr({tuple,_,Es0}, Bs) -> + {values,Es} = guard_exprs(Es0, Bs), + {value,list_to_tuple(Es)}; +guard_expr({bin,_,Flds}, Bs) -> + {value,V,_Bs} = + eval_bits:expr_grp(Flds, Bs, + fun(E,B) -> + {value,V} = guard_expr(E,B), + {value,V,B} + end, [], false), + {value,V}. + +%% match(Pattern,Term,Bs) -> {match,Bs} | nomatch +match(Pat, Term, Bs) -> + try match1(Pat, Term, Bs, Bs) + catch + Result -> Result + end. + +match1({value,_,V}, V, Bs,_BBs) -> + {match,Bs}; +match1({var,_,'_'}, Term, Bs,_BBs) -> % Anonymous variable matches + {match,add_anon(Term, Bs)}; % everything,save it anyway +match1({var,_,Name}, Term, Bs, _BBs) -> + case binding(Name, Bs) of + {value,Term} -> + {match,Bs}; + {value,_} -> + throw(nomatch); + unbound -> + {match,[{Name,Term}|Bs]} % Add the new binding + end; +match1({match,_,Pat1,Pat2}, Term, Bs0, BBs) -> + {match,Bs1} = match1(Pat1, Term, Bs0, BBs), + match1(Pat2, Term, Bs1, BBs); +match1({cons,_,H,T}, [H1|T1], Bs0, BBs) -> + {match,Bs} = match1(H, H1, Bs0, BBs), + match1(T, T1, Bs, BBs); +match1({tuple,_,Elts}, Tuple, Bs, BBs) + when length(Elts) =:= tuple_size(Tuple) -> + match_tuple(Elts, Tuple, 1, Bs, BBs); +match1({bin,_,Fs}, B, Bs0, BBs0) when is_bitstring(B) -> + Bs1 = lists:sort(Bs0), %Kludge. + BBs = lists:sort(BBs0), + try eval_bits:match_bits(Fs, B, Bs1, BBs, + fun(L, R, Bs) -> match1(L, R, Bs, BBs) end, + fun(E, Bs) -> expr(E, Bs, #ieval{}) end, + false) of + Match -> Match + catch + _:_ -> throw(nomatch) + end; +match1(_,_,_,_) -> + throw(nomatch). + +match_tuple([E|Es], Tuple, I, Bs0, BBs) -> + {match,Bs} = match1(E, element(I, Tuple), Bs0, BBs), + match_tuple(Es, Tuple, I+1, Bs, BBs); +match_tuple([], _, _, Bs, _BBs) -> + {match,Bs}. + +head_match([Par|Pars], [Arg|Args], Bs0, BBs) -> + try match1(Par, Arg, Bs0, BBs) of + {match,Bs} -> head_match(Pars, Args, Bs, BBs) + catch + Result -> Result + end; +head_match([],[],Bs,_) -> {match,Bs}. + +rec_match([Par],Msg,Bs0) -> + match(Par,Msg,Bs0). + +binding(Name,[{Name,Val}|_]) -> + {value,Val}; +binding(Name,[_,{Name,Val}|_]) -> + {value,Val}; +binding(Name,[_,_,{Name,Val}|_]) -> + {value,Val}; +binding(Name,[_,_,_,{Name,Val}|_]) -> + {value,Val}; +binding(Name,[_,_,_,_,{Name,Val}|_]) -> + {value,Val}; +binding(Name,[_,_,_,_,_,{Name,Val}|_]) -> + {value,Val}; +binding(Name,[_,_,_,_,_,_|Bs]) -> + binding(Name,Bs); +binding(Name,[_,_,_,_,_|Bs]) -> + binding(Name,Bs); +binding(Name,[_,_,_,_|Bs]) -> + binding(Name,Bs); +binding(Name,[_,_,_|Bs]) -> + binding(Name,Bs); +binding(Name,[_,_|Bs]) -> + binding(Name,Bs); +binding(Name,[_|Bs]) -> + binding(Name,Bs); +binding(_,[]) -> + unbound. + +add_anon(Val,[{'_',_}|Bs]) -> + [{'_',Val}|Bs]; +add_anon(Val,[B1,{'_',_}|Bs]) -> + [B1,{'_',Val}|Bs]; +add_anon(Val,[B1,B2,{'_',_}|Bs]) -> + [B1,B2,{'_',Val}|Bs]; +add_anon(Val,[B1,B2,B3,{'_',_}|Bs]) -> + [B1,B2,B3,{'_',Val}|Bs]; +add_anon(Val,[B1,B2,B3,B4,{'_',_}|Bs]) -> + [B1,B2,B3,B4,{'_',Val}|Bs]; +add_anon(Val,[B1,B2,B3,B4,B5,{'_',_}|Bs]) -> + [B1,B2,B3,B4,B5,{'_',Val}|Bs]; +add_anon(Val,[B1,B2,B3,B4,B5,B6|Bs]) -> + [B1,B2,B3,B4,B5,B6|add_anon(Val,Bs)]; +add_anon(Val,[B1,B2,B3,B4,B5|Bs]) -> + [B1,B2,B3,B4,B5|add_anon(Val,Bs)]; +add_anon(Val,[B1,B2,B3,B4|Bs]) -> + [B1,B2,B3,B4|add_anon(Val,Bs)]; +add_anon(Val,[B1,B2,B3|Bs]) -> + [B1,B2,B3|add_anon(Val,Bs)]; +add_anon(Val,[B1,B2|Bs]) -> + [B1,B2|add_anon(Val,Bs)]; +add_anon(Val,[B1|Bs]) -> + [B1|add_anon(Val,Bs)]; +add_anon(Val,[]) -> + [{'_',Val}]. + +%% merge_bindings(Bindings1, Bindings2, Ieval) +%% Merge bindings detecting bad matches. +%% Special case '_',save the new one !!! +%% Bindings1 is the newest bindings. +merge_bindings(Bs, Bs, _Ieval) -> + Bs; % Identical bindings +merge_bindings([{Name,V}|B1s], B2s, Ieval) -> + case binding(Name, B2s) of + {value,V} -> % Already there, and the same + merge_bindings(B1s, B2s, Ieval); + {value,_} when Name=='_' -> % Already there, but anonymous + B2s1 = lists:keydelete('_', 1, B2s), + [{Name,V}|merge_bindings(B1s, B2s1, Ieval)]; + {value,_} -> % Already there, but different => badmatch + exception(error, {badmatch,V}, B2s, Ieval); + unbound -> % Not there,add it + [{Name,V}|merge_bindings(B1s, B2s, Ieval)] + end; +merge_bindings([], B2s, _Ieval) -> + B2s. + +%% add_bindings(Bindings1,Bindings2) +%% Add Bindings1 to Bindings2. Bindings in +%% Bindings1 hides bindings in Bindings2. +%% Used in list comprehensions (and funs). +add_bindings(Bs1,[]) -> + Bs1; +add_bindings([{Name,V}|Bs],ToBs0) -> + ToBs = add_binding(Name,V,ToBs0), + add_bindings(Bs,ToBs); +add_bindings([],ToBs) -> + ToBs. + +add_binding(N,Val,[{N,_}|Bs]) -> + [{N,Val}|Bs]; +add_binding(N,Val,[B1,{N,_}|Bs]) -> + [B1,{N,Val}|Bs]; +add_binding(N,Val,[B1,B2,{N,_}|Bs]) -> + [B1,B2,{N,Val}|Bs]; +add_binding(N,Val,[B1,B2,B3,{N,_}|Bs]) -> + [B1,B2,B3,{N,Val}|Bs]; +add_binding(N,Val,[B1,B2,B3,B4,{N,_}|Bs]) -> + [B1,B2,B3,B4,{N,Val}|Bs]; +add_binding(N,Val,[B1,B2,B3,B4,B5,{N,_}|Bs]) -> + [B1,B2,B3,B4,B5,{N,Val}|Bs]; +add_binding(N,Val,[B1,B2,B3,B4,B5,B6|Bs]) -> + [B1,B2,B3,B4,B5,B6|add_binding(N,Val,Bs)]; +add_binding(N,Val,[B1,B2,B3,B4,B5|Bs]) -> + [B1,B2,B3,B4,B5|add_binding(N,Val,Bs)]; +add_binding(N,Val,[B1,B2,B3,B4|Bs]) -> + [B1,B2,B3,B4|add_binding(N,Val,Bs)]; +add_binding(N,Val,[B1,B2,B3|Bs]) -> + [B1,B2,B3|add_binding(N,Val,Bs)]; +add_binding(N,Val,[B1,B2|Bs]) -> + [B1,B2|add_binding(N,Val,Bs)]; +add_binding(N,Val,[B1|Bs]) -> + [B1|add_binding(N,Val,Bs)]; +add_binding(N,Val,[]) -> + [{N,Val}]. diff --git a/lib/debugger/src/dbg_ieval.hrl b/lib/debugger/src/dbg_ieval.hrl new file mode 100644 index 0000000000..a344748f48 --- /dev/null +++ b/lib/debugger/src/dbg_ieval.hrl @@ -0,0 +1,26 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-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% +%% +-record(ieval, {level = 1, % Current call level + line = -1, % Current source code line (of module) + module, % MFA which called the currently + function, % interpreted function + arguments, % + last_call = false % True if current expression is + }). % the VERY last to be evaluated + % (ie at all, not only in a clause) diff --git a/lib/debugger/src/dbg_iload.erl b/lib/debugger/src/dbg_iload.erl new file mode 100644 index 0000000000..1216338006 --- /dev/null +++ b/lib/debugger/src/dbg_iload.erl @@ -0,0 +1,669 @@ +%% +%% %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_iload). + +%% External exports +-export([load_mod/4]). + +%% Internal exports +-export([load_mod1/4]). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% load_mod(Mod, File, Binary, Db) -> {ok, Mod} +%% Mod = atom() +%% File = string() Source file (including path) +%% Binary = binary() +%% Db = ETS identifier +%% Load a new module into the database. +%% +%% We want the loading of a module to be syncronous so no other +%% process tries to interpret code in a module not being completely +%% loaded. This is achieved as this function is called from +%% dbg_iserver. We are suspended until the module has been loaded. +%%-------------------------------------------------------------------- +load_mod(Mod, File, Binary, Db) -> + Flag = process_flag(trap_exit, true), + Pid = spawn_link(?MODULE, load_mod1, [Mod, File, Binary, Db]), + receive + {'EXIT', Pid, What} -> + process_flag(trap_exit, Flag), + What + end. + +%%==================================================================== +%% Internal exports +%%==================================================================== + +load_mod1(Mod, File, Binary, Db) -> + store_module(Mod, File, Binary, Db), + exit({ok, Mod}). + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +store_module(Mod, File, Binary, Db) -> + {interpreter_module, Exp, Abst, Src, MD5} = binary_to_term(Binary), + Forms = case abstr(Abst) of + {abstract_v1,Forms0} -> Forms0; + {abstract_v2,Forms0} -> Forms0; + {raw_abstract_v1,Code0} -> + Code = interpret_file_attribute(Code0), + {_,_,Forms0,_} = sys_pre_expand:module(Code, []), + Forms0 + end, + dbg_idb:insert(Db, mod_file, File), + dbg_idb:insert(Db, exports, Exp), + dbg_idb:insert(Db, defs, []), + + put(vcount, 0), + put(fun_count, 0), + put(funs, []), + put(mod_md5, MD5), + Attr = store_forms(Forms, Mod, Db, Exp, []), + erase(mod_md5), + erase(current_function), +% store_funs(Db, Mod), + erase(vcount), + erase(funs), + erase(fun_count), + + dbg_idb:insert(Db, attributes, Attr), + NewBinary = store_mod_line_no(Mod, Db, binary_to_list(Src)), + dbg_idb:insert(Db, mod_bin, NewBinary), + dbg_idb:insert(Db, mod_raw, <<Src/binary,0:8>>), %% Add eos + dbg_idb:insert(Db, module, Mod). +%% Adjust line numbers using the file/2 attribute. +%% Also take the absolute value of line numbers. +%% This simple fix will make the marker point at the correct line +%% (assuming the file attributes are correct) in the source; it will +%% not point at code in included files. +interpret_file_attribute(Code) -> + epp:interpret_file_attribute(Code). + + +abstr(Bin) when is_binary(Bin) -> binary_to_term(Bin); +abstr(Term) -> Term. + +% store_funs(Db, Mod) -> +% store_funs_1(get(funs), Db, Mod). + +% store_funs_1([{Name,Index,Uniq,_,_,Arity,Cs}|Fs], Db, Mod) -> +% dbg_idb:insert(Db, {Mod,Name,Arity,false}, Cs), +% dbg_idb:insert(Db, {'fun',Mod,Index,Uniq}, {Name,Arity,Cs}), +% store_funs_1(Fs, Db, Mod); +% store_funs_1([], _, _) -> ok. + +store_forms([{function,_,module_info,0,_}|Fs], Mod, Db, Exp, Attr) -> + Cs = [{clause,0,[],[], [{module_info_0,0,Mod}]}], + dbg_idb:insert(Db, {Mod,module_info,0,true}, Cs), + store_forms(Fs, Mod, Db, Exp, Attr); +store_forms([{function,_,module_info,1,_}|Fs], Mod, Db, Exp, Attr) -> + Cs = [{clause,0,[{var,0,'What'}],[], [{module_info_1,0,Mod,[{var,0,'What'}]}]}], + dbg_idb:insert(Db, {Mod,module_info,1,true}, Cs), + store_forms(Fs, Mod, Db, Exp, Attr); +store_forms([{function,_,Name,Arity,Cs0}|Fs], Mod, Db, Exp, Attr) -> + FA = {Name,Arity}, + put(current_function, FA), + Cs = clauses(Cs0), + Exported = lists:member(FA, Exp), + dbg_idb:insert(Db, {Mod,Name,Arity,Exported}, Cs), + store_forms(Fs, Mod, Db, Exp, Attr); +store_forms([{attribute,_,Name,Val}|Fs], Mod, Db, Exp, Attr) -> + store_forms(Fs, Mod, Db, Exp, [{Name,Val}|Attr]); +store_forms([F|_], _Mod, _Db, _Exp, _Attr) -> + exit({unknown_form,F}); +store_forms([], _, _, _, Attr) -> + lists:reverse(Attr). + +store_mod_line_no(Mod, Db, Contents) -> + store_mod_line_no(Mod, Db, Contents, 1, 0, []). + +store_mod_line_no(_, _, [], _, _, NewCont) -> + list_to_binary(lists:reverse(NewCont)); +store_mod_line_no(Mod, Db, Contents, LineNo, Pos, NewCont) when is_integer(LineNo) -> + {ContTail,Pos1,NewCont1} = store_line(Mod, Db, Contents, LineNo, Pos, NewCont), + store_mod_line_no(Mod, Db, ContTail, LineNo+1, Pos1, NewCont1). + +store_line(_, Db, Contents, LineNo, Pos, NewCont) -> + {ContHead,ContTail,PosNL} = get_nl(Contents,Pos+8,[]), + dbg_idb:insert(Db,LineNo,{Pos+8,PosNL}), + {ContTail,PosNL+1,[make_lineno(LineNo, 8, ContHead)|NewCont]}. + +make_lineno(N, P, Acc) -> + S = integer_to_list(N), + S ++ [$:|spaces(P-length(S)-1, Acc)]. + +spaces(P, Acc) when P > 0 -> + spaces(P-1, [$\s|Acc]); +spaces(_, Acc) -> Acc. + +get_nl([10|T],Pos,Head) -> {lists:reverse([10|Head]),T,Pos}; +get_nl([H|T],Pos,Head) -> + get_nl(T,Pos+1,[H|Head]); +get_nl([],Pos,Head) -> {lists:reverse(Head),[],Pos}. + +%%% Rewrite the abstract syntax tree to that it will be easier (== faster) +%%% to interpret. + +clauses([C0|Cs]) -> + C1 = clause(C0), + [C1|clauses(Cs)]; +clauses([]) -> []. + +clause({clause,Line,H0,G0,B0}) -> + H1 = head(H0), + G1 = guard(G0), + B1 = exprs(B0), + {clause,Line,H1,G1,B1}. + +head(Ps) -> patterns(Ps). + +%% These patterns are processed "sequentially" for purposes of variable +%% definition etc. + +patterns([P0|Ps]) -> + P1 = pattern(P0), + [P1|patterns(Ps)]; +patterns([]) -> []. + +%% N.B. Only valid patterns are included here. + +pattern({var,Line,V}) -> {var,Line,V}; +pattern({char,Line,I}) -> {value,Line,I}; +pattern({integer,Line,I}) -> {value,Line,I}; +pattern({match,Line,Pat1,Pat2}) -> + {match,Line,pattern(Pat1),pattern(Pat2)}; +pattern({float,Line,F}) -> {value,Line,F}; +pattern({atom,Line,A}) -> {value,Line,A}; +pattern({string,Line,S}) -> {value,Line,S}; +pattern({nil,Line}) -> {value,Line,[]}; +pattern({cons,Line,H0,T0}) -> + H1 = pattern(H0), + T1 = pattern(T0), + {cons,Line,H1,T1}; +pattern({tuple,Line,Ps0}) -> + Ps1 = pattern_list(Ps0), + {tuple,Line,Ps1}; +pattern({op,_,'-',{integer,Line,I}}) -> + {value,Line,-I}; +pattern({op,_,'+',{integer,Line,I}}) -> + {value,Line,I}; +pattern({op,_,'-',{char,Line,I}}) -> + {value,Line,-I}; +pattern({op,_,'+',{char,Line,I}}) -> + {value,Line,I}; +pattern({op,_,'-',{float,Line,I}}) -> + {value,Line,-I}; +pattern({op,_,'+',{float,Line,I}}) -> + {value,Line,I}; +pattern({bin,Line,Grp}) -> + Grp1 = pattern_list(Grp), + {bin,Line,Grp1}; +pattern({bin_element,Line,Expr,Size,Type}) -> + Expr1 = pattern(Expr), + Size1 = expr(Size), + {bin_element,Line,Expr1,Size1,Type}. + +%% These patterns are processed "in parallel" for purposes of variable +%% definition etc. + +pattern_list([P0|Ps]) -> + P1 = pattern(P0), + [P1|pattern_list(Ps)]; +pattern_list([]) -> []. + +guard([G0|Gs]) -> + G1 = and_guard(G0), + [G1|guard(Gs)]; +guard([]) -> []. + +and_guard([{atom,_,true}|Gs]) -> + and_guard(Gs); +and_guard([G0|Gs]) -> + G1 = guard_test(G0), + [G1|and_guard(Gs)]; +and_guard([]) -> []. + +guard_test({call,Line,{remote,_,{atom,_,erlang},{atom,_,F}},As0}) -> + As = gexpr_list(As0), + case map_guard_bif(F, length(As0)) of + {ok,Name} -> + {safe_bif,Line,erlang,Name,As}; + error -> + {safe_bif,Line,erlang,F,As} + end; +guard_test({op,Line,Op,L0}) -> + true = erl_internal:arith_op(Op, 1) orelse %Assertion. + erl_internal:bool_op(Op, 1), + L1 = gexpr(L0), + {safe_bif,Line,erlang,Op,[L1]}; +guard_test({op,Line,Op,L0,R0}) when Op =:= 'andalso'; Op =:= 'orelse' -> + L1 = gexpr(L0), + R1 = gexpr(R0), %They see the same variables + {Op,Line,L1,R1}; +guard_test({op,Line,Op,L0,R0}) -> + true = erl_internal:comp_op(Op, 2) orelse %Assertion. + erl_internal:bool_op(Op, 2) orelse + erl_internal:arith_op(Op, 2), + L1 = gexpr(L0), + R1 = gexpr(R0), %They see the same variables + {safe_bif,Line,erlang,Op,[L1,R1]}; +guard_test({integer,_,_}=I) -> I; +guard_test({char,_,_}=C) -> C; +guard_test({float,_,_}=F) -> F; +guard_test({atom,_,_}=A) -> A; +guard_test({nil,_}=N) -> N; +guard_test({var,_,_}=V) ->V. % Boolean var + +map_guard_bif(integer, 1) -> {ok,is_integer}; +map_guard_bif(float, 1) -> {ok,is_float}; +map_guard_bif(number, 1) -> {ok,is_number}; +map_guard_bif(atom, 1) -> {ok,is_atom}; +map_guard_bif(list, 1) -> {ok,is_list}; +map_guard_bif(tuple, 1) -> {ok,is_tuple}; +map_guard_bif(pid, 1) -> {ok,is_pid}; +map_guard_bif(reference, 1) -> {ok,is_reference}; +map_guard_bif(port, 1) -> {ok,is_port}; +map_guard_bif(binary, 1) -> {ok,is_binary}; +map_guard_bif(function, 1) -> {ok,is_function}; +map_guard_bif(_, _) -> error. + +gexpr({var,Line,V}) -> {var,Line,V}; +gexpr({integer,Line,I}) -> {value,Line,I}; +gexpr({char,Line,I}) -> {value,Line,I}; +gexpr({float,Line,F}) -> {value,Line,F}; +gexpr({atom,Line,A}) -> {value,Line,A}; +gexpr({string,Line,S}) -> {value,Line,S}; +gexpr({nil,Line}) -> {value,Line,[]}; +gexpr({cons,Line,H0,T0}) -> + case {gexpr(H0),gexpr(T0)} of + {{value,Line,H1},{value,Line,T1}} -> {value,Line,[H1|T1]}; + {H1,T1} -> {cons,Line,H1,T1} + end; +gexpr({tuple,Line,Es0}) -> + Es1 = gexpr_list(Es0), + {tuple,Line,Es1}; +gexpr({bin,Line,Flds0}) -> + Flds = gexpr_list(Flds0), + {bin,Line,Flds}; +gexpr({bin_element,Line,Expr0,Size0,Type}) -> + Expr = gexpr(Expr0), + Size = gexpr(Size0), + {bin_element,Line,Expr,Size,Type}; +%%% The previous passes have added the module name 'erlang' to +%%% all BIF calls, even in guards. +gexpr({call,Line,{remote,_,{atom,_,erlang},{atom,_,self}},[]}) -> + {dbg, Line, self, []}; +gexpr({call,Line,{remote,_,{atom,_,erlang},{atom,_,F}},As0}) -> + As = gexpr_list(As0), + {safe_bif,Line,erlang,F,As}; +gexpr({op,Line,Op,A0}) -> + erl_internal:arith_op(Op, 1), + A1 = gexpr(A0), + {safe_bif,Line,erlang,Op,[A1]}; +gexpr({op,Line,Op,L0,R0}) when Op =:= 'andalso'; Op =:= 'orelse' -> + L1 = gexpr(L0), + R1 = gexpr(R0), %They see the same variables + {Op,Line,L1,R1}; +gexpr({op,Line,Op,L0,R0}) -> + true = erl_internal:arith_op(Op, 2) orelse erl_internal:comp_op(Op, 2) + orelse erl_internal:bool_op(Op, 2), + L1 = gexpr(L0), + R1 = gexpr(R0), %They see the same variables + {safe_bif,Line,erlang,Op,[L1,R1]}. + +%% These expressions are processed "in parallel" for purposes of variable +%% definition etc. + +gexpr_list([E0|Es]) -> + E1 = gexpr(E0), + [E1|gexpr_list(Es)]; +gexpr_list([]) -> []. + +%% These expressions are processed "sequentially" for purposes of variable +%% definition etc. + +exprs([E0|Es]) -> + E1 = expr(E0), + [E1|exprs(Es)]; +exprs([]) -> []. + +expr({var,Line,V}) -> {var,Line,V}; +expr({integer,Line,I}) -> {value,Line,I}; +expr({char,Line,I}) -> {value,Line,I}; +expr({float,Line,F}) -> {value,Line,F}; +expr({atom,Line,A}) -> {value,Line,A}; +expr({string,Line,S}) -> {value,Line,S}; +expr({nil,Line}) -> {value,Line,[]}; +expr({cons,Line,H0,T0}) -> + case {expr(H0),expr(T0)} of + {{value,Line,H1},{value,Line,T1}} -> {value,Line,[H1|T1]}; + {H1,T1} -> {cons,Line,H1,T1} + end; +expr({tuple,Line,Es0}) -> + Es1 = expr_list(Es0), + {tuple,Line,Es1}; +expr({block,Line,Es0}) -> + %% Unfold block into a sequence. + Es1 = exprs(Es0), + {block,Line,Es1}; +expr({'if',Line,Cs0}) -> + Cs1 = icr_clauses(Cs0), + {'if',Line,Cs1}; +expr({'case',Line,E0,Cs0}) -> + E1 = expr(E0), + Cs1 = icr_clauses(Cs0), + {'case',Line,E1,Cs1}; +expr({'receive',Line,Cs0}) -> + Cs1 = icr_clauses(Cs0), + {'receive',Line,Cs1}; +expr({'receive',Line,Cs0,To0,ToEs0}) -> + To1 = expr(To0), + ToEs1 = exprs(ToEs0), + Cs1 = icr_clauses(Cs0), + {'receive',Line,Cs1,To1,ToEs1}; +expr({'fun',Line,{clauses,Cs0},{_,_,Name}}) when is_atom(Name) -> + %% New R10B-2 format (abstract_v2). + Cs = fun_clauses(Cs0), + {make_fun,Line,Name,Cs}; +expr({'fun',Line,{clauses,Cs0},{_,_,_,_,Name}}) when is_atom(Name) -> + %% New R8 format (abstract_v2). + Cs = fun_clauses(Cs0), + {make_fun,Line,Name,Cs}; +expr({'fun',Line,{function,F,A},{_Index,_OldUniq,Name}}) -> + %% New R8 format (abstract_v2). + As = new_vars(A, Line), + Cs = [{clause,Line,As,[],[{local_call,Line,F,As}]}], + {make_fun,Line,Name,Cs}; +expr({'fun',_,{clauses,_},{_OldUniq,_Hvss,_Free}}) -> + %% Old format (abstract_v1). + exit({?MODULE,old_funs}); +expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,self}},[]}) -> + {dbg,Line,self,[]}; +expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,get_stacktrace}},[]}) -> + {dbg,Line,get_stacktrace,[]}; +expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,throw}},[_]=As}) -> + {dbg,Line,throw,expr_list(As)}; +expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,error}},[_]=As}) -> + {dbg,Line,error,expr_list(As)}; +expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,fault}},[_]=As}) -> + {dbg,Line,fault,expr_list(As)}; +expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,exit}},[_]=As}) -> + {dbg,Line,exit,expr_list(As)}; +expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,apply}},As0}) + when length(As0) == 3 -> + As = expr_list(As0), + {apply,Line,As}; +expr({call,Line,{remote,_,{atom,_,Mod},{atom,_,Func}},As0}) -> + As = expr_list(As0), + case erlang:is_builtin(Mod, Func, length(As)) of + false -> + {call_remote,Line,Mod,Func,As}; + true -> + case bif_type(Mod, Func) of + safe -> {safe_bif,Line,Mod,Func,As}; + spawn -> {spawn_bif,Line,Mod,Func,As}; + unsafe ->{bif,Line,Mod,Func,As} + end + end; +expr({call,Line,{remote,_,Mod0,Func0},As0}) -> + %% New R8 format (abstract_v2). + Mod = expr(Mod0), + Func = expr(Func0), + As = consify(expr_list(As0)), + {apply,Line,[Mod,Func,As]}; +expr({call,Line,{atom,_,Func},As0}) -> + As = expr_list(As0), + {local_call,Line,Func,As}; +expr({call,Line,Fun0,As0}) -> + Fun = expr(Fun0), + As = expr_list(As0), + {apply_fun,Line,Fun,As}; +expr({'catch',Line,E0}) -> + %% No new variables added. + E1 = expr(E0), + {'catch',Line,E1}; +expr({'try',Line,Es0,CaseCs0,CatchCs0,As0}) -> + %% No new variables added. + Es = expr_list(Es0), + CaseCs = icr_clauses(CaseCs0), + CatchCs = icr_clauses(CatchCs0), + As = expr_list(As0), + {'try',Line,Es,CaseCs,CatchCs,As}; +expr({'query', Line, E0}) -> + E = expr(E0), + {'query', Line, E}; +expr({lc,Line,E0,Gs0}) -> %R8. + Gs = lists:map(fun ({generate,L,P0,Qs}) -> + {generate,L,expr(P0),expr(Qs)}; + ({b_generate,L,P0,Qs}) -> %R12. + {b_generate,L,expr(P0),expr(Qs)}; + (Expr) -> + case is_guard_test(Expr) of + true -> {guard,[[guard_test(Expr)]]}; + false -> expr(Expr) + end + end, Gs0), + {lc,Line,expr(E0),Gs}; +expr({bc,Line,E0,Gs0}) -> %R12. + Gs = lists:map(fun ({generate,L,P0,Qs}) -> + {generate,L,expr(P0),expr(Qs)}; + ({b_generate,L,P0,Qs}) -> %R12. + {b_generate,L,expr(P0),expr(Qs)}; + (Expr) -> + case is_guard_test(Expr) of + true -> {guard,[[guard_test(Expr)]]}; + false -> expr(Expr) + end + end, Gs0), + {bc,Line,expr(E0),Gs}; +expr({match,Line,P0,E0}) -> + E1 = expr(E0), + P1 = pattern(P0), + {match,Line,P1,E1}; +expr({op,Line,Op,A0}) -> + A1 = expr(A0), + {op,Line,Op,[A1]}; +expr({op,Line,'++',L0,R0}) -> + L1 = expr(L0), + R1 = expr(R0), %They see the same variables + {op,Line,append,[L1,R1]}; +expr({op,Line,'--',L0,R0}) -> + L1 = expr(L0), + R1 = expr(R0), %They see the same variables + {op,Line,subtract,[L1,R1]}; +expr({op,Line,'!',L0,R0}) -> + L1 = expr(L0), + R1 = expr(R0), %They see the same variables + {send,Line,L1,R1}; +expr({op,Line,Op,L0,R0}) when Op =:= 'andalso'; Op =:= 'orelse' -> + L1 = expr(L0), + R1 = expr(R0), %They see the same variables + {Op,Line,L1,R1}; +expr({op,Line,Op,L0,R0}) -> + L1 = expr(L0), + R1 = expr(R0), %They see the same variables + {op,Line,Op,[L1,R1]}; +expr({bin,Line,Grp}) -> + Grp1 = expr_list(Grp), + {bin,Line,Grp1}; +expr({bin_element,Line,Expr,Size,Type}) -> + Expr1 = expr(Expr), + Size1 = expr(Size), + {bin_element,Line,Expr1,Size1,Type}; +expr(Other) -> + exit({?MODULE,{unknown_expr,Other}}). + +%% is_guard_test(Expression) -> true | false. +%% Test if a general expression is a guard test. Cannot use erl_lint +%% here as sys_pre_expand has transformed source. + +is_guard_test({op,_,Op,L,R}) -> + case erl_internal:comp_op(Op, 2) of + true -> is_gexpr_list([L,R]); + false -> false + end; +is_guard_test({call,_,{remote,_,{atom,_,erlang},{atom,_,Test}},As}) -> + case erl_internal:type_test(Test, length(As)) of + true -> is_gexpr_list(As); + false -> false + end; +is_guard_test({atom,_,true}) -> true; +is_guard_test(_) -> false. + +is_gexpr({var,_,_}) -> true; +is_gexpr({atom,_,_}) -> true; +is_gexpr({integer,_,_}) -> true; +is_gexpr({char,_,_}) -> true; +is_gexpr({float,_,_}) -> true; +is_gexpr({string,_,_}) -> true; +is_gexpr({nil,_}) -> true; +is_gexpr({cons,_,H,T}) -> is_gexpr_list([H,T]); +is_gexpr({tuple,_,Es}) -> is_gexpr_list(Es); +is_gexpr({call,_,{remote,_,{atom,_,erlang},{atom,_,F}},As}) -> + Ar = length(As), + case erl_internal:guard_bif(F, Ar) of + true -> is_gexpr_list(As); + false -> + case erl_internal:arith_op(F, Ar) of + true -> is_gexpr_list(As); + false -> false + end + end; +is_gexpr({op,_,Op,A}) -> + case erl_internal:arith_op(Op, 1) of + true -> is_gexpr(A); + false -> false + end; +is_gexpr({op,_,Op,A1,A2}) -> + case erl_internal:arith_op(Op, 2) of + true -> is_gexpr_list([A1,A2]); + false -> false + end; +is_gexpr(_) -> false. + +is_gexpr_list(Es) -> lists:all(fun (E) -> is_gexpr(E) end, Es). + +consify([A|As]) -> + {cons,0,A,consify(As)}; +consify([]) -> {value,0,[]}. + + +%% -type expr_list([Expression]) -> [Expression]. +%% These expressions are processed "in parallel" for purposes of variable +%% definition etc. + +expr_list([E0|Es]) -> + E1 = expr(E0), + [E1|expr_list(Es)]; +expr_list([]) -> []. + +icr_clauses([C0|Cs]) -> + C1 = clause(C0), + [C1|icr_clauses(Cs)]; +icr_clauses([]) -> []. + +fun_clauses([{clause,L,H,G,B}|Cs]) -> + [{clause,L,head(H),guard(G),exprs(B)}|fun_clauses(Cs)]; +fun_clauses([]) -> []. + +%% new_var_name() -> VarName. + +new_var_name() -> + C = get(vcount), + put(vcount, C+1), + list_to_atom("%" ++ integer_to_list(C)). + +%% new_vars(Count, Line) -> [Var]. +%% Make Count new variables. + +new_vars(N, L) -> new_vars(N, L, []). + +new_vars(N, L, Vs) when N > 0 -> + V = {var,L,new_var_name()}, + new_vars(N-1, L, [V|Vs]); +new_vars(0, _, Vs) -> Vs. + +bif_type(erlang, Name) -> bif_type(Name); +bif_type(_, _) -> unsafe. + +bif_type(register) -> safe; +bif_type(unregister) -> safe; +bif_type(whereis) -> safe; +bif_type(registered) -> safe; +bif_type(abs) -> safe; +bif_type(float) -> safe; +bif_type(trunc) -> safe; +bif_type(round) -> safe; +bif_type(math) -> safe; +bif_type(node) -> safe; +bif_type(length) -> safe; +bif_type(hd) -> safe; +bif_type(tl) -> safe; +bif_type(size) -> safe; +bif_type(element) -> safe; +bif_type(setelement) -> safe; +bif_type(atom_to_list) -> safe; +bif_type(list_to_atom) -> safe; +bif_type(integer_to_list) -> safe; +bif_type(list_to_integer) -> safe; +bif_type(float_to_list) -> safe; +bif_type(list_to_float) -> safe; +bif_type(tuple_to_list) -> safe; +bif_type(list_to_tuple) -> safe; +bif_type(make_ref) -> safe; +bif_type(time) -> safe; +bif_type(date) -> safe; +bif_type(processes) -> safe; +bif_type(process_info) -> safe; +bif_type(load_module) -> safe; +bif_type(delete_module) -> safe; +bif_type(halt) -> safe; +bif_type(check_process_code) -> safe; +bif_type(purge_module) -> safe; +bif_type(pid_to_list) -> safe; +bif_type(list_to_pid) -> safe; +bif_type(module_loaded) -> safe; +bif_type(binary_to_term) -> safe; +bif_type(term_to_binary) -> safe; +bif_type(alive) -> safe; +bif_type(notalive) -> safe; +bif_type(nodes) -> safe; +bif_type(is_alive) -> safe; +bif_type(disconnect_node) -> safe; +bif_type(binary_to_list) -> safe; +bif_type(list_to_binary) -> safe; +bif_type(split_binary) -> safe; +bif_type(concat_binary) -> safe; +bif_type(term_to_atom) -> safe; +bif_type(hash) -> safe; +bif_type(pre_loaded) -> safe; +bif_type(info) -> safe; +bif_type(set_cookie) -> safe; +bif_type(get_cookie) -> safe; +bif_type(spawn) -> spawn; +bif_type(spawn_link) -> spawn; +bif_type(spawn_opt) -> spawn; +bif_type(_) -> unsafe. diff --git a/lib/debugger/src/dbg_iserver.erl b/lib/debugger/src/dbg_iserver.erl new file mode 100644 index 0000000000..4c1e9ccb7b --- /dev/null +++ b/lib/debugger/src/dbg_iserver.erl @@ -0,0 +1,606 @@ +%% +%% %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_iserver). +-behaviour(gen_server). + +%% External exports +-export([start/0, stop/0, find/0, + call/1, call/2, cast/1, cast/2, safe_call/1, safe_cast/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(proc, {pid, % pid() Debugged process + meta, % pid() Meta process + attpid, % pid() | undefined Attached process + status, % running | exit | idle | waiting + info = {}, % {} | term() + exit_info= {}, % {} | {{Mod,Line}, Bs, Stack} + function % {Mod,Func,Args} Initial function call + }). + +-record(state, {db, % ETS table + procs = [], % [#proc{}] + breaks = [], % [{{M,L},Options} Breakpoints + auto, % Auto attach settings + stack, % Stack trace settings + subs = [] % [pid()] Subscribers (Debugger) + }). + + +%%==================================================================== +%% External exports +%%==================================================================== + +start() -> + gen_server:start({local, ?MODULE}, ?MODULE, [], []). + +stop() -> + gen_server:cast(?MODULE, stop). + +find() -> + global:whereis_name(?MODULE). + +call(Request) -> + gen_server:call(?MODULE, Request, infinity). + +call(Int, Request) -> + gen_server:call(Int, Request, infinity). + +cast(Request) -> + gen_server:cast(?MODULE, Request). + +cast(Int, Request) -> + gen_server:cast(Int, Request). + +safe_call(Request) -> + ensure_started(), + call(Request). + +safe_cast(Request) -> + ensure_started(), + cast(Request). + +ensure_started() -> + case whereis(?MODULE) of + undefined -> start(); + _Pid -> ignore + end. + +%%--Module database--------------------------------------------------- +%% This server creates an ETS table, where the following information +%% is saved: +%% +%% Key Value +%% --- ----- +%% {Mod, refs} [ModDb] +%% ModDb [pid()] +%% +%% In each ModDb, the following information is saved by dbg_iload: +%% +%% Key Value +%% --- ----- +%% attributes Attr +%% exports Exp +%% defs [] +%% mod_bin Binary +%% mod_raw Raw Binary +%% mod_file File +%% module Mod +%% {Mod,Name,Arity,Exported} Cs +%% {'fun',Mod,Index,Uniq} {Name,Arity,Cs} +%% Line {Pos,PosNL} + + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +init([]) -> + process_flag(trap_exit, true), + global:register_name(?MODULE, self()), + Db = ets:new(?MODULE, [ordered_set, protected]), + {ok, #state{db=Db, auto=false, stack=all}}. + +%% Attaching to a process +handle_call({attached, AttPid, Pid}, _From, State) -> + {true, Proc} = get_proc({pid, Pid}, State#state.procs), + case Proc#proc.attpid of + undefined -> + link(AttPid), + case Proc#proc.status of + exit -> + Args = [self(), + AttPid,Pid,Proc#proc.info, + Proc#proc.exit_info], + Meta = spawn_link(dbg_ieval, exit_info, Args), + Proc2 = Proc#proc{meta=Meta, attpid=AttPid}, + Procs = lists:keyreplace(Pid, #proc.pid, + State#state.procs, Proc2), + {reply, {ok,Meta}, State#state{procs=Procs}}; + _Status -> + Meta = Proc#proc.meta, + send(Meta, {attached, AttPid}), + Procs = lists:keyreplace(Pid, #proc.pid, + State#state.procs, + Proc#proc{attpid=AttPid}), + {reply, {ok, Meta}, State#state{procs=Procs}} + end; + _AttPid -> % there is already an attached process + {reply, error, State} + end; + +%% Getting and setting options +handle_call(get_auto_attach, _From, State) -> + {reply, State#state.auto, State}; +handle_call(get_stack_trace, _From, State) -> + {reply, State#state.stack, State}; + +%% Retrieving information +handle_call(snapshot, _From, State) -> + Reply = lists:map(fun(Proc) -> + {Proc#proc.pid, Proc#proc.function, + Proc#proc.status, Proc#proc.info} + end, + State#state.procs), + {reply, Reply, State}; +handle_call({get_meta, Pid}, _From, State) -> + Reply = case get_proc({pid, Pid}, State#state.procs) of + {true, Proc} -> + {ok, Proc#proc.meta}; + false -> + {error, not_interpreted} + end, + {reply, Reply, State}; +handle_call({get_attpid, Pid}, _From, State) -> + Reply = case get_proc({pid, Pid}, State#state.procs) of + {true, Proc} -> + {ok, Proc#proc.attpid}; + false -> + {error, not_interpreted} + end, + {reply, Reply, State}; + + +%% Breakpoint handling +handle_call({new_break, Point, Options}, _From, State) -> + case lists:keysearch(Point, 1, State#state.breaks) of + false -> + Break = {Point, Options}, + send_all([subscriber, meta, attached], + {new_break, Break}, State), + Breaks = keyinsert(Break, 1, State#state.breaks), + {reply, ok, State#state{breaks=Breaks}}; + {value, _Break} -> + {reply, {error, break_exists}, State} + end; +handle_call(all_breaks, _From, State) -> + {reply, State#state.breaks, State}; +handle_call({all_breaks, Mod}, _From, State) -> + Reply = lists:filter(fun({{M,_L}, _Options}) -> + if M==Mod -> true; true -> false end + end, + State#state.breaks), + {reply, Reply, State}; + +%% From Meta process +handle_call({new_process, Pid, Meta, Function}, _From, State) -> + link(Meta), + + %% A new, debugged process has been started. Return its status, + %% ie running (running as usual) or break (stop) + %% The status depends on if the process is automatically attached to + %% or not. + Reply = case auto_attach(init, State#state.auto, Pid) of + AttPid when is_pid(AttPid) -> break; + ignore -> running + end, + + %% Do not add AttPid, it should call attached/2 when started instead + Proc = #proc{pid=Pid, meta=Meta, status=running, function=Function}, + send_all(subscriber, + {new_process, {Pid,Function,running,{}}}, State), + + {reply, Reply, State#state{procs=State#state.procs++[Proc]}}; + +%% Code loading +handle_call({load, Mod, Src, Bin}, _From, State) -> + + %% Create an ETS table for storing information about the module + Db = State#state.db, + ModDb = ets:new(Mod, [ordered_set, public]), + ModDbs = case ets:lookup(Db, {Mod, refs}) of + [] -> []; + [{{Mod, refs}, ModDbs1}] -> ModDbs1 + end, + ets:insert(Db, {{Mod, refs}, [ModDb|ModDbs]}), + ets:insert(Db, {ModDb, []}), + + %% Load the code + {ok, Mod} = dbg_iload:load_mod(Mod, Src, Bin, ModDb), + + %% Inform all subscribers and attached processes + send_all([subscriber, attached], {interpret, Mod}, State), + + {reply, {module, Mod}, State}; + +%% Module database +handle_call({get_module_db, Mod, Pid}, _From, State) -> + Db = State#state.db, + Reply = case ets:lookup(Db, {Mod, refs}) of + [] -> not_found; + [{{Mod, refs}, [ModDb|_ModDbs]}] -> + [{ModDb, Pids}] = ets:lookup(Db, ModDb), + ets:insert(Db, {ModDb, [Pid|Pids]}), + ModDb + end, + {reply, Reply, State}; +handle_call({lookup, Mod, Key}, _From, State) -> + Db = State#state.db, + Reply = case ets:lookup(Db, {Mod, refs}) of + [] -> not_found; + [{{Mod, refs}, [ModDb|_ModDbs]}] -> + case ets:lookup(ModDb, Key) of + [] -> not_found; + [{Key, Value}] -> {ok, Value} + end + end, + {reply, Reply, State}; +handle_call({functions, Mod}, _From, State) -> + Db = State#state.db, + Reply = case ets:lookup(Db, {Mod, refs}) of + [] -> []; + [{{Mod, refs}, [ModDb|_ModDbs]}] -> + Pattern = {{Mod,'$1','$2','_'}, '_'}, + ets:match(ModDb, Pattern) + end, + {reply, Reply, State}; +handle_call({contents, Mod, Pid}, _From, State) -> + Db = State#state.db, + [{{Mod, refs}, ModDbs}] = ets:lookup(Db, {Mod, refs}), + ModDb = if + Pid==any -> hd(ModDbs); + true -> + lists:foldl(fun(T, not_found) -> + [{T, Pids}] = ets:lookup(Db, T), + case lists:member(Pid, Pids) of + true -> T; + false -> not_found + end; + (_T, T) -> T + end, + not_found, + ModDbs) + end, + [{mod_bin, Bin}] = ets:lookup(ModDb, mod_bin), + {reply, {ok, Bin}, State}; +handle_call({raw_contents, Mod, Pid}, _From, State) -> + Db = State#state.db, + [{{Mod, refs}, ModDbs}] = ets:lookup(Db, {Mod, refs}), + ModDb = if + Pid==any -> hd(ModDbs); + true -> + lists:foldl(fun(T, not_found) -> + [{T, Pids}] = ets:lookup(Db, T), + case lists:member(Pid, Pids) of + true -> T; + false -> not_found + end; + (_T, T) -> T + end, + not_found, + ModDbs) + end, + [{mod_raw, Bin}] = ets:lookup(ModDb, mod_raw), + {reply, {ok, Bin}, State}; +handle_call({is_interpreted, Mod, Name, Arity}, _From, State) -> + Db = State#state.db, + Reply = case ets:lookup(Db, {Mod, refs}) of + [] -> false; + [{{Mod, refs}, [ModDb|_ModDbs]}] -> + Pattern = {{Mod,Name,Arity,'_'}, '_'}, + case ets:match_object(ModDb, Pattern) of + [{_Key, Clauses}] -> {true, Clauses}; + [] -> false + end + end, + {reply, Reply, State}; +handle_call(all_interpreted, _From, State) -> + Db = State#state.db, + Mods = ets:select(Db, [{{{'$1',refs},'_'},[],['$1']}]), + {reply, Mods, State}; +handle_call({file, Mod}, From, State) -> + {reply, Res, _} = handle_call({lookup, Mod, mod_file}, From, State), + Reply = case Res of + {ok, File} -> File; + not_found -> {error, not_loaded} + end, + {reply, Reply, State}. + + +handle_cast(stop, State) -> + {stop, shutdown, State}; +handle_cast({subscribe, Sub}, State) -> + {noreply, State#state{subs=[Sub|State#state.subs]}}; + +%% Attaching to a process +handle_cast({attach, Pid, {Mod, Func, Args}}, State) -> + %% Simply spawn process, which should call int:attached(Pid) + spawn(Mod, Func, [Pid | Args]), + {noreply, State}; + +%% Getting and setting options +handle_cast({set_auto_attach, false}, State) -> + send_all(subscriber, {auto_attach, false}, State), + {noreply, State#state{auto=false}}; +handle_cast({set_auto_attach, Flags, Function}, State) -> + send_all(subscriber, {auto_attach, {Flags, Function}}, State), + {noreply, State#state{auto={Flags, Function}}}; +handle_cast({set_stack_trace, Flag}, State) -> + send_all(subscriber, {stack_trace, Flag}, State), + {noreply, State#state{stack=Flag}}; + +%% Retrieving information +handle_cast(clear, State) -> + Procs = lists:filter(fun(#proc{status=Status}) -> + if Status==exit -> false; true -> true end + end, + State#state.procs), + {noreply, State#state{procs=Procs}}; + +%% Breakpoint handling +handle_cast({delete_break, Point}, State) -> + case lists:keysearch(Point, 1, State#state.breaks) of + {value, _Break} -> + send_all([subscriber, meta, attached], + {delete_break, Point}, State), + Breaks = lists:keydelete(Point, 1, State#state.breaks), + {noreply, State#state{breaks=Breaks}}; + false -> + {noreply, State} + end; +handle_cast({break_option, Point, Option, Value}, State) -> + case lists:keysearch(Point, 1, State#state.breaks) of + {value, {Point, Options}} -> + N = case Option of + status -> 1; + action -> 2; + condition -> 4 + end, + Options2 = list_setelement(N, Options, Value), + send_all([subscriber, meta, attached], + {break_options, {Point, Options2}}, State), + Breaks = lists:keyreplace(Point, 1, State#state.breaks, + {Point, Options2}), + {noreply, State#state{breaks=Breaks}}; + false -> + {noreply, State} + end; +handle_cast(no_break, State) -> + send_all([subscriber, meta, attached], no_break, State), + {noreply, State#state{breaks=[]}}; +handle_cast({no_break, Mod}, State) -> + send_all([subscriber, meta, attached], {no_break, Mod}, State), + Breaks = lists:filter(fun({{M, _L}, _O}) -> + if M==Mod -> false; true -> true end + end, + State#state.breaks), + {noreply, State#state{breaks=Breaks}}; + +%% From Meta process +handle_cast({set_status, Meta, Status, Info}, State) -> + {true, Proc} = get_proc({meta, Meta}, State#state.procs), + send_all(subscriber, {new_status, Proc#proc.pid, Status, Info}, State), + if + Status==break -> + auto_attach(break, State#state.auto, Proc); + true -> ignore + end, + Proc2 = Proc#proc{status=Status, info=Info}, + {noreply, State#state{procs=lists:keyreplace(Meta, #proc.meta, + State#state.procs, Proc2)}}; +handle_cast({set_exit_info, Meta, ExitInfo}, State) -> + {true, Proc} = get_proc({meta, Meta}, State#state.procs), + Procs = lists:keyreplace(Meta, #proc.meta, State#state.procs, + Proc#proc{exit_info=ExitInfo}), + {noreply,State#state{procs=Procs}}; + +%% Code loading +handle_cast({delete, Mod}, State) -> + + %% Remove the ETS table with information about the module + Db = State#state.db, + case ets:lookup(Db, {Mod, refs}) of + [] -> % Mod is not interpreted + {noreply, State}; + [{{Mod, refs}, ModDbs}] -> + ets:delete(Db, {Mod, refs}), + AllPids = lists:foldl( + fun(ModDb, PidsAcc) -> + [{ModDb, Pids}] = ets:lookup(Db, ModDb), + ets:delete(Db, ModDb), + ets:delete(ModDb), + PidsAcc++Pids + end, + [], + ModDbs), + lists:foreach(fun(Pid) -> + case get_proc({pid, Pid}, + State#state.procs) of + {true, Proc} -> + send(Proc#proc.meta, + {old_code, Mod}); + false -> ignore % pid may have exited + end + end, + AllPids), + + send_all([subscriber,attached], {no_interpret, Mod}, State), + + %% Remove all breakpoints for Mod + handle_cast({no_break, Mod}, State) + end. + +%% Process exits +handle_info({'EXIT',Who,Why}, State) -> + case get_proc({meta, Who}, State#state.procs) of + + %% Exited process is a meta process for exit_info + {true,#proc{status=exit}} -> + {noreply,State}; + + %% Exited process is a meta process + {true,Proc} -> + Pid = Proc#proc.pid, + ExitInfo = Proc#proc.exit_info, + %% Check if someone is attached to the debugged process, + %% if so a new meta process should be started + Meta = case Proc#proc.attpid of + AttPid when is_pid(AttPid) -> + spawn_link(dbg_ieval, exit_info, + [self(),AttPid,Pid,Why,ExitInfo]); + undefined -> + %% Otherwise, auto attach if necessary + auto_attach(exit, State#state.auto, Pid), + Who + end, + send_all(subscriber, {new_status,Pid,exit,Why}, State), + Procs = lists:keyreplace(Who, #proc.meta, State#state.procs, + Proc#proc{meta=Meta, + status=exit, + info=Why}), + {noreply,State#state{procs=Procs}}; + + false -> + case get_proc({attpid, Who}, State#state.procs) of + + %% Exited process is an attached process + {true, Proc} -> + %% If status==exit, then the meta process is a + %% simple meta for a terminated process and can be + %% terminated as well (it is only needed by + %% the attached process) + case Proc#proc.status of + exit -> send(Proc#proc.meta, stop); + _Status -> send(Proc#proc.meta, detached) + end, + Procs = lists:keyreplace(Proc#proc.pid, #proc.pid, + State#state.procs, + Proc#proc{attpid=undefined}), + {noreply, State#state{procs=Procs}}; + + %% Otherwise exited process must be a subscriber + false -> + Subs = lists:delete(Who, State#state.subs), + {noreply, State#state{subs=Subs}} + end + end. + +terminate(_Reason, _State) -> + EbinDir = filename:join(code:lib_dir(debugger), "ebin"), + code:unstick_dir(EbinDir), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +auto_attach(Why, Auto, Proc) when is_record(Proc, proc) -> + case Proc#proc.attpid of + AttPid when is_pid(AttPid) -> ignore; + undefined -> + auto_attach(Why, Auto, Proc#proc.pid) + end; +auto_attach(Why, Auto, Pid) when is_pid(Pid) -> + case Auto of + false -> ignore; + {Flags, {Mod, Func, Args}} -> + case lists:member(Why, Flags) of + true -> + spawn(Mod, Func, [Pid | Args]); + false -> ignore + end + end. + +keyinsert(Tuple1, N, [Tuple2|Tuples]) -> + if + element(N, Tuple1)<element(N, Tuple2) -> + [Tuple1, Tuple2|Tuples]; + true -> + [Tuple2 | keyinsert(Tuple1, N, Tuples)] + end; +keyinsert(Tuple, _N, []) -> + [Tuple]. + +list_setelement(N, L, E) -> list_setelement(1, N, L, E). + +list_setelement(I, I, [_|T], E) -> + [E|T]; +list_setelement(I, N, [H|T], E) -> + [H|list_setelement(I+1, N, T, E)]. + +mapfilter(Fun, [H|T]) -> + case Fun(H) of + ignore -> mapfilter(Fun, T); + H2 -> [H2|mapfilter(Fun, T)] + end; +mapfilter(_Fun, []) -> + []. + +send_all([Type|Types], Msg, State) -> + send_all(Type, Msg, State), + send_all(Types, Msg, State); +send_all([], _Msg, _State) -> ok; + +send_all(subscriber, Msg, State) -> + send_all(State#state.subs, Msg); +send_all(meta, Msg, State) -> + Metas = lists:map(fun(Proc) -> Proc#proc.meta end, State#state.procs), + send_all(Metas, Msg); +send_all(attached, Msg, State) -> + AttPids= mapfilter(fun(Proc) -> + case Proc#proc.attpid of + Pid when is_pid(Pid) -> Pid; + undefined -> ignore + end + end, + State#state.procs), + send_all(AttPids, Msg). + +send_all(Pids, Msg) -> + lists:foreach(fun(Pid) -> send(Pid, Msg) end, Pids). + +send(Pid, Msg) -> + Pid ! {int, Msg}. + +get_proc({Type, Pid}, Procs) -> + Index = case Type of + pid -> #proc.pid; + meta -> #proc.meta; + attpid -> #proc.attpid + end, + case lists:keysearch(Pid, Index, Procs) of + {value, Proc} -> {true, Proc}; + false -> false + end. diff --git a/lib/debugger/src/dbg_ui_break.erl b/lib/debugger/src/dbg_ui_break.erl new file mode 100644 index 0000000000..8b9a236ce7 --- /dev/null +++ b/lib/debugger/src/dbg_ui_break.erl @@ -0,0 +1,98 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-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_ui_break). + +%% External exports +-export([start/3, start/4, start/5]). + +%% Internal exports +-export([init/5]). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start(GS, Pos, Type) +%% start(GS, Pos, Type, Module, Line) +%% GS = graphics system identifier +%% Pos = {X, Y} +%% X = Y = integer() +%% Type = line | conditional | function +%% Module = atom() +%% Line = integer() +%%-------------------------------------------------------------------- +start(GS, Pos, Type) -> + start(GS, Pos, Type, "", ""). +start(GS, Pos, Type, Mod) -> + start(GS, Pos, Type, Mod, ""). +start(GS, Pos, Type, Mod, Line) -> + spawn_link(?MODULE, init, [GS, Pos, Type, Mod, Line]). + + +%%==================================================================== +%% Internal exports +%%==================================================================== + +init(GS, Pos, Type, Mod, Line) -> + Win = dbg_ui_break_win:create_win(GS, Pos, Type, Mod, Line), + if + Type==function, is_atom(Mod) -> + Win2 = gui_cmd({module, Mod}, Win), + loop(Win2); + true -> + loop(Win) + end. + +loop(Win) -> + receive + + %% From the GUI + GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs -> + Cmd = dbg_ui_break_win:handle_event(GuiEvent, Win), + Win2 = gui_cmd(Cmd, Win), + loop(Win2) + end. + +gui_cmd(ignore, Win) -> + Win; +gui_cmd(stopped, _Win) -> + exit(normal); +gui_cmd({win, Win2}, _Win) -> + Win2; +gui_cmd({module, Mod}, Win) -> + Funcs = int:functions(Mod), + dbg_ui_break_win:update_functions(Win, Funcs); +gui_cmd({break, DataL, Action}, _Win) -> + Fun = + fun(Data) -> + case Data of + [Mod, Line] -> + int:break(Mod, Line), + int:action_at_break(Mod, Line, Action); + [Mod, Line, CMod, CFunc] -> + int:break(Mod, Line), + int:test_at_break(Mod, Line, {CMod, CFunc}), + int:action_at_break(Mod, Line, Action); + [Mod, Func, Arity] -> + int:break_in(Mod, Func, Arity) + end + end, + lists:foreach(Fun, DataL), + exit(normal). diff --git a/lib/debugger/src/dbg_ui_break_win.erl b/lib/debugger/src/dbg_ui_break_win.erl new file mode 100644 index 0000000000..a56fe36828 --- /dev/null +++ b/lib/debugger/src/dbg_ui_break_win.erl @@ -0,0 +1,307 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-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_ui_break_win). + +%% External exports +-export([create_win/5, + update_functions/2, + handle_event/2]). + +-record(winInfo, {type, % line | conditional | function + win, % gsobj() + packer, % gsobj() | undefined + entries, % [{atom|integer, GSobj()}] + trigger, % enable | disable | delete + ok, % gsobj() + cancel, % gsobj() + listbox, % gsobj() + funcs=[] % [[Name, Arity]] + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% create_win(GS, Pos, Type, Mod, Line) -> #winInfo{} +%% GS = graphics system identifier +%% Pos = {X, Y} +%% X = Y = integer() +%% Type = line | conditional | function +%% Mod = atom() | "" +%% Line = integer() | "" +%%-------------------------------------------------------------------- +create_win(GS, {X, Y}, function, Mod, _Line) -> + Pad = 8, + W = 230, + + Font = dbg_ui_win:font(normal), + + %% Window + Win = gs:window(GS, [{title, "Function Break"}, {x, X}, {y, Y}, + {destroy, true}, {configure, true}, + {keypress, true}, {data, window}]), + + %% Frame + Frm = gs:frame(Win, [{x, 0}, {y, 0}, {width, W}, {height, 190}, + {packer_x, [{fixed, 70}, {stretch, 1, W-80}, + {fixed, 10}]}, + {packer_y, [{fixed, 10}, {fixed, 30}, + {stretch, 1, 100}, {fixed, 40}]}]), + + %% Create input field (label+entry) + gs:label(Frm, [{label, {text,"Module:"}}, {font, Font}, {align, e}, + {pack_x, 1}, {pack_y, 2}]), + Ent = gs:entry(Frm, [{text, Mod}, + {pack_x, 2}, {pack_y, 2}, + {keypress, true}, {setfocus, true}, + {buttonpress, true}]), + Entries = [{Ent, atom}], + + %% Create a listbox containing the functions of the module + gs:label(Frm, [{label, {text,"Function:"}}, {font, Font}, {align, ne}, + {pack_x, 1}, {pack_y, 3}]), + Lb = gs:listbox(Frm, [{bw, 2}, {relief, ridge}, {vscroll, right}, + {pack_x, 2}, {pack_y, 3}, + {selectmode, multiple}]), + + %% Add Ok and Cancel buttons + {Wbtn, Hbtn} = dbg_ui_win:min_size(["Ok","Cancel"], 70, 30), + Bot = gs:frame(Frm, [{pack_x, {1, 3}}, {pack_y, 4}]), + Ok = gs:button(Bot, [{x, Pad}, {y, Pad}, + {width, Wbtn}, {height, Hbtn}, + {label, {text,"Ok"}}, {font, Font}]), + Cancel = gs:button(Bot, [{x, W-Pad-Wbtn}, {y, Pad}, + {width, Wbtn}, {height, Hbtn}, + {label, {text,"Cancel"}}, {font, Font}]), + + Wfrm = gs:read(Frm, width), Hfrm = gs:read(Frm, height), + gs:config(Win, [{width, Wfrm}, {height, Hfrm}, {map, true}]), + #winInfo{type=function, win=Win, + packer=Frm, entries=Entries, trigger=enable, + ok=Ok, cancel=Cancel, listbox=Lb, funcs=[]}; +create_win(GS, {X, Y}, Type, Mod, Line) -> + Pad = 8, + W = 230, + + Font = dbg_ui_win:font(normal), + + %% Window + Title = case Type of + line -> "Line Break"; + conditional -> "Conditional Break" + end, + Win = gs:window(GS, [{title, Title}, {x, X}, {y, Y}, + {destroy, true}]), + + %% Create input fields (label+entry) + {Wlbl, Hlbl} = dbg_ui_win:min_size(["C-Function:"], 10, 30), + Went = W-Wlbl-2*Pad, + Labels = case Type of + line -> + [{atom,"Module:",Mod}, {integer,"Line:",Line}]; + conditional -> + [{atom,"Module:",Mod}, {integer,"Line:",Line}, + {atom,"C-Module:",""}, {atom,"C-Function:",""}] + end, + Fun = fun({DataType, Label, Default}, Yin) -> + gs:create(label, Win, [{x, Pad}, {y, Yin}, + {width,Wlbl}, {height,Hlbl}, + {label, {text,Label}}, + {font, Font}, {align, e}]), + Ent = gs:create(entry, Win, [{x, Pad+Wlbl}, {y, Yin}, + {width, Went}, + {height, Hlbl}, + {text, Default}, + {keypress, true}]), + {{Ent, DataType}, Yin+Hlbl} + end, + {Entries, Yacc} = lists:mapfoldl(Fun, Pad, Labels), + {First, _DataType} = hd(Entries), + gs:config(First, [{buttonpress, true}, {setfocus, true}]), + + %% Add 'trigger action' buttons + {Wlbl2, Hlbl2} = dbg_ui_win:min_size(["Trigger Action"], 100, 20), + Wfrm = Wlbl2+8, Hfrm = Hlbl2*4+4, + Grp = erlang:now(), + Frm = gs:frame(Win, [{x, W/2-Wfrm/2-2}, {y, Yacc+Pad-2}, + {width, Wfrm}, {height, Hfrm}, {bw, 2}]), + gs:label(Frm, [{label, {text, "Trigger Action"}}, {font, Font}, + {x, 2}, {y, 0}, {width, Wlbl2}, {height, Hlbl2}]), + gs:radiobutton(Frm, [{label, {text, "Enable"}}, {font, Font}, + {x, 10}, {y, Hlbl2}, + {width, Wlbl2-10}, {height, Hlbl2}, + {align, w}, {group, Grp}, + {data, {trigger, enable}}, + {select, true}]), + gs:radiobutton(Frm, [{label, {text, "Disable"}}, {font, Font}, + {x, 10}, {y, Hlbl2*2}, + {width, Wlbl2-10}, {height, Hlbl2}, + {align, w}, {group, Grp}, + {data, {trigger, disable}}]), + gs:radiobutton(Frm, [{label, {text, "Delete"}}, {font, Font}, + {x, 10}, {y, Hlbl2*3}, + {width, Wlbl2-10}, {height, Hlbl2}, + {align, w}, {group, Grp}, + {data, {trigger, delete}}]), + + %% Add Ok and Cancel buttons + {Wbtn, Hbtn} = dbg_ui_win:min_size(["Ok","Cancel"], 70, 30), + Ybtn = Yacc + Pad + Hfrm + Pad, + Ok = gs:button(Win, [{x, Pad}, {y, Ybtn}, + {width, Wbtn}, {height, Hbtn}, + {label, {text,"Ok"}}, {font, Font}]), + gs:button(Win, [{x, W-Pad-Wbtn}, {y, Ybtn}, + {width, Wbtn}, {height, Hbtn}, + {label, {text,"Cancel"}}, {font, Font}]), + + Hwin = Ybtn + Hbtn + Pad, + gs:config(Win, [{width, W}, {height, Hwin}, {map, true}]), + + #winInfo{type=Type, win=Win, + entries=Entries, trigger=enable, ok=Ok}. + +%%-------------------------------------------------------------------- +%% update_functions(WinInfo, Funcs) -> WinInfo +%% WinInfo = #winInfo{} +%% Funcs = [{Name, Arity}] +%% Name = atom() +%% Arity = integer() +%%-------------------------------------------------------------------- +update_functions(WinInfo, Funcs) -> + Items = lists:map(fun([N, A]) -> io_lib:format("~p/~p", [N, A]) end, + Funcs), + gs:config(WinInfo#winInfo.listbox, [{items, Items}, + {setfocus, true}]), + WinInfo#winInfo{funcs=Funcs}. + +%%-------------------------------------------------------------------- +%% handle_event(GSEvent, WinInfo) -> Command +%% GSEvent = {gs, Id, Event, Data, Arg} +%% WinInfo = #winInfo{} +%% Command = ignore +%% | stopped +%% | {win, WinInfo} +%% | {module, Mod} +%% | {break, [[Mod, Line]], Action} +%% | {break, [[Mod, Line, CMod, CFunc]], Action} +%% | {break, [[Mod, Func, Arity]], Action} +%%-------------------------------------------------------------------- +handle_event({gs, _Id, destroy, _Data, _Arg}, _WinInfo) -> + stopped; +handle_event({gs, _Id, configure, _Data, [W, H|_]}, WinInfo) -> + gs:config(WinInfo#winInfo.packer, [{width, W-10}, {height, H-10}]), + gs:config(WinInfo#winInfo.cancel, [{x, W-80}]), + ignore; +handle_event({gs, Ent, buttonpress, _,[N,X0,Y0|_]}, WinInfo) when N>1 -> + %% Right (middle) mouse button click in module entry, display a + %% menu containing all interpreted modules + Mods = int:interpreted(), + X = gs:read(Ent, x) + X0, + Y = gs:read(Ent, y) + Y0, + Menu = gs:menu(WinInfo#winInfo.win, [{post_at,{X,Y}}]), + lists:foreach(fun(Mod) -> + gs:menuitem(Menu, [{label,{text,Mod}}, + {data,{module,Mod}}]) + end, + Mods), + ignore; +handle_event({gs, LB, keypress, window, [Key|_]}, WinInfo) -> + %% Used for functional break window, since listboxes for some + %% reason doesn't generate keypress events + if + Key/='Tab', Key/='Return' -> + ignore; + true -> + handle_event({gs, LB, click, listbox, ["Ok"]}, WinInfo) + end; +handle_event({gs, Ent, keypress, Data, [Key|_]}, WinInfo) -> + case WinInfo#winInfo.type of + function when Key/='Tab', Key/='Return' -> + case gs:read(WinInfo#winInfo.listbox, items) of + [] -> ignore; + _Items -> + gs:config(WinInfo#winInfo.listbox, clear), + {win, WinInfo#winInfo{funcs=[]}} + end; + function -> % 'Return' | 'Tab' pressed in Module entry + case check_input(WinInfo#winInfo.entries) of + error -> ignore; + [Mod] -> {module, Mod} + end; + _Type when Key=='Tab'; Key=='Return' -> + case next_entry(Ent, WinInfo#winInfo.entries) of + last -> + gs:config(WinInfo#winInfo.ok, flash), + handle_event({gs, Ent, click, Data, ["Ok"]}, WinInfo); + Next -> + gs:config(Next, {setfocus, true}), + ignore + end; + _Type -> ignore + end; +handle_event({gs, _Id, click, _Data, ["Ok"|_]}, WinInfo) -> + case check_input(WinInfo#winInfo.entries) of + error -> ignore; + Data when WinInfo#winInfo.type/=function -> + {break, [Data], WinInfo#winInfo.trigger}; + [Mod] -> % Function break window + case gs:read(WinInfo#winInfo.listbox, selection) of + [] -> + {module, Mod}; + IndexL -> + Funcs = WinInfo#winInfo.funcs, + Breaks = + lists:map(fun(Index) -> + Func = lists:nth(Index+1, + Funcs), + [Mod | Func] + end, + IndexL), + {break, Breaks, enable} + end + end; +handle_event({gs, _Id, click, _Data, ["Cancel"|_]}, _WinInfo) -> + stopped; +handle_event({gs, _Id, click, {trigger,Trigger}, _Arg}, WinInfo) -> + {win, WinInfo#winInfo{trigger=Trigger}}; +handle_event({gs, _Id, click, {module, Mod}, _Arg}, WinInfo) -> + {Ent, _DataType} = hd(WinInfo#winInfo.entries), + gs:config(Ent, {insert,{0,Mod}}), + ignore; +handle_event(_GSEvent, _WinInfo) -> + ignore. + +check_input(Entries) -> + check_input(Entries, []). +check_input([{Entry, Type} | Entries], Data) -> + Str = gs:read(Entry, text), + case erl_scan:string(Str) of + {ok, [{Type, _Line, Val}], _EndLine} -> + check_input(Entries, [Val|Data]); + _Error -> error + end; +check_input([], Data) -> lists:reverse(Data). + +next_entry(Entry, [{Entry, _Type}]) -> + last; +next_entry(Entry, [{Entry, _Type1}, {Next, _Type2}|_]) -> + Next; +next_entry(Entry, [_|Entries]) -> + next_entry(Entry, Entries). diff --git a/lib/debugger/src/dbg_ui_edit.erl b/lib/debugger/src/dbg_ui_edit.erl new file mode 100644 index 0000000000..390e6acdb4 --- /dev/null +++ b/lib/debugger/src/dbg_ui_edit.erl @@ -0,0 +1,91 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-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_ui_edit). + +%% External exports +-export([start/5]). + +%% Internal exports +-export([init/6]). + +-record(state, {win, % term() Edit dialog window data + pid, % pid() Parent + prompt % atom() + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start(GS, Pos, Title, Prompt, {Type, Value}) +%% GS = graphics system identifier +%% Pos = {X, Y} +%% X = Y = integer() +%% Title = string() +%% Prompt = atom() +%% Type = term | atom | float | integer | string +%% Value = term() +%%-------------------------------------------------------------------- +start(GS, Pos, Title, Prompt, Edit) -> + case dbg_ui_winman:is_started(Title) of + true -> ignore; + false -> + spawn(?MODULE, init, [self(), GS, Pos, Title, Prompt, Edit]) + end. + + +%%==================================================================== +%% Internal exports +%%==================================================================== + +init(Pid, GS, Pos, Title, Prompt, Edit) -> + + %% Create edit dialog window + Win = dbg_ui_edit_win:create_win(GS, Pos, Title, Prompt, Edit), + Window = dbg_ui_edit_win:get_window(Win), + dbg_ui_winman:insert(Title, Window), + State = #state{win=Win, pid=Pid, prompt=Prompt}, + + loop(State). + +loop(State) -> + receive + + %% From the GUI + GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs -> + Cmd = dbg_ui_edit_win:handle_event(GuiEvent, + State#state.win), + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the dbg_ui_winman process (Debugger window manager) + {dbg_ui_winman, update_windows_menu, _Data} -> + loop(State); + {dbg_ui_winman, destroy} -> + exit(normal) + end. + +gui_cmd(ignore, State) -> + State; +gui_cmd(stopped, _State) -> + exit(normal); +gui_cmd({edit, Value}, State) -> + State#state.pid ! {dbg_ui_edit, State#state.prompt, Value}, + exit(normal). diff --git a/lib/debugger/src/dbg_ui_edit_win.erl b/lib/debugger/src/dbg_ui_edit_win.erl new file mode 100644 index 0000000000..badaf4bef4 --- /dev/null +++ b/lib/debugger/src/dbg_ui_edit_win.erl @@ -0,0 +1,122 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-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_ui_edit_win). + +%% External exports +-export([create_win/5, get_window/1, + handle_event/2]). + +-record(winInfo, {window, % gsobj() + entry, % gsobj() + button, % gsobj() + type % atom() + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% create_win(GS, Pos, Title, Prompt, {Type, Value}) -> #winInfo{} +%% GS = graphics system identifier +%% Pos = {X, Y} +%% X = Y = integer() +%% Title = string() +%% Prompt = atom() +%% Type = term | atom | float | integer | string +%% Value = term() +%%-------------------------------------------------------------------- +create_win(GS, {X, Y}, Title, Prompt, {Type, Value}) -> + Pad=8, + + Font = dbg_ui_win:font(normal), + + %% Window + Win = gs:window(GS, [{title, Title}, {x, X}, {y, Y}, + {destroy, true}]), + + %% Label + {Wlbl, Hlbl} = dbg_ui_win:min_size([Prompt], 50, 30), + gs:label(Win, [{x, Pad}, {y, Pad}, {width, Wlbl}, {height, Hlbl}, + {align, e}, {label, {text, Prompt}}, {font, Font}]), + + + %% Entry + {Went, _Hent} = dbg_ui_win:min_size([Value], 100, 20), + Ent = gs:entry(Win, [{x, Pad+Wlbl}, {y, Pad}, + {width, Went}, {height, Hlbl}, + {text, Value}, + {keypress, true}]), + + %% Ok and Cancel buttons + W = Pad + Wlbl + Went + Pad, + {Wbtn, Hbtn} = dbg_ui_win:min_size(["Cancel"], 70, 30), + Ybtn = Pad + Hlbl + Pad, + Btn = gs:button(Win, [{x, Pad}, {y, Ybtn}, + {width, Wbtn}, {height, Hbtn}, + {label, {text,"Ok"}}, {font, Font}]), + gs:button(Win, [{x, W-Pad-Wbtn}, {y, Ybtn}, + {width, Wbtn}, {height, Hbtn}, + {label, {text,"Cancel"}}, {font, Font}]), + + H = Ybtn + Hbtn + Pad, + gs:config(Win, [{width, W}, {height, H}, {map, true}]), + + #winInfo{window=Win, entry=Ent, button=Btn, type=Type}. + +%%-------------------------------------------------------------------- +%% get_window(WinInfo) -> Window +%% WinInfo = #winInfo{} +%% Window = gsobj() +%%-------------------------------------------------------------------- +get_window(WinInfo) -> + WinInfo#winInfo.window. + +%%-------------------------------------------------------------------- +%% handle_event(GSEvent, WinInfo) -> Command +%% GSEvent = {gs, Id, Event, Data, Arg} +%% WinInfo = #winInfo{} +%% Command = ignore +%% | stopped +%% | {edit, Value} +%%-------------------------------------------------------------------- +handle_event({gs, _Id, destroy, _Data, _Arg}, _WinInfo) -> + stopped; +handle_event({gs, Id, keypress, Data, ['Return'|_]}, WinInfo) -> + gs:config(WinInfo#winInfo.button, flash), + handle_event({gs, Id, click, Data, ["Ok"]}, WinInfo); +handle_event({gs, _Id, click, _Data, ["Ok"|_]}, WinInfo) -> + Ent = WinInfo#winInfo.entry, + Str = gs:read(Ent, text), + Type = WinInfo#winInfo.type, + case erl_scan:string(Str) of + {ok, Tokens, _EndLine} when Type==term -> + case erl_parse:parse_term(Tokens++[{dot, 1}]) of + {ok, Value} -> {edit, Value}; + _Error -> ignore + end; + {ok, [{Type, _Line, Value}], _EndLine} when Type/=term -> + {edit, Value}; + _ -> + ignore + end; +handle_event({gs, _Id, click, _Data, ["Cancel"|_]}, _WinInfo) -> + stopped; +handle_event(_GSEvent, _WinInfo) -> + ignore. diff --git a/lib/debugger/src/dbg_ui_filedialog_win.erl b/lib/debugger/src/dbg_ui_filedialog_win.erl new file mode 100644 index 0000000000..f7d76076a5 --- /dev/null +++ b/lib/debugger/src/dbg_ui_filedialog_win.erl @@ -0,0 +1,338 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-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_ui_filedialog_win). + +%% External exports +-export([create_win/6, create_win/7, get_window/1, + tag/2, + handle_event/2]). + +-record(winInfo, {window, % gsobj() + extra, % fun() + cwd, % string() + pattern % string() + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% create_win(GS, Title, Pos, Mode, Filter, Extra) +%% create_win(GS, Title, Pos, Mode, Filter, Extra, FileName) -> #winInfo{} +%% GS = term() +%% Title = string() +%% Pos = {X,Y} +%% Mode = normal | multiselect +%% Filter = string() File name that may include * symbols. +%% Extra = fun(File) -> {true, tag} | true | {error, term()} +%% FileName = string() Suggested file name when saving +%%-------------------------------------------------------------------- +create_win(GS, Title, {X,Y}, Mode, Filter, Extra) -> + create_win(GS, Title, {X,Y}, Mode, Filter, Extra, null). +create_win(GS, Title, {X,Y}, Mode, Filter, Extra, FileName) -> + Pad = 8, + Wlb = 480, Hlb = 130, + + Font = dbg_ui_win:font(normal), + + {Wlbl, Hlbl} = dbg_ui_win:min_size(["Directories"], 80, 20), + {Wbtn, Hbtn} = dbg_ui_win:min_size(["Filter","Cancel"], 70, 30), + + %% Window + Win = gs:window(GS, [{title,Title}, {x, X}, {y,Y}, {destroy,true}]), + + %% 'Filter' label and entry (for selecting directory) + gs:label(Win, [{label, {text,"Filter"}}, {font, Font}, {align, sw}, + {x, Pad+2}, {y, Pad}, {width,Wlbl}, {height,Hlbl}]), + gs:entry('Filter', Win, [{x, Pad}, {y, Pad+Hlbl}, + {width, Wlb}, {height, Hbtn}, + {keypress, true}]), + + %% Listboxes (showing directories and files) + Xmid = Pad + Wlb/2, + Y2 = Pad + Hlbl + Hbtn + Pad, + gs:label(Win, [{label, {text,"Directories"}}, + {font, Font}, {align, sw}, + {x, Pad+2}, {y, Y2}, + {width, Wlbl}, {height, Hlbl}]), + gs:label(Win, [{label, {text,"Files"}}, + {font, Font}, {align, sw}, + {x, Xmid+Pad/2+2}, {y, Y2}, + {width, Wlbl}, {height, Hlbl}]), + gs:listbox('Dirs', Win, [{x, Pad}, {y, Y2+Hlbl}, + {width, Wlb/2-Pad/2}, {height, Hlb}, + {vscroll, right}, + {click, true}, {doubleclick, true}]), + gs:listbox('Files', Win, [{x, Xmid+Pad/2}, {y, Y2+Hlbl}, + {width, Wlb/2-Pad/2}, {height, Hlb}, + {vscroll, right}, + {click, true}, {doubleclick, true}]), + + %% 'Selection' label and entry (for selecting file) + Y3 = Y2 + Hlbl + Hlb, + gs:label(Win, [{label, {text,"Selection"}}, {font,Font}, {align,sw}, + {x, Pad+2}, {y, Y3}, {width, Wlbl}, {height, Hlbl}]), + gs:entry('Selection', Win, [{x, Pad}, {y, Y3+Hlbl}, + {width, Wlb}, {height, Hbtn}, + {keypress, true}]), + + %% Buttons + Y4 = Y3 + Hlbl + Hbtn + Pad, + Wb = Wlb - Wbtn, + Opts = [{y, Y4}, {width, Wbtn}, {height, Hbtn}, {font, Font}], + case Mode of + normal -> + gs:button(Win, [{label, {text,"Ok"}}, {x, Pad}, + {data, select} | Opts]), + gs:button(Win, [{label, {text,"Filter"}}, {x, Wlb/2-Wbtn/2}, + {data, filter} | Opts]), + gs:button(Win, [{label, {text,"Cancel"}}, {x, Pad+Wb}, + {data, done} | Opts]); + multiselect -> + gs:button(Win, [{label, {text,"Choose"}}, {x, Pad}, + {data, select} | Opts]), + gs:button(Win, [{label, {text,"All"}}, {x, Pad+Wb/3}, + {data, multiselect} | Opts]), + gs:button(Win, [{label, {text,"Filter"}}, {x, Pad+2*Wb/3}, + {data, filter} | Opts]), + gs:button(Win, [{label, {text,"Done"}}, {x, Pad+Wb}, + {data, done} | Opts]) + end, + + %% Insert contents + {ok, Home} = file:get_cwd(), + {Cwd, Pattern} = update_win(Filter, Extra, Home), + if + is_list(FileName) -> + gs:config('Selection', {text, filename:join(Cwd,FileName)}); + true -> ignore + end, + + Wwin = Pad + Wlb + Pad, + Hwin = Y4 + Hbtn + Pad, + gs:config(Win, [{width, Wwin}, {height, Hwin}, {map, true}]), + + #winInfo{window=Win, extra=Extra, cwd=Cwd, pattern=Pattern}. + +%%-------------------------------------------------------------------- +%% get_window(WinInfo) -> Window +%% WinInfo = #winInfo{} +%% Window = gsobj() +%%-------------------------------------------------------------------- +get_window(WinInfo) -> + WinInfo#winInfo.window. + +%%-------------------------------------------------------------------- +%% tag(WinInfo, File) +%% WinInfo = #winInfo{} +%% File = string() +%%-------------------------------------------------------------------- +tag(WinInfo, File0) -> + File = relfile(WinInfo#winInfo.cwd, File0), + case member(File, gs:read('Files', items)) of + {true, Index} -> gs:config('Files', {change, {Index, tag(File)}}); + false -> ignore + end. + +tag(Str) -> [$*|Str]. +untag([$*|Str]) -> Str; +untag([$(|Str]) -> [$)|Rts] = lists:reverse(Str),lists:reverse(Rts); +untag(Str) -> Str. + +member(E, L) -> member(E, L, 0). +member(E, [E|_], I) -> {true, I}; +member(E, [_|T], I) -> member(E, T, I+1); +member(_E, [], _I) -> false. + +%%-------------------------------------------------------------------- +%% handle_event(GSEvent, WinInfo) -> Command +%% GSEvent = {gs, Id, Event, Data, Arg} +%% WinInfo = #winInfo{} +%% Command = ignore +%% | {stopped, Dir} +%% | {win, WinInfo} +%% | {select, File} | {multiselect, Dir, FileNames} +%%-------------------------------------------------------------------- +handle_event({gs, _Id, destroy, _Data, _Args}, WinInfo) -> + {stopped, WinInfo#winInfo.cwd}; + +handle_event({gs, 'Filter', keypress, _Data, ['Return'|_]}, WinInfo) -> + handle_event({gs, null, click, filter, null}, WinInfo); +handle_event({gs, 'Selection', keypress, _Data, ['Return'|_]}, WinInfo) -> + handle_event({gs, null, click, select, null}, WinInfo); + +handle_event({gs, 'Dirs', click, _Data, [0,"..",true|_]}, WinInfo) -> + Filter = filename:join(filename:dirname(WinInfo#winInfo.cwd), + WinInfo#winInfo.pattern), + gs:config('Filter', {text, Filter}), + ignore; +handle_event({gs, 'Dirs', click, _Data, [_Index,Str,true|_]}, WinInfo) -> + Filter = filename:join([WinInfo#winInfo.cwd, Str, + WinInfo#winInfo.pattern]), + gs:config('Filter', {text, Filter}), + ignore; +handle_event({gs, 'Dirs', doubleclick, _Data, _Arg}, WinInfo) -> + handle_event({gs, null, click, filter, null}, WinInfo); + +handle_event({gs, 'Files', click, _Data, [_Index,Str,true|_]}, WinInfo) -> + Selection = filename:join(WinInfo#winInfo.cwd, untag(Str)), + gs:config('Selection', {text, Selection}), + ignore; +handle_event({gs, 'Files', doubleclick, _Data, _Arg}, WinInfo) -> + handle_event({gs, null, click, select, null}, WinInfo); + +handle_event({gs, _Id, click, select, _Arg}, _WinInfo) -> + {select, gs:read('Selection', text)}; +handle_event({gs, _Id, click, multiselect, _Arg}, WinInfo) -> + Files = lists:map(fun(File) -> untag(File) end, + gs:read('Files', items)), + {multiselect, WinInfo#winInfo.cwd, Files}; +handle_event({gs, _Id, click, filter, _Arg}, WinInfo) -> + {Cwd, Pattern} = update_win(gs:read('Filter', text), + WinInfo#winInfo.extra, + WinInfo#winInfo.cwd), + {win, WinInfo#winInfo{cwd=Cwd, pattern=Pattern}}; +handle_event({gs, _Id, click, done, _Arg}, WinInfo) -> + {stopped, WinInfo#winInfo.cwd}; + +handle_event(_GSEvent, _WinInfo) -> + ignore. + +%%==================================================================== +%% Internal functions +%%==================================================================== + +update_win(Filter, ExtraFilter, Prev) -> + {Res, {Filter2, Cwd, FilePattern}} = check_filter(Filter, Prev), + + Dirs = [".." | get_subdirs(Cwd)], + + gs:config('Filter', {text, Filter2}), + gs:config('Dirs', {items, Dirs}), + gs:config('Selection', {text, Cwd}), + + case Res of + ok -> + Matching = lists:sort(filelib:wildcard(Filter2, erl_prim_loader)), + Files = extra_filter(Matching, Cwd, ExtraFilter), + gs:config('Files', {items, Files}); + error -> + gs:config('Files', beep) + end, + + {Cwd, FilePattern}. + +%% check_filter(Filter, Prev) -> {ok, Res} | {error, Res} +%% Res = {Filter, Cwd, FilePattern} +%% Filter = Prev = Cwd = FilePattern = string() +check_filter(Filter0, Prev) -> + Filter = case filename:pathtype(Filter0) of + absolute -> Filter0; + _Relative -> filename:absname(Filter0, Prev) + end, + Comps = filename:split(Filter), + Last = lists:last(Comps), + FilePattern = case is_pattern(Last) of + true -> Last; + false -> "*" + end, + {Cwd, Rest} = max_existing(Comps), + case Rest of + [] -> + %% Filter = existing file or directory + Res = case filelib:is_dir(Filter, erl_prim_loader) of + true -> {filename:join(Filter, "*"), Filter, "*"}; + false -> {Filter, filename:dirname(Filter), + filename:basename(Filter)} + end, + {ok, Res}; + [FilePattern] -> + %% Filter = existing dir and valid pattern + {ok, {Filter, Cwd, FilePattern}}; + Comps -> + %% Filter = garbage + {error, {Prev, Prev, "*"}}; + [Name|_Names] -> + %% Filter = existing dir ++ pattern or non-existing file/dir + case is_pattern(Name) of + true -> {ok, {Filter, Cwd, FilePattern}}; + false -> {error, {Cwd, Cwd, ""}} + end + end. + +max_existing([Name | Names]) -> + case filelib:is_file(Name, erl_prim_loader) of + true -> max_existing(Name, Names); + false -> {[], [Name | Names]} + end. +max_existing(Dir, [Name | Names]) -> + Dir2 = filename:join(Dir, Name), + case filelib:is_file(Dir2, erl_prim_loader) of + true when Names==[] -> {Dir2, []}; + true -> max_existing(Dir2, Names); + false -> {Dir, [Name | Names]} + end. + +is_pattern(Str) -> + lists:member($*, Str). + +extra_filter([File|Files], Dir, Fun) -> + case Fun(File) of + true -> + [relfile(Dir, File) | extra_filter(Files, Dir, Fun)]; + {true,tag} -> + [[$*|relfile(Dir,File)] | extra_filter(Files, Dir, Fun)]; + {true,disable} -> + [[$(|relfile(Dir,File)]++[$)] | extra_filter(Files, Dir, Fun)]; + {error, _Reason} -> extra_filter(Files, Dir, Fun) + end; +extra_filter([], _Dir, _Fun) -> []. + +get_subdirs(Dir) -> + case erl_prim_loader:list_dir(Dir) of + {ok, FileNames} -> + X = lists:filter(fun(FileName) -> + File = filename:join(Dir, FileName), + filelib:is_dir(File, erl_prim_loader) + end, + FileNames), + lists:sort(X); + _Error -> + [] + end. + +%% Return the "remainder" of a file name relative a dir name, examples: +%% relfile("/home/gunilla", "/home/gunilla/m.erl") -> "m.erl" +%% relfile("/home/gunilla/dir", "/home/gunilla/dir/m.erl") -> "dir/m.erl" +%% relfile("/home/gunilla", "/home/arne/m.erl") -> "/home/arne/m.erl" +relfile(Dir, File) -> + case compare(Dir, File) of + error -> File; + RelFile -> RelFile + end. + +compare([_|Dir], [_|File]) -> + compare(Dir, File); +compare([], [$/|File]) -> + File; +compare(_, _) -> + error. + diff --git a/lib/debugger/src/dbg_ui_interpret.erl b/lib/debugger/src/dbg_ui_interpret.erl new file mode 100644 index 0000000000..952e73b537 --- /dev/null +++ b/lib/debugger/src/dbg_ui_interpret.erl @@ -0,0 +1,161 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-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_ui_interpret). + +-include_lib("kernel/include/file.hrl"). + +%% External exports +-export([start/4]). + +%% Internal exports +-export([init/6]). + +-record(state, {gs, % term() Graphics system id + win, % term() Interpret dialog window data + monitor, % pid() Monitor pid + mode % local | global + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start(GS, Pos, Dir, Mode) +%% GS = Graphics system id +%% Dir = string() +%% Pos = {X,Y} +%% Mode = local | global +%%-------------------------------------------------------------------- +start(GS, Pos, Dir, Mode) -> + Title = "Interpret Dialog", + case dbg_ui_winman:is_started(Title) of + true -> ignore; + false -> + spawn(?MODULE, init, [self(), GS, Pos, Title, Dir, Mode]) + end. + +%%==================================================================== +%% Internal exports +%%==================================================================== + +init(Monitor, GS, Pos, Title, Dir, Mode) -> + Filter = filename:join(Dir, "*.erl"), + Extra = fun(File) -> + case int:interpretable(File) of + true -> + ModS = filename:basename(File, ".erl"), + Mod = list_to_atom(ModS), + case int:file(Mod) of + File -> {true, tag}; + _ -> true % {error,not_loaded} | File2 + end; + _Error -> {true,disable} + end + end, + + %% Create interpret dialog window + Win = dbg_ui_filedialog_win:create_win(GS, Title, Pos, multiselect, + Filter, Extra), + Window = dbg_ui_filedialog_win:get_window(Win), + dbg_ui_winman:insert(Title, Window), + + State = #state{gs=GS, win=Win, monitor=Monitor, mode=Mode}, + loop(State). + + +%%==================================================================== +%% Main loop and message handling +%%==================================================================== + +loop(State) -> + receive + + %% From the GUI + GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs -> + Cmd = dbg_ui_filedialog_win:handle_event(GuiEvent, + State#state.win), + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the dbg_ui_winman process (Debugger window manager) + {dbg_ui_winman, update_windows_menu, _Data} -> + loop(State); + {dbg_ui_winman, destroy} -> + exit(normal) + end. + +gui_cmd(ignore, State) -> + State; +gui_cmd({stopped, Dir}, State) -> + State#state.monitor ! {dbg_ui_interpret, Dir}, + exit(normal); +gui_cmd({win, Win}, State) -> + State#state{win=Win}; +gui_cmd({select, File}, State) -> + Res = case State#state.mode of + local -> int:i(File); + global -> int:ni(File) + end, + + case Res of + %% Interpretation succeeded, tag the file name + {module, _Mod} -> + dbg_ui_filedialog_win:tag(State#state.win, File); + + %% Interpretation failed + error -> + Error = format_error(int:interpretable(File)), + Msg = ["Error when interpreting:", File, Error], + Window = dbg_ui_filedialog_win:get_window(State#state.win), + tool_utils:notify(Window, Msg) + end, + State; +gui_cmd({multiselect, Dir, FileNames}, State) -> + interpret_all(State, Dir, FileNames), + State. + +interpret_all(State, Dir, [File0|Files]) -> + File = filename:join(Dir, File0), + Res = case State#state.mode of + local -> int:i(File); + global -> int:ni(File) + end, + case Res of + {module, _Mod} -> + dbg_ui_filedialog_win:tag(State#state.win, File), + interpret_all(State, Dir, Files); + error -> + Window = dbg_ui_filedialog_win:get_window(State#state.win), + Error = format_error(int:interpretable(File)), + Msg = ["Error when interpreting:", File, Error, + "Ok to continue?"], + case tool_utils:confirm(Window, Msg) of + ok -> interpret_all(State, Dir, Files); + cancel -> true + end + end; +interpret_all(_State, _Dir, []) -> + true. + +format_error({error,no_beam}) -> "No BEAM file"; +format_error({error,no_debug_info}) -> "No debug_info in BEAM file"; +format_error({error,badarg}) -> "File does not exist"; +format_error({error,{app,App}}) -> + "Cannot interpret "++atom_to_list(App)++" modules". diff --git a/lib/debugger/src/dbg_ui_mon.erl b/lib/debugger/src/dbg_ui_mon.erl new file mode 100644 index 0000000000..63cc9b66d1 --- /dev/null +++ b/lib/debugger/src/dbg_ui_mon.erl @@ -0,0 +1,744 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-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_ui_mon). + +-include_lib("kernel/include/file.hrl"). + +%% External exports +-export([start/2, stop/0]). + +-define(TRACEWIN, ['Button Area', 'Evaluator Area', 'Bindings Area']). +-define(BACKTRACE, 100). + +-record(pinfo, {pid, % pid() + status % break | exit | idle | running | waiting + }). + +-record(state, {mode, % local | global + starter, % bool() 'true' if int was started by me + + gs, % term() Graphics system id + win, % term() Monitor window data + focus, % undefined | #pinfo{} Process in focus + coords, % {X,Y} Mouse pointer position + + intdir, % string() Default dir + pinfos, % [#pinfo{}] Debugged processes + + tracewin, % [Area] Areas shown in trace window + backtrace, % integer() Number of call frames to fetch + + attach, % false | {Flags, Function} + + sfile, % default | string() Settings file + changed % boolean() Settings have been changed + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start(Mode, SFile) -> {ok, Pid} | {error, Reason} +%% Mode = local | global +%% SFile = string() | default Settings file +%% Pid = pid() +%% Reason = {already_started,Pid} | term() +%%-------------------------------------------------------------------- +start(Mode, SFile) -> + case whereis(?MODULE) of + undefined -> + CallingPid = self(), + Pid = spawn(fun () -> init(CallingPid, Mode, SFile) end), + receive + {initialization_complete, Pid} -> + {ok, Pid}; + Error -> + Error + end; + + Pid -> + {error, {already_started,Pid}} + end. + +%%-------------------------------------------------------------------- +%% stop() -> ok +%%-------------------------------------------------------------------- +stop() -> + case whereis(?MODULE) of + undefined -> + ok; + Pid -> + Flag = process_flag(trap_exit, true), + link(Pid), + Pid ! stop, + receive + {'EXIT', Pid, stop} -> + process_flag(trap_exit, Flag), + ok + end + end. + + +%%==================================================================== +%% Initialization +%%==================================================================== + +init(CallingPid, Mode, SFile) -> + register(?MODULE, self()), + + %% Graphics system + case catch dbg_ui_mon_win:init() of + {'EXIT', Reason} -> + CallingPid ! {error, Reason}; + GS -> + init2(CallingPid, Mode, SFile, GS) + end. + +init2(CallingPid, Mode, SFile, GS) -> + + %% Start Int if necessary and subscribe to information from it + Bool = case int:start() of + {ok, _Int} -> true; + {error, {already_started, _Int}} -> false + end, + int:subscribe(), + + %% Start other necessary stuff + dbg_ui_winman:start(), % Debugger window manager + + %% Create monitor window + Title = "Monitor", + Win = dbg_ui_mon_win:create_win(GS, Title, menus()), + Window = dbg_ui_mon_win:get_window(Win), + dbg_ui_winman:insert(Title, Window), + + %% Initial process state + State1 = #state{mode = Mode, + starter = Bool, + + gs = GS, + win = Win, + focus = undefined, + coords = {0,0}, + + intdir = element(2, file:get_cwd()), + pinfos = [], + + sfile = SFile, + changed = false + }, + + State2 = init_options(?TRACEWIN, % Trace Window + int:auto_attach(), % Auto Attach + int:stack_trace(), % Stack Trace + ?BACKTRACE, % Back Trace Size + State1), + + State3 = init_contents(int:interpreted(), % Modules + int:all_breaks(), % Breakpoints + int:snapshot(), % Processes + State2), + + %% Disable/enable functionality according to process in focus (none) + gui_enable_functions(State3#state.focus), + + CallingPid ! {initialization_complete, self()}, + + if + SFile==default -> + loop(State3); + true -> + loop(load_settings(SFile, State3)) + end. + +init_options(TraceWin, AutoAttach, StackTrace, BackTrace, State) -> + lists:foreach(fun(Area) -> + dbg_ui_mon_win:select(Area, true) + end, + TraceWin), + + case AutoAttach of + false -> ignore; + {Flags, _Function} -> + dbg_ui_mon_win:show_option(State#state.win, + auto_attach, Flags), + lists:foreach(fun(Flag) -> + dbg_ui_mon_win:select(map(Flag), true) + end, + Flags) + end, + + dbg_ui_mon_win:show_option(State#state.win, + stack_trace, StackTrace), + dbg_ui_mon_win:select(map(StackTrace), true), + + dbg_ui_mon_win:show_option(State#state.win, back_trace, BackTrace), + + State#state{tracewin=TraceWin, backtrace=BackTrace}. + +init_contents(Mods, Breaks, Processes, State) -> + Win2 = + lists:foldl(fun(Mod, Win) -> + dbg_ui_mon_win:add_module(Win,'Module',Mod) + end, + State#state.win, + Mods), + + Win3 = + lists:foldl(fun(Break, Win) -> + dbg_ui_mon_win:add_break(Win,'Break',Break) + end, + Win2, + Breaks), + + lists:foldl(fun(PidTuple, State0) -> + int_cmd({new_process, PidTuple}, State0) + end, + State#state{win=Win3}, + Processes). + + +%%==================================================================== +%% Main loop and message handling +%%==================================================================== + +loop(State) -> + receive + + stop -> + gui_cmd(stopped, State); + + %% From the GUI + GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs -> + Cmd = dbg_ui_mon_win:handle_event(GuiEvent,State#state.win), + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the interpreter process + {int, Cmd} -> + State2 = int_cmd(Cmd, State), + loop(State2); + + %% From the dbg_ui_interpret process + {dbg_ui_interpret, Dir} -> + loop(State#state{intdir=Dir}); + + %% From the dbg_ui_edit process + {dbg_ui_edit, 'Backtrace:', BackTrace} -> + dbg_ui_mon_win:show_option(State#state.win, + back_trace, BackTrace), + loop(State#state{backtrace=BackTrace}); + + %% From the dbg_ui_settings process + {dbg_ui_settings, SFile, Action} -> + State2 = case Action of + load -> load_settings(SFile, State); + save -> save_settings(SFile, State) + end, + loop(State2); + + %% From the dbg_ui_winman process (Debugger window manager) + {dbg_ui_winman, update_windows_menu, Data} -> + dbg_ui_winman:update_windows_menu(Data), + loop(State) + end. + +%%--Commands from the GUI--------------------------------------------- +%% Act upon a command from the GUI. In most cases, it is only necessary +%% to call a relevant int-function. int will then report when the action +%% has been taken. + +gui_cmd(ignore, State) -> + State; +gui_cmd(stopped, State) -> + if + State#state.starter==true -> int:stop(); + true -> int:auto_attach(false) + end, + exit(stop); +gui_cmd({coords, Coords}, State) -> + State#state{coords=Coords}; + +gui_cmd({shortcut, Key}, State) -> + case shortcut(Key) of + {always, Cmd} -> gui_cmd(Cmd, State); + {if_enabled, Cmd} -> + case dbg_ui_mon_win:is_enabled(Cmd) of + true -> gui_cmd(Cmd, State); + false -> State + end; + false -> State + end; + +%% File Menu +gui_cmd('Load Settings...', State) -> + Window = dbg_ui_mon_win:get_window(State#state.win), + dbg_ui_settings:start(Window, State#state.coords, + load, State#state.sfile), + State; +gui_cmd('Save Settings...', State) -> + Window = dbg_ui_mon_win:get_window(State#state.win), + dbg_ui_settings:start(Window, State#state.coords, + save, State#state.sfile), + State; +gui_cmd('Exit', State) -> + gui_cmd(stopped, State); + +%% Edit Menu +gui_cmd('Refresh', State) -> + int:clear(), + Win = dbg_ui_mon_win:clear_processes(State#state.win), + gui_enable_functions(undefined), + State2 = State#state{win=Win, focus=undefined, pinfos=[]}, + lists:foldl(fun(PidTuple, S) -> + int_cmd({new_process,PidTuple}, S) + end, + State2, + int:snapshot()); +gui_cmd('Kill All', State) -> + lists:foreach(fun(PInfo) -> + case PInfo#pinfo.status of + exit -> ignore; + _Status -> exit(PInfo#pinfo.pid, kill) + end + end, + State#state.pinfos), + State; + +%% Module Menu +gui_cmd('Interpret...', State) -> + dbg_ui_interpret:start(State#state.gs, State#state.coords, + State#state.intdir, State#state.mode), + State; +gui_cmd('Delete All Modules', State) -> + lists:foreach(fun(Mod) -> int:nn(Mod) end, int:interpreted()), + State; +gui_cmd({module, Mod, What}, State) -> + case What of + delete -> int:nn(Mod); + view -> dbg_ui_view:start(State#state.gs, Mod) + end, + State; + +%% Process Menu +gui_cmd('Step', State) -> + int:step((State#state.focus)#pinfo.pid), + State; +gui_cmd('Next', State) -> + int:next((State#state.focus)#pinfo.pid), + State; +gui_cmd('Continue', State) -> + int:continue((State#state.focus)#pinfo.pid), + State; +gui_cmd('Finish ', State) -> + int:finish((State#state.focus)#pinfo.pid), + State; +gui_cmd('Attach', State) -> + Pid = (State#state.focus)#pinfo.pid, + case dbg_ui_winman:is_started(dbg_ui_trace:title(Pid)) of + true -> ignore; + false -> int:attach(Pid, trace_function(State)) + end, + State; +gui_cmd('Kill', State) -> + exit((State#state.focus)#pinfo.pid, kill), + State; + +%% Break Menu +gui_cmd('Line Break...', State) -> + dbg_ui_break:start(State#state.gs, State#state.coords, line), + State; +gui_cmd('Conditional Break...', State) -> + dbg_ui_break:start(State#state.gs, State#state.coords, conditional), + State; +gui_cmd('Function Break...', State) -> + dbg_ui_break:start(State#state.gs, State#state.coords, function), + State; +gui_cmd('Enable All', State) -> + Breaks = int:all_breaks(), + lists:foreach(fun ({{Mod, Line}, _Options}) -> + int:enable_break(Mod, Line) + end, + Breaks), + State; +gui_cmd('Disable All', State) -> + Breaks = int:all_breaks(), + lists:foreach(fun ({{Mod, Line}, _Options}) -> + int:disable_break(Mod, Line) + end, + Breaks), + State; +gui_cmd('Delete All', State) -> + int:no_break(), + State; +gui_cmd({break, {Mod, Line}, What}, State) -> + case What of + delete -> int:delete_break(Mod, Line); + {status, inactive} -> int:disable_break(Mod, Line); + {status, active} -> int:enable_break(Mod, Line); + {trigger, Action} -> int:action_at_break(Mod, Line, Action) + end, + State; + +%% Options Commands +gui_cmd({'Trace Window', TraceWin}, State) -> + State2 = State#state{tracewin=TraceWin}, + case State#state.attach of + false -> ignore; + {Flags, {dbg_ui_trace, start, StartFlags}} -> + case trace_function(State2) of + {_, _, StartFlags} -> ignore; + NewFunction -> % {_, _, NewStartFlags} + int:auto_attach(Flags, NewFunction) + end; + _AutoAttach -> ignore + end, + State2; +gui_cmd({'Auto Attach', When}, State) -> + if + When==[] -> int:auto_attach(false); + true -> + Flags = lists:map(fun(Name) -> map(Name) end, When), + int:auto_attach(Flags, trace_function(State)) + end, + State; +gui_cmd({'Stack Trace', [Name]}, State) -> + int:stack_trace(map(Name)), + State; +gui_cmd('Back Trace Size...', State) -> + dbg_ui_edit:start(State#state.gs, State#state.coords, "Backtrace", + 'Backtrace:', {integer, State#state.backtrace}), + State; + +%% Help Menu +gui_cmd('Debugger', State) -> + HelpFile = filename:join([code:lib_dir(debugger), + "doc", "html", "part_frame.html"]), + Window = dbg_ui_mon_win:get_window(State#state.win), + tool_utils:open_help(Window, HelpFile), + State; + +gui_cmd({focus, Pid, Win}, State) -> + {value, PInfo} = + lists:keysearch(Pid, #pinfo.pid, State#state.pinfos), + gui_enable_functions(PInfo), + State#state{win=Win, focus=PInfo}; +gui_cmd(default, State) -> + case lists:member('Attach', menus(enabled, State#state.focus)) of + true -> gui_cmd('Attach', State); + false -> State + end. + +%%--Commands from the interpreter------------------------------------- + +int_cmd({interpret, Mod}, State) -> + Win = dbg_ui_mon_win:add_module(State#state.win, 'Module', Mod), + State#state{win=Win}; +int_cmd({no_interpret, Mod}, State) -> + Win = dbg_ui_mon_win:delete_module(State#state.win, Mod), + State#state{win=Win}; + +int_cmd({new_process, {Pid, Function, Status, Info}}, State) -> + + %% Create record with information about the process + Name = registered_name(Pid), + PInfo = #pinfo{pid=Pid, status=Status}, + + %% Update window + Win = dbg_ui_mon_win:add_process(State#state.win, + Pid, Name, Function, Status, Info), + + %% Store process information + PInfos = State#state.pinfos ++ [PInfo], + State#state{win=Win, pinfos=PInfos}; +int_cmd({new_status, Pid, Status, Info}, State) -> + + %% Find stored information about the process + PInfos = State#state.pinfos, + {value, PInfo} = lists:keysearch(Pid, #pinfo.pid, PInfos), + + %% Update process information + PInfo2 = PInfo#pinfo{status=Status}, + PInfos2 = lists:keyreplace(Pid, #pinfo.pid, PInfos, PInfo2), + State2 = State#state{pinfos=PInfos2}, + + %% Update window + dbg_ui_mon_win:update_process(State2#state.win, Pid, Status, Info), + case State2#state.focus of + #pinfo{pid=Pid} -> + gui_enable_functions(PInfo2), + State2#state{focus=PInfo2}; + _ -> + State2 + end; + +int_cmd({new_break, Break}, State) -> + Win = dbg_ui_mon_win:add_break(State#state.win, 'Break', Break), + State#state{win=Win}; +int_cmd({delete_break, Point}, State) -> + Win = dbg_ui_mon_win:delete_break(State#state.win, Point), + State#state{win=Win}; +int_cmd({break_options, Break}, State) -> + dbg_ui_mon_win:update_break(State#state.win, Break), + State; +int_cmd(no_break, State) -> + Win = dbg_ui_mon_win:clear_breaks(State#state.win), + State#state{win=Win}; +int_cmd({no_break, Mod}, State) -> + Win = dbg_ui_mon_win:clear_breaks(State#state.win, Mod), + State#state{win=Win}; + +int_cmd({auto_attach, AutoAttach}, State) -> + OnFlags = case AutoAttach of + false -> []; + {Flags, _Function} -> Flags + end, + OffFlags = [init, exit, break] -- OnFlags, + dbg_ui_mon_win:show_option(State#state.win, auto_attach, OnFlags), + lists:foreach(fun(Flag) -> + dbg_ui_mon_win:select(map(Flag), true) + end, + OnFlags), + lists:foreach(fun(Flag) -> + dbg_ui_mon_win:select(map(Flag), false) + end, + OffFlags), + State#state{attach=AutoAttach}; +int_cmd({stack_trace, Flag}, State) -> + dbg_ui_mon_win:show_option(State#state.win, stack_trace, Flag), + dbg_ui_mon_win:select(map(Flag), true), + State. + + +%%==================================================================== +%% GUI auxiliary functions +%%==================================================================== + +menus() -> + [{'File', [{'Load Settings...', 0}, + {'Save Settings...', 2}, + separator, + {'Exit', 0}]}, + {'Edit', [{'Refresh', no}, + {'Kill All', no}]}, + {'Module', [{'Interpret...', 0}, + {'Delete All Modules', no}, + separator]}, + {'Process', [{'Step', 0}, + {'Next', 0}, + {'Continue', 0}, + {'Finish ', 0}, + separator, + {'Attach', 0}, + {'Kill', no}]}, + {'Break', [{'Line Break...', 5}, + {'Conditional Break...', no}, + {'Function Break...', no}, + separator, + {'Enable All', no}, + {'Disable All', no}, + {'Delete All', 0}, + separator]}, + {'Options', [{'Trace Window', no, cascade, + [{'Button Area', no, check}, + {'Evaluator Area', no, check}, + {'Bindings Area', no, check}, + {'Trace Area', no, check}]}, + {'Auto Attach', no, cascade, + [{'First Call', no, check}, + {'On Break', no, check}, + {'On Exit', no, check}]}, + {'Stack Trace', no, cascade, + [{'Stack On, Tail', no, radio}, + {'Stack On, No Tail', no, radio}, + {'Stack Off', no, radio}]}, + {'Back Trace Size...', no}]}, + {'Help', [{'Debugger', no}]}]. + +menus(enabled, undefined) -> + []; +menus(disabled, undefined) -> + ['Step','Next','Continue','Finish ','Attach','Kill']; +menus(enabled, #pinfo{status=exit}) -> + ['Attach']; +menus(disabled, #pinfo{status=exit}) -> + ['Step','Next','Continue','Finish ','Kill']; +menus(enabled, #pinfo{status=break}) -> + ['Step','Next','Continue','Finish ','Attach','Kill']; +menus(disabled, #pinfo{status=break}) -> + []; +menus(enabled, _PInfo) -> + ['Attach','Kill']; +menus(disabled, _PInfo) -> + ['Step','Next','Continue','Finish ']. + +shortcut(l) -> {always, 'Load Settings...'}; +shortcut(v) -> {always, 'Save Settings...'}; +shortcut(e) -> {always, 'Exit'}; + +shortcut(i) -> {always, 'Interpret...'}; + +shortcut(s) -> {if_enabled, 'Step'}; +shortcut(n) -> {if_enabled, 'Next'}; +shortcut(c) -> {if_enabled, 'Continue'}; +shortcut(f) -> {if_enabled, 'Finish '}; +shortcut(a) -> {if_enabled, 'Attach'}; + +shortcut(b) -> {always, 'Line Break...'}; +shortcut(d) -> {always, 'Delete All'}; + +shortcut(_) -> false. + +%% Enable/disable functionality depending on the state of the process +%% currently in Focus +gui_enable_functions(PInfo) -> + Enabled = menus(enabled, PInfo), + Disabled = menus(disabled, PInfo), + dbg_ui_mon_win:enable(Enabled, true), + dbg_ui_mon_win:enable(Disabled, false). + +%% Map values used by int to/from GUI names +map('First Call') -> init; % Auto attach +map('On Exit') -> exit; +map('On Break') -> break; +map(init) -> 'First Call'; +map(exit) -> 'On Exit'; +map(break) -> 'On Break'; + +map('Stack On, Tail') -> all; % Stack trace +map('Stack On, No Tail') -> no_tail; +map('Stack Off') -> false; +map(all) -> 'Stack On, Tail'; +map(true) -> 'Stack On, Tail'; +map(no_tail) -> 'Stack On, No Tail'; +map(false) -> 'Stack Off'. + + +%%==================================================================== +%% Debugger settings +%%==================================================================== + +load_settings(SFile, State) -> + case file:read_file(SFile) of + {ok, Binary} -> + case catch binary_to_term(Binary) of + {debugger_settings, Settings} -> + load_settings2(Settings, + State#state{sfile=SFile, + changed=false}); + _Error -> State + end; + {error, _Reason} -> State + end. + +load_settings2(Settings, State) -> + {TraceWin, AutoAttach, StackTrace, BackTrace, Files, Breaks} = + Settings, + + TraceWinAll = ['Button Area', 'Evaluator Area', 'Bindings Area', + 'Trace Area'], + lists:foreach(fun(Area) -> dbg_ui_mon_win:select(Area, true) end, + TraceWin), + lists:foreach(fun(Area) -> dbg_ui_mon_win:select(Area, false) end, + TraceWinAll--TraceWin), + + case AutoAttach of + false -> int:auto_attach(false); + {Flags, Function} -> int:auto_attach(Flags, Function) + end, + + int:stack_trace(StackTrace), + + dbg_ui_mon_win:show_option(State#state.win, back_trace, BackTrace), + + case State#state.mode of + local -> lists:foreach(fun(File) -> int:i(File) end, Files); + global -> lists:foreach(fun(File) -> int:ni(File) end, Files) + end, + lists:foreach(fun(Break) -> + {{Mod, Line}, [Status, Action, _, Cond]} = + Break, + int:break(Mod, Line), + if + Status==inactive -> + int:disable_break(Mod, Line); + true -> ignore + end, + if + Action/=enable -> + int:action_at_break(Mod,Line,Action); + true -> ignore + end, + case Cond of + CFunction when is_tuple(CFunction) -> + int:test_at_break(Mod,Line,CFunction); + null -> ignore + end + end, + Breaks), + + State#state{tracewin=TraceWin, backtrace=BackTrace}. + +save_settings(SFile, State) -> + Settings = {State#state.tracewin, + int:auto_attach(), + int:stack_trace(), + State#state.backtrace, + lists:map(fun(Mod) -> + int:file(Mod) + end, + int:interpreted()), + int:all_breaks()}, + + Binary = term_to_binary({debugger_settings, Settings}), + case file:write_file(SFile, Binary) of + ok -> + State#state{sfile=SFile, changed=false}; + {error, _Reason} -> + State + end. + + +%%==================================================================== +%% Other internal functions +%%==================================================================== + +registered_name(Pid) -> + + %% Yield in order to give Pid more time to register its name + timer:sleep(200), + + Node = node(Pid), + if + Node==node() -> + case erlang:process_info(Pid, registered_name) of + {registered_name, Name} -> Name; + _ -> undefined + end; + true -> + case rpc:call(Node,erlang,process_info, + [Pid,registered_name]) of + {registered_name, Name} -> Name; + _ -> undefined + end + end. + +trace_function(State) -> + {dbg_ui_trace, start, [State#state.tracewin,State#state.backtrace]}. diff --git a/lib/debugger/src/dbg_ui_mon_win.erl b/lib/debugger/src/dbg_ui_mon_win.erl new file mode 100644 index 0000000000..ba2f94c550 --- /dev/null +++ b/lib/debugger/src/dbg_ui_mon_win.erl @@ -0,0 +1,564 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-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_ui_mon_win). + +%% External exports +-export([init/0]). +-export([create_win/3, get_window/1, + show_option/3, + enable/2, is_enabled/1, select/2, + add_module/3, delete_module/2, + add_process/6, update_process/4, clear_processes/1, + add_break/3, update_break/2, delete_break/2, + clear_breaks/1, clear_breaks/2, + handle_event/2 + ]). + +-define(default_rows,50). + +-record(moduleInfo, {module, menubtn}). +-record(procInfo, {pid, row}). +-record(breakInfo, {point, status, break}). +-record(winInfo, {window, % gsobj() + grid, % gsobj() + row, % int() Last row in grid + + focus, % int() Selected row in grid + + modules=[], % [#moduleInfo{}] Known modules + processes=[], % [#procInfo{}] Known processes + breaks=[], % [#breakInfo{}] Known breakpoints + + listbox, % gsobj() Listinng known modules + + %% Auto attach buttons + fbutton, % gsobj() + bbutton, % gsobj() + ebutton, % gsobj() + selected=[], % ['First Call'|'On Break'|'On Exit'] + + slabel, % showing Stack Trace option + blabel % showing Back Trace Size + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +init() -> + dbg_ui_win:init(). + +%%-------------------------------------------------------------------- +%% create_win(GS, Title, Menus) -> #winInfo{} +%% GS = gsobj() +%% Title = string() +%% Menus = [menu()] See dbg_ui_win.erl +%%-------------------------------------------------------------------- + +-define(PAD, 5). +-define(Wf, 150). +-define(Wg, 770). +-define(W, 800). +-define(H, 390). + +create_win(GS, Title, Menus) -> + Win = gs:window(GS, [{title, Title}, + {width, ?W}, {height, ?H}, + {configure,true}, {destroy,true}, + {keypress,true}, {motion,true}]), + + MenuBar = gs:menubar(Win, []), + dbg_ui_win:create_menus(MenuBar, Menus), + dbg_ui_winman:windows_menu(MenuBar), + + Font = dbg_ui_win:font(normal), + + Frame = gs:frame(Win, [{x, ?PAD}, {y, 30}, + {width, ?Wf}, {height, ?H}]), + Hlb = 200, + Listbox = gs:listbox(Frame, [{x, 0}, {y, 0}, + {width, ?Wf}, {height, Hlb}, + {data, listbox}, + {doubleclick, true}, + {items, []}]), + gs:label(Frame, [{x, 0}, {y, Hlb}, {width, ?Wf}, {height, 20}, + {align, w}, + {label, {text, "Auto Attach:"}}, {font, Font}]), + Fbtn = gs:checkbutton(Frame, [{x, 0}, {y, Hlb+20}, + {width, ?Wf}, {height, 20}, + {label, {text, 'First Call'}}, + {align, w}, {font, Font}, + {data, autoattach}]), + Bbtn = gs:checkbutton(Frame, [{x, 0}, {y, Hlb+40}, + {width, ?Wf}, {height, 20}, + {label, {text, 'On Break'}}, + {align, w}, {font, Font}, + {data, autoattach}]), + Ebtn = gs:checkbutton(Frame, [{x, 0}, {y, Hlb+60}, + {width, ?Wf}, {height, 20}, + {label, {text, 'On Exit'}}, + {align, w}, {font, Font}, + {data, autoattach}]), + SLabel = gs:label(Frame, [{x, 0}, {y, Hlb+80}, + {width, ?Wf}, {height, 40}, + {font, Font}, {align, w}]), + BLabel = gs:label(Frame, [{x, 0}, {y, Hlb+120}, + {width, ?Wf}, {height, 40}, + {font, Font}, {align, w}]), + + Grid = gs:grid(Win, [{x, 2*?PAD+?Wf}, {y, 30}, + {width, ?W-(2*?PAD+?Wf)}, {height, ?H-30}, + {bg, grey}, {fg, black}, + {vscroll, right}, {hscroll, bottom}, + calc_columnwidths(?Wg), + {rows, {1,?default_rows}}]), + gs:gridline(Grid, [{row, 1}, {bw, 5}, {fg, blue}, + {font, Font}, + {text, {1,"Pid"}}, {text, {2,"Initial Call"}}, + {text, {3,"Name"}}, {text, {4,"Status"}}, + {text, {5,"Information"}}]), + + gs:config(Win, {map, true}), + #winInfo{window=Win, grid=Grid, row=1, focus=0, + listbox=Listbox, + fbutton=Fbtn, bbutton=Bbtn, ebutton=Ebtn, + slabel=SLabel, blabel=BLabel}. + +%%-------------------------------------------------------------------- +%% get_window(WinInfo) -> Window +%% WinInfo = #winInfo{} +%% Window = gsobj() +%%-------------------------------------------------------------------- +get_window(WinInfo) -> + WinInfo#winInfo.window. + +%%-------------------------------------------------------------------- +%% show_option(WinInfo, Option, Value) -> void() +%% WinInfo = #winInfo{} +%% Option = auto_attach | stack_trace | back_trace +%% Value = [Flag] % Option==auto_attach +%% Flag = init | break | exit +%% | true | all | no_tail | false % Option==stack_trace +%% | int() % Option==back_trace +%%-------------------------------------------------------------------- +show_option(WinInfo, Option, Value) -> + case Option of + + auto_attach -> + lists:foreach(fun (Button) -> + gs:config(Button, {select, false}) + end, + option_buttons(WinInfo, [init, break, exit])), + lists:foreach(fun (Button) -> + gs:config(Button, {select, true}) + end, + option_buttons(WinInfo, Value)); + + stack_trace -> + Text = case Value of + all -> "Stack Trace:\n On (with tail)"; + true -> "Stack Trace:\n On (with tail)"; + no_tail -> "Stack Trace:\n On (no tail)"; + false -> "Stack Trace:\n Off" + end, + gs:config(WinInfo#winInfo.slabel, {label, {text, Text}}); + + back_trace -> + Text = "Back Trace Size:\n " ++ integer_to_list(Value), + gs:config(WinInfo#winInfo.blabel, {label, {text, Text}}) + end. + +option_buttons(WinInfo, [init|Flags]) -> + [WinInfo#winInfo.fbutton|option_buttons(WinInfo, Flags)]; +option_buttons(WinInfo, [break|Flags]) -> + [WinInfo#winInfo.bbutton|option_buttons(WinInfo, Flags)]; +option_buttons(WinInfo, [exit|Flags]) -> + [WinInfo#winInfo.ebutton|option_buttons(WinInfo, Flags)]; +option_buttons(_WinInfo, []) -> + []. + +%%-------------------------------------------------------------------- +%% enable([MenuItem], Bool) +%% is_enabled(MenuItem) -> Bool +%% MenuItem = atom() +%% Bool = boolean() +%%-------------------------------------------------------------------- +enable(MenuItems, Bool) -> + lists:foreach(fun(MenuItem) -> + gs:config(MenuItem, {enable, Bool}) + end, + MenuItems). + +is_enabled(MenuItem) -> + gs:read(MenuItem, enable). + +%%-------------------------------------------------------------------- +%% select(MenuItem, Bool) +%% MenuItem = atom() +%% Bool = boolean() +%%-------------------------------------------------------------------- +select(MenuItem, Bool) -> + dbg_ui_win:select(MenuItem, Bool). + +%%-------------------------------------------------------------------- +%% add_module(WinInfo, Name, Mod) -> WinInfo +%% WinInfo = #winInfo{} +%% Name = atom() +%% Mod = atom() +%%-------------------------------------------------------------------- +add_module(WinInfo, Menu, Mod) -> + Modules = WinInfo#winInfo.modules, + case lists:keysearch(Mod, #moduleInfo.module, Modules) of + {value, _ModInfo} -> WinInfo; + false -> + %% Create a menu for the module + Font = dbg_ui_win:font(normal), + MenuBtn = gs:menuitem(Menu, [{label, {text,Mod}}, + {font, Font}, + {itemtype, cascade}]), + SubMenu = gs:menu(MenuBtn, []), + gs:menuitem(SubMenu, [{label, {text,"View"}}, + {font, Font}, + {data, {module,Mod,view}}]), + gs:menuitem(SubMenu, [{label, {text,"Delete"}}, + {font, Font}, + {data, {module,Mod,delete}}]), + + %% Add the module to the listbox + gs:config(WinInfo#winInfo.listbox, {add, Mod}), + + ModInfo = #moduleInfo{module=Mod, menubtn=MenuBtn}, + WinInfo#winInfo{modules=[ModInfo | Modules]} + end. + +%%-------------------------------------------------------------------- +%% delete_module(WinInfo, Mod) -> WinInfo +%% WinInfo = #winInfo{} +%% Mod = atom() +%%-------------------------------------------------------------------- +delete_module(WinInfo, Mod) -> + {value, ModInfo} = lists:keysearch(Mod, #moduleInfo.module, + WinInfo#winInfo.modules), + gs:destroy(ModInfo#moduleInfo.menubtn), + delete_module(WinInfo#winInfo.listbox, atom_to_list(Mod), 0), + WinInfo#winInfo{modules=lists:keydelete(Mod, #moduleInfo.module, + WinInfo#winInfo.modules)}. + +delete_module(Listbox, ModS, Index) -> + case gs:read(Listbox, {get, Index}) of + ModS -> + gs:config(Listbox, {del, Index}); + _OtherModS -> + delete_module(Listbox, ModS, Index+1) + end. + +%%-------------------------------------------------------------------- +%% add_process(WinInfo, Pid, Name, Function, Status, Info) -> WinInfo +%% WinInfo = #winInfo{} +%% Pid = pid() +%% Name = undefined | atom() +%% Function = {Mod, Func, Args} +%% Status = idle | running | break | exit +%% Info = {} | term() +%%-------------------------------------------------------------------- +add_process(WinInfo, Pid, Name, {Mod,Func,Args}, Status, Info) -> + Grid = WinInfo#winInfo.grid, + Row = (WinInfo#winInfo.row)+1, + GridLine = case gs:read(Grid, {obj_at_row, Row}) of + undefined -> + if Row>?default_rows -> + gs:config(Grid,[{rows,{1,Row}}]); + true -> ok + end, + gs:gridline(Grid,[{row,Row}, {bw,5}, {fg,black}, + {font,dbg_ui_win:font(normal)}, + {click, true}, + {doubleclick, true}]); + GSObj -> + GSObj + end, + Name2 = case Name of undefined -> ""; _ -> Name end, + FuncS = io_lib:format("~w:~w/~w", [Mod, Func, length(Args)]), + Info2 = case Info of {} -> ""; _ -> Info end, + Options = [{text, {1,Pid}}, {text, {2,FuncS}}, {text, {3,Name2}}, + {text, {4,Status}}, {text, {5,Info2}}, + {data, {gridline, Pid}}], + gs:config(GridLine, Options), + + ProcInfo = #procInfo{pid=Pid, row=Row}, + WinInfo#winInfo{processes=[ProcInfo|WinInfo#winInfo.processes], + row=Row}. + +%%-------------------------------------------------------------------- +%% update_process(WinInfo, Pid, Status, Info) +%% WinInfo = #winInfo{} +%% Pid = pid() +%% Status = idle | running | break | exit +%% Info = {} | term() +%%-------------------------------------------------------------------- +update_process(WinInfo, Pid, Status, Info) -> + {value, ProcInfo} = lists:keysearch(Pid, #procInfo.pid, + WinInfo#winInfo.processes), + + Grid = WinInfo#winInfo.grid, + GridLine = gs:read(Grid, {obj_at_row, ProcInfo#procInfo.row}), + + Info2 = case Info of {} -> ""; _ -> Info end, + gs:config(GridLine, [{text, {4,Status}}, {text, {5,Info2}}]). + +%%-------------------------------------------------------------------- +%% clear_processes(WinInfo) -> WinInfo +%% WinInfo = #winInfo{} +%%-------------------------------------------------------------------- +clear_processes(WinInfo) -> + Grid = WinInfo#winInfo.grid, + Max = WinInfo#winInfo.row, + clear_processes(Grid, 2, Max), + gs:config(Grid,[{rows,{1,?default_rows}}]), + WinInfo#winInfo{row=1, focus=0, processes=[]}. + +clear_processes(Grid, Row, Max) when Row=<Max -> + GridLine = gs:read(Grid, {obj_at_row, Row}), + case gs:read(GridLine,{text,4}) of + "exit" -> + Pid = list_to_pid(gs:read(GridLine,{text,1})), + dbg_ui_winman:clear_process(dbg_ui_trace:title(Pid)); + _ -> + ok + end, + + Options = [{fg, black}, + {{text,1}, ""}, {{text,2},""}, {{text,3},""}, + {{text,4}, ""}, {{text,5},""}, + {data, []}], + gs:config(GridLine, Options), + clear_processes(Grid, Row+1, Max); +clear_processes(_Grid, Row, Max) when Row>Max -> + done. + +%%-------------------------------------------------------------------- +%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo +%% WinInfo = #winInfo{} +%% Name = atom() +%% Point = {Mod, Line} +%% Options = [Status, Action, Mods, Cond] +%% Status = active | inactive +%% Action = enable | disable | delete +%% Mods = null (not used) +%% Cond = null | {Mod, Func} +%%-------------------------------------------------------------------- +add_break(WinInfo, Menu, {Point, Options}) -> + Break = dbg_ui_win:add_break(Menu, Point), + dbg_ui_win:update_break(Break, Options), + BreakInfo = #breakInfo{point=Point, break=Break}, + WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}. + +%%-------------------------------------------------------------------- +%% update_break(WinInfo, {Point, Options}) +%% WinInfo = #winInfo{} +%% Point = {Mod, Line} +%% Options = [Status, Action, Mods, Cond] +%% Status = active | inactive +%% Action = enable | disable | delete +%% Mods = null (not used) +%% Cond = null | {Mod, Func} +%%-------------------------------------------------------------------- +update_break(WinInfo, {Point, Options}) -> + {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks), + dbg_ui_win:update_break(BreakInfo#breakInfo.break, Options). + +%%-------------------------------------------------------------------- +%% delete_break(WinInfo, Point) -> WinInfo +%% WinInfo = #winInfo{} +%% Point = {Mod, Line} +%%-------------------------------------------------------------------- +delete_break(WinInfo, Point) -> + {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks), + dbg_ui_win:delete_break(BreakInfo#breakInfo.break), + WinInfo#winInfo{breaks=lists:keydelete(Point, #breakInfo.point, + WinInfo#winInfo.breaks)}. + +%%-------------------------------------------------------------------- +%% clear_breaks(WinInfo) -> WinInfo +%% clear_breaks(WinInfo, Mod) -> WinInfo +%% WinInfo = #winInfo{} +%%-------------------------------------------------------------------- +clear_breaks(WinInfo) -> + lists:foreach(fun(BreakInfo) -> + dbg_ui_win:delete_break(BreakInfo#breakInfo.break) + end, + WinInfo#winInfo.breaks), + WinInfo#winInfo{breaks=[]}. +clear_breaks(WinInfo, Mod) -> + Fun = + fun(BreakInfo) -> + case BreakInfo#breakInfo.point of + {Mod, _Line} -> + dbg_ui_win:delete_break(BreakInfo#breakInfo.break), + false; + _ -> true + end + end, + Breaks = lists:filter(Fun, WinInfo#winInfo.breaks), + WinInfo#winInfo{breaks=Breaks}. + +%%-------------------------------------------------------------------- +%% handle_event(GSEvent, WinInfo) -> Command +%% GSEvent = {gs, Id, Event, Data, Arg} +%% WinInfo = #winInfo{} +%% Command = ignore +%% | stopped +%% | {coords, {X,Y}} +%% +%% | {shortcut, Key} +%% | MenuItem | {Menu, [MenuItem]} +%% MenuItem = Menu = atom() +%% | {break, Point, What} +%% What = delete | {status, Status} | {trigger, Trigger} +%% | {module, Mod, What} +%% What = view | delete +%% +%% | {focus, Pid, WinInfo} +%% | default +%%-------------------------------------------------------------------- +%% Window events +handle_event({gs, _Id, configure, _Data, [W, H |_]}, WinInfo) -> + configure(WinInfo, {W, H}), + ignore; +handle_event({gs, _Id, destroy, _Data, _Arg}, _WinInfo) -> + stopped; +handle_event({gs, _Id, motion, _Data, [X,Y]}, WinInfo) -> + {LastX, LastY} = dbg_ui_win:motion(X, Y), + Win = WinInfo#winInfo.window, + {coords, {gs:read(Win, x)+LastX-5, gs:read(Win, y)+LastY-5}}; + +%% Menus and keyboard shortcuts +handle_event({gs, _Id, keypress, _Data, [Key,_,_,1]}, _WinInfo) when + Key/='Up', Key/='Down', Key/=p, Key/=n -> + {shortcut, Key}; +handle_event({gs, _Id, click, {dbg_ui_winman, Win}, _Arg}, _WinInfo) -> + dbg_ui_winman:raise(Win), + ignore; +handle_event({gs, _Id, click, {menuitem, Name}, _Arg}, _WinInfo) -> + Name; +handle_event({gs, _Id, click, {menu, Menu}, _Arg}, _WinInfo) -> + Names = dbg_ui_win:selected(Menu), + {Menu, Names}; +handle_event({gs, _Id, click, {break, Point, What}, _Arg}, _WinInfo) -> + {break, Point, What}; +handle_event({gs, _Id, click, {module, Mod, What}, _Arg}, _WinInfo) -> + {module, Mod, What}; + +%% Listbox +handle_event({gs, _Id, doubleclick, listbox, [_Index, ModS|_]}, _WI) -> + {module, list_to_atom(ModS), view}; + +%% Auto attach buttons +handle_event({gs, _Id, click, autoattach, _Arg}, WinInfo) -> + Names = lists:foldl(fun (Button, NamesAcc) -> + case gs:read(Button, select) of + true -> + {text, Name} = + gs:read(Button, label), + [list_to_atom(Name)|NamesAcc]; + false -> + NamesAcc + end + end, + [], + [WinInfo#winInfo.ebutton, + WinInfo#winInfo.bbutton, + WinInfo#winInfo.fbutton]), + {'Auto Attach', Names}; + +%% Process grid +handle_event({gs, _Id, keypress, _Data, [Key|_]}, WinInfo) when + Key=='Up'; Key=='Down' -> + Dir = if Key=='Up' -> up; Key=='Down' -> down end, + Row = move(WinInfo, Dir), + + if Row>1 -> + WinInfo2 = highlight(WinInfo, Row), + {value, #procInfo{pid=Pid}} = + lists:keysearch(Row, #procInfo.row, WinInfo#winInfo.processes), + {focus, Pid, WinInfo2}; + true -> + ignore + end; +handle_event({gs, _Id, click, {gridline, Pid}, [_Col,Row|_]}, WinInfo) -> + WinInfo2 = highlight(WinInfo, Row), + {focus, Pid, WinInfo2}; +handle_event({gs, _Id, doubleclick, _Data, _Arg}, _WinInfo) -> + default; + +handle_event(_GSEvent, _WinInfo) -> + ignore. + +move(WinInfo, Dir) -> + Row = WinInfo#winInfo.focus, + Last = WinInfo#winInfo.row, + + if + Dir==up, Row>1 -> Row-1; + Dir==down, Row<Last -> Row+1; + true -> Row + end. + +highlight(WinInfo, Row) -> + Grid = WinInfo#winInfo.grid, + case WinInfo#winInfo.focus of + 0 -> ignore; + Focus -> + GridLine1 = gs:read(Grid, {obj_at_row, Focus}), + gs:config(GridLine1, {fg, black}) + end, + GridLine2 = gs:read(Grid, {obj_at_row, Row}), + gs:config(GridLine2, {fg, white}), + WinInfo#winInfo{focus=Row}. + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +configure(WinInfo, {W, H}) -> + Grid = WinInfo#winInfo.grid, + NewW = W - (2*?PAD+?Wf), + Dx = NewW - gs:read(Grid, width), + Dy = H-42 - gs:read(Grid, height), + if + (Dx+Dy)=/=0 -> + gs:config(Grid, [{width, NewW}, {height, H-30}]), + Cols = calc_columnwidths(NewW), + gs:config(Grid, Cols); + true -> + ok + end. + +calc_columnwidths(Width) -> + W = if + Width=<?Wg -> ?Wg; + true -> Width + end, + First = lists:map(fun (X) -> round(X) end, + [0.13*W, 0.27*W, 0.18*W, 0.18*W]), + Last = W - lists:sum(First) - 30, + {columnwidths, First++[Last]}. diff --git a/lib/debugger/src/dbg_ui_settings.erl b/lib/debugger/src/dbg_ui_settings.erl new file mode 100644 index 0000000000..146aa7e239 --- /dev/null +++ b/lib/debugger/src/dbg_ui_settings.erl @@ -0,0 +1,162 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-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_ui_settings). + +-include_lib("kernel/include/file.hrl"). + +%% External exports +-export([start/4]). + +%% Internal exports +-export([init/6]). + +%% OTP-6011 What's denoted gs="Graphics system id" is now in fact +%% the object id of the monitor window. +-record(state, {gs, % term() Graphics system id + win, % term() Settings dialog window data + monitor, % pid() Monitor pid + action % load | save + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start(GS, Pos, Action, SFile) +%% GS = Graphics system id +%% Pos = {X,Y} +%% Action = load | save +%% SFile = default | string() +%%-------------------------------------------------------------------- +start(GS, Pos, Action, SFile) -> + Title = case Action of + load -> "Load Settings Dialog"; + save -> "Save Settings Dialog" + end, + case dbg_ui_winman:is_started(Title) of + true -> ignore; + false -> + spawn(?MODULE, init, [self(), GS, Pos, Title, Action, SFile]) + end. + +%%==================================================================== +%% Internal exports +%%==================================================================== + +init(Monitor, GS, Pos, Title, Action, SFile) -> + {SDir, SFileName} = + if + %% If settings are saved for the first time, and to + %% the default directory HOME/erlang.tools/debugger, + %% make sure the directory exists, or create it if + %% desired and possible + SFile==default -> {default_settings_dir(GS), "NoName.state"}; + true -> {filename:dirname(SFile), filename:basename(SFile)} + end, + + Filter = filename:join(SDir, "*.state"), + Extra = fun(_File) -> true end, + + %% Create window + Win = case Action of + load -> + dbg_ui_filedialog_win:create_win(GS, Title, Pos, normal, + Filter, Extra); + save -> + dbg_ui_filedialog_win:create_win(GS, Title, Pos, normal, + Filter, Extra, SFileName) + end, + Window = dbg_ui_filedialog_win:get_window(Win), + dbg_ui_winman:insert(Title, Window), + + State = #state{gs=GS, win=Win, monitor=Monitor, action=Action}, + loop(State). + + +%%==================================================================== +%% Main loop and message handling +%%==================================================================== + +loop(State) -> + receive + + %% From the GUI + GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs -> + Cmd = dbg_ui_filedialog_win:handle_event(GuiEvent, + State#state.win), + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the dbg_ui_winman process (Debugger window manager) + {dbg_ui_winman, update_windows_menu, _Data} -> + loop(State); + {dbg_ui_winman, destroy} -> + exit(normal) + end. + +gui_cmd(ignore, State) -> + State; +gui_cmd({stopped, _Dir}, _State) -> + exit(normal); +gui_cmd({win, Win}, State) -> + State#state{win=Win}; +gui_cmd({select, File}, State) -> + State#state.monitor ! {dbg_ui_settings, File, State#state.action}, + exit(normal). + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +default_settings_dir(GS) -> + {ok, [[Home]]} = init:get_argument(home), + DefDir = filename:join([Home, ".erlang_tools", "debugger"]), + + case filelib:is_dir(DefDir) of + true -> DefDir; + false -> + {ok, CWD} = file:get_cwd(), + + Msg = ["Default directory", DefDir, "does not exist.", + "Click Ok to create it or", + "Cancel to use other directory!"], + case tool_utils:confirm(GS, Msg) of + ok -> + ToolsDir = filename:dirname(DefDir), + case filelib:is_dir(ToolsDir) of + true -> + case file:make_dir(DefDir) of + ok -> DefDir; + _Error -> CWD + end; + false -> + case file:make_dir(ToolsDir) of + ok -> + case file:make_dir(DefDir) of + ok -> DefDir; + _Error -> CWD + end; + _Error -> CWD + end + end; + cancel -> CWD + end + end. diff --git a/lib/debugger/src/dbg_ui_trace.erl b/lib/debugger/src/dbg_ui_trace.erl new file mode 100644 index 0000000000..d318987f60 --- /dev/null +++ b/lib/debugger/src/dbg_ui_trace.erl @@ -0,0 +1,814 @@ +%% +%% %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_ui_trace). + +%% External exports +-export([start/1, start/3]). +-export([title/1]). + +-define(TRACEWIN, ['Button Area', 'Evaluator Area', 'Bindings Area']). +-define(BACKTRACE, 100). + +-record(state, {gs, % term() Graphics system id + win, % term() Attach process window data + coords, % {X,Y} Mouse point position + + pid, % pid() Debugged process + meta, % pid() Meta process + status, % {Status,Mod,Line} � {exit,Where,Reason} + % Status = init � idle | break + % | wait_break � wait_running + % � running + % Where={Mod,Line} | null + + cm, % atom() | undefined Current module + cm_obsolete=false, % boolean() Curr mod needs reloading + + stack, % {Cur,Max} + + trace, % boolean() + stack_trace, % all | no_tail | false + backtrace % integer() #call frames to fetch + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start(Pid) +%% start(Pid, TraceWin, BackTrace) +%% Pid = pid() +%% TraceWin = [WinArea] +%% WinArea = 'Button|Evaluator|Bindings|Trace Area' +%% Backtrace = integer() +%%-------------------------------------------------------------------- +start(Pid) -> % Used by debugger:quick/3 (no monitor) + start(Pid, ?TRACEWIN, ?BACKTRACE). +start(Pid, TraceWin, BackTrace) -> + case {whereis(dbg_wx_mon), whereis(dbg_ui_mon)} of + {undefined, undefined} -> + case which_gui() of + gs -> + start2(Pid, TraceWin, BackTrace); + wx -> + dbg_wx_trace:start(Pid, TraceWin, BackTrace) + end; + {undefined, Monitor} when is_pid(Monitor) -> + start2(Pid, TraceWin, BackTrace); + {Monitor, _} when is_pid(Monitor) -> + dbg_wx_trace:start(Pid, TraceWin, BackTrace) + end. + +start2(Pid, TraceWin, BackTrace) -> + %% Inform int about my existence and get the meta pid back + case int:attached(Pid) of + {ok, Meta} -> + init(Pid, Meta, TraceWin, BackTrace); + error -> + ignore + end. + +which_gui() -> + try + wx:new(), + wx:destroy(), + wx + catch _:_ -> + gs + end. + +%%-------------------------------------------------------------------- +%% title(Pid) -> string() +%% By exporting this function, dbg_ui_mon may check with dbg_ui_winman +%% if there already is an attach window for a given pid and thus avoid +%% spawning processes unnecessarily. +%%-------------------------------------------------------------------- +title(Pid) -> + "Attach Process " ++ pid_to_list(Pid). + + +%%==================================================================== +%% Main loop and message handling +%%==================================================================== + +init(Pid, Meta, TraceWin, BackTrace) -> + + %% Start necessary stuff + GS = dbg_ui_trace_win:init(), % Graphics system + + %% Create attach process window + Title = title(Pid), + Win = dbg_ui_trace_win:create_win(GS, Title, TraceWin, menus()), + Window = dbg_ui_trace_win:get_window(Win), + dbg_ui_winman:insert(Title, Window), + + %% Initial process state + State1 = #state{gs=GS, win=Win, coords={0,0}, pid=Pid, meta=Meta, + status={idle,null,null}, + stack={1,1}}, + + State2 = init_options(TraceWin, + int:stack_trace(), % Stack Trace + BackTrace, % Back trace size + State1), + + State3 = init_contents(int:all_breaks(), % Breakpoints + State2), + + int:meta(Meta, trace, State3#state.trace), + + gui_enable_updown(stack_trace, {1,1}), + gui_enable_btrace(false, false), + dbg_ui_trace_win:display(idle), + + loop(State3). + +init_options(TraceWin, StackTrace, BackTrace, State) -> + lists:foreach(fun(Area) -> dbg_ui_trace_win:select(Area, true) end, + TraceWin), + + Trace = lists:member('Trace Area', TraceWin), + + dbg_ui_trace_win:select(map(StackTrace), true), + + %% Backtrace size is (currently) not shown in window + + State#state{trace=Trace,stack_trace=StackTrace,backtrace=BackTrace}. + +init_contents(Breaks, State) -> + Win = + lists:foldl(fun(Break, Win) -> + dbg_ui_trace_win:add_break(Win, + 'Break',Break) + end, + State#state.win, + Breaks), + + State#state{win=Win}. + +loop(#state{meta=Meta} = State) -> + receive + + %% From the GUI main window + GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs -> + Cmd = + dbg_ui_trace_win:handle_event(GuiEvent,State#state.win), + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the GUI help windows + {gui, Cmd} -> + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the interpreter + {int, Cmd} -> + State2 = int_cmd(Cmd, State), + loop(State2); + + %% From the meta process + {Meta, Cmd} -> + State2 = meta_cmd(Cmd, State), + loop(State2); + {NewMeta, {exit_at, Where, Reason, Cur}} -> + State2 = meta_cmd({exit_at, Where, Reason, Cur}, + State#state{meta=NewMeta}), + loop(State2); + + %% From the dbg_ui_edit process + {dbg_ui_edit, 'Backtrace:', BackTrace} -> + loop(State#state{backtrace=BackTrace}); + {dbg_ui_edit, Var, Val} -> + Cmd = atom_to_list(Var)++"="++io_lib:format("~p", [Val]), + State2 = gui_cmd({user_command, lists:flatten(Cmd)}, State), + loop(State2); + + %% From the dbg_ui_winman process (Debugger window manager) + {dbg_ui_winman, update_windows_menu, Data} -> + dbg_ui_winman:update_windows_menu(Data), + loop(State); + {dbg_ui_winman, destroy} -> + exit(stop) + end. + +%%--Commands from the GUI--------------------------------------------- + +gui_cmd(ignore, State) -> + State; +gui_cmd({win, Win}, State) -> + State#state{win=Win}; +gui_cmd(stopped, _State) -> + exit(stop); +gui_cmd({coords, Coords}, State) -> + State#state{coords=Coords}; + +gui_cmd({shortcut, Key}, State) -> + case shortcut(Key) of + {always, Cmd} -> gui_cmd(Cmd, State); + {if_enabled, Cmd} -> + case dbg_ui_trace_win:is_enabled(Cmd) of + true -> gui_cmd(Cmd, State); + false -> State + end; + false -> State + end; + +%% File menu +gui_cmd('Close', State) -> + gui_cmd(stopped, State); + +%% Edit menu +gui_cmd('Go To Line...', State) -> + %% Will result in message handled below: {gui, {gotoline, Line}} + dbg_ui_trace_win:helpwin(gotoline, State#state.win, + State#state.gs, State#state.coords), + State; +gui_cmd({gotoline, Line}, State) -> + Win = dbg_ui_trace_win:select_line(State#state.win, Line), + State#state{win=Win}; +gui_cmd('Search...', State) -> + dbg_ui_trace_win:helpwin(search, State#state.win, + State#state.gs, State#state.coords), + State; + +%% Process menu +gui_cmd('Step', State) -> + int:meta(State#state.meta, step), + State; +gui_cmd('Next', State) -> + int:meta(State#state.meta, next), + State; +gui_cmd('Continue', State) -> + int:meta(State#state.meta, continue), + {Status, Mod, Line} = State#state.status, + if + Status==wait_break -> + Win = dbg_ui_trace_win:unmark_line(State#state.win), + gui_enable_functions(wait_running), + State#state{win=Win, status={wait_running,Mod,Line}}; + true -> + dbg_ui_trace_win:enable(['Stop'], true), + dbg_ui_trace_win:enable(['Continue'], false), + State + end; +gui_cmd('Finish', State) -> + int:meta(State#state.meta, finish), + State; +gui_cmd('Skip', State) -> + int:meta(State#state.meta, skip), + State; +gui_cmd('Time Out', State) -> + int:meta(State#state.meta, timeout), + State; +gui_cmd('Stop', State) -> + int:meta(State#state.meta, stop), + {Status, Mod, Line} = State#state.status, + if + Status==wait_running -> + Win = dbg_ui_trace_win:mark_line(State#state.win, Line, + break), + gui_enable_functions(wait_break), + gui_enable_updown(State#state.stack_trace, + State#state.stack), + gui_enable_btrace(State#state.trace, + State#state.stack_trace), + dbg_ui_trace_win:display({wait, Mod, Line}), + State#state{win=Win, status={wait_break,Mod,Line}}; + true -> + dbg_ui_trace_win:enable(['Stop'], false), + dbg_ui_trace_win:enable(['Continue'], true), + State + end; +gui_cmd('Where', State) -> + {_Cur, Max} = State#state.stack, + Stack = {Max, Max}, + {_Status, Mod, Line} = State#state.status, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, break), + gui_update_bindings(State#state.meta), + gui_enable_updown(State#state.stack_trace, Stack), + dbg_ui_trace_win:display(State#state.status), + State#state{win=Win, cm=Mod, stack=Stack}; + +gui_cmd('Kill', State) -> + exit(State#state.pid, kill), + State; +gui_cmd('Messages', State) -> + case int:meta(State#state.meta, messages) of + [] -> + dbg_ui_trace_win:eval_output("< No Messages!\n", bold); + Messages -> + dbg_ui_trace_win:eval_output("< --- Current Messages ---\n", + bold), + lists:foldl( + fun(Msg, N) -> + Str1 = io_lib:format(" ~w:", [N]), + dbg_ui_trace_win:eval_output(Str1, bold), + Str2 = io_lib:format(" ~s~n",[io_lib:print(Msg)]), + dbg_ui_trace_win:eval_output(Str2, normal), + N+1 + end, + 1, + Messages) + end, + State; +gui_cmd('Back Trace', State) -> + dbg_ui_trace_win:trace_output("\nBACK TRACE\n----------\n"), + lists:foreach( + fun({Le, {Mod,Func,Args}}) -> + Str = io_lib:format("~p > ~p:~p~p~n", + [Le, Mod, Func, Args]), + dbg_ui_trace_win:trace_output(Str); + ({Le, {Fun,Args}}) -> + Str = io_lib:format("~p > ~p~p~n", [Le, Fun, Args]), + dbg_ui_trace_win:trace_output(Str); + (_) -> ignore + end, + int:meta(State#state.meta, backtrace, State#state.backtrace)), + dbg_ui_trace_win:trace_output("\n"), + State; +gui_cmd('Up', State) -> + {Cur, Max} = State#state.stack, + case int:meta(State#state.meta, stack_frame, {up, Cur}) of + {New, {undefined,-1}, _Bs} -> % call from non-interpreted code + Stack = {New, Max}, + Win = dbg_ui_trace_win:show_no_code(State#state.win), + dbg_ui_trace_win:update_bindings([]), + gui_enable_updown(State#state.stack_trace, Stack), + dbg_ui_trace_win:display({New,null,null}), + State#state{win=Win, cm=null, stack=Stack}; + + {New, {Mod,Line}, Bs} -> + Stack = {New, Max}, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, + where), + dbg_ui_trace_win:update_bindings(Bs), + gui_enable_updown(State#state.stack_trace, Stack), + dbg_ui_trace_win:display({New,Mod,Line}), + State#state{win=Win, cm=Mod, stack=Stack}; + top -> + dbg_ui_trace_win:enable(['Up'], false), + State + end; +gui_cmd('Down', State) -> + {Cur, Max} = State#state.stack, + case int:meta(State#state.meta, stack_frame, {down, Cur}) of + {New, {undefined,-1}, _Bs} -> % call from non-interpreted code + Stack = {New, Max}, + Win = dbg_ui_trace_win:show_no_code(State#state.win), + dbg_ui_trace_win:update_bindings([]), + gui_enable_updown(State#state.stack_trace, Stack), + dbg_ui_trace_win:display({New,null,null}), + State#state{win=Win, cm=null, stack=Stack}; + + {New, {Mod,Line}, Bs} -> + Stack = {New, Max}, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, + where), + dbg_ui_trace_win:update_bindings(Bs), + gui_enable_updown(State#state.stack_trace, Stack), + dbg_ui_trace_win:display({New,Mod,Line}), + State#state{win=Win, cm=Mod, stack=Stack}; + + bottom -> + gui_cmd('Where', State) + end; + +%% Break menu +gui_cmd('Line Break...', State) -> + add_break(State#state.gs, State#state.coords, line, + State#state.cm, + dbg_ui_trace_win:selected_line(State#state.win)), + State; +gui_cmd('Conditional Break...', State) -> + add_break(State#state.gs, State#state.coords, conditional, + State#state.cm, + dbg_ui_trace_win:selected_line(State#state.win)), + State; +gui_cmd('Function Break...', State) -> + add_break(State#state.gs, State#state.coords, function, + State#state.cm, undefined), + State; +gui_cmd('Enable All', State) -> + Breaks = int:all_breaks(), + ThisMod = State#state.cm, + lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod -> + int:enable_break(Mod, Line); + (_Break) -> + ignore + end, + Breaks), + State; +gui_cmd('Disable All', State) -> + Breaks = int:all_breaks(), + ThisMod = State#state.cm, + lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod -> + int:disable_break(Mod, Line); + (_Break) -> + ignore + end, + Breaks), + State; +gui_cmd('Delete All', State) -> + int:no_break(State#state.cm), + State; +gui_cmd({break, {Mod, Line}, What}, State) -> + case What of + add -> int:break(Mod, Line); + delete -> int:delete_break(Mod, Line); + {status, inactive} -> int:disable_break(Mod, Line); + {status, active} -> int:enable_break(Mod, Line); + {trigger, Action} -> int:action_at_break(Mod, Line, Action) + end, + State; + +%% Options menu +gui_cmd({'Trace Window', TraceWin}, State) -> + Trace = lists:member('Trace Area', TraceWin), + int:meta(State#state.meta, trace, Trace), + Win = dbg_ui_trace_win:configure(State#state.win, TraceWin), + {Status,_,_} = State#state.status, + if + Status==break; Status==wait_break -> + gui_enable_btrace(Trace, State#state.stack_trace); + true -> ignore + end, + State#state{win=Win, trace=Trace}; +gui_cmd({'Stack Trace', [Name]}, State) -> + int:meta(State#state.meta, stack_trace, map(Name)), + {Status,_,_} = State#state.status, + if + Status==break; Status==wait_break -> + gui_enable_btrace(State#state.trace, map(Name)); + true -> ignore + end, + State; +gui_cmd('Back Trace Size...', State) -> + dbg_ui_edit:start(State#state.gs, State#state.coords, "Backtrace", + 'Backtrace:', {integer, State#state.backtrace}), + State; + +%% Help menu +gui_cmd('Debugger', State) -> + Window = dbg_ui_trace_win:get_window(State#state.win), + HelpFile = filename:join([code:lib_dir(debugger), + "doc", "html", "part_frame.html"]), + tool_utils:open_help(Window, HelpFile), + State; + +gui_cmd({user_command, Cmd}, State) -> + {Status, _Mod, _Line} = State#state.status, + if + Status==break; + Status==wait_break; + Status==wait_running -> + Cm = State#state.cm, + Arg = case State#state.stack of + {Cur, Max} when Cur<Max -> {Cm, Cmd, Cur}; + _Stack -> {Cm, Cmd} + end, + + %% Reply will be received as {Meta, {eval_rsp, Res}} + int:meta(State#state.meta, eval, Arg); + true -> + Str = "Commands not allowed", + dbg_ui_trace_win:eval_output([$<,Str,10], normal) + end, + State; + +gui_cmd({edit, {Var, Val}}, State) -> + dbg_ui_edit:start(State#state.gs, State#state.coords, + "Edit variable", Var, {term, Val}), + State. + +add_break(GS, Coords, Type, undefined, _Line) -> + dbg_ui_break:start(GS, Coords, Type); +add_break(GS, Coords, Type, Mod, undefined) -> + dbg_ui_break:start(GS, Coords, Type, Mod); +add_break(GS, Coords, Type, Mod, Line) -> + dbg_ui_break:start(GS, Coords, Type, Mod, Line). + +%%--Commands from the interpreter------------------------------------- + +int_cmd({interpret, Mod}, State) -> + if + Mod==State#state.cm -> + State#state{cm_obsolete=true}; + true -> + State + end; +int_cmd({no_interpret, Mod}, State) -> + if + Mod==State#state.cm -> + State#state{cm_obsolete=true}; + true -> + Win = dbg_ui_trace_win:remove_code(State#state.win, Mod), + State#state{win=Win} + end; + +int_cmd({new_break, Break}, State) -> + Win = dbg_ui_trace_win:add_break(State#state.win, 'Break', Break), + State#state{win=Win}; +int_cmd({delete_break, Point}, State) -> + Win = dbg_ui_trace_win:delete_break(State#state.win, Point), + State#state{win=Win}; +int_cmd({break_options, Break}, State) -> + Win = dbg_ui_trace_win:update_break(State#state.win, Break), + State#state{win=Win}; +int_cmd(no_break, State) -> + Win = dbg_ui_trace_win:clear_breaks(State#state.win), + State#state{win=Win}; +int_cmd({no_break, Mod}, State) -> + Win = dbg_ui_trace_win:clear_breaks(State#state.win, Mod), + State#state{win=Win}. + +%%--Commands from the meta process------------------------------------ + +%% Message received when first attached to a living process +%% '_Trace' is a boolean indicating if the process is traced or not -- +%% ignore this as we already have ordered tracing or not depending on if +%% the Trace Area is shown or not. +meta_cmd({attached, Mod, Line, _Trace}, State) -> + Win = if + Mod/=undefined -> + gui_enable_functions(init), + gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, + break); + true -> State#state.win + end, + State#state{win=Win, status={init,Mod,Line}, cm=Mod}; + +%% Message received when returning to interpreted code +meta_cmd({re_entry, dbg_ieval, eval_fun}, State) -> + State; +meta_cmd({re_entry, Mod, _Func}, State) -> + Obs = State#state.cm_obsolete, + case State#state.cm of + Mod when Obs==true -> + Win = gui_load_module(State#state.win, Mod,State#state.pid), + State#state{win=Win, cm_obsolete=false}; + Mod -> State; + Cm -> + Win = gui_show_module(State#state.win, Mod, 0, + Cm, State#state.pid, break), + State#state{win=Win, cm=Mod} + end; + +%% Message received when attached to a terminated process +meta_cmd({exit_at, null, Reason, Cur}, State) -> + Stack = {Cur, Cur}, + gui_enable_functions(exit), + gui_enable_updown(false, Stack), + dbg_ui_trace_win:display({exit, null, Reason}), + State#state{status={exit,null,Reason}, stack=Stack}; +meta_cmd({exit_at, {Mod,Line}, Reason, Cur}, State) -> + Stack = {Cur+1, Cur+1}, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, break), + gui_enable_functions(exit), + gui_enable_updown(State#state.stack_trace, Stack), + gui_enable_btrace(State#state.trace, State#state.stack_trace), + gui_update_bindings(State#state.meta), + dbg_ui_trace_win:display({exit, {Mod,Line}, Reason}), + State#state{win=Win, cm=Mod,status={exit,{Mod,Line},Reason}, + stack=Stack}; + +meta_cmd({break_at, Mod, Line, Cur}, State) -> + Stack = {Cur,Cur}, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, break), + gui_enable_functions(break), + gui_enable_updown(State#state.stack_trace, Stack), + gui_enable_btrace(State#state.trace, State#state.stack_trace), + gui_update_bindings(State#state.meta), + dbg_ui_trace_win:display({break, Mod, Line}), + State#state{win=Win, cm=Mod, status={break,Mod,Line}, stack=Stack}; +meta_cmd({func_at, Mod, Line, Cur}, State) -> + Stack = {Cur,Cur}, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, where), + gui_enable_functions(idle), + dbg_ui_trace_win:display(idle), + State#state{win=Win, cm=Mod, status={idle,Mod,Line}, stack=Stack}; +meta_cmd({wait_at, Mod, Line, Cur}, #state{status={Status,_,_}}=State) + when Status/=init, Status/=break -> + Stack = {Cur,Cur}, + gui_enable_functions(wait_running), + dbg_ui_trace_win:display({wait,Mod,Line}), + State#state{status={wait_running,Mod,Line}, stack=Stack}; +meta_cmd({wait_at, Mod, Line, Cur}, State) -> + Stack = {Cur,Cur}, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, break), + gui_enable_functions(wait_break), + gui_enable_updown(State#state.stack_trace, Stack), + gui_enable_btrace(State#state.trace, State#state.stack_trace), + gui_update_bindings(State#state.meta), + dbg_ui_trace_win:display({wait, Mod, Line}), + State#state{win=Win, cm=Mod, status={wait_break,Mod,Line}, + stack=Stack}; +meta_cmd({wait_after_at, Mod, Line, Sp}, State) -> + meta_cmd({wait_at, Mod, Line, Sp}, State); +meta_cmd(running, State) -> + Win = dbg_ui_trace_win:unmark_line(State#state.win), + gui_enable_functions(running), + dbg_ui_trace_win:update_bindings([]), + dbg_ui_trace_win:display({running, State#state.cm}), + State#state{win=Win, status={running,null,null}}; + +meta_cmd(idle, State) -> + Win = dbg_ui_trace_win:show_no_code(State#state.win), + gui_enable_functions(idle), + dbg_ui_trace_win:update_bindings([]), + dbg_ui_trace_win:display(idle), + State#state{win=Win, status={idle,null,null}, cm=undefined}; + +%% Message about changed trace option can be ignored, the change must +%% have been ordered by this process. (In theory, the change could have +%% been ordered by another attached process. The Debugger, though, +%% allows max one attached process per debugged process). +meta_cmd({trace, _Bool}, State) -> + State; + +meta_cmd({stack_trace, Flag}, State) -> + dbg_ui_trace_win:select(map(Flag), true), + gui_enable_updown(Flag, State#state.stack), + {Status,_,_} = State#state.status, + if + Status==break; Status==wait_break -> + gui_enable_btrace(State#state.trace, Flag); + true -> ignore + end, + State#state{stack_trace=Flag}; + +meta_cmd({trace_output, Str}, State) -> + dbg_ui_trace_win:trace_output(Str), + State; + +%% Reply on a user command +meta_cmd({eval_rsp, Res}, State) -> + Str = io_lib:print(Res), + dbg_ui_trace_win:eval_output([$<,Str,10], normal), + State. + + +%%==================================================================== +%% GUI auxiliary functions +%%==================================================================== + +menus() -> + [{'File', [{'Close', no}]}, + {'Edit', [{'Go To Line...', no}, + {'Search...', no}]}, + {'Process', [{'Step', 0}, + {'Next', 0}, + {'Continue', 0}, + {'Finish', 0}, + {'Skip', no}, + {'Time Out', no}, + {'Stop', no}, + separator, + {'Kill', no}, + separator, + {'Messages', 0}, + {'Back Trace', no}, + separator, + {'Where', 0}, + {'Up', no}, + {'Down', no}]}, + {'Break', [{'Line Break...', 5}, + {'Conditional Break...', no}, + {'Function Break...', no}, + separator, + {'Enable All', no}, + {'Disable All', no}, + {'Delete All', 0}, + separator]}, + {'Options', [{'Trace Window', no, cascade, + [{'Button Area', no, check}, + {'Evaluator Area', no, check}, + {'Bindings Area', no, check}, + {'Trace Area', no, check}]}, + {'Stack Trace', no, cascade, + [{'Stack On, Tail', no, radio}, + {'Stack On, No Tail', no, radio}, + {'Stack Off', no, radio}]}, + {'Back Trace Size...', no}]}, + {'Help', [{'Debugger', no}]}]. + +%% enable(Status) -> [MenuItem] +%% Status = init % when first message from Meta has arrived +%% | idle | break | exit | wait_break � wait_running | running +enable(init) -> []; +enable(idle) -> ['Stop','Kill']; +enable(break) -> ['Step','Next','Continue','Finish','Skip', + 'Kill','Messages']; +enable(exit) -> []; +enable(wait_break) -> ['Continue','Time Out','Kill']; +enable(wait_running) -> ['Stop','Kill']; +enable(running) -> ['Stop','Kill']. + +all_buttons() -> + ['Step','Next','Continue','Finish','Skip','Time Out','Stop', + 'Kill','Messages','Back Trace','Where','Up','Down']. + +shortcut(s) -> {if_enabled, 'Step'}; +shortcut(n) -> {if_enabled, 'Next'}; +shortcut(c) -> {if_enabled, 'Continue'}; +shortcut(f) -> {if_enabled, 'Finish'}; +shortcut(m) -> {if_enabled, 'Messages'}; +shortcut(w) -> {if_enabled, 'Where'}; + +shortcut(b) -> {always, 'Line Break...'}; +shortcut(d) -> {always, 'Delete All'}; + +shortcut(_) -> false. + +map('Stack On, Tail') -> all; % Stack trace +map('Stack On, No Tail') -> no_tail; +map('Stack Off') -> false; +map(all) -> 'Stack On, Tail'; +map(true) -> 'Stack On, Tail'; +map(no_tail) -> 'Stack On, No Tail'; +map(false) -> 'Stack Off'. + + +%% gui_show_module(Win, Mod, Line, Cm, Pid, How) -> Win +%% gui_show_module(Win, {Mod,Line}, _Reason, Cm, Pid, How) -> Win +%% How = where | break +%% Show contents of a module in code area +gui_show_module(Win, {Mod,Line}, _Reason, Cm, Pid, How) -> + gui_show_module(Win, Mod, Line, Cm, Pid, How); +gui_show_module(Win, Mod, Line, Mod, _Pid, How) -> + dbg_ui_trace_win:mark_line(Win, Line, How); +gui_show_module(Win, Mod, Line, _Cm, Pid, How) -> + Win2 = case dbg_ui_trace_win:is_shown(Win, Mod) of + {true, Win3} -> Win3; + false -> gui_load_module(Win, Mod, Pid) + end, + dbg_ui_trace_win:mark_line(Win2, Line, How). + +gui_load_module(Win, Mod, Pid) -> + dbg_ui_trace_win:display({text, "Loading module..."}), + Contents = int:contents(Mod, Pid), + Win2 = dbg_ui_trace_win:show_code(Win, Mod, Contents), + dbg_ui_trace_win:display({text, ""}), + Win2. + +gui_update_bindings(Meta) -> + Bs = int:meta(Meta, bindings, nostack), + dbg_ui_trace_win:update_bindings(Bs). + +gui_enable_functions(Status) -> + Enable = enable(Status), + Disable = all_buttons() -- Enable, + dbg_ui_trace_win:enable(Disable, false), + dbg_ui_trace_win:enable(Enable, true). + +gui_enable_updown(Flag, Stack) -> + {Enable, Disable} = + if + Flag==false -> {[], ['Up', 'Down']}; + true -> + case Stack of + {1,1} -> {[], ['Up', 'Down']}; + {2,2} -> {[], ['Up', 'Down']}; + {Max,Max} -> {['Up'], ['Down']}; + {2,_Max} -> {['Down'], ['Up']}; + {_Cur,_Max} -> {['Up', 'Down'], []} + end + end, + dbg_ui_trace_win:enable(Enable, true), + dbg_ui_trace_win:enable(Disable, false), + if + Enable==[] -> dbg_ui_trace_win:enable(['Where'], false); + true -> dbg_ui_trace_win:enable(['Where'], true) + end. + +gui_enable_btrace(Trace, StackTrace) -> + Bool = if + Trace==false -> false; + StackTrace==false -> false; + true -> true + end, + dbg_ui_trace_win:enable(['Back Trace'], Bool). diff --git a/lib/debugger/src/dbg_ui_trace_win.erl b/lib/debugger/src/dbg_ui_trace_win.erl new file mode 100644 index 0000000000..dbf93c7f45 --- /dev/null +++ b/lib/debugger/src/dbg_ui_trace_win.erl @@ -0,0 +1,1597 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-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_ui_trace_win). + +%% External exports +-export([init/0]). +-export([create_win/4, get_window/1, + configure/2, + enable/2, is_enabled/1, select/2, + add_break/3, update_break/2, delete_break/2, + clear_breaks/1, clear_breaks/2, + display/1, % Help messages + is_shown/2, % Code area + show_code/3, show_no_code/1, remove_code/2, + mark_line/3, unmark_line/1, + select_line/2, selected_line/1, + eval_output/2, % Evaluator area + update_bindings/1, % Bindings area + trace_output/1, % Trace area + handle_event/2 + ]). +-export([helpwin/4, % Help windows + helpwin/5]). + +-record(breakInfo, {point, status, break}). +-record(winInfo, {window, % gsobj() + size, % {W, H} + flags, % {F,F,F,F} F = open|close + + marked_line=0, % integer() Current line + selected_line=0, % integer() Selected line + + breaks=[], % [#breakInfo{}] Known breakpoints + + editor, % {Mod, Editor} Visible code editor + editors=[] % [{Mod,Editor}] Code editors + }). + + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% init() -> GS +%% GS = term() +%%-------------------------------------------------------------------- +init() -> + dbg_ui_win:init(). + +%%-------------------------------------------------------------------- +%% create_win(GS, Title, TraceWin, Menus) -> #winInfo{} +%% GS = gsobj() +%% Title = string() +%% TraceWin = [WinArea] +%% WinArea = 'Button|Evaluator|Bindings|Trace Area' +%% Menus = [menu()] See dbg_ui_win.erl +%%-------------------------------------------------------------------- +create_win(GS, Title, TraceWin, Menus) -> + Bu = flip(lists:member('Button Area', TraceWin)), + Ev = flip(lists:member('Evaluator Area', TraceWin)), + Bi = flip(lists:member('Bindings Area', TraceWin)), + Tr = flip(lists:member('Trace Area', TraceWin)), + + Win = gs:window(trace_window, GS, [{title, Title}, + {width, 550}, + {configure,true}, {destroy,true}, + {keypress,true}, {motion,true}]), + + MenuBar = gs:menubar(Win, []), + dbg_ui_win:create_menus(MenuBar, Menus), + dbg_ui_winman:windows_menu(MenuBar), + + FrameOpts = [{anchor,nw}, {relief,raised}, {bw,2}, {keypress,true}], + Editor = code_area(2, 25, FrameOpts, Win), + button_area(Bu, 2, 235, FrameOpts, Win), + eval_area({Ev,Bi}, 2, 265, FrameOpts, Win), + bind_area({Ev,Bi}, 300, 265, FrameOpts, Win), + trace_area(Tr, 2, 475, FrameOpts, Win), + + Flags = {Bu, Ev, Bi, Tr}, + resizebar(rb1(Flags), 'RB1', 2, 225, 710, 10, Win), + resizebar(rb2(Flags), 'RB2', 2, 465, 710, 10, Win), + resizebar(rb3(Flags), 'RB3', 290, 265, 10, 200, Win), + config_v(), + config_h(), + + gs:config(Win,{height, + 25 + + gs:read('CodeArea', height) + + gs:read('RB1', height) + + gs:read('ButtonArea', height) + + max(gs:read('EvalArea', height), + gs:read('BindArea', height)) + + gs:read('RB2', height) + + gs:read('TraceArea', height)}), + + gs:config(Win, {map, true}), + #winInfo{window=Win, size={gs:read(Win,width), gs:read(Win,height)}, + flags=Flags, + editor={'$top', Editor}, editors=[{'$top', Editor}]}. + +flip(true) -> open; +flip(false) -> close. + +%%-------------------------------------------------------------------- +%% get_window(WinInfo) -> Window +%% WinInfo = #winInfo{} +%% Window = gsobj() +%%-------------------------------------------------------------------- +get_window(WinInfo) -> + WinInfo#winInfo.window. + +%%-------------------------------------------------------------------- +%% configure(WinInfo, TraceWin) -> WinInfo +%% WinInfo = #winInfo{} +%% TraceWin = [WinArea] +%% WinArea = 'Button|Evaluator|Bindings|Trace Area' +%% Window areas should be opened or closed. +%%-------------------------------------------------------------------- +configure(WinInfo, TraceWin) -> + {Bu1, Ev1, Bi1, Tr1} = OldFlags = WinInfo#winInfo.flags, + Bu2 = flip(lists:member('Button Area', TraceWin)), + Ev2 = flip(lists:member('Evaluator Area', TraceWin)), + Bi2 = flip(lists:member('Bindings Area', TraceWin)), + Tr2 = flip(lists:member('Trace Area', TraceWin)), + NewFlags = {Bu2, Ev2, Bi2, Tr2}, + + Win = WinInfo#winInfo.window, + W = gs:read(Win, width), + H = gs:read(Win, height), + + H2 = if + Bu1==close, Bu2==open -> + resize_button_area(open, width, W-4), + gs:config('ButtonArea', {height, 30}), + H+30; + Bu1==open, Bu2==close -> + gs:config('ButtonArea', [{width, 0}, {height, 0}]), + H-30; + true -> H + end, + H3 = if + Ev1==close, Ev2==open, Bi1==open -> + Wnew1 = round((W-10-4)/2), % W = window/2 - rb - pads + Hbi1 = gs:read('BindArea', height), % H = bind area h + resize_eval_area(open, width, Wnew1), + resize_eval_area(open, height, Hbi1), + gs:config('RB3', {width, 10}), + gs:config('RB3', {height, Hbi1}), + resize_bind_area(open, width, + Wnew1-gs:read('BindArea', width)), + H2; + Ev1==close, Ev2==open, Bi1==close -> + resize_eval_area(open, width, W-4), + resize_eval_area(open, height, 200), + H2+200; + Ev1==open, Ev2==close, Bi1==open -> + gs:config('EvalArea', [{width,0}, {height,0}]), + gs:config('RB3', [{width, 0}, {height, 0}]), + Wnew2 = W-4, + resize_bind_area(open, width, + Wnew2-gs:read('BindArea', width)), + H2; + Ev1==open, Ev2==close, Bi1==close -> + Hs1 = gs:read('EvalArea', height), + gs:config('EvalArea', [{width, 0}, {height, 0}]), + H2-Hs1; + true -> H2 + end, + H4 = if + Bi1==close, Bi2==open, Ev2==open -> + Wnew3 = round((W-10-4)/2), % W = window/2 - rb - pads + Hs2 = gs:read('EvalArea', height), % H = eval area h + resize_bind_area(open, width, Wnew3), + resize_bind_area(open, height, Hs2), + gs:config('RB3', [{width,10},{height,Hs2}]), + resize_eval_area(open, width, + Wnew3-gs:read('EvalArea', width)), + H3; + Bi1==close, Bi2==open, Ev2==close -> + resize_bind_area(open, width, W-4), + resize_bind_area(open, height, 200), + H3+200; + Bi1==open, Bi2==close, Ev2==open -> + gs:config('BindArea', [{width, 0}, {height, 0}]), + gs:config('RB3', [{width, 0}, {height, 0}]), + Wnew4 = W-4, + resize_eval_area(open, width, + Wnew4-gs:read('EvalArea', width)), + H3; + Bi1==open, Bi2==close, Ev2==close -> + Hbi2 = gs:read('BindArea', height), + gs:config('BindArea', [{width, 0}, {height, 0}]), + H3-Hbi2; + true -> H3 + end, + H5 = if + Tr1==close, Tr2==open -> + resize_trace_area(open, width, W-4), + resize_trace_area(open, height, 200), + H4+200; + Tr1==open, Tr2==close -> + Hf = gs:read('TraceArea', height), + gs:config('TraceArea', [{width, 0}, {height, 0}]), + H4-Hf; + true -> H4 + end, + gs:config(Win, {height, H5}), + + RB1old = rb1(OldFlags), RB1new = rb1(NewFlags), + if + RB1old==close, RB1new==open -> + gs:config('RB1', [{width, W-4}, {height, 10}]), + gs:config(Win, {height, gs:read(Win, height)+10}); + RB1old==open, RB1new==close -> + gs:config('RB1', [{width, 0}, {height, 0}, lower]), + gs:config(Win, {height, gs:read(Win, height)-10}); + true -> ignore + end, + + RB2old = rb2(OldFlags), RB2new = rb2(NewFlags), + if + RB2old==close, RB2new==open -> + gs:config('RB2', [{width, W-4}, {height, 10}]), + gs:config(Win, {height,gs:read(Win, height)+10}); + RB2old==open, RB2new==close -> + gs:config('RB2', [{width, 0}, {height, 0}, lower]), + gs:config(Win, {height, gs:read(Win, height)-10}); + true -> ignore + end, + config_v(), + config_h(), + + flush_configure(), + + WinInfo#winInfo{size={gs:read(Win, width), gs:read(Win, height)}, + flags=NewFlags}. + +flush_configure() -> + receive + {gs, _Id, configure, _Data, _Arg} -> + flush_configure() + after 100 -> + true + end. + +%%-------------------------------------------------------------------- +%% enable([MenuItem], Bool) +%% is_enabled(MenuItem) -> Bool +%% MenuItem = atom() +%% Bool = boolean() +%%-------------------------------------------------------------------- +enable(MenuItems, Bool) -> + lists:foreach(fun(MenuItem) -> + gs:config(MenuItem, {enable, Bool}), + case is_button(MenuItem) of + {true, Button} -> + gs:config(Button, {enable, Bool}); + false -> ignore + end + end, + MenuItems). + +is_enabled(MenuItem) -> + gs:read(MenuItem, enable). + +%%-------------------------------------------------------------------- +%% select(MenuItem, Bool) +%% MenuItem = atom() +%% Bool = boolean() +%%-------------------------------------------------------------------- +select(MenuItem, Bool) -> + dbg_ui_win:select(MenuItem, Bool). + +%%-------------------------------------------------------------------- +%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo +%% WinInfo = #winInfo{} +%% Name = atom() Menu name +%% Point = {Mod, Line} +%% Options = [Status, Action, Mods, Cond] +%% Status = active | inactive +%% Action = enable | disable | delete +%% Mods = null (not used) +%% Cond = null | {Mod, Func} +%%-------------------------------------------------------------------- +add_break(WinInfo, Menu, {{Mod,Line},[Status|_Options]}=Break) -> + case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of + {value, {Mod, Editor}} -> + add_break_to_code(Editor, Line, Status); + false -> ignore + end, + add_break_to_menu(WinInfo, Menu, Break). + +add_break_to_code(Editor, Line, Status) -> + Color = if Status==active -> red; Status==inactive -> blue end, + config_editor(Editor, [{overwrite,{{Line,0},"-@- "}}, + {fg,{{{Line,0},{Line,lineend}}, Color}}]). + +add_break_to_menu(WinInfo, Menu, {Point, [Status|_Options]=Options}) -> + Break = dbg_ui_win:add_break(Menu, Point), + dbg_ui_win:update_break(Break, Options), + BreakInfo = #breakInfo{point=Point, status=Status, break=Break}, + WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}. + +%%-------------------------------------------------------------------- +%% update_break(WinInfo, {Point, Options}) -> WinInfo +%% WinInfo = #winInfo{} +%% Point = {Mod, Line} +%% Options = [Status, Action, Mods, Cond] +%% Status = active | inactive +%% Action = enable | disable | delete +%% Mods = null (not used) +%% Cond = null | {Mod, Func} +%%-------------------------------------------------------------------- +update_break(WinInfo, {{Mod,Line},[Status|_Options]}=Break) -> + case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of + {value, {Mod, Editor}} -> + add_break_to_code(Editor, Line, Status); + false -> ignore + end, + update_break_in_menu(WinInfo, Break). + +update_break_in_menu(WinInfo, {Point, [Status|_Options]=Options}) -> + {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks), + dbg_ui_win:update_break(BreakInfo#breakInfo.break, Options), + BreakInfo2 = BreakInfo#breakInfo{status=Status}, + WinInfo#winInfo{breaks=lists:keyreplace(Point, #breakInfo.point, + WinInfo#winInfo.breaks, + BreakInfo2)}. + +%%-------------------------------------------------------------------- +%% delete_break(WinInfo, Point) -> WinInfo +%% WinInfo = #winInfo{} +%% Point = {Mod, Line} +%%-------------------------------------------------------------------- +delete_break(WinInfo, {Mod,Line}=Point) -> + case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of + {value, {Mod, Editor}} -> delete_break_from_code(Editor, Line); + false -> ignore + end, + delete_break_from_menu(WinInfo, Point). + +delete_break_from_code(Editor, Line) -> + Prefix = string:substr(integer_to_list(Line)++": ", 1, 5), + config_editor(Editor, [{overwrite,{{Line,0},Prefix}}, + {fg,{{{Line,0},{Line,lineend}}, black}}]). + +delete_break_from_menu(WinInfo, Point) -> + {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks), + dbg_ui_win:delete_break(BreakInfo#breakInfo.break), + WinInfo#winInfo{breaks=lists:keydelete(Point, #breakInfo.point, + WinInfo#winInfo.breaks)}. + +%%-------------------------------------------------------------------- +%% clear_breaks(WinInfo) -> WinInfo +%% clear_breaks(WinInfo, Mod) -> WinInfo +%% WinInfo = #winInfo{} +%%-------------------------------------------------------------------- +clear_breaks(WinInfo) -> + clear_breaks(WinInfo, all). +clear_breaks(WinInfo, Mod) -> + Remove = if + Mod==all -> WinInfo#winInfo.breaks; + true -> + lists:filter(fun(#breakInfo{point={Mod2,_L}}) -> + if + Mod2==Mod -> true; + true -> false + end + end, + WinInfo#winInfo.breaks) + end, + lists:foreach(fun(#breakInfo{point=Point}) -> + delete_break(WinInfo, Point) + end, + Remove), + Remain = WinInfo#winInfo.breaks -- Remove, + WinInfo#winInfo{breaks=Remain}. + +%%-------------------------------------------------------------------- +%% display(Arg) +%% Arg = idle | {Status,Mod,Line} | {running,Mod} +%% � {exit,Where,Reason} | {text,Text} +%% Status = break | wait � Level +%% Level = int() +%% Mod = atom() +%% Line = integer() +%% Where = {Mod,Line} | null +%% Reason = term() +%% Text = string() +%%-------------------------------------------------------------------- +display(Arg) -> + Str = case Arg of + idle -> "State: uninterpreted"; + {exit, {Mod,Line}, Reason} -> + gs:config(trace_window, raise), + io_lib:format("State: EXITED [~w.erl/~w], Reason:~w", + [Mod, Line, Reason]); + {exit, null, Reason} -> + gs:config(trace_window, raise), + io_lib:format("State: EXITED [uninterpreted], " + "Reason:~w", [Reason]); + {Level, null, _Line} when is_integer(Level) -> + io_lib:format("*** Call level #~w " + "(in non-interpreted code)", + [Level]); + {Level, Mod, Line} when is_integer(Level) -> + io_lib:format("*** Call level #~w [~w.erl/~w]", + [Level, Mod, Line]); + {Status, Mod, Line} -> + What = case Status of + wait -> 'receive'; + _ -> Status + end, + io_lib:format("State: ~w [~w.erl/~w]", + [What, Mod, Line]); + {running, Mod} -> + io_lib:format("State: running [~w.erl]", [Mod]); + {text, Text} -> Text + end, + gs:config(info_window, {label,{text,lists:flatten(Str)}}). + +%%-------------------------------------------------------------------- +%% is_shown(WinInfo, Mod) -> {true, WinInfo} | false +%% show_code(WinInfo, Mod, Contents) -> WinInfo +%% show_no_code(WinInfo) -> WinInfo +%% remove_code(WinInfo, Mod) -> WinInfo +%% WinInfo = #winInfo{} +%% Mod = atom() +%% Contents = string() +%% Note: remove_code/2 should not be used for currently shown module. +%%-------------------------------------------------------------------- +is_shown(WinInfo, Mod) -> + case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of + {value, {Mod, Editor}} -> + gs:config(Editor, raise), + {true, WinInfo#winInfo{editor={Mod, Editor}}}; + false -> false + end. + +show_code(WinInfo, Mod, Contents) -> + Editors = WinInfo#winInfo.editors, + {Flag, Editor} = case lists:keysearch(Mod, 1, Editors) of + {value, {Mod, Ed}} -> {existing, Ed}; + false -> {new, code_editor()} + end, + + %% Insert code and update breakpoints, if any + config_editor(Editor, [raise, clear]), + show_code(Editor, Contents), + lists:foreach(fun(BreakInfo) -> + case BreakInfo#breakInfo.point of + {Mod2, Line} when Mod2==Mod -> + Status = BreakInfo#breakInfo.status, + add_break_to_code(Editor, Line,Status); + _Point -> ignore + end + end, + WinInfo#winInfo.breaks), + + case Flag of + existing -> + WinInfo#winInfo{editor={Mod, Editor}}; + new -> + WinInfo#winInfo{editor={Mod, Editor}, + editors=[{Mod, Editor} | Editors]} + end. + +show_code(Editor, Text) when length(Text)>1500 -> + %% Add some text at a time so that other processes may get scheduled + Str = string:sub_string(Text, 1, 1500), + config_editor(Editor, {insert,{'end', Str}}), + show_code(Editor, string:sub_string(Text, 1501)); +show_code(Editor, Text) -> + config_editor(Editor, {insert,{'end',Text}}). + +show_no_code(WinInfo) -> + {value, {'$top', Editor}} = + lists:keysearch('$top', 1, WinInfo#winInfo.editors), + gs:config(Editor, raise), + WinInfo#winInfo{editor={'$top', Editor}}. + +remove_code(WinInfo, Mod) -> + Editors = WinInfo#winInfo.editors, + case lists:keysearch(Mod, 1, Editors) of + {value, {Mod, Editor}} -> + gs:destroy(Editor), + WinInfo#winInfo{editors=lists:keydelete(Mod, 1, Editors)}; + false -> + WinInfo + end. + + +%%-------------------------------------------------------------------- +%% mark_line(WinInfo, Line, How) -> WinInfo +%% WinInfo = #winInfo{} +%% Line = integer() +%% How = break | where +%% Mark the code line where the process is executing. +%%-------------------------------------------------------------------- +mark_line(WinInfo, Line, How) -> + {_Mod, Editor} = WinInfo#winInfo.editor, + mark_line2(Editor, WinInfo#winInfo.marked_line, false), + mark_line2(Editor, Line, How), + if + Line/=0 -> config_editor(Editor, {vscrollpos, Line-5}); + true -> ignore + end, + WinInfo#winInfo{marked_line=Line}. + +unmark_line(WinInfo) -> + mark_line(WinInfo, 0, false). + +mark_line2(Editor, Line, How) -> + Prefix = case How of + break -> "-->"; + where -> ">>>"; + false -> " " + end, + Font = if + How==false -> dbg_ui_win:font(normal); + true -> dbg_ui_win:font(bold) + end, + config_editor(Editor, [{overwrite, {{Line,5}, Prefix}}, + {font_style, + {{{Line,0},{Line,lineend}}, Font}}]). + +%%-------------------------------------------------------------------- +%% select_line(WinInfo, Line) -> WinInfo +%% selected_line(WinInfo) -> undefined | Line +%% WinInfo = #winInfo{} +%% Line = integer() +%% Select/unselect a line (unselect if Line=0). +%%-------------------------------------------------------------------- +select_line(WinInfo, Line) -> + {_Mod, Editor} = WinInfo#winInfo.editor, + + %% Since 'Line' may be specified by the user in the 'Go To Line' + %% help window, it must be checked that it is correct + Size = gs:read(Editor, size), + if + Line==0 -> + select_line(Editor, WinInfo#winInfo.selected_line, false), + WinInfo#winInfo{selected_line=0}; + Line<Size -> + select_line(Editor, Line, true), + config_editor(Editor, {vscrollpos, Line-5}), + WinInfo#winInfo{selected_line=Line}; + true -> + WinInfo + end. + +select_line(Editor, Line, true) -> + config_editor(Editor, {selection, {{Line,0}, {Line,lineend}}}); +select_line(Editor, _Line, false) -> + config_editor(Editor, {selection, {{1,0}, {1,0}}}). + +selected_line(WinInfo) -> + case WinInfo#winInfo.selected_line of + 0 -> undefined; + Line -> Line + end. + +%%-------------------------------------------------------------------- +%% eval_output(Str, Face) +%% Str = string() +%% Face = normal | bold +%%-------------------------------------------------------------------- +eval_output(Text, Face) -> + Y1 = gs:read('EvalEditor', size), + config_editor('EvalEditor', {insert,{'end',Text}}), + Y2 = gs:read('EvalEditor', size), + + Font = dbg_ui_win:font(Face), + config_editor('EvalEditor', + [{font_style, {{{Y1,0},{Y2,lineend}}, Font}}, + {vscrollpos,Y2}]). + +%%-------------------------------------------------------------------- +%% update_bindings(Bs) +%% Bs = [{Var,Val}] +%%-------------------------------------------------------------------- +update_bindings(Bs) -> + gs:config('BindGrid', {rows, {1,length(Bs)+1}}), + Font = dbg_ui_win:font(normal), + Last = + lists:foldl(fun({Var, Val}, Row) -> + Opts = [{text, {1,atom_to_list(Var)}}, + {text, {2,io_lib:format("~P", + [Val, 4])}}, + {doubleclick, true}, + {data, {binding,{Var,Val}}}], + case gs:read('BindGrid',{obj_at_row,Row}) of + undefined -> + gs:gridline('BindGrid', + [{row, Row}, + {height, 14}, + {font, Font} | Opts]); + GridLine -> + gs:config(GridLine, Opts) + end, + Row+1 + end, + 2, + Bs), + delete_gridlines(Last). + +delete_gridlines(Row) -> + case gs:read('BindGrid', {obj_at_row, Row}) of + undefined -> true; + GridLine -> + gs:destroy(GridLine), + delete_gridlines(Row+1) + end. + +%%-------------------------------------------------------------------- +%% trace_output(Str) +%% Str = string() +%%-------------------------------------------------------------------- +trace_output(Str) -> + Font = dbg_ui_win:font(normal), + config_editor('TraceEditor', + [{insert, {'end',Str}}, + {fg, {{{1,0},'end'},black}}, + {font_style, {{{1,0},'end'},Font}}]), + Max = gs:read('TraceEditor', size), + config_editor('TraceEditor', {vscrollpos, Max}). + +%%-------------------------------------------------------------------- +%% handle_event(GSEvent, WinInfo) -> Command +%% GSEvent = {gs, Id, Event, Data, Arg} +%% WinInfo = #winInfo{} +%% Command = ignore +%% | {win, WinInfo} +%% | stopped +%% | {coords, {X,Y}} +%% +%% | {shortcut, Key} +%% | MenuItem | {Menu, [MenuItem]} +%% MenuItem = Menu = atom() +%% | {break, Point, What} +%% What = add | delete | {status,Status} |{trigger,Trigger} +%% | {module, Mod, view} +%% +%% | {user_command, Cmd} +%% +%% | {edit, {Var, Val}} +%%-------------------------------------------------------------------- +%% Window events +handle_event({gs, _Id, configure, _Data, [W, H|_]}, WinInfo) -> + case WinInfo#winInfo.size of + {W, H} -> ignore; + _Size -> + configure(WinInfo, W, H), + {win, WinInfo#winInfo{size={W, H}}} + end; +handle_event({gs, _Id, destroy, _Data, _Arg}, _WinInfo) -> + stopped; +handle_event({gs, _Id, motion, _Data, [X,Y]}, WinInfo) -> + {LastX, LastY} = dbg_ui_win:motion(X, Y), + Win = WinInfo#winInfo.window, + {coords, {gs:read(Win, x)+LastX-5, gs:read(Win, y)+LastY-5}}; +handle_event({gs, RB, buttonpress, resizebar, _Arg}, WinInfo) -> + resize(WinInfo, RB), % Resize window contents + ignore; + +%% Menus, buttons and keyboard shortcuts +handle_event({gs, _Id, keypress, _Data, [Key,_,_,1]}, _WinInfo) -> + {shortcut, Key}; +handle_event({gs, _Id, click, {dbg_ui_winman, Win}, _Arg}, _WinInfo) -> + dbg_ui_winman:raise(Win), + ignore; +handle_event({gs, _Id, click, {menuitem, Name}, _Arg}, _WinInfo) -> + Name; +handle_event({gs, _Id, click, {menu, Menu}, _Arg}, _WinInfo) -> + Names = dbg_ui_win:selected(Menu), + {Menu, Names}; +handle_event({gs, _Id, click, {break, Point, What}, _Arg}, _WinInfo) -> + {break, Point, What}; +handle_event({gs, _Id, click, {module, Mod, view}, _Arg}, _WinInfo) -> + {module, Mod, view}; + +%% Code area +handle_event({gs, Editor, buttonpress, code_editor, _Arg}, WinInfo) -> + {Row, _Col} = gs:read(Editor, insertpos), + Again = receive + {gs, Editor, buttonpress, code_editor, _} -> + gs:read(Editor, insertpos) + after 500 -> + false + end, + case Again of + {Row, _} -> + {Mod, _Editor} = WinInfo#winInfo.editor, + Point = {Mod, Row}, + case lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks) of + {value, _BreakInfo} -> {break, Point, delete}; + false -> {break, Point, add} + end; + {Row2, _} -> + select_line(Editor, Row2, true), + {win, WinInfo#winInfo{selected_line=Row2}}; + false -> + select_line(Editor, Row, true), + {win, WinInfo#winInfo{selected_line=Row}} + end; + +%% Button area +handle_event({gs, _Id, click, {button, Name}, _Arg}, _WinInfo) -> + Name; + +%% Evaluator area +handle_event({gs, 'EvalEntry', keypress, _Data, ['Return'|_]}, _WI) -> + Command = case gs:read('EvalEntry', text) of + [10] -> + eval_output("\n", normal), + ignore; + Cmd -> + eval_output([$>, Cmd, 10], normal), + {user_command, Cmd} + end, + gs:config('EvalEntry', [{text,""}, {focus,false}]), + Command; + +%% Bindings area +handle_event({gs, _Id, click, {binding, {Var, Val}}, _Arg}, _WinInfo) -> + Str = io_lib:format("< ~p = ~p~n", [Var, Val]), + eval_output(Str, bold), + ignore; +handle_event({gs, _Id, doubleclick, {binding, B}, _Arg}, _WinInfo) -> + {edit, B}; + +handle_event(_GSEvent, _WinInfo) -> + ignore. + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +%%--Code Area--------------------------------------------------------- + +code_area(X, Y, FrameOpts, Win) -> + gs:frame('CodeArea', Win, + [{x,X}, {y,Y}, {width,546}, {height,400} | FrameOpts]), + gs:label(info_window, 'CodeArea', + [{label,{text,""}}, {font,dbg_ui_win:font(normal)}, + {x,5}, {y,10}, {width,406}, {height,15}, + {anchor,nw}, {align,w}]), + code_editor('CodeEditor', 536, 365). + +code_editor() -> + W = gs:read('CodeEditor', width), + H = gs:read('CodeEditor', height), + code_editor(null, W, H). + +code_editor(Name, W, H) -> + Editor = if + Name==null -> gs:editor('CodeArea', []); + true -> gs:editor(Name, 'CodeArea', []) + end, + gs:config(Editor, [{x,5}, {y,30}, {width,W}, {height,H}, + {keypress,false}, {buttonpress,true}, + {data,code_editor}]), + config_editor(Editor, [{vscroll,right}, {hscroll,bottom}]), + Font = dbg_ui_win:font(normal), + config_editor(Editor, [{wrap,none}, {fg,{{{1,0},'end'},black}}, + {font, Font}, + {font_style, {{{1,0},'end'},Font}}]), + Editor. + +resize_code_area(WinInfo, Key, Diff) -> + gs:config('CodeArea', {Key,gs:read('CodeArea', Key)+Diff}), + case Key of + width -> + gs:config(info_window, {Key,gs:read(info_window,Key)+Diff}); + height -> ignore + end, + + %% Resize all code editors + Value = gs:read('CodeEditor', Key)+Diff, + gs:config('CodeEditor', {Key,Value}), + Editors = WinInfo#winInfo.editors, + lists:foreach(fun({_Mod, Editor}) -> + gs:config(Editor, {Key,Value}) + end, + Editors). + +%%--Button Area------------------------------------------------------- + +buttons() -> + [{'Step','StepButton'}, {'Next','NextButton'}, + {'Continue','ContinueButton'}, {'Finish','FinishButton'}, + {'Where','WhereButton'}, {'Up','UpButton'}, {'Down','DownButton'}]. + +is_button(Name) -> + case lists:keysearch(Name, 1, buttons()) of + {value, {Name, Button}} -> {true, Button}; + false -> false + end. + +button_area(Bu, X, Y, FrameOpts, Win) -> + {W,H} = case Bu of + open -> {546,30}; + close -> {0,0} + end, + gs:frame('ButtonArea', Win, + [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]), + Font = dbg_ui_win:font(normal), + lists:foldl(fun({Name, Button}, Xb) -> + gs:button(Button, 'ButtonArea', + [{label, {text,Name}}, {font,Font}, + {x, Xb}, {y, 1}, + {width, 77}, {height, 24}, + {data, {button,Name}}]), + Xb+78 + end, + 1, + buttons()). + +resize_button_area(close, width, _Diff) -> + ignore; +resize_button_area(open, width, Diff) -> + gs:config('ButtonArea', {width, gs:read('ButtonArea', width)+Diff}). + +%%--Evaluator Area---------------------------------------------------- + +eval_area({Ev,Bi}, X, Y, FrameOpts, Win) -> + {W,H} = if + Ev==open -> {289,200}; + true -> {0,0} + end, + Font = dbg_ui_win:font(normal), + gs:frame('EvalArea', Win, + [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]), + gs:label('EvalArea', + [{label,{text,"Evaluator:"}}, {font, Font}, + {x,5}, {y,35}, {width,80}, {height,25}, + {anchor,sw}, {align,center}]), + gs:entry('EvalEntry', 'EvalArea', + [{font, Font}, + {x,80}, {y,35}, {width,185}, {height,25}, + {anchor,sw}, {keypress,true}]), + gs:editor('EvalEditor', 'EvalArea', + [{x,5}, {y,35}, {width, 280}, {height, 160}, + {keypress,false}, + {vscroll,right}, {hscroll,bottom}, + {wrap,none}, {fg,{{{1,0},'end'},black}}, + {font, Font}, + {font_style,{{{1,0},'end'},Font}}]), + gs:config('EvalEditor', {enable, false}), + if + Ev==open, Bi==close -> resize_eval_area(Ev, width, 257); + true -> ignore + end. + +resize_eval_area(close, _Key, _Diff) -> + ignore; +resize_eval_area(open, Key, Diff) -> + New = gs:read('EvalArea', Key)+Diff, + gs:config('EvalArea', {Key,New}), + case Key of + width -> + gs:config('EvalEntry', {width,New-104}), + gs:config('EvalEditor', {width,New-9}); + height -> + gs:config('EvalEditor', {height,New-40}) + end. + +%%--Bindings Area----------------------------------------------------- + +bind_area({Ev,Bi}, X, Y, FrameOpts, Win) -> + {W,H} = if + Bi==open -> {249,200}; + true -> {0,0} + end, + gs:frame('BindArea', Win, + [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]), + + Font = dbg_ui_win:font(bold), + gs:grid('BindGrid', 'BindArea', + [{x,2}, {y,2}, {height,193}, {width,241}, + {fg,black}, {vscroll,right}, {hscroll,bottom}, + {font,Font}, + calc_columnwidths(241), {rows, {1,50}}]), + gs:gridline('BindGrid', + [{row,1}, {height,14}, {fg,blue}, + {text,{1,"Name"}}, {text,{2,"Value"}}, {font,Font}]), + gs:config('BindGrid', {rows,{1,1}}), + if + Bi==open, Ev==close -> resize_bind_area(Bi, width, 297); + true -> ignore + end. + +resize_bind_area(close, _Key, _Diff) -> + ignore; +resize_bind_area(open, Key, Diff) -> + New = gs:read('BindArea', Key)+Diff, + gs:config('BindArea', {Key,New}), + case Key of + width -> + gs:config('BindGrid', {width,New-8}), + Cols = calc_columnwidths(New-8), + gs:config('BindGrid', Cols); + height -> + gs:config('BindGrid', {height,New-7}) + end. + +calc_columnwidths(Width) -> + if Width =< 291 -> + {columnwidths,[90,198]}; + true -> + S = (Width)/(90+198), + {columnwidths,[round(90*S),round(198*S)]} + end. + +%%--Trace Area-------------------------------------------------------- + +trace_area(Tr, X, Y, FrameOpts, Win) -> + {W,H} = case Tr of + open -> {546,200}; + close -> {0,0} + end, + gs:frame('TraceArea', Win, + [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]), + Editor = gs:editor('TraceEditor', 'TraceArea', + [{x,5}, {y,5}, {width,536}, {height,190}, + {keypress,false}]), + Font = dbg_ui_win:font(normal), + config_editor(Editor, + [{vscroll,right}, {hscroll,bottom}, + {wrap,none},{fg,{{{1,0},'end'},black}}, + {font, Font}, + {font_style,{{{1,0},'end'},Font}}]). + +resize_trace_area(close, _Key, _Diff) -> + ignore; +resize_trace_area(open, Key, Diff) -> + New = gs:read('TraceArea', Key)+Diff, + gs:config('TraceArea', {Key,New}), + gs:config('TraceEditor', {Key,New-10}). + +%%--Editors----------------------------------------------------------- + +config_editor(Editor, Opts) -> + gs:config(Editor, {enable,true}), + gs:config(Editor, Opts), + gs:config(Editor, {enable,false}). + +%%--Resize Bars------------------------------------------------------- +%% The resize bars are used to resize the areas within the window. + +%%-------------------------------------------------------------------- +%% resizebar(Flag, Name, X, Y, W, H, Obj) -> resizebar() +%% Flag = open | close +%% Name = atom() +%% X = Y = integer() Coordinates relative to Obj +%% W = H = integer() Width and height +%% Obj = gsobj() +%% Creates a 'resize bar', a frame object over which the cursor will +%% be of the 'resize' type. +%%-------------------------------------------------------------------- +resizebar(Flag, Name, X, Y, W, H, Obj) -> + {W2,H2} = case Flag of + open -> {W,H}; + close -> {0,0} + end, + gs:create(frame, Name, Obj, [{x,X}, {y,Y}, {width,W2}, {height,H2}, + {bw,2}, + {cursor,resize}, + {buttonpress,true}, {buttonrelease,true}, + {data,resizebar}]). + +rb1({_Bu,Ev,Bi,Tr}) -> + if + Ev==close, Bi==close, Tr==close -> close; + true -> open + end. + +rb2({_Bu,Ev,Bi,Tr}) -> + if + Tr==open -> + if + Ev==close, Bi==close -> close; + true -> open + end; + true -> close + end. + +rb3({_Bu,Ev,Bi,_Tr}) -> + if + Ev==open, Bi==open -> open; + true -> close + end. + +%%--Configuration----------------------------------------------------- +%% Resize the window as well as its contents + +%%-------------------------------------------------------------------- +%% config_v() +%% Reconfigure the window vertically. +%%-------------------------------------------------------------------- +config_v() -> + Y1 = 25+gs:read('CodeArea', height), + gs:config('RB1', {y,Y1}), + + Y2 = Y1+gs:read('RB1', height), + gs:config('ButtonArea', {y,Y2}), + + Y3 = Y2+gs:read('ButtonArea', height), + gs:config('EvalArea', {y,Y3}), + gs:config('RB3', {y,Y3}), + gs:config('BindArea', {y,Y3}), + + Y4 = Y3 + max(gs:read('EvalArea', height), + gs:read('BindArea', height)), + gs:config('RB2', {y,Y4}), + + Y5 = Y4 + gs:read('RB2', height), + gs:config('TraceArea', {y,Y5}). + +%%-------------------------------------------------------------------- +%% config_h() +%% Reconfigure the window horizontally. +%%-------------------------------------------------------------------- +config_h() -> + X1 = 2+gs:read('EvalArea', width), + gs:config('RB3', {x,X1}), + + X2 = X1+gs:read('RB3', width), + gs:config('BindArea', {x,X2}). + +%%-------------------------------------------------------------------- +%% configure(WinInfo, W, H) +%% The window has been resized, now its contents must be resized too. +%%-------------------------------------------------------------------- +configure(WinInfo, NewW, NewH) -> + {Bu,Ev,Bi,Tr} = Flags = WinInfo#winInfo.flags, + + OldW = gs:read('CodeArea', width)+4, + OldH = 25+gs:read('CodeArea', height)+ + gs:read('RB1', height)+ + gs:read('ButtonArea', height)+ + max(gs:read('EvalArea', height), gs:read('BindArea', height))+ + gs:read('RB2', height)+ + gs:read('TraceArea', height), + + %% Adjust width unless it is unchanged or less than minimum width + if + OldW/=NewW -> + {Dcode,Deval,Dbind} = configure_widths(OldW,NewW,Flags), + resize_code_area(WinInfo, width, Dcode), + case rb1(Flags) of + open -> + gs:config('RB1', {width,gs:read('RB1',width)+Dcode}); + close -> ignore + end, + resize_button_area(Bu, width, Dcode), + resize_eval_area(Ev, width, Deval), + resize_bind_area(Bi, width, Dbind), + case rb2(Flags) of + open -> + gs:config('RB2', {width,gs:read('RB2',width)+Dcode}); + close -> ignore + end, + resize_trace_area(Tr, width, Dcode), + config_h(); + true -> ignore + end, + + %% Adjust height unless it is unchanged or less than minimum height + if + OldH/=NewH -> + {Dcode2,Deval2,Dtrace} = configure_heights(OldH,NewH,Flags), + resize_code_area(WinInfo, height, Dcode2), + resize_eval_area(Ev, height, Deval2), + case rb3(Flags) of + open -> + gs:config('RB3', + {height,gs:read('RB3',height)+Deval2}); + close -> ignore + end, + resize_bind_area(Bi, height, Deval2), + resize_trace_area(Tr, height, Dtrace), + config_v(); + true -> ignore + end. + +%% Compute how much the width of each frame must be increased or +%% decreased in order to adjust to the new window width. +configure_widths(OldW, NewW, Flags) -> + {_Bu,Ev,Bi,_Tr} = Flags, + + %% Difference between old and new width, considering min window width + Diff = abs(max(OldW,330)-max(NewW,330)), + + %% Check how much the frames can be resized in reality + Limits = if + %% Window larger + NewW>OldW -> + if + Ev==open,Bi==open -> {0,Diff,Diff}; + Ev==open -> {0,Diff,0}; + Bi==open -> {0,0,Diff}; + true -> {Diff,0,0} + end; + + %% Window smaller; get difference between min size + %% and current size + OldW>NewW -> + if + Ev==open,Bi==open -> + {0, + gs:read('EvalArea',width)-204, + gs:read('BindArea',width)-112}; + Ev==open -> {0,Diff,0}; + Bi==open -> {0,0,Diff}; + true -> {Diff,0,0} + end + end, + + case Limits of + + %% No Shell or Bind frame, larger window + {T,0,0} when NewW>OldW -> {T,0,0}; + + %% No Shell or Bind frame, smaller window + {T,0,0} when OldW>NewW -> {-T,0,0}; + + %% Window larger; divide Diff among the frames and return result + {_,Sf,B} when NewW>OldW -> + {_,Sf2,B2} = divide([{0,0},{0,Sf},{0,B}],Diff), + {Sf2+B2,Sf2,B2}; + + %% Window smaller; divide Diff among the frames and return + %% the inverted result (the frames should shrink) + {_,Sf,B} when OldW>NewW -> + {_,Sf2,B2} = divide([{0,0},{0,Sf},{0,B}],Diff), + {-(Sf2+B2),-Sf2,-B2} + end. + +%% Compute how much the height of each frame must be increased or +%% decreased in order to adjust to the new window height. +configure_heights(OldH, NewH, Flags) -> + {_Bu,Ev,Bi,Tr} = Flags, + + %% Difference between old and new height, considering min win height + MinH = min_height(Flags), + Diff = abs(max(OldH,MinH)-max(NewH,MinH)), + + %% Check how much the frames can be resized in reality + {T,Sf,Ff} = if + %% Window larger + NewH>OldH -> + {Diff, + if + Ev==close, Bi==close -> 0; + true -> Diff + end, + if + Tr==open -> Diff; + true -> 0 + end}; + + %% Window smaller; get difference between min size + %% and current size + OldH>NewH -> + {gs:read('CodeArea',height)-100, + if + Ev==close, Bi==close -> 0; + true -> + if + Ev==open -> + gs:read('EvalArea',height)-100; + Bi==open -> + gs:read('BindArea',height)-100 + end + end, + if + Tr==open -> gs:read('TraceArea',height)-100; + true -> 0 + end} + end, + + if + %% Window larger; divide Diff among the frames and return result + NewH>OldH -> divide([{0,T},{0,Sf},{0,Ff}],Diff); + + %% Window smaller; divide Diff among the frames and return + %% the inverted result (the frames should shrink) + OldH>NewH -> + {T2,Sf2,Ff2} = divide([{0,T},{0,Sf},{0,Ff}],Diff), + {-T2,-Sf2,-Ff2} + end. + +%% Compute minimum window height +min_height(Flags) -> + {Bu,S,Bi,F} = Flags, + H1 = 25 + 100 + 2, % Upper pad + Trace frame + lower pad + H2 = H1 + bu(Bu) + s_bi(S,Bi) + f(F), + H3 = case rb1(Flags) of + open -> H2+10; + close -> H2 + end, + H4 = case rb2(Flags) of + open -> H3+10; + close -> H3 + end, + H4. + +bu(close) -> 0; +bu(open) -> 30. + +s_bi(close,close) -> 0; +s_bi(_,_) -> 100. + +f(close) -> 0; +f(open) -> 100. + +%% Try to distribute Diff as evenly as possible between E1, E2 and E3. +divide([{T,T},{S,S},{F,F}], _Diff) -> + {T,S,F}; +divide(L, Diff) -> + [{T,Tmax},{S,Smax},{F,Fmax}] = L, + + %% Count how many elements in L can still be filled + Rem = remaining(L), + + %% Divide Diff by Rem + D = Diff div Rem, + + if + %% All of Diff has been distributed + D==0 -> {T,S,F}; + + true -> + + %% For each element, try to add as much as possible of D + {NewT,Dt} = divide2(D,T,Tmax), + {NewS,Ds} = divide2(D,S,Smax), + {NewF,Df} = divide2(D,F,Fmax), + + %% Recur with a list of elements with new current values + %% and decreased Diff + divide([{NewT,Tmax},{NewS,Smax},{NewF,Fmax}], + (Diff rem Rem)+Dt+Ds+Df) + end. + +%% Count the number of 'non-filled' elements in L, ie where Curr<Max. +remaining([]) -> 0; +remaining([{Max,Max}|T]) -> remaining(T); +remaining([_H|T]) -> 1 + remaining(T). + +divide2(_Diff, Max, Max) -> {Max,0}; +divide2(Diff, Curr, Max) -> + New = Curr+Diff, + if + New>Max -> {Max,New-Max}; + true -> {New,0} + end. + +%%--Resizing using resize bars---------------------------------------- +%% Motions event will move the ResizeBar accordingly in Win, when +%% the mouse button is released, the window is reconfigured. + +resize(WinInfo, ResizeBar) -> + + %% Get window dimensions + W = gs:read(WinInfo#winInfo.window, width), + H = gs:read(WinInfo#winInfo.window, height), + + %% Call resize loop with min and max for the resize bars derived + %% from the window dimensions + resizeloop(WinInfo, ResizeBar, null, + rblimits('RB1',W,H), + rblimits('RB2',W,H), + rblimits('RB3',W,H)). + +resizeloop(WI, RB, Prev, {Min1,Max1},{Min2,Max2},{Min3,Max3}) -> + receive + {gs,_,motion,_,[_,Y]} when RB=='RB1', Y>Min1,Y<Max1 -> + gs:config('RB1', {y,Y}), + resizeloop(WI, RB, Y, {Min1,Max1},{Min2,Max2},{Min3,Max3}); + {gs,_,motion,_,_} when RB=='RB1' -> + resizeloop(WI, RB, Prev,{Min1,Max1},{Min2,Max2},{Min3,Max3}); + + {gs,_,motion,_,[_,Y]} when RB=='RB2', Y>Min2,Y<Max2 -> + gs:config('RB2', {y,Y}), + resizeloop(WI, RB, Y, {Min1,Max1},{Min2,Max2},{Min3,Max3}); + {gs,_,motion,_,_} when RB=='RB2' -> + resizeloop(WI, RB, Prev,{Min1,Max1},{Min2,Max2},{Min3,Max3}); + + {gs,_,motion,_,[X,_]} when RB=='RB3', X>Min3,X<Max3 -> + gs:config('RB3', {x,X}), + resizeloop(WI, RB, X, {Min1,Max1},{Min2,Max2},{Min3,Max3}); + {gs,_,motion,_,_} when RB=='RB3' -> + resizeloop(WI, RB, Prev,{Min1,Max1},{Min2,Max2},{Min3,Max3}); + + {gs,_,buttonrelease,_,_} -> + resize_win(WI, RB, Prev) + end. + +resize_win(_WinInfo, _RB, null) -> ignore; +resize_win(WinInfo, 'RB1', Y) -> + {_Bu,S,Bi,F} = Flags = WinInfo#winInfo.flags, + H = gs:read('CodeArea', height), + Diff = H-(Y-25), + + %% Resize Code, Evaluator and Binding areas + resize_code_area(WinInfo, height, -Diff), + if + S==close, Bi==close, F==open -> + resize_trace_area(open, height, Diff); + true -> + resize_eval_area(S, height, Diff), + resize_bind_area(Bi, height, Diff) + end, + + %% Resize vertical resize bar + case rb3(Flags) of + open -> gs:config('RB3', {height,gs:read('RB3',height)+Diff}); + close -> ignore + end, + + %% Adjust the frames y coordinates + config_v(); +resize_win(WinInfo, 'RB2', Y) -> + {_Bu,S,Bi,F} = Flags = WinInfo#winInfo.flags, + Prev = gs:read('TraceArea',y), + Diff = Prev-(Y+10), + + %% Resize Trace, Evaluator and Binding areas + resize_trace_area(F, height, Diff), + resize_eval_area(S, height, -Diff), + resize_bind_area(Bi, height, -Diff), + + %% Resize vertical resize bar + case rb3(Flags) of + open -> gs:config('RB3', {height,gs:read('RB3',height)-Diff}); + close -> ignore + end, + + %% Adjust the frames y coordinates + config_v(); + +resize_win(WinInfo, 'RB3', X) -> + {_Bu,S,Bi,_F} = WinInfo#winInfo.flags, + Prev = gs:read('BindArea', x), + Diff = Prev-(X+10), + + %% Resize Binding and Trace areas + resize_bind_area(Bi, width, Diff), + resize_eval_area(S, width, -Diff), + + %% Adjust the frames x coordinates + config_h(). + +%% Given the window dimensions, return the limits for a resize bar. +rblimits('RB1',_W,H) -> + + %% Code frame should not have height <100 + Min = 126, + + %% Max is decided by a minimum distance to 'RB2' + %% unless 'RB2' is invisible and 'CodeArea' is visible + %% (=> EvalFrame and BindFrame invisible) in which case + %% TraceFrame should not have height <100 + RB2 = gs:read('RB2',height), + FF = gs:read('TraceArea',height), + Max = case RB2 of + 0 when FF/=0 -> + H-112; + _ -> + Y = gs:read('RB2',y), + max(Min,Y-140) + end, + + {Min,Max}; +rblimits('RB2',_W,H) -> + + %% TraceFrame should not have height <100 + Max = H-112, + + %% Min is decided by a minimum distance to 'RB1' + Y = gs:read('RB1',y), + Min = min(Max,Y+140), + + {Min,Max}; + +rblimits('RB3',W,_H) -> + + %% Neither CodeArea nor BindArea should occupy + %% less than 1/3 of the total window width and EvalFrame should + %% be at least 289 pixels wide + {max(round(W/3),289),round(2*W/3)}. + +max(A, B) when A>B -> A; +max(_A, B) -> B. + +min(A, B) when A<B -> A; +min(_A, B) -> B. + + +%%==================================================================== +%% 'Go To Line' and 'Search' help windows +%%==================================================================== + +helpwin(gotoline, WinInfo, GS, Coords) -> + spawn_link(?MODULE, helpwin, [gotoline, WinInfo, GS, Coords,self()]); +helpwin(search, WinInfo, GS, Coords) -> + spawn_link(?MODULE, helpwin, [search, WinInfo, GS, Coords, self()]). + +helpwin(Type, WinInfo, GS, Coords, AttPid) -> + {_Mod, Editor} = WinInfo#winInfo.editor, + Data = case Type of + gotoline -> null; + search -> + {{1, 0}, false} + end, + Win = helpwin(Type, GS, Coords), + helpwin_loop(Type, AttPid, Editor, Data, Win). + +helpwin_loop(Type, AttPid, Editor, Data, Win) -> + receive + {gs, _Id, destroy, _Data, _Arg} -> + helpwin_stop(Type, AttPid, Editor, Data), + true; + + {gs, _Id, keypress, _Data, ['Return'|_]} -> + gs:config(btn(Win), flash), + Data2 = helpwin_action(Type, default, + AttPid, Editor, Data, Win), + helpwin_loop(Type, AttPid, Editor, Data2, Win); + {gs, _Id, keypress, _Data, _Arg} -> + helpwin_loop(Type, AttPid, Editor, Data, Win); + + {gs, _Id, click, _Data, ["Clear"]} -> + gs:config(ent(Win), {delete, {0,last}}), + Data2 = helpwin_clear(Type, AttPid, Editor, Data, Win), + helpwin_loop(Type, AttPid, Editor, Data2, Win); + {gs, _Id, click, _Data, ["Close"]} -> + helpwin_stop(Type, AttPid, Editor, Data), + true; + {gs, _Id, click, Action, _Arg} -> + Data2 = + helpwin_action(Type, Action, AttPid, Editor, Data, Win), + helpwin_loop(Type, AttPid, Editor, Data2, Win) + end. + +helpwin_stop(gotoline, _AttPid, _Editor, _Data) -> + ignore; +helpwin_stop(search, _AttPid, Editor, {Pos, _CS}) -> + unmark_string(Editor, Pos). + +helpwin_clear(gotoline, _AttPid, _Editor, Data, _Win) -> + Data; +helpwin_clear(search, _AttPid, Editor, {Pos, CS}, Win) -> + unmark_string(Editor, Pos), + gs:config(lbl(Win), {label, {text,""}}), + {{1, 0}, CS}. + +helpwin_action(gotoline, default, AttPid, _Editor, Data, Win) -> + case string:strip(gs:read(ent(Win), text)) of + "" -> ignore; + Str -> + case catch list_to_integer(Str) of + {'EXIT', _Reason} -> ignore; + Line -> AttPid ! {gui, {gotoline, Line}} + end + end, + Data; +helpwin_action(search, case_sensitive, _AttPid, _Ed, {Pos, CS}, _Win) -> + Bool = if CS==true -> false; CS==false -> true end, + {Pos, Bool}; +helpwin_action(search, default, _AttPid, Editor, {Pos, CS}, Win) -> + gs:config(lbl(Win), {label, {text, ""}}), + unmark_string(Editor, Pos), + case gs:read(ent(Win), text) of + "" -> {Pos, CS}; + Str -> + gs:config(lbl(Win), {label, {text,"Searching..."}}), + Str2 = lowercase(CS, Str), + case search(Str2, Editor, gs:read(Editor, size), Pos, CS) of + {Row, Col} -> + gs:config(lbl(Win), {label, {text,""}}), + mark_string(Editor, {Row, Col}, Str), + {{Row, Col}, CS}; + not_found -> + gs:config(lbl(Win), {label, {text,"Not found"}}), + {Pos, CS} + end + end. + +search(_Str, _Editor, Max, {Row, _Col}, _CS) when Row>Max -> + not_found; +search(Str, Editor, Max, {Row, Col}, CS) -> + SearchIn = lowercase(CS, gs:read(Editor, + {get,{{Row,Col+1},{Row,lineend}}})), + case string:str(SearchIn, Str) of + 0 -> search(Str, Editor, Max, {Row+1, 0}, CS); + N -> {Row, Col+N} + end. + +lowercase(true, Str) -> Str; +lowercase(false, Str) -> + lists:map(fun(Char) -> + if + Char>=$A, Char=<$Z -> Char+32; + true -> Char + end + end, + Str). + +mark_string(Editor, {Row, Col}, Str) -> + Between = {{Row,Col}, {Row,Col+length(Str)}}, + Font = dbg_ui_win:font(bold), + gs:config(Editor, [{vscrollpos, Row-5}, + {font_style, {Between, Font}}, + {fg, {Between, red}}]). + +unmark_string(Editor, {Row, Col}) -> + Between = {{Row,Col}, {Row,lineend}}, + Font = dbg_ui_win:font(normal), + gs:config(Editor, [{vscrollpos, Row-5}, + {font_style, {Between, Font}}, + {fg, {Between, black}}]). + +helpwin(Type, GS, {X, Y}) -> + W = 200, Pad=10, Wbtn = 50, + + Title = + case Type of search -> "Search"; gotoline -> "Go To Line" end, + Win = gs:window(GS, [{title, Title}, {x, X}, {y, Y}, {width, W}, + {destroy, true}]), + + Ent = gs:entry(Win, [{x, Pad}, {y, Pad}, {width, W-2*Pad}, + {keypress, true}]), + Hent = gs:read(Ent, height), + + Font = dbg_ui_win:font(normal), + + {Ybtn, Lbl} = + case Type of + search -> + Ycb = Pad+Hent, + gs:checkbutton(Win, [{label, {text, "Case Sensitive"}}, + {font, Font}, + {align, w}, + {x, Pad}, {y, Ycb}, + {width, W-2*Pad}, {height, 15}, + {data, case_sensitive}]), + Ylbl = Ycb+15, + {Ylbl+Hent+Pad, + gs:label(Win, [{x, Pad}, {y, Ylbl}, + {width, W-2*Pad}, {height, Hent}])}; + gotoline -> {Pad+Hent+Pad, null} + end, + + BtnLbl = case Type of search -> "Search"; gotoline -> "Go" end, + Btn = gs:button(Win, [{label, {text, BtnLbl}}, {font, Font}, + {x, W/2-3/2*Wbtn-Pad}, {y, Ybtn}, + {width, Wbtn}, {height, Hent}, + {data, default}]), + gs:button(Win, [{label, {text, "Clear"}}, {font, Font}, + {x, W/2-1/2*Wbtn}, {y, Ybtn}, + {width, Wbtn}, {height, Hent}]), + gs:button(Win, [{label, {text, "Close"}}, {font, Font}, + {x, W/2+1/2*Wbtn+Pad}, {y, Ybtn}, + {width, Wbtn}, {height, Hent}]), + + H = Ybtn+Hent+Pad, + gs:config(Win, [{height, H}, {map, true}]), + {Ent, Lbl, Btn}. + +ent(Win) -> element(1, Win). +lbl(Win) -> element(2, Win). +btn(Win) -> element(3, Win). diff --git a/lib/debugger/src/dbg_ui_view.erl b/lib/debugger/src/dbg_ui_view.erl new file mode 100644 index 0000000000..075275f196 --- /dev/null +++ b/lib/debugger/src/dbg_ui_view.erl @@ -0,0 +1,256 @@ +%% +%% %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_ui_view). + +%% External exports +-export([start/2]). + +%% Internal exports +-export([init/3]). + +-record(state, {gs, % term() Graphics system id + win, % term() Attach process window data + coords, % {X,Y} Mouse point position + mod % atom() Module + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start(GS, Mod) +%% Mod = atom() +%%-------------------------------------------------------------------- +start(GS, Mod) -> + Title = "View Module " ++ atom_to_list(Mod), + case dbg_ui_winman:is_started(Title) of + true -> ignore; + false -> spawn(?MODULE, init, [GS, Mod, Title]) + end. + + +%%==================================================================== +%% Main loop and message handling +%%==================================================================== + +init(GS, Mod, Title) -> + + %% Subscribe to messages from the interpreter + int:subscribe(), + + %% Create attach process window + Win1 = dbg_ui_trace_win:create_win(GS, Title, ['Code Area'], menus()), + Window = dbg_ui_trace_win:get_window(Win1), + dbg_ui_winman:insert(Title, Window), + + Win2 = gui_load_module(Win1, Mod), + Win3 = + lists:foldl(fun(Break, Win) -> + dbg_ui_trace_win:add_break(Win, 'Break', Break) + end, + Win2, + int:all_breaks(Mod)), + + loop(#state{gs=GS, win=Win3, coords={0,0}, mod=Mod}). + +loop(State) -> + receive + + %% From the GUI main window + GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs -> + Cmd = dbg_ui_trace_win:handle_event(GuiEvent, State#state.win), + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the GUI help windows + {gui, Cmd} -> + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the interpreter + {int, Cmd} -> + State2 = int_cmd(Cmd, State), + loop(State2); + + %% From the dbg_ui_winman process (Debugger window manager) + {dbg_ui_winman, update_windows_menu, Data} -> + dbg_ui_winman:update_windows_menu(Data), + loop(State); + {dbg_ui_winman, destroy} -> + exit(stop); + + %% Help window termination -- ignore + {'EXIT', _Pid, _Reason} -> + loop(State) + end. + +%%--Commands from the GUI--------------------------------------------- + +gui_cmd(ignore, State) -> + State; +gui_cmd({win, Win}, State) -> + State#state{win=Win}; +gui_cmd(stopped, _State) -> + exit(stop); +gui_cmd({coords, Coords}, State) -> + State#state{coords=Coords}; + +gui_cmd({shortcut, Key}, State) -> + case shortcut(Key) of + false -> State; + Cmd -> gui_cmd(Cmd, State) + end; + +%% File menu +gui_cmd('Close', State) -> + gui_cmd(stopped, State); + +%% Edit menu +gui_cmd('Go To Line...', State) -> + %% Will result in message handled below: {gui, {gotoline, Line}} + dbg_ui_trace_win:helpwin(gotoline, State#state.win, + State#state.gs, State#state.coords), + State; +gui_cmd({gotoline, Line}, State) -> + Win = dbg_ui_trace_win:select_line(State#state.win, Line), + State#state{win=Win}; +gui_cmd('Search...', State) -> + dbg_ui_trace_win:helpwin(search, State#state.win, + State#state.gs, State#state.coords), + State; + +%% Break menu +gui_cmd('Line Break...', State) -> + add_break(State#state.gs, State#state.coords, line, + State#state.mod, + dbg_ui_trace_win:selected_line(State#state.win)), + State; +gui_cmd('Conditional Break...', State) -> + add_break(State#state.gs, State#state.coords, conditional, + State#state.mod, + dbg_ui_trace_win:selected_line(State#state.win)), + State; +gui_cmd('Function Break...', State) -> + add_break(State#state.gs, State#state.coords, function, + State#state.mod, undefined), + State; +gui_cmd('Enable All', State) -> + Breaks = int:all_breaks(), + ThisMod = State#state.mod, + lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod -> + int:enable_break(Mod, Line); + (_Break) -> + ignore + end, + Breaks), + State; +gui_cmd('Disable All', State) -> + Breaks = int:all_breaks(), + ThisMod = State#state.mod, + lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod -> + int:disable_break(Mod, Line); + (_Break) -> + ignore + end, + Breaks), + State; +gui_cmd('Delete All', State) -> + int:no_break(State#state.mod), + State; +gui_cmd({break, {Mod, Line}, What}, State) -> + case What of + add -> int:break(Mod, Line); + delete -> int:delete_break(Mod, Line); + {status, inactive} -> int:disable_break(Mod, Line); + {status, active} -> int:enable_break(Mod, Line); + {trigger, Action} -> int:action_at_break(Mod, Line, Action) + end, + State; + +%% Help menu +gui_cmd('Debugger', State) -> + Window = dbg_ui_trace_win:get_window(State#state.win), + HelpFile = filename:join([code:lib_dir(debugger), + "doc", "html", "part_frame.html"]), + tool_utils:open_help(Window, HelpFile), + State. + +add_break(GS, Coords, Type, undefined, _Line) -> + dbg_ui_break:start(GS, Coords, Type); +add_break(GS, Coords, Type, Mod, undefined) -> + dbg_ui_break:start(GS, Coords, Type, Mod); +add_break(GS, Coords, Type, Mod, Line) -> + dbg_ui_break:start(GS, Coords, Type, Mod, Line). + +%%--Commands from the interpreter------------------------------------- + +int_cmd({new_break, {{Mod,_Line},_Options}=Break}, #state{mod=Mod}=State) -> + Win = dbg_ui_trace_win:add_break(State#state.win, 'Break', Break), + State#state{win=Win}; +int_cmd({delete_break, {Mod,_Line}=Point}, #state{mod=Mod}=State) -> + Win = dbg_ui_trace_win:delete_break(State#state.win, Point), + State#state{win=Win}; +int_cmd({break_options, {{Mod,_Line},_Options}=Break}, #state{mod=Mod}=State) -> + Win = dbg_ui_trace_win:update_break(State#state.win, Break), + State#state{win=Win}; +int_cmd(no_break, State) -> + Win = dbg_ui_trace_win:clear_breaks(State#state.win), + State#state{win=Win}; +int_cmd({no_break, _Mod}, State) -> + Win = dbg_ui_trace_win:clear_breaks(State#state.win), + State#state{win=Win}; +int_cmd(_, State) -> + State. + + +%%==================================================================== +%% GUI auxiliary functions +%%==================================================================== + +menus() -> + [{'File', [{'Close', 0}]}, + {'Edit', [{'Go To Line...', 0}, + {'Search...', 0}]}, + {'Break', [{'Line Break...', 5}, + {'Conditional Break...', 13}, + {'Function Break...', 0}, + separator, + {'Enable All', no}, + {'Disable All', no}, + {'Delete All', 0}, + separator]}, + {'Help', [{'Debugger', no}]}]. + +shortcut(c) -> 'Close'; +shortcut(g) -> 'Go To Line...'; +shortcut(s) -> 'Search...'; +shortcut(b) -> 'Line Break...'; +shortcut(r) -> 'Conditional Break...'; +shortcut(f) -> 'Function Break...'; +shortcut(d) -> 'Delete All'; + +shortcut(_) -> false. + +gui_load_module(Win, Mod) -> + dbg_ui_trace_win:display({text, "Loading module..."}), + Contents = int:contents(Mod, any), + Win2 = dbg_ui_trace_win:show_code(Win, Mod, Contents), + dbg_ui_trace_win:display({text, ""}), + Win2. diff --git a/lib/debugger/src/dbg_ui_win.erl b/lib/debugger/src/dbg_ui_win.erl new file mode 100644 index 0000000000..9840aa54da --- /dev/null +++ b/lib/debugger/src/dbg_ui_win.erl @@ -0,0 +1,277 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-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_ui_win). + +%% External exports +-export([init/0, + font/1, min_size/3, min_size/4, + create_menus/2, select/2, selected/1, + add_break/2, update_break/2, delete_break/1, + motion/2 + + ]). + +-record(break, {mb, smi, emi, dimi, demi}). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% init() -> GS +%% GS = term() +%%-------------------------------------------------------------------- +init() -> + gs:start([{kernel, true}]). + +%%-------------------------------------------------------------------- +%% font(Style) -> Font +%% Style = normal | bold +%% Select a suitable font. Defaults to {screen,12} and, if it does not +%% exist, {courier,12}. +%%-------------------------------------------------------------------- +font(Style) -> + GS = init(), + Style2 = if + Style==normal -> []; + true -> [Style] + end, + case gs:read(GS, {choose_font, {screen,Style2,12}}) of + Font when element(1, Font)==screen -> + Font; + _ -> + gs:read(GS, {choose_font, {courier,Style2,12}}) + end. + +%%-------------------------------------------------------------------- +%% min_size(Strings, MinW, MinH) -> {W, H} +%% min_size(Font, Strings, MinW, MinH) -> {W, H} +%% Font = GS font - defaults to dbg_ui_win:font(normal) +%% Strings = [string()] +%% MinW = MinH = int() +%% W = H = int() +%%-------------------------------------------------------------------- +min_size(Strings, MinW, MinH) -> + min_size(font(normal), Strings, MinW, MinH). + +min_size(Font, Strings, MinW, MinH) -> + GS = init(), + min_size(GS, Font, Strings, MinW, MinH). + +min_size(GS, Font, [String|Strings], MinW, MinH) -> + {W, H} = gs:read(GS, {font_wh, {Font, String}}), + min_size(GS, Font, Strings, max(MinW, W), max(MinH, H)); +min_size(_GS, _Font, [], W, H) -> + {W, H}. + +max(X, Y) when X>Y -> X; +max(_X, Y) -> Y. + +%%-------------------------------------------------------------------- +%% create_menus(MenuBar, [Menu]) +%% MenuBar = gsobj() +%% Menu = {Name, [Item]} +%% Name = atom() +%% Item = {Name, N} | {Name, N, Type} | {Name, N, cascade, [Item]} +%% | separator +%% N = no | integer() +%% Type = check | radio +%% Create the specified menus and menuitems. +%% +%% Normal menuitems are specified as {Name, N}. Generates the event: +%% {gs, _Id, click, {menuitem, Name}, _Arg} +%% +%% Check and radio menuitems are specified as {Name, N, check|radio}. +%% They are assumed to be children to a cascade menuitem! (And all children +%% to one cascade menuitem are assumed to be either check OR radio +%% menuitems)! +%% Selecting a check/radio menuitem generates the event: +%% {gs, _Id, click, {menu, Menu}, _Arg} +%% where Menu is the name of the parent, the cascade menuitem. +%% Use selected(Menu) to retrieve which check/radio menuitems are +%% selected. +%%-------------------------------------------------------------------- +create_menus(MenuBar, [{Title, Items}|Menus]) -> + Title2 = " "++(atom_to_list(Title))++" ", + MenuBtn = gs:menubutton(MenuBar, [{label, {text,Title2}}, + {font, font(normal)}]), + case Title of + 'Help' -> gs:config(MenuBtn, {side, right}); + _ -> ignore + end, + Menu = gs:menu(Title, MenuBtn, []), + create_items(Menu, Items, Title), + create_menus(MenuBar, Menus); +create_menus(_MenuBar, []) -> + done. + +create_items(Menu, [Item|Items], Group) -> + create_item(Menu, Item, Group), + create_items(Menu, Items, Group); +create_items(_Menu, [], _Group) -> + done. + +create_item(Menu, {Name, _N, cascade, Items}, _Group) -> + MenuBtn = gs:menuitem(Menu, [{label, {text,Name}}, + {font, font(normal)}, + {itemtype, cascade}]), + SubMenu = gs:menu(Name, MenuBtn, []), + create_items(SubMenu, Items, Name); +create_item(Menu, separator, _Group) -> + gs:menuitem(Menu, [{itemtype, separator}]); +create_item(Menu, MenuItem, Group) -> + Options = case MenuItem of + {Name, N} -> + [{data, {menuitem,Name}}]; + {Name, N, check} -> + [{itemtype, check}, {data, {menu, Group}}]; + {Name, N, radio} -> + [{itemtype, radio}, {data, {menu, Group}}, + {group, group(Group)}] + end, + gs:menuitem(Name, Menu, [{label, {text,Name}}, + {font, font(normal)} | Options]), + if + is_integer(N) -> gs:config(Name, {underline, N}); + true -> ignore + end. + +%% When grouping radio buttons, the group id must be an atom unique for +%% each window. +group(Group) -> + list_to_atom(atom_to_list(Group)++pid_to_list(self())). + +%%-------------------------------------------------------------------- +%% select(MenuItem, Bool) +%% MenuItem = atom() +%% Bool = boolean() +%%-------------------------------------------------------------------- +select(MenuItem, Bool) -> + gs:config(MenuItem, {select, Bool}). + +%%-------------------------------------------------------------------- +%% selected(Menu) -> [Name] +%% Menu = Name = atom() +%%-------------------------------------------------------------------- +selected(Menu) -> + Children = gs:read(Menu, children), + Selected = lists:filter(fun(Child) -> gs:read(Child, select) end, + Children), + lists:map(fun(Child) -> + {text, Name} = gs:read(Child, label), + list_to_atom(Name) + end, + Selected). + +%%-------------------------------------------------------------------- +%% add_break(Name, Point) -> #break{} +%% Name = atom() +%% Point = {Mod, Line} +%% The break will generate the following events: +%% {gs, _Id, click, {break, Point, Event}, _Arg} +%% Event = delete | {trigger, Action} | {status, Status} +%% Action = enable | disable | delete +%% Status = active | inactive +%%-------------------------------------------------------------------- +add_break(Menu, Point) -> + Font = font(normal), + + %% Create a name for the breakpoint + {Mod, Line} = Point, + Label = io_lib:format("~w ~5w", [Mod, Line]), + + %% Create a menu for the breakpoint + MenuBtn = gs:menuitem(Menu, [{label, {text,Label}}, {font, Font}, + {itemtype, cascade}]), + SubMenu = gs:menu(MenuBtn, []), + SMI = gs:menuitem(SubMenu, [{data, {break,Point,null}}]), + gs:menuitem(SubMenu, [{label, {text,"Delete"}}, {font, Font}, + {data, {break,Point,delete}}]), + TriggerMenuBtn = gs:menuitem(SubMenu, + [{label,{text,"Trigger Action"}}, + {font, Font}, + {itemtype, cascade}]), + TriggerMenu = gs:menu(TriggerMenuBtn, []), + Group = element(3, erlang:now()), + EMI = gs:menuitem(TriggerMenu, [{label, {text,"Enable"}}, + {font, Font}, + {itemtype, radio}, {group, Group}, + {data, + {break,Point,{trigger,enable}}}]), + DiMI = gs:menuitem(TriggerMenu, [{label, {text,"Disable"}}, + {font, Font}, + {itemtype, radio}, {group, Group}, + {data, + {break,Point,{trigger,disable}}}]), + DeMI = gs:menuitem(TriggerMenu, [{label, {text,"Delete"}}, + {font, Font}, + {itemtype, radio}, {group, Group}, + {data, + {break,Point,{trigger,delete}}}]), + + #break{mb=MenuBtn, smi=SMI, emi=EMI, dimi=DiMI, demi=DeMI}. + +%%-------------------------------------------------------------------- +%% update_break(Break, Options) +%% Break = #break{} +%% Options = [Status, Action, Mods, Cond] +%% Status = active | inactive +%% Action = enable | disable | delete +%% Mods = null (not used) +%% Cond = null | {Mod, Func} +%%-------------------------------------------------------------------- +update_break(Break, Options) -> + [Status, Trigger|_] = Options, + {break, Point, _Status} = gs:read(Break#break.smi, data), + + {Label, Data} = case Status of + active -> + {"Disable", {break,Point,{status,inactive}}}; + inactive -> + {"Enable", {break,Point,{status,active}}} + end, + gs:config(Break#break.smi, [{label, {text,Label}}, + {font, font(normal)}, + {data, Data}]), + + TriggerMI = case Trigger of + enable -> Break#break.emi; + disable -> Break#break.dimi; + delete -> Break#break.demi + end, + gs:config(TriggerMI, {select, true}). + +%%-------------------------------------------------------------------- +%% delete_break(Break) +%% Break = #break{} +%%-------------------------------------------------------------------- +delete_break(Break) -> + gs:destroy(Break#break.mb). + +%%-------------------------------------------------------------------- +%% motion(X, Y) -> {X, Y} +%% X = Y = integer() +%%-------------------------------------------------------------------- +motion(X, Y) -> + receive + {gs, _Id, motion, _Data, [NX,NY]} -> + motion(NX, NY) + after 0 -> + {X, Y} + end. diff --git a/lib/debugger/src/dbg_ui_winman.erl b/lib/debugger/src/dbg_ui_winman.erl new file mode 100644 index 0000000000..71023cd0d6 --- /dev/null +++ b/lib/debugger/src/dbg_ui_winman.erl @@ -0,0 +1,177 @@ +%% +%% %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_ui_winman). +-behaviour(gen_server). + +%% External exports +-export([start/0]). +-export([insert/2, is_started/1, + clear_process/1, + raise/1, + windows_menu/1, update_windows_menu/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(win, {owner, % pid() + title, % string() + win % gsobj() + }). + +-record(state, {wins=[] % [#win{}] + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start() +%%-------------------------------------------------------------------- +start() -> + gen_server:start({local,?MODULE}, ?MODULE, [], []). + +%%-------------------------------------------------------------------- +%% insert(Title, Win) +%% Title = string() +%% Win = gsobj() +%%-------------------------------------------------------------------- +insert(Title, Win) -> + gen_server:cast(?MODULE, {insert, self(), Title, Win}). + +%%-------------------------------------------------------------------- +%% is_started(Title) -> true | false +%% Title = string() +%%-------------------------------------------------------------------- +is_started(Title) -> + case gen_server:call(?MODULE, {is_started, Title}, infinity) of + {true, Win} -> + raise(Win), + true; + false -> + false + end. + +%%-------------------------------------------------------------------- +%% clear_process(Title) +%% Title = string() +%%-------------------------------------------------------------------- +clear_process(Title) -> + gen_server:cast(?MODULE, {clear_process, Title}). + +%%-------------------------------------------------------------------- +%% raise(Win) +%% Win = gsobj() +%%-------------------------------------------------------------------- +raise(Win) -> + gs:config(Win, [raise, {iconify, false}, {setfocus, true}]). + +%%-------------------------------------------------------------------- +%% windows_menu(MenuBar) +%% MenuBar = gsobj() +%%-------------------------------------------------------------------- +windows_menu(MenuBar) -> + gs:menubutton('WindowsMenuBtn', MenuBar, + [{label,{text," Windows "}}, + {font, dbg_ui_win:font(normal)}]), + gs:menu('WindowsMenu', 'WindowsMenuBtn', []). + +%%-------------------------------------------------------------------- +%% update_windows_menu(Data) +%% Data = {New, Old} +%% New = Old = list() +%%-------------------------------------------------------------------- +update_windows_menu([MonInfo|Infos]) -> + gs:destroy('WindowsMenu'), + gs:menu('WindowsMenu', 'WindowsMenuBtn', []), + menuitem(MonInfo), + gs:menuitem(separator, 'WindowsMenu', [{itemtype, separator}]), + lists:foreach(fun(Info) -> menuitem(Info) end, Infos). + +menuitem({Title, Win}) -> + gs:menuitem(Title, 'WindowsMenu', [{label, {text,Title}}, + {font, dbg_ui_win:font(normal)}, + {data, {dbg_ui_winman,Win}}]). + + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +init(_Arg) -> + process_flag(trap_exit, true), + {ok, #state{}}. + +handle_call({is_started, Title}, _From, State) -> + Reply = case lists:keysearch(Title, #win.title, State#state.wins) of + {value, Win} -> {true, Win#win.win}; + false -> false + end, + {reply, Reply, State}. + +handle_cast({insert, Pid, Title, Win}, State) -> + link(Pid), + Wins = State#state.wins ++ [#win{owner=Pid, title=Title, win=Win}], + inform_all(Wins), + {noreply, State#state{wins=Wins}}; + +handle_cast({clear_process, Title}, State) -> + OldWins = State#state.wins, + Wins = case lists:keysearch(Title, #win.title, OldWins) of + {value, #win{owner=Pid}} -> + Msg = {dbg_ui_winman, destroy}, + Pid ! Msg, + lists:keydelete(Title, #win.title, OldWins); + false -> + OldWins + end, + {noreply, State#state{wins=Wins}}. + +handle_info({'EXIT', Pid, _Reason}, State) -> + [Mon | _Wins] = State#state.wins, + if + Pid==Mon#win.owner -> {stop, normal, State}; + true -> + Wins2 = lists:keydelete(Pid, #win.owner, State#state.wins), + inform_all(Wins2), + {noreply, State#state{wins=Wins2}} + end. + +terminate(_Reason, State) -> + delete_all(State#state.wins), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +inform_all(Wins) -> + Infos = lists:map(fun(#win{title=Title, win=Win}) -> {Title, Win} end, + Wins), + Msg = {dbg_ui_winman, update_windows_menu, Infos}, + lists:foreach(fun(#win{owner=Pid}) -> Pid ! Msg end, Wins). + +delete_all(Wins) -> + Msg = {dbg_ui_winman, destroy}, + lists:foreach(fun(#win{owner=Pid}) -> Pid ! Msg end, Wins). diff --git a/lib/debugger/src/dbg_wx_break.erl b/lib/debugger/src/dbg_wx_break.erl new file mode 100644 index 0000000000..a19203f7d0 --- /dev/null +++ b/lib/debugger/src/dbg_wx_break.erl @@ -0,0 +1,102 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_break). + +%% External exports +-export([start/3, start/4, start/5]). + +%% Internal exports +-export([init/6]). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start(Wx, Pos, Type) +%% start(Wx, Pos, Type, Module, Line) +%% Wx = Parent Window +%% Pos = {X, Y} +%% X = Y = integer() +%% Type = line | conditional | function +%% Module = atom() +%% Line = integer() +%%-------------------------------------------------------------------- +start(Wx, Pos, Type) -> + start(Wx, Pos, Type, "", ""). +start(Wx, Pos, Type, Mod) -> + start(Wx, Pos, Type, Mod, ""). +start(Wx, Pos, Type, Mod, Line) -> + Env = wx:get_env(), + spawn_link(?MODULE, init, [Wx, Env, Pos, Type, Mod, Line]). + + +%%==================================================================== +%% Internal exports +%%==================================================================== + +init(Wx, Env, Pos, Type, Mod, Line) -> + wx:set_env(Env), + Win = wx:batch(fun() -> dbg_wx_break_win:create_win(Wx, Pos, Type, Mod, Line) end), + if + Type==function, is_atom(Mod) -> + Win2 = gui_cmd({module, Mod}, Win), + loop(Win2); + true -> + loop(Win) + end. + +loop(Win) -> + receive + + %% From the GUI + GuiEvent when element(1, GuiEvent)==gs; element(1, GuiEvent)==wx -> + Cmd = wx:batch(fun() -> dbg_wx_break_win:handle_event(GuiEvent, Win) end), + Win2 = gui_cmd(Cmd, Win), + loop(Win2) + end. + +gui_cmd(ignore, Win) -> + Win; +gui_cmd(stopped, _Win) -> + exit(normal); +gui_cmd({win, Win2}, _Win) -> + Win2; +gui_cmd({module, Mod}, Win) -> + Funcs = int:functions(Mod), + dbg_wx_break_win:update_functions(Win, Funcs); +gui_cmd({break, DataL, Action}, _Win) -> + Fun = + fun(Data) -> + case Data of + [Mod, Line] -> + int:break(Mod, Line), + int:action_at_break(Mod, Line, Action); + [Mod, Line, CMod, CFunc] -> + int:break(Mod, Line), + int:test_at_break(Mod, Line, {CMod, CFunc}), + int:action_at_break(Mod, Line, Action); + [Mod, Func, Arity] -> + int:break_in(Mod, Func, Arity) + end + end, + lists:foreach(Fun, DataL), + exit(normal). diff --git a/lib/debugger/src/dbg_wx_break_win.erl b/lib/debugger/src/dbg_wx_break_win.erl new file mode 100644 index 0000000000..5dafb0fbe6 --- /dev/null +++ b/lib/debugger/src/dbg_wx_break_win.erl @@ -0,0 +1,272 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_break_win). + +%% External exports +-export([create_win/5, + update_functions/2, + handle_event/2]). + +-include_lib("wx/include/wx.hrl"). + +-record(winInfo, {type, % line | conditional | function + win, % wxobj() + entries, % [{atom|integer, wxobj()}] + trigger, % [{wxobj(),enable | disable | delete}] + listbox, % wxobj() + text, % wxobj() + ok, % wxobj() + funcs=[] % [[Name, Arity]] + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% create_win(Win, Pos, Type, Mod, Line) -> #winInfo{} +%% Win = Top level window +%% Pos = {X, Y} +%% X = Y = integer() +%% Type = line | conditional | function +%% Mod = atom() | "" +%% Line = integer() | "" +%%-------------------------------------------------------------------- + +create_win(Parent, Pos, function, Mod, _Line) -> + Win = wxDialog:new(Parent, ?wxID_ANY, "Function Break", + [{pos, Pos}, + {style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]), + MainS = wxBoxSizer:new(?wxVERTICAL), + Label = wxStaticText:new(Win, ?wxID_ANY, "Module:"), + Int = int:interpreted(), + IntStrs = [atom_to_list(M) || M <- Int], + Text = wxComboBox:new(Win, ?wxID_ANY, + [{value, dbg_wx_win:to_string(Mod)}, + {choices, IntStrs}]), + + Expand = [{border, 5}, {flag,?wxLEFT bor ?wxRIGHT bor ?wxEXPAND}], + wxSizer:add(MainS, Label, [{border,5}, + {flag,?wxTOP bor ?wxLEFT bor ?wxRIGHT}]), + wxSizer:add(MainS, Text, Expand), + FunLabel = wxStaticText:new(Win, ?wxID_ANY, "Function:"), + LB = wxListBox:new(Win, ?wxID_ANY, [{size,{-1, 100}},{style,?wxLB_MULTIPLE}]), + wxSizer:add(MainS, FunLabel, Expand), + wxSizer:add(MainS, LB, [{proportion,1}|Expand]), + wxSizer:setMinSize(MainS, 300, 400), + OK = wxDialog:createStdDialogButtonSizer(Win, ?wxOK bor ?wxCANCEL), + wxSizer:add(MainS, OK, [{border,5},{flag,?wxALL}]), + wxDialog:setSizer(Win,MainS), + wxSizer:fit(MainS, Win), + wxSizer:setSizeHints(MainS,Win), + wxComboBox:setFocus(Text), + wxDialog:connect(Win, command_button_clicked), + wxComboBox:connect(Text, command_text_updated), + wxListBox:connect(LB, command_listbox_selected), + wxListBox:connect(LB, command_listbox_doubleclicked), + OkId = wxDialog:getAffirmativeId(Win), + OKButt = wxWindow:findWindowById(OkId, [{parent, Win}]), + wxWindow:disable(OKButt), + wxDialog:centreOnParent(Win), + wxDialog:show(Win), + + #winInfo{type=function, win=Win, text=Text, ok=OKButt, + entries=[], trigger=enable, + listbox=LB, funcs=[]}; + +create_win(Parent, Pos, Type, Mod, Line) -> + Title = case Type of + line -> "Line Break"; + conditional -> "Conditional Break" + end, + Style = ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER, + Win = wxDialog:new(Parent, ?wxID_ANY, Title, + [{pos, Pos}, + {style, Style}]), + %% Create Sizers + MainS = wxBoxSizer:new(?wxVERTICAL), + + %% Add module + Int = int:interpreted(), + IntStrs = [atom_to_list(M) || M <- Int], + ModT = wxComboBox:new(Win, ?wxID_ANY, [{choices,IntStrs}]), + ModSz = create_label_of_control(Win, "Module:", ModT, Mod), + wxSizer:add(MainS,ModSz,[{flag, ?wxEXPAND}]), + %% Create rest of text input fields + Add = fun({IType, Label, Def}) -> + {Sz, Text} = create_sizer_with_text(Win, Label, Def), + wxSizer:add(MainS, Sz, [{flag, ?wxEXPAND}]), + {Text, IType} + end, + Inputs = case Type of + line -> + [{integer,"Line:",Line}]; + conditional -> + [{integer,"Line:",Line}, + {atom,"C-Module:",""}, + {atom,"C-Function:",""}] + end, + %% Add the rest of the entries + Entries = wx:map(Add, Inputs), + %% Create and add radio box + {TriggerBox,Trigger} = create_trigger_box(Win), + wxSizer:add(MainS, TriggerBox, [{border,5},{flag,?wxALL bor ?wxEXPAND}]), + + wxSizer:addStretchSpacer(MainS), + %% Put it together + OK = wxDialog:createStdDialogButtonSizer(Win, ?wxOK bor ?wxCANCEL), + wxSizer:add(MainS, OK, [{border,5},{flag,?wxALL}]), + wxSizer:setMinSize(MainS, 300, -1), + wxDialog:setSizer(Win,MainS), + wxSizer:fit(MainS, Win), + wxSizer:setSizeHints(MainS,Win), + wxComboBox:setFocus(ModT), + wxDialog:connect(Win, command_button_clicked), + wxDialog:connect(Win, command_text_updated), + OkId = wxDialog:getAffirmativeId(Win), + OKButt = wxWindow:findWindowById(OkId), + wxWindow:disable(OKButt), + wxDialog:centreOnParent(Win), + wxDialog:show(Win), + #winInfo{type=Type, win=Win, text=ModT, + entries=Entries, trigger=Trigger, ok=OKButt}. + +%%-------------------------------------------------------------------- +%% update_functions(WinInfo, Funcs) -> WinInfo +%% WinInfo = #winInfo{} +%% Funcs = [{Name, Arity}] +%% Name = atom() +%% Arity = integer() +%%-------------------------------------------------------------------- +update_functions(WinInfo, Funcs) -> + Items = lists:map(fun([N, A]) -> + lists:flatten(io_lib:format("~p/~p", [N,A])) + end, + Funcs), + wxListBox:set(WinInfo#winInfo.listbox, Items), + WinInfo#winInfo{funcs=Funcs}. + +%%-------------------------------------------------------------------- +%% handle_event(WxEvent, WinInfo) -> Command +%% WxEvent = #wx{} +%% WinInfo = #winInfo{} +%% Command = ignore +%% | stopped +%% | {win, WinInfo} +%% | {module, Mod} +%% | {break, [[Mod, Line]], Action} +%% | {break, [[Mod, Line, CMod, CFunc]], Action} +%% | {break, [[Mod, Func, Arity]], Action} +%%-------------------------------------------------------------------- +handle_event(#wx{id=?wxID_CANCEL}, #winInfo{win=Win}) -> + wxDialog:destroy(Win), + stopped; +handle_event(#wx{event=#wxCommand{type=command_text_updated}}, + #winInfo{type=function, text=Text, ok=Ok}) -> + Module = wxComboBox:getValue(Text), + wxWindow:disable(Ok), + {module, list_to_atom(Module)}; +handle_event(#wx{event=#wxCommand{type=command_text_updated}}, + #winInfo{text=Text, ok=Ok, entries=Es}) -> + Module = wxComboBox:getValue(Text), + case check_input(Es) of + error -> wxWindow:disable(Ok); + _Data when Module =/= "" -> wxWindow:enable(Ok); + _ -> wxWindow:disable(Ok) + end, + ignore; +handle_event(#wx{event=#wxCommand{type=command_listbox_selected}}, + #winInfo{type=function, listbox=LB, ok=Ok}) -> + case wxListBox:getSelections(LB) of + {N,_} when N > 0 -> wxWindow:enable(Ok); + _ -> wxWindow:disable(Ok) + end, + ignore; +handle_event(#wx{id=OKorListBox, event=#wxCommand{type=OkorDoubleClick}}, + #winInfo{type=function,win=Win,listbox=LB,funcs=Funcs,text=Text}) + when OKorListBox =:= ?wxID_OK; + OkorDoubleClick =:= command_listbox_doubleclicked -> + Mod = wxComboBox:getValue(Text), + {_, IndexL} = wxListBox:getSelections(LB), + Breaks = lists:map(fun(Index) -> + Func = lists:nth(Index+1, Funcs), + [list_to_atom(Mod) | Func] + end, + IndexL), + wxDialog:destroy(Win), + {break, Breaks, enable}; +handle_event(#wx{id=?wxID_OK},#winInfo{win=Win,text=Text, entries=Es, trigger=Trigger}) -> + %% Non function box + Mod = wxComboBox:getValue(Text), + Data = check_input(Es), + Trigged = get_trigger(Trigger), + wxDialog:destroy(Win), + {break, [[list_to_atom(Mod)|Data]], Trigged}; + +handle_event(_WxEvent, _WinInfo) -> + %% io:format("Ev: ~p ~n", [_WxEvent]), + ignore. + +check_input(Entries) -> + check_input(Entries, []). +check_input([{Entry, Type} | Entries], Data) -> + Str = wxTextCtrl:getValue(Entry), + case erl_scan:string(Str) of + {ok, [{Type, _Line, Val}], _EndLine} -> + check_input(Entries, [Val|Data]); + _Error -> error + end; +check_input([], Data) -> lists:reverse(Data). + +create_sizer_with_text(Parent,Label,Def) -> + Text = wxTextCtrl:new(Parent, ?wxID_ANY), + Sz = create_label_of_control(Parent, Label, Text, Def), + {Sz, Text}. + +create_label_of_control(Parent, Label, Control, Def) -> + Sizer = wxBoxSizer:new(?wxHORIZONTAL), + Text = wxStaticText:new(Parent, ?wxID_ANY, Label), + Border = {border, 5}, + Flag = ?wxRIGHT bor ?wxLEFT bor ?wxALIGN_CENTRE_VERTICAL, + wxSizer:add(Sizer, Text, [{proportion,1}, {flag,Flag}, Border]), + wxSizer:add(Sizer, Control, [{proportion,3}, {flag,Flag bor ?wxEXPAND}, Border]), + wxControl:setLabel(Control, dbg_wx_win:to_string(Def)), + Sizer. + +create_trigger_box(Win) -> + SBox = wxStaticBox:new(Win, ?wxID_ANY, "Trigger Action:"), + SBS = wxStaticBoxSizer:new(SBox, ?wxVERTICAL), + Ebtn = wxRadioButton:new(Win, ?wxID_ANY, "Enable"), + wxSizer:add(SBS,Ebtn), + Dibtn = wxRadioButton:new(Win, ?wxID_ANY, "Disable"), + wxSizer:add(SBS,Dibtn), + Debtn = wxRadioButton:new(Win, ?wxID_ANY, "Delete"), + wxSizer:add(SBS,Debtn), + wxRadioButton:setValue(Ebtn, true), + {SBS, [{Ebtn,enable},{Dibtn,disable},{Debtn,delete}]}. + +get_trigger([{Btn,Op}|R]) -> + case wxRadioButton:getValue(Btn) of + true -> Op; + false -> get_trigger(R) + end. + + diff --git a/lib/debugger/src/dbg_wx_code.erl b/lib/debugger/src/dbg_wx_code.erl new file mode 100644 index 0000000000..99826d9bdb --- /dev/null +++ b/lib/debugger/src/dbg_wx_code.erl @@ -0,0 +1,163 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_code). + +-export([code_area/1, + load_code/2, unload_code/1, + add_break_to_code/3, del_break_from_code/2, + find/4, + goto_pos/2, current_pos/1, + mark_line/3, get_no_lines/1, goto_line/2]). + +-include_lib("wx/include/wx.hrl"). + +-define(stc, wxStyledTextCtrl). + +code_area(Parent) -> + FixedFont = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL,[]), + %%Ed = wxStyledTextCtrl:new(Parent, [{size, {700, 500}}]), + Ed = wxStyledTextCtrl:new(Parent), + + ?stc:styleClearAll(Ed), + ?stc:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont), + ?stc:setLexer(Ed, ?wxSTC_LEX_ERLANG), + ?stc:setMarginType(Ed, 0, ?wxSTC_MARGIN_NUMBER), + LW = ?stc:textWidth(Ed, ?wxSTC_STYLE_LINENUMBER, "9"), + ?stc:setMarginWidth(Ed, 0, LW), + + ?stc:setSelectionMode(Ed, ?wxSTC_SEL_LINES), + %%?stc:hideSelection(Ed, true), + + Styles = [{?wxSTC_ERLANG_DEFAULT, {0,0,0}}, + {?wxSTC_ERLANG_COMMENT, {160,53,35}}, + {?wxSTC_ERLANG_VARIABLE, {150,100,40}}, + {?wxSTC_ERLANG_NUMBER, {5,5,100}}, + {?wxSTC_ERLANG_KEYWORD, {130,40,172}}, + {?wxSTC_ERLANG_STRING, {170,45,132}}, + {?wxSTC_ERLANG_OPERATOR, {30,0,0}}, + {?wxSTC_ERLANG_ATOM, {0,0,0}}, + {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}}, + {?wxSTC_ERLANG_CHARACTER,{236,155,172}}, + {?wxSTC_ERLANG_MACRO, {40,144,170}}, + {?wxSTC_ERLANG_RECORD, {40,100,20}}, + {?wxSTC_ERLANG_SEPARATOR,{0,0,0}}, + {?wxSTC_ERLANG_NODE_NAME,{0,0,0}}], + SetStyle = fun({Style, Color}) -> + ?stc:styleSetFont(Ed, Style, FixedFont), + ?stc:styleSetForeground(Ed, Style, Color) + end, + [SetStyle(Style) || Style <- Styles], + ?stc:setKeyWords(Ed, 0, keyWords()), + + %% Margins Markers + %% Breakpoint Should be a pixmap? + ?stc:markerDefine(Ed, 0, ?wxSTC_MARK_CIRCLE, [{foreground, {170,20,20}}]), + ?stc:markerDefine(Ed, 0, ?wxSTC_MARK_CIRCLE, [{background, {200,120,120}}]), + %% Disabled Breakpoint + ?stc:markerDefine(Ed, 1, ?wxSTC_MARK_CIRCLE, [{foreground, {20,20,170}}]), + ?stc:markerDefine(Ed, 1, ?wxSTC_MARK_CIRCLE, [{background, {120,120,200}}]), + + %% Current Line + ?stc:markerDefine(Ed, 2, ?wxSTC_MARK_ARROW, [{foreground, {20,170,20}}]), + ?stc:markerDefine(Ed, 2, ?wxSTC_MARK_ARROW, [{background, {200,255,200}}]), + ?stc:markerDefine(Ed, 3, ?wxSTC_MARK_BACKGROUND, [{background, {200,255,200}}]), + + %% Scrolling + Policy = ?wxSTC_CARET_SLOP bor ?wxSTC_CARET_JUMPS bor ?wxSTC_CARET_EVEN, + ?stc:setYCaretPolicy(Ed, Policy, 3), + ?stc:setVisiblePolicy(Ed, Policy, 3), + + ?stc:connect(Ed, stc_doubleclick), + ?stc:setReadOnly(Ed, true), + Ed. + +load_code(Ed, Code) -> + ?stc:setReadOnly(Ed, false), + ?stc:setTextRaw(Ed, Code), + Lines = ?stc:getLineCount(Ed), + Sz = trunc(math:log10(Lines))+1, + LW = ?stc:textWidth(Ed, ?wxSTC_STYLE_LINENUMBER, lists:duplicate(Sz, $9)), + %%io:format("~p ~p ~p~n", [Lines, Sz, LW]), + ?stc:setMarginWidth(Ed, 0, LW+5), + ?stc:setReadOnly(Ed, true), + Ed. + +unload_code(Ed) -> + ?stc:setReadOnly(Ed, false), + ?stc:setTextRaw(Ed, <<0:8>>), + ?stc:setReadOnly(Ed, true), + Ed. + +add_break_to_code(Ed, Line, active) -> + ?stc:markerDelete(Ed, Line-1, 1), + ?stc:markerAdd(Ed, Line-1, 0); +add_break_to_code(Ed, Line, inactive) -> + ?stc:markerDelete(Ed, Line-1, 0), + ?stc:markerAdd(Ed, Line-1, 1). + +del_break_from_code(Ed,Line) -> + ?stc:markerDelete(Ed, Line-1, 0), + ?stc:markerDelete(Ed, Line-1, 1). + +mark_line(Ed,Prev,Line) -> + goto_line(Ed, Line), + ?stc:markerDelete(Ed, Prev-1, 2), + ?stc:markerAdd(Ed, Line-1, 2), + ?stc:markerDelete(Ed, Prev-1, 3), + ?stc:markerAdd(Ed, Line-1, 3). + +get_no_lines(Ed) -> + ?stc:getLineCount(Ed). + +goto_line(_Ed,0) -> ignore; +goto_line(Ed,Line) -> + ?stc:gotoLine(Ed, Line-1). + +current_pos(Ed) -> + ?stc:getCurrentPos(Ed). + +goto_pos(Ed,Pos) -> + ?stc:gotoPos(Ed, Pos). + +find(Ed, Str, Case, Next) -> + ?stc:searchAnchor(Ed), + Flag = + if Case -> ?wxSTC_FIND_MATCHCASE; + true -> 0 + end, + Res = + if + Next -> ?stc:searchNext(Ed, Flag, Str); + true -> ?stc:searchPrev(Ed, Flag, Str) + end, + case Res >= 0 of + true -> + %% io:format("Found ~p ~n",[Res]), + ?stc:scrollToLine(Ed,?stc:lineFromPosition(Ed,Res) - 3), + true; + false -> + false + end. + +keyWords() -> + L = ["after","begin","case","try","cond","catch","andalso","orelse", + "end","fun","if","let","of","query","receive","when","bnot","not", + "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"], + lists:flatten([K ++ " " || K <- L] ++ [0]). diff --git a/lib/debugger/src/dbg_wx_filedialog_win.erl b/lib/debugger/src/dbg_wx_filedialog_win.erl new file mode 100644 index 0000000000..d883438639 --- /dev/null +++ b/lib/debugger/src/dbg_wx_filedialog_win.erl @@ -0,0 +1,572 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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_wx_filedialog_win). + +-behaviour(wx_object). + +%% API +-export([new/3, getFilename/1, getFilenames/1, getDirectory/1, destroy/1]). + +%% Internal +-export([init/1, handle_call/3, handle_event/2, + handle_info/2, code_change/3, terminate/2]). + +-include_lib("wx/include/wx.hrl"). +-include("dbg_wx_filedialog_win.hrl"). + +-define(ID_PATH, 200). +-define(COMPLETION_WIN, 201). +-record(state, {win, + back, forward, up, %% Buttons + text, %% Text (Path) + ptext, + icons=[], + completion, + list, + path, + files, + rstack = [], + fstack = [], + filter, + sort, + cancel, + ok}). + +-record(file, + {name = "", %% File or dir name + type = "file", %% Type descr + date = "", %% Modification date + icon = 0, %% Icon + color = {0,0,0} + }). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Client API + + +%% Options are: +%% {message, Title} +%% {defaultDir, Path} +%% {filter, fun(Dir,File) -> skip | {DescriptionStr, icon_type}} +%% {icons, [{icon_type,wxBitmap}]} + +new(Parent, Id, Options0) -> + wx_object:start_link(?MODULE, [Parent, Id, Options0], []). + +getFilename(FD) -> + wx_object:call(FD, getFilename). +getFilenames(FD) -> + wx_object:call(FD, getFilenames). +getDirectory(FD) -> + wx_object:call(FD, getDirectory). +destroy(FD) -> + wx_object:call(FD, destroy). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Object Callbacks +init([Parent, Id, Options0]) -> + Name = proplists:get_value(message, Options0, "Open"), + Size = proplists:get_value(size, Options0, ?wxDefaultSize), + Pos = proplists:get_value(pos, Options0, ?wxDefaultPosition), + {ok, DefPath} = file:get_cwd(), + Path = proplists:get_value(defaultDir, Options0, DefPath), + ExtraIcons = proplists:get_value(icons, Options0, []), + Filter = proplists:get_value(filter, Options0, fun file_type_and_icon/2), + SortCol = sort_col(proplists:get_value(sort, Options0, name)), + Dlg = wxDialog:new(Parent, Id, Name, [{size,Size}, {pos,Pos}, + {style, ?wxDEFAULT_DIALOG_STYLE + bor ?wxRESIZE_BORDER}]), + + %% Top + Back = wxButton:new(Dlg, ?wxID_BACKWARD), + wxButton:disable(Back), + Forw = wxButton:new(Dlg, ?wxID_FORWARD), + wxButton:disable(Forw), + Up = wxButton:new(Dlg, ?wxID_UP), + Dir = wxTextCtrl:new(Dlg, ?ID_PATH, [{style, ?wxTE_PROCESS_ENTER}]), + update_dir(Path, Dir), + wxTextCtrl:connect(Dir, command_text_updated), + wxTextCtrl:connect(Dir, command_text_enter), + Self = self(), + IsTab = fun(Ev = #wx{event=#wxKey{keyCode=KC, + controlDown=false,shiftDown=false, altDown=false}}, + _Object) when KC =:= ?WXK_TAB ; KC =:= ?WXK_ESCAPE -> + Self ! Ev; + (_Ev, Object) -> + %% Let the default handler handle anything else + wxEvent:skip(Object) + end, + + wxTextCtrl:connect(Dir, char, [{callback, IsTab}]), + + Top = wxBoxSizer:new(?wxHORIZONTAL), + wxSizer:add(Top, Back, [{border, 2},{flag,?wxALL bor ?wxEXPAND}]), + wxSizer:add(Top, Forw, [{border, 2},{flag,?wxALL bor ?wxEXPAND}]), + wxSizer:add(Top, Up, [{border, 2},{flag,?wxALL bor ?wxEXPAND}]), + + %% List Ctrl + {Art, IconMap} = create_icons(ExtraIcons), + + LC = wxListCtrl:new(Dlg, [{style, ?wxLC_REPORT bor ?wxVSCROLL}, {size, {400,200}}]), + wxListCtrl:assignImageList(LC, Art, ?wxIMAGE_LIST_SMALL), + + LI = wxListItem:new(), + Add = fun(MenuName, Row) -> + wxListItem:setText(LI, MenuName), + wxListItem:setAlign(LI, ?wxLIST_FORMAT_LEFT), + wxListCtrl:insertColumn(LC, Row, LI), + Row + 1 + end, + lists:foldl(Add, 0, ["Name", "Type", "Modified"]), + wxListItem:destroy(LI), + + Files = list_files(Path, {SortCol,false}, Filter), + update_files(Files,LC,IconMap), + + wxListCtrl:setColumnWidth(LC, 0, ?wxLIST_AUTOSIZE), + wxListCtrl:setColumnWidth(LC, 1, ?wxLIST_AUTOSIZE), + wxListCtrl:setColumnWidth(LC, 2, ?wxLIST_AUTOSIZE), + wxListCtrl:connect(LC, command_list_item_activated), + wxListCtrl:connect(LC, command_list_col_click), + wxListCtrl:connect(LC, size, [{skip, true}]), + + %% Bottom buttons + Bott = wxDialog:createButtonSizer(Dlg, ?wxCANCEL bor ?wxOK), + wxDialog:connect(Dlg, command_button_clicked), + + %% Ok done + Box = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(Box, Top, [{border, 2}, {flag,?wxALL bor ?wxEXPAND}]), + wxSizer:add(Box, Dir, [{border, 2}, {flag,?wxALL bor ?wxEXPAND}]), + wxSizer:add(Box, LC, [{border, 2}, {flag,?wxALL bor ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(Box, Bott, [{border, 2}, {flag,?wxALL bor ?wxEXPAND}]), + + wxWindow:setSizer(Dlg, Box), + wxSizer:fit(Box, Dlg), + wxSizer:setSizeHints(Box,Dlg), + State = #state{win=Dlg, + back=Back, forward=Forw, up=Up, + text=Dir, + list=LC, + icons=IconMap, + sort ={SortCol,false}, + filter = Filter, + path=Path, + files=Files + }, + {Dlg, State}. + +%% calls +handle_call(getFilename, _From, State = #state{list=LC, files=Fs}) -> + case wxListCtrl:getNextItem(LC, -1, [{state,?wxLIST_STATE_SELECTED}]) of + -1 -> + {reply, "", State}; + Item -> + {reply, (lists:nth(Item+1,Fs))#file.name, State} + end; + +handle_call(getFilenames, _From, State = #state{list=LC, files=Fs}) -> + Items = get_selection(LC,-1, []), + Files = [(lists:nth(Item+1,Fs))#file.name || Item <- Items], + {reply, Files, State}; + +handle_call(getDirectory, _From, State = #state{path=Dir}) -> + {reply, Dir, State}; + +handle_call(destroy, _From, State) -> + {stop, normal, ok, State}. + +%% events +handle_event(#wx{id=?wxID_UP}, State0) -> + State = update_window(change_dir(0, State0)), + {noreply, State}; + +handle_event(#wx{id=?wxID_BACKWARD}, State0 = #state{rstack=[Prev|Stack]}) -> + State = update_window(change_dir(Prev, State0#state{rstack=Stack}, forward)), + {noreply, State}; + +handle_event(#wx{id=?wxID_FORWARD}, State0 = #state{fstack=[Prev|Stack]}) -> + State = update_window(change_dir(Prev, State0#state{fstack=Stack}, reverse)), + {noreply, State}; + +handle_event(#wx{id=Id=?wxID_CANCEL}, State = #state{win=Win}) -> + wxDialog:endModal(Win,Id), + {noreply, State}; + +handle_event(#wx{id=Id=?wxID_OK}, State = #state{win=Win}) -> + wxDialog:endModal(Win,Id), + {noreply, State}; + +handle_event(#wx{event=#wxList{type=command_list_col_click, col=Column0}}, + State0 = #state{files=Fs, sort=Sort0}) -> + case Column0 >= 0 of + true -> + Column = sort_col(Column0+1), + Sort = case Sort0 of + {Column,Bool} -> {Column, not Bool}; + {_,_} -> {Column, false} + end, + {noreply, update_window(State0#state{files=sort_files(Fs,Sort),sort=Sort})}; + false -> + {noreply, State0} + end; + +handle_event(#wx{event=#wxList{itemIndex=Index}}, + State0 = #state{files=Fs,win=Win}) -> + case lists:nth(Index+1, Fs) of + #file{type="directory"} -> + State = update_window(change_dir(Index, State0)), + {noreply, State}; + _Dbg = #file{} -> + wxDialog:endModal(Win,?wxID_OK), + {noreply, State0} + end; + +handle_event(#wx{event=#wxCommand{type=command_text_updated, cmdString=Wanted}}, + State = #state{ptext=Previous, completion=Comp}) -> + case Previous =:= undefined orelse lists:prefix(Wanted, Previous) of + true -> + case Comp of + {Temp,_} -> wxWindow:destroy(Temp); + undefined -> ok + end, + {noreply, State#state{ptext=Wanted,completion=undefined}}; + false -> + {noreply, show_completion(Wanted, State)} + end; + +handle_event(#wx{event=#wxCommand{type=command_text_enter, cmdString=Wanted}}, + State) -> + case filelib:is_dir(Wanted, erl_prim_loader) of + true -> + {Path0, Dir} = split_dir(Wanted), + Path = filename:join(Path0,Dir), + {noreply, update_window(change_dir(Path, State))}; + false -> + {Path, _} = split_dir(Wanted), + {noreply, update_window(change_dir(Path, State))} + end; + +handle_event(#wx{event=#wxKey{keyCode=?WXK_TAB}}, + State = #state{text=TC, ptext=Wanted, completion=Comp}) -> + case wxTextCtrl:getSelection(TC) of + {Pos,Pos} -> + {noreply, show_completion(Wanted, State)}; + _ -> + wxTextCtrl:setInsertionPointEnd(TC), + destroy_completion(Comp), + {noreply, State#state{completion=undefined}} + end; + +handle_event(#wx{id=?COMPLETION_WIN, event=#wxCommand{cmdString=[]}}, State) -> + {noreply, State}; +handle_event(#wx{id=?COMPLETION_WIN, obj=LB, + event=_Ev=#wxCommand{cmdString=Dir,commandInt=N}}, + State = #state{ptext=Wanted0, text=TC}) -> + case wxListBox:isSelected(LB, N) of + true -> + Wanted = case Wanted0 of + undefined -> wxTextCtrl:getValue(TC); + _ -> Wanted0 + end, + Path1 = case filelib:is_dir(Wanted, erl_prim_loader) of + true -> Wanted; + false -> + {ThePath, _} = split_dir(Wanted), + ThePath + end, + Path = filename:join(Path1, Dir), + {noreply, update_window(change_dir(Path, State))}; + false -> + {noreply, State} + end; + +handle_event(#wx{event=#wxSize{size={Width,_}}}, State = #state{list=LC}) -> + wx:batch(fun() -> + Tot = wx:foldl(fun(C,Sum) -> + Sum + wxListCtrl:getColumnWidth(LC, C) + end, 0, [1,2]), + wxListCtrl:setColumnWidth(LC, 0, Width-Tot-30) + end), + {noreply, State}; + +handle_event(Event,State) -> + io:format("~p Got ~p ~n",[self(), Event]), + {noreply, State}. + +handle_info(_Msg, State) -> + {noreply,State}. + +terminate(_Reason,State) -> + wxDialog:destroy(State#state.win), + ok. + +code_change(_,_,State) -> + State. + +%%%%%%%%%%%%%%%%%%%%%%% Internal %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +update_window(State = #state{files=Fs, list=LC, + path=Path, text=TC, + icons=Icons, + completion = Comp}) -> + update_files(Fs, LC, Icons), + update_dir(Path, TC), + if State#state.rstack == [] -> wxButton:disable(State#state.back); + true -> wxButton:enable(State#state.back) + end, + if State#state.fstack == [] -> wxButton:disable(State#state.forward); + true -> wxButton:enable(State#state.forward) + end, + if Path == "/" -> wxButton:disable(State#state.up); + true -> wxButton:enable(State#state.up) + end, + destroy_completion(Comp), + State#state{completion=undefined, ptext=undefined}. + +update_dir(Path, TC) -> + case Path of + "/" -> + wxTextCtrl:setValue(TC, Path); + _ -> + wxTextCtrl:setValue(TC, Path ++ "/") + end, + wxTextCtrl:setInsertionPointEnd(TC). + +update_files(Files,LC, Icons) -> + wxListCtrl:deleteAllItems(LC), + wx:foldl(fun(F=#file{name=Name,type=TypeStr,date=Date, color=Color},Row) -> + wxListCtrl:insertItem(LC, Row, ""), + if (Row rem 2) =:= 0 -> + wxListCtrl:setItemBackgroundColour(LC, Row, {240,240,255}); + true -> ignore + end, + wxListCtrl:setItemTextColour(LC, Row, Color), + wxListCtrl:setItem(LC, Row, 0, Name, [{imageId,get_icon(F,Icons)}]), + wxListCtrl:setItem(LC, Row, 2, format_date(Date)), + wxListCtrl:setItem(LC, Row, 1, TypeStr), + Row+1 + end, 0, Files). + +show_completion(undefined, State = #state{text=TC}) -> + show_completion(wxTextCtrl:getValue(TC), State); +show_completion(Wanted, State = #state{text=TC, win=Win, list=LC, completion=Comp}) -> + Paths0 = filelib:wildcard(Wanted ++ "*", erl_prim_loader), + Paths = [File || File <- Paths0, filelib:is_dir(File, erl_prim_loader)], + case Paths of + [Path] -> + Start = length(Wanted), + wxTextCtrl:setValue(TC, Path++"/"), + wxTextCtrl:setInsertionPoint(TC, Start), + wxTextCtrl:setSelection(TC, Start, -1), + destroy_completion(Comp), + wxWindow:setFocus(TC), + State#state{ptext=Path, completion=undefined}; + Paths when Comp =:= undefined -> + {PosX,PosY} = wxListCtrl:getPosition(LC), + {SzX, SzY} = wxListCtrl:getSize(LC), + Pos0 = {PosX+5,PosY}, + Size = {SzX-50,SzY-50}, + Files = [filename:basename(File) || File <- Paths], + Temp = case os:type() of + {win32,nt} -> + Pos = wxWindow:clientToScreen(Win,Pos0), + wxFrame:new(Win, -1, "", + [{pos, Pos}, {size, Size}, + {style, ?wxFRAME_FLOAT_ON_PARENT}]); + _ -> + wxWindow:new(Win, -1, + [{pos, Pos0}, {size, Size}, + {style, ?wxFRAME_FLOAT_ON_PARENT}]) + end, + LB = wxListBox:new(Temp, ?COMPLETION_WIN, + [{style, ?wxLB_SINGLE}, {choices, Files}, {size, Size}]), + + wxListBox:connect(LB, command_listbox_doubleclicked), + wxListBox:connect(LB, command_listbox_selected), + wxWindow:show(Temp), + wxWindow:setFocus(TC), + State#state{completion = {Temp, LB}, ptext=Wanted}; + Paths -> + {_Temp, LB} = Comp, + wxListBox:clear(LB), + Files = [filename:basename(File) || File <- Paths], + wxListBox:insertItems(LB,Files,0), + wxWindow:setFocus(TC), + State#state{ptext=Wanted} + end. + +destroy_completion(undefined) -> ok; +destroy_completion({Window, _}) -> + Parent = wxWindow:getParent(Window), + wxWindow:destroy(Window), + wxWindow:refresh(Parent). + +split_dir(Path0) -> + Split1 = filename:split(Path0), + case lists:reverse(Split1) of + [File| Split2] when Split2 =/= [] -> + Split3 = lists:reverse(Split2), + Path = filename:join(Split3), + {Path, File}; + _ -> + {"/", ""} + end. + +change_dir(What,State) -> + change_dir(What,State,new). + +change_dir(Num, State = #state{files=Fs0, path=Path0},Stack) + when is_integer(Num) -> + case lists:nth(Num+1, Fs0) of + #file{name=".."} -> + {Path,_} = split_dir(Path0); + #file{name=Dir} -> + Path = filename:join(Path0, Dir) + end, + change_dir(Path, State, Stack); +change_dir(Path, State0 = #state{path=Path0, sort=Sort, filter=Filter},StackDir) -> + Files = list_files(Path, Sort, Filter), + add_to_stack(StackDir, Path0, State0#state{files=Files, path=Path}). + +add_to_stack(new, Path, State = #state{rstack=Stack0}) -> + Stack = [Path|Stack0], + State#state{rstack=Stack, fstack=[]}; +add_to_stack(reverse, Path, State = #state{rstack=Stack0}) -> + Stack = [Path|Stack0], + State#state{rstack=Stack}; +add_to_stack(forward, Path, State = #state{fstack=Stack0}) -> + Stack = [Path|Stack0], + State#state{fstack=Stack}. + +list_files(Dir, Sort, Filter) -> + Contents0 = filelib:wildcard(Dir ++ "/*", erl_prim_loader), + Contents = case Dir of + "/" -> Contents0; + _ -> [".."|Contents0] + end, + {Ds0,Fs0} = get_file_info(Contents, Dir, Filter, [], []), + sort_files(lists:reverse(Ds0), Fs0, Sort). + +sort_files(Mixed, Sort) -> + {Ds,Fs} = + lists:foldr(fun(Dir = #file{type="directory"}, {Ds,Fs}) -> + {[Dir|Ds],Fs}; + (File, {Ds,Fs}) -> + {Ds,[File|Fs]} + end, {[],[]}, Mixed), + sort_files(Ds,Fs,Sort). + +sort_files(Ds0, Fs0, {SortElement, Rev}) -> + {Top,Ds1} = case Ds0 of + [Up=#file{name=".."}|Rest] -> {Up,Rest}; + _ -> {undefined, Ds0} + end, + Ds = lists:keysort(SortElement, Ds1), + Fs = case Rev of + true -> lists:reverse(lists:keysort(SortElement,Fs0)); + false -> lists:keysort(SortElement,Fs0) + end, + case Top of + undefined -> Ds ++ Fs; + _ -> [Top|Ds++Fs] + end. + +get_file_info([AbsName|Rest],Dir,Filter, Files,Dirs) -> + Name = filename:basename(AbsName), + Mod = filelib:last_modified(AbsName, erl_prim_loader), + IsDir = filelib:is_dir(AbsName, erl_prim_loader), + Entry0 = #file{name=Name, date=Mod}, + case IsDir of + true when Name =:= ".." -> + Entry = Entry0#file{type="directory",icon=prev_dir}, + get_file_info(Rest, Dir, Filter, Files, [Entry|Dirs]); + true -> + Entry = Entry0#file{type="directory",icon=dir}, + get_file_info(Rest, Dir, Filter, Files, [Entry|Dirs]); + false -> + case Filter(Dir,Name) of + {Type,Icon,Color} -> + Entry = Entry0#file{type=Type,icon=Icon,color=Color}, + get_file_info(Rest, Dir, Filter, [Entry|Files], Dirs); + skip -> + get_file_info(Rest, Dir, Filter, Files, Dir) + end + end; +get_file_info([], _, _, Fs,Ds) -> + {Ds,Fs}. + +format_date({{Y,M,D},{H,Mi,S}}) -> + lists:flatten(io_lib:format("~w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",[Y,M,D,H,Mi,S])); +format_date(_) -> + "unknown". + +get_selection(LC, Prev, Acc) -> + case wxListCtrl:getNextItem(LC, Prev, [{state,?wxLIST_STATE_SELECTED}]) of + -1 -> + lists:reverse(Acc); + Item -> + get_selection(LC, Item, [Item|Acc]) + end. + +file_type_and_icon(_Dir, Name) -> + case filename:extension(Name) of + ".erl" -> + {"erl src", erl_src, {0,90,0}}; + ".hrl" -> + {"erl hrl", erl_hrl, {0,90,0}}; + ".beam" -> + {"erl bin", erl_bin, {0,0,0}}; + _ -> + {"file", file, {0,0,0}} + end. + +create_icons(Extra) -> + Art = wxImageList:new(16,16), + BuiltIn0 = [{file, "wxART_NORMAL_FILE"}, + {dir, "wxART_FOLDER"}, + {prev_dir, "wxART_GO_DIR_UP"}], + BuiltIn = [{Type, wxArtProvider:getBitmap(ArtID, [{size, {16,16}}])} || + {Type,ArtID} <- BuiltIn0], + + Test = [{Type, wxBitmap:new(wxImage:new(16,16,Bin))} + || {Type,Bin} <- + [{erl_src, ?ERL_SRC}, + {erl_hrl, ?ERL_HRL}, + {erl_bin, ?ERL_BIN}]], + + Icons = BuiltIn ++ Test ++ Extra, + [wxImageList:add(Art, Image) || {_, Image} <- Icons], + {Art, ids(Icons, 0)}. + +get_icon(#file{icon=Icon}, Icons) -> + proplists:get_value(Icon,Icons, 0). + +ids([{Type,_}|Rest], Id) -> + [{Type,Id}|ids(Rest,Id+1)]; +ids([],_) -> []. + +sort_col(1) -> #file.name; +sort_col(2) -> #file.type; +sort_col(3) -> #file.date; +sort_col(name) -> #file.name; +sort_col(type) -> #file.type; +sort_col(date) -> #file.date. + diff --git a/lib/debugger/src/dbg_wx_filedialog_win.hrl b/lib/debugger/src/dbg_wx_filedialog_win.hrl new file mode 100644 index 0000000000..aa739ad49e --- /dev/null +++ b/lib/debugger/src/dbg_wx_filedialog_win.hrl @@ -0,0 +1,180 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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% +%% + +-define(ERL_SRC, + <<255,255,255,255,255,255,255,255,255,192,192,192,192,192, + 192,192,192,192,192,192,192,192,192,192,192,192,192,192, + 192,192,192,192,192,192,192,192,160,160,160,191,191,191, + 255,255,255,255,255,255,191,191,191,128,128,128,128,128, + 128,96,96,96,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 192,192,192,144,144,144,191,191,191,255,255,255,128,128, + 128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128, + 128,128,0,0,0,0,0,0,192,192,192,192,192,192,144,144,144, + 191,191,191,128,128,128,0,0,0,64,64,64,128,128,128,128, + 128,128,0,0,0,191,191,191,128,128,128,0,0,0,128,128,128, + 0,0,0,0,0,0,223,223,223,128,128,128,128,128,128,64,64, + 64,128,128,128,0,0,0,128,128,128,128,128,128,128,128, + 128,0,0,0,128,128,128,0,0,0,0,0,0,128,128,128,0,0,0,0,0, + 0,255,255,255,255,255,255,255,255,255,128,128,128,128, + 128,128,0,0,0,0,0,0,128,128,128,64,64,64,0,0,0,64,64,64, + 0,0,0,0,0,0,128,128,128,0,0,0,0,0,0,255,255,255,255,255, + 255,255,255,255,128,128,128,160,160,192,64,64,128,64,64, + 128,64,64,128,64,64,128,64,64,128,64,64,128,64,64,128, + 64,64,128,64,64,128,64,64,128,64,64,128,255,255,255,255, + 255,255,255,255,255,128,128,128,239,239,239,224,224,224, + 224,224,224,192,192,192,224,224,224,224,224,224,224,224, + 224,224,224,224,224,224,224,224,224,224,224,224,224,224, + 224,224,255,255,255,255,255,255,255,255,255,128,128,128, + 255,255,255,255,255,255,255,255,255,192,192,192,255,255, + 255,192,128,128,239,239,239,255,255,255,255,255,255,239, + 239,239,255,255,255,255,255,255,255,255,255,208,176,176, + 223,191,191,128,128,128,255,255,255,255,255,255,255,255, + 255,192,192,192,255,255,255,176,112,112,255,255,255,255, + 255,255,223,191,191,128,0,0,160,64,64,255,255,255,255, + 255,255,239,239,239,192,128,128,128,128,128,255,255,255, + 255,255,255,255,255,255,192,192,192,255,255,255,192,128, + 128,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,192,128,128, + 128,128,128,255,255,255,255,255,255,255,255,255,192,192, + 192,255,255,255,192,128,128,255,255,255,255,255,255,223, + 191,191,192,128,128,192,128,128,192,128,128,192,128,128, + 192,128,128,192,128,128,128,128,128,255,255,255,255,255, + 255,255,255,255,192,192,192,255,255,255,192,128,128,255, + 255,255,255,255,255,192,128,128,128,0,0,128,0,0,128,0,0, + 144,48,48,128,0,0,192,128,128,128,128,128,255,255,255, + 255,255,255,255,255,255,192,192,192,255,255,255,176,112, + 112,255,255,255,255,255,255,239,239,239,144,48,48,128,0, + 0,144,48,48,239,239,239,192,160,160,192,128,128,128,128, + 128,255,255,255,255,255,255,255,255,255,192,192,192,255, + 255,255,192,128,128,239,239,239,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,208,176, + 176,223,191,191,128,128,128,255,255,255,255,255,255,255, + 255,255,192,192,192,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,64,64,64>>). + +-define(ERL_HRL, + <<255,255,255,255,255,255,255,255,255,192,192,192,192,192, + 192,192,192,192,192,192,192,192,192,192,192,192,192,192, + 192,192,192,192,192,192,192,192,160,160,160,191,191,191, + 255,255,255,255,255,255,191,191,191,128,128,128,128,128, + 128,96,96,96,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 192,192,192,144,144,144,191,191,191,255,255,255,128,128, + 128,0,0,0,128,128,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128, + 128,128,0,0,0,0,0,0,0,0,0,192,192,192,192,192,192,144, + 144,144,191,191,191,128,128,128,0,0,0,128,128,128,128, + 128,128,64,64,64,128,128,128,128,128,128,64,64,64,128, + 128,128,0,0,0,0,0,0,0,0,0,223,223,223,128,128,128,128, + 128,128,64,64,64,128,128,128,0,0,0,128,128,128,0,0,0, + 128,128,128,128,128,128,0,0,0,0,0,0,128,128,128,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,255,255,255,128,128, + 128,128,128,128,0,0,0,64,64,64,0,0,0,64,64,64,64,64,64, + 0,0,0,0,0,0,64,64,64,64,64,64,0,0,0,0,0,0,255,255,255, + 255,255,255,255,255,255,128,128,128,160,160,192,64,64, + 128,64,64,128,64,64,128,64,64,128,64,64,128,64,64,128, + 64,64,128,64,64,128,64,64,128,64,64,128,64,64,128,255, + 255,255,255,255,255,255,255,255,128,128,128,239,239,239, + 224,224,224,224,224,224,192,192,192,224,224,224,224,224, + 224,224,224,224,224,224,224,224,224,224,224,224,224,224, + 224,224,224,224,224,255,255,255,255,255,255,255,255,255, + 128,128,128,255,255,255,255,255,255,255,255,255,192,192, + 192,255,255,255,192,128,128,239,239,239,255,255,255,255, + 255,255,239,239,239,255,255,255,255,255,255,255,255,255, + 208,176,176,223,191,191,128,128,128,255,255,255,255,255, + 255,255,255,255,192,192,192,255,255,255,176,112,112,255, + 255,255,255,255,255,223,191,191,128,0,0,160,64,64,255, + 255,255,255,255,255,239,239,239,192,128,128,128,128,128, + 255,255,255,255,255,255,255,255,255,192,192,192,255,255, + 255,192,128,128,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 192,128,128,128,128,128,255,255,255,255,255,255,255,255, + 255,192,192,192,255,255,255,192,128,128,255,255,255,255, + 255,255,223,191,191,192,128,128,192,128,128,192,128,128, + 192,128,128,192,128,128,192,128,128,128,128,128,255,255, + 255,255,255,255,255,255,255,192,192,192,255,255,255,192, + 128,128,255,255,255,255,255,255,192,128,128,128,0,0,128, + 0,0,128,0,0,144,48,48,128,0,0,192,128,128,128,128,128, + 255,255,255,255,255,255,255,255,255,192,192,192,255,255, + 255,176,112,112,255,255,255,255,255,255,239,239,239,144, + 48,48,128,0,0,144,48,48,239,239,239,192,160,160,192,128, + 128,128,128,128,255,255,255,255,255,255,255,255,255,192, + 192,192,255,255,255,192,128,128,239,239,239,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,208,176,176,223,191,191,128,128,128,255,255,255,255, + 255,255,255,255,255,192,192,192,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,64, + 64,64>>). + +-define(ERL_BIN, + <<255,255,255,255,255,255,255,255,255,192,192,192,192,192, + 192,192,192,192,192,192,192,192,192,192,192,192,192,192, + 192,192,192,192,192,192,192,192,160,160,160,191,191,191, + 255,255,255,255,255,255,239,239,239,224,224,224,224,224, + 224,192,192,192,224,224,224,224,224,224,224,224,224,224, + 224,224,224,224,224,224,224,224,224,224,224,224,224,224, + 192,192,192,144,144,144,191,191,191,255,255,255,160,160, + 192,64,64,128,64,64,128,64,64,128,64,64,128,64,64,128, + 64,64,128,64,64,128,64,64,128,64,64,128,64,64,128,64,64, + 128,192,192,192,192,192,192,144,144,144,191,191,191,128, + 128,128,64,64,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,223,223,223,128,128,128,128,128, + 128,64,64,64,128,128,128,191,191,191,64,64,64,0,0,0,128, + 128,128,0,0,0,64,64,64,128,128,128,64,64,64,64,64,64,64, + 64,64,0,0,0,255,255,255,255,255,255,255,255,255,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,0,0,0,255,255,255,255,255,255, + 255,255,255,128,128,128,128,128,128,191,191,191,128,128, + 128,64,64,64,128,128,128,0,0,0,128,128,128,191,191,191, + 128,128,128,128,128,128,128,128,128,0,0,0,255,255,255, + 255,255,255,255,255,255,128,128,128,191,191,191,128,128, + 128,128,128,128,96,96,96,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,255,255,255,255,255,255,255,255,255,128,128, + 128,255,255,255,255,255,255,255,255,255,192,192,192,255, + 255,255,192,128,128,239,239,239,255,255,255,255,255,255, + 239,239,239,255,255,255,255,255,255,255,255,255,208,176, + 176,223,191,191,128,128,128,255,255,255,255,255,255,255, + 255,255,192,192,192,255,255,255,176,112,112,255,255,255, + 255,255,255,223,191,191,128,0,0,160,64,64,255,255,255, + 255,255,255,239,239,239,192,128,128,128,128,128,255,255, + 255,255,255,255,255,255,255,192,192,192,255,255,255,192, + 128,128,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,192,128, + 128,128,128,128,255,255,255,255,255,255,255,255,255,192, + 192,192,255,255,255,192,128,128,255,255,255,255,255,255, + 223,191,191,192,128,128,192,128,128,192,128,128,192,128, + 128,192,128,128,192,128,128,128,128,128,255,255,255,255, + 255,255,255,255,255,192,192,192,255,255,255,192,128,128, + 255,255,255,255,255,255,192,128,128,128,0,0,128,0,0,128, + 0,0,144,48,48,128,0,0,192,128,128,128,128,128,255,255, + 255,255,255,255,255,255,255,192,192,192,255,255,255,176, + 112,112,255,255,255,255,255,255,239,239,239,144,48,48, + 128,0,0,144,48,48,239,239,239,192,160,160,192,128,128, + 128,128,128,255,255,255,255,255,255,255,255,255,192,192, + 192,255,255,255,192,128,128,239,239,239,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 208,176,176,223,191,191,128,128,128,255,255,255,255,255, + 255,255,255,255,192,192,192,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,64,64,64>>). + diff --git a/lib/debugger/src/dbg_wx_interpret.erl b/lib/debugger/src/dbg_wx_interpret.erl new file mode 100644 index 0000000000..f711ba679d --- /dev/null +++ b/lib/debugger/src/dbg_wx_interpret.erl @@ -0,0 +1,131 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_interpret). + +-include_lib("kernel/include/file.hrl"). +-include_lib("wx/include/wx.hrl"). + +%% External exports +-export([start/4]). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start(Win, Pos, Dir, Mode) +%% GS = Graphics system id +%% Dir = string() +%% Pos = {X,Y} +%% Mode = local | global +%%-------------------------------------------------------------------- +start(Win, Pos, SDir, Mode) -> + Title = "Interpret Modules", + + FD = dbg_wx_filedialog_win:new(Win, -1, + [{message,Title}, {pos,Pos}, + {defaultDir,SDir}, + {sort, type}, + {filter, fun filter_files/2}]), + + case wxFileDialog:showModal(FD) of + ?wxID_OK -> + Files = dbg_wx_filedialog_win:getFilenames(FD), + Dir = dbg_wx_filedialog_win:getDirectory(FD), + dbg_wx_filedialog_win:destroy(FD), + interpret_all(Dir, Files, Mode, Win, []), + self() ! {dbg_ui_interpret, Dir}, + ok; + _ -> + dbg_wx_filedialog_win:destroy(FD), + cancel + end. + +filter_files(Dir, Name) -> + case filename:extension(Name) of + ".erl" -> + File = filename:join(Dir, Name), + case int:interpretable(File) of + true -> + {"erl src", erl_src, {0,0,0}}; + _ -> + {"erl src no dbg", erl_src, {128,128,128}} + end; + ".hrl" -> + {"erl hrl", erl_hrl, {128,128,128}}; + ".beam" -> + {"erl bin", erl_bin, {128,128,128}}; + _ -> + {"file", file, {128,128,128}} + end. + +%%% Standard file browser variant +%% start(Win, Pos, SDir, Mode) -> +%% Title = "Interpret Dialog", +%% Filter = "*.erl", + +%% FD = FileDialog:new(Win, [{message,Title},{pos, Pos}, +%% {defaultDir,SDir}, +%% {wildCard, Filter}, +%% {style,?wxFD_OPEN bor ?wxFD_MULTIPLE}]), + +%% case wxFileDialog:showModal(FD) of +%% ?wxID_OK -> +%% Files = wxFileDialog:getFilenames(FD), +%% Dir = wxFileDialog:getDirectory(FD), +%% wxFileDialog:destroy(FD), +%% interpret_all(Dir, Files, Mode, Win), +%% self() ! {dbg_ui_interpret, Dir}, +%% ok; +%% _ -> +%% wxFileDialog:destroy(FD), +%% cancel +%% end. + +interpret_all(Dir, [File0|Files], Mode, Window, Errors) -> + File = filename:join(Dir, File0), + Res = case Mode of + local -> int:i(File); + global -> int:ni(File) + end, + case Res of + {module, _Mod} -> + interpret_all(Dir, Files, Mode, Window, Errors); + error -> + interpret_all(Dir, Files, Mode, Window, [File0|Errors]) + end; +interpret_all(_Dir, [], _Mode, _Window, []) -> + true; +interpret_all(Dir, [], _Mode, Window, Errors) -> + Msg = lists:map(fun(Name) -> + File = filename:join(Dir, Name), + Error = format_error(int:interpretable(File)), + ["\n ",Name,": ",Error] + end, Errors), + All = ["Error when interpreting: ", Msg], + dbg_wx_win:confirm(Window, lists:flatten(All)), + true. + +format_error({error,no_beam}) -> "No BEAM file"; +format_error({error,no_debug_info}) -> "No debug_info in BEAM file"; +format_error({error,badarg}) -> "File does not exist"; +format_error({error,{app,App}}) -> + "Cannot interpret "++atom_to_list(App)++" modules". diff --git a/lib/debugger/src/dbg_wx_mon.erl b/lib/debugger/src/dbg_wx_mon.erl new file mode 100644 index 0000000000..d81069ec90 --- /dev/null +++ b/lib/debugger/src/dbg_wx_mon.erl @@ -0,0 +1,759 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_mon). + +-include_lib("kernel/include/file.hrl"). +-include_lib("wx/include/wx.hrl"). + +%% External exports +-export([start/2, stop/0]). + +-define(TRACEWIN, ['Search Area', 'Button Area', 'Evaluator Area', 'Bindings Area']). +-define(BACKTRACE, 100). + +-record(pinfo, {pid, % pid() + status % break | exit | idle | running | waiting + }). + +-record(state, {mode, % local | global + starter, % bool() 'true' if int was started by me + + win, % term() Monitor window data + focus, % undefined | #pinfo{} Process in focus + coords, % {X,Y} Mouse pointer position + + intdir, % string() Default dir + pinfos, % [#pinfo{}] Debugged processes + + tracewin, % [Area] Areas shown in trace window + backtrace, % integer() Number of call frames to fetch + + attach, % false | {Flags, Function} + + sfile, % default | string() Settings file + changed % boolean() Settings have been changed + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start(Mode, SFile) -> {ok, Pid} | {error, Reason} +%% Mode = local | global +%% SFile = string() | default Settings file +%% Pid = pid() +%% Reason = {already_started,Pid} | term() +%%-------------------------------------------------------------------- +start(Mode, SFile) -> + case whereis(?MODULE) of + undefined -> + CallingPid = self(), + Pid = spawn(fun () -> init(CallingPid, Mode, SFile) end), + receive + {initialization_complete, Pid} -> + {ok, Pid}; + Error -> + Error + end; + + Pid -> + {error, {already_started,Pid}} + end. + +%%-------------------------------------------------------------------- +%% stop() -> ok +%%-------------------------------------------------------------------- +stop() -> + case whereis(?MODULE) of + undefined -> + ok; + Pid -> + Flag = process_flag(trap_exit, true), + link(Pid), + Pid ! stop, + receive + {'EXIT', Pid, stop} -> + process_flag(trap_exit, Flag), + ok + end + end. + + +%%==================================================================== +%% Initialization +%%==================================================================== + +init(CallingPid, Mode, SFile) -> + register(?MODULE, self()), + + %% Graphics system + case catch dbg_wx_mon_win:init() of + {'EXIT', Reason} -> + CallingPid ! {error, Reason}; + GS -> + try + init2(CallingPid, Mode, SFile, GS) + catch + exit:stop -> stop; + Error:Reason -> + io:format("~p: Crashed {~p,~p} in~n ~p", + [?MODULE, Error, Reason, erlang:get_stacktrace()]) + end + end. + +init2(CallingPid, Mode, SFile, GS) -> + %% Start Int if necessary and subscribe to information from it + Bool = case int:start() of + {ok, _Int} -> true; + {error, {already_started, _Int}} -> false + end, + int:subscribe(), + + %% Start other necessary stuff + dbg_wx_win:init(), + dbg_wx_winman:start(), % Debugger window manager + + %% Create monitor window + Title = "Monitor", + Win = dbg_wx_mon_win:create_win(GS, Title, menus()), + Window = dbg_wx_mon_win:get_window(Win), + dbg_wx_winman:insert(Title, Window), + + %% Initial process state + State1 = #state{mode = Mode, + starter = Bool, + + win = Win, + focus = undefined, + coords = {0,0}, + + intdir = element(2, file:get_cwd()), + pinfos = [], + + sfile = SFile, + changed = false + }, + + State2 = init_options(?TRACEWIN, % Trace Window + int:auto_attach(), % Auto Attach + int:stack_trace(), % Stack Trace + ?BACKTRACE, % Back Trace Size + State1), + + State3 = init_contents(int:interpreted(), % Modules + int:all_breaks(), % Breakpoints + int:snapshot(), % Processes + State2), + + %% Disable/enable functionality according to process in focus (none) + gui_enable_functions(State3#state.focus), + + CallingPid ! {initialization_complete, self()}, + + if + SFile==default -> + loop(State3); + true -> + loop(load_settings(SFile, State3)) + end. + +init_options(TraceWin, AutoAttach, StackTrace, BackTrace, State) -> + lists:foreach(fun(Area) -> + dbg_wx_mon_win:select(Area, true) + end, + TraceWin), + + case AutoAttach of + false -> ignore; + {Flags, _Function} -> + dbg_wx_mon_win:show_option(State#state.win, + auto_attach, Flags), + lists:foreach(fun(Flag) -> + dbg_wx_mon_win:select(map(Flag), true) + end, + Flags) + end, + + dbg_wx_mon_win:show_option(State#state.win, + stack_trace, StackTrace), + dbg_wx_mon_win:select(map(StackTrace), true), + + dbg_wx_mon_win:show_option(State#state.win, back_trace, BackTrace), + + State#state{tracewin=TraceWin, backtrace=BackTrace}. + +init_contents(Mods, Breaks, Processes, State) -> + Win2 = + lists:foldl(fun(Mod, Win) -> + dbg_wx_mon_win:add_module(Win,'Module',Mod) + end, + State#state.win, + Mods), + + Win3 = + lists:foldl(fun(Break, Win) -> + dbg_wx_mon_win:add_break(Win,'Break',Break) + end, + Win2, + Breaks), + + lists:foldl(fun(PidTuple, State0) -> + int_cmd({new_process, PidTuple}, State0) + end, + State#state{win=Win3}, + Processes). + + +%%==================================================================== +%% Main loop and message handling +%%==================================================================== + +loop(State) -> + receive + stop -> + gui_cmd(stopped, State); + + %% From the wx-GUI + #wx{} = GuiEvent -> + Cmd = dbg_wx_mon_win:handle_event(GuiEvent,State#state.win), + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the interpreter process + {int, Cmd} -> + State2 = int_cmd(Cmd, State), + loop(State2); + + %% From the dbg_ui_interpret process + {dbg_ui_interpret, Dir} -> + loop(State#state{intdir=Dir}); + + %% From the dbg_wx_winman process (Debugger window manager) + {dbg_ui_winman, update_windows_menu, Data} -> + Window = dbg_wx_mon_win:get_window(State#state.win), + dbg_wx_winman:update_windows_menu(Window,Data), + loop(State); + + %% From the trace window + {dbg_wx_trace, From, get_env} -> + From ! {env, self(), wx:get_env(), dbg_wx_mon_win:get_window(State#state.win)}, + loop(State) + end. + +%%--Commands from the GUI--------------------------------------------- +%% Act upon a command from the GUI. In most cases, it is only necessary +%% to call a relevant int-function. int will then report when the action +%% has been taken. + +gui_cmd(ignore, State) -> + State; +gui_cmd(stopped, State) -> + if + State#state.starter==true -> int:stop(); + true -> int:auto_attach(false) + end, + exit(stop); +gui_cmd({coords, Coords}, State) -> + State#state{coords=Coords}; + +gui_cmd({shortcut, Key}, State) -> + case shortcut(Key) of + {always, Cmd} -> gui_cmd(Cmd, State); + {if_enabled, Cmd} -> + case dbg_wx_mon_win:is_enabled(Cmd) of + true -> gui_cmd(Cmd, State); + false -> State + end; + false -> State + end; + +%% File Menu +gui_cmd('Load Settings...', State) -> + Window = dbg_wx_mon_win:get_window(State#state.win), + case dbg_wx_settings:load(Window, State#state.coords,State#state.sfile) of + cancel -> State; + {ok, File} -> load_settings(File,State) + end; +gui_cmd('Save Settings...', State) -> + Window = dbg_wx_mon_win:get_window(State#state.win), + case dbg_wx_settings:save(Window, State#state.coords,State#state.sfile) of + cancel -> State; + {ok, File} -> save_settings(File,State) + end; +gui_cmd('Exit', State) -> + gui_cmd(stopped, State); + +%% Edit Menu +gui_cmd('Refresh', State) -> + int:clear(), + Win = dbg_wx_mon_win:clear_processes(State#state.win), + gui_enable_functions(undefined), + State2 = State#state{win=Win, focus=undefined, pinfos=[]}, + lists:foldl(fun(PidTuple, S) -> + int_cmd({new_process,PidTuple}, S) + end, + State2, + int:snapshot()); +gui_cmd('Kill All', State) -> + lists:foreach(fun(PInfo) -> + case PInfo#pinfo.status of + exit -> ignore; + _Status -> exit(PInfo#pinfo.pid, kill) + end + end, + State#state.pinfos), + State; + +%% Module Menu +gui_cmd('Interpret...', State) -> + Window = dbg_wx_mon_win:get_window(State#state.win), + dbg_wx_interpret:start(Window, State#state.coords, + State#state.intdir, State#state.mode), + State; +gui_cmd('Delete All Modules', State) -> + lists:foreach(fun(Mod) -> int:nn(Mod) end, int:interpreted()), + State; +gui_cmd({module, Mod, What}, State) -> + case What of + delete -> int:nn(Mod); + view -> + Window = dbg_wx_mon_win:get_window(State#state.win), + dbg_wx_view:start(Window, Mod) + end, + State; + +%% Process Menu +gui_cmd('Step', State) -> + int:step((State#state.focus)#pinfo.pid), + State; +gui_cmd('Next', State) -> + int:next((State#state.focus)#pinfo.pid), + State; +gui_cmd('Continue', State) -> + int:continue((State#state.focus)#pinfo.pid), + State; +gui_cmd('Finish ', State) -> + int:finish((State#state.focus)#pinfo.pid), + State; +gui_cmd('Attach', State) -> + Pid = (State#state.focus)#pinfo.pid, + case dbg_wx_winman:is_started(dbg_wx_trace:title(Pid)) of + true -> ignore; + false -> int:attach(Pid, trace_function(State)) + end, + State; +gui_cmd('Kill', State) -> + exit((State#state.focus)#pinfo.pid, kill), + State; + +%% Break Menu +gui_cmd('Line Break...', State) -> + Window = dbg_wx_mon_win:get_window(State#state.win), + dbg_wx_break:start(Window, State#state.coords, line), + State; +gui_cmd('Conditional Break...', State) -> + Window = dbg_wx_mon_win:get_window(State#state.win), + dbg_wx_break:start(Window, State#state.coords, conditional), + State; +gui_cmd('Function Break...', State) -> + Window = dbg_wx_mon_win:get_window(State#state.win), + dbg_wx_break:start(Window, State#state.coords, function), + State; +gui_cmd('Enable All', State) -> + Breaks = int:all_breaks(), + lists:foreach(fun ({{Mod, Line}, _Options}) -> + int:enable_break(Mod, Line) + end, + Breaks), + State; +gui_cmd('Disable All', State) -> + Breaks = int:all_breaks(), + lists:foreach(fun ({{Mod, Line}, _Options}) -> + int:disable_break(Mod, Line) + end, + Breaks), + State; +gui_cmd('Delete All', State) -> + int:no_break(), + State; +gui_cmd({break, {Mod, Line}, What}, State) -> + case What of + delete -> int:delete_break(Mod, Line); + {status, inactive} -> int:disable_break(Mod, Line); + {status, active} -> int:enable_break(Mod, Line); + {trigger, Action} -> int:action_at_break(Mod, Line, Action) + end, + State; + +%% Options Commands +gui_cmd({'Trace Window', TraceWin}, State) -> + State2 = State#state{tracewin=TraceWin}, + case State#state.attach of + false -> ignore; + {Flags, {dbg_ui_trace, start, StartFlags}} -> + case trace_function(State2) of + {_, _, StartFlags} -> ignore; + NewFunction -> % {_, _, NewStartFlags} + int:auto_attach(Flags, NewFunction) + end; + _AutoAttach -> ignore + end, + State2; +gui_cmd({'Auto Attach', When}, State) -> + if + When==[] -> int:auto_attach(false); + true -> + Flags = lists:map(fun(Name) -> map(Name) end, When), + int:auto_attach(Flags, trace_function(State)) + end, + State; +gui_cmd({'Stack Trace', [Name]}, State) -> + int:stack_trace(map(Name)), + State; +gui_cmd('Back Trace Size...', State) -> + Window = dbg_wx_mon_win:get_window(State#state.win), + What = {integer, State#state.backtrace}, + case dbg_wx_win:entry(Window, "Backtrace", 'Backtrace:', What) of + cancel -> + State; + {_,BackTrace} -> + dbg_wx_mon_win:show_option(State#state.win,back_trace, BackTrace), + State#state{backtrace=BackTrace} + end; + +%% Help Menu +gui_cmd('Debugger', State) -> + HelpFile = filename:join([code:lib_dir(debugger), + "doc", "html", "part_frame.html"]), + Window = dbg_wx_mon_win:get_window(State#state.win), + dbg_wx_win:open_help(Window, HelpFile), + State; + +gui_cmd({focus, Pid, Win}, State) -> + {value, PInfo} = + lists:keysearch(Pid, #pinfo.pid, State#state.pinfos), + gui_enable_functions(PInfo), + State#state{win=Win, focus=PInfo}; +gui_cmd(default, State) -> + case lists:member('Attach', menus(enabled, State#state.focus)) of + true -> gui_cmd('Attach', State); + false -> State + end. + +%%--Commands from the interpreter------------------------------------- + +int_cmd({interpret, Mod}, State) -> + Win = dbg_wx_mon_win:add_module(State#state.win, 'Module', Mod), + State#state{win=Win}; +int_cmd({no_interpret, Mod}, State) -> + Win = dbg_wx_mon_win:delete_module(State#state.win, Mod), + State#state{win=Win}; + +int_cmd({new_process, {Pid, Function, Status, Info}}, State) -> + + %% Create record with information about the process + Name = registered_name(Pid), + PInfo = #pinfo{pid=Pid, status=Status}, + + %% Update window + Win = dbg_wx_mon_win:add_process(State#state.win, + Pid, Name, Function, Status, Info), + + %% Store process information + PInfos = State#state.pinfos ++ [PInfo], + State#state{win=Win, pinfos=PInfos}; +int_cmd({new_status, Pid, Status, Info}, State) -> + + %% Find stored information about the process + PInfos = State#state.pinfos, + {value, PInfo} = lists:keysearch(Pid, #pinfo.pid, PInfos), + + %% Update process information + PInfo2 = PInfo#pinfo{status=Status}, + PInfos2 = lists:keyreplace(Pid, #pinfo.pid, PInfos, PInfo2), + State2 = State#state{pinfos=PInfos2}, + + %% Update window + dbg_wx_mon_win:update_process(State2#state.win, Pid, Status, Info), + case State2#state.focus of + #pinfo{pid=Pid} -> + gui_enable_functions(PInfo2), + State2#state{focus=PInfo2}; + _ -> + State2 + end; + +int_cmd({new_break, Break}, State) -> + Win = dbg_wx_mon_win:add_break(State#state.win, 'Break', Break), + State#state{win=Win}; +int_cmd({delete_break, Point}, State) -> + Win = dbg_wx_mon_win:delete_break(State#state.win, Point), + State#state{win=Win}; +int_cmd({break_options, Break}, State) -> + dbg_wx_mon_win:update_break(State#state.win, Break), + State; +int_cmd(no_break, State) -> + Win = dbg_wx_mon_win:clear_breaks(State#state.win), + State#state{win=Win}; +int_cmd({no_break, Mod}, State) -> + Win = dbg_wx_mon_win:clear_breaks(State#state.win, Mod), + State#state{win=Win}; + +int_cmd({auto_attach, AutoAttach}, State) -> + OnFlags = case AutoAttach of + false -> []; + {Flags, _Function} -> Flags + end, + OffFlags = [init, exit, break] -- OnFlags, + dbg_wx_mon_win:show_option(State#state.win, auto_attach, OnFlags), + lists:foreach(fun(Flag) -> + dbg_wx_mon_win:select(map(Flag), true) + end, + OnFlags), + lists:foreach(fun(Flag) -> + dbg_wx_mon_win:select(map(Flag), false) + end, + OffFlags), + State#state{attach=AutoAttach}; +int_cmd({stack_trace, Flag}, State) -> + dbg_wx_mon_win:show_option(State#state.win, stack_trace, Flag), + dbg_wx_mon_win:select(map(Flag), true), + State. + + +%%==================================================================== +%% GUI auxiliary functions +%%==================================================================== + +menus() -> + [{'File', [{'Load Settings...', 0}, + {'Save Settings...', 2}, + separator, + {'Exit', 0}]}, + {'Edit', [{'Refresh', no}, + {'Kill All', no}]}, + {'Module', [{'Interpret...', 0}, + {'Delete All Modules', no}, + separator]}, + {'Process', [{'Step', 0}, + {'Next', 0}, + {'Continue', 0}, + {'Finish ', 0}, + separator, + {'Attach', 0}, + {'Kill', no}]}, + {'Break', [{'Line Break...', 5}, + {'Conditional Break...', no}, + {'Function Break...', no}, + separator, + {'Enable All', no}, + {'Disable All', no}, + {'Delete All', 0}, + separator]}, + {'Options', [{'Trace Window', no, cascade, + [{'Search Area', no, check}, + {'Button Area', no, check}, + {'Evaluator Area', no, check}, + {'Bindings Area', no, check}, + {'Trace Area', no, check}]}, + {'Auto Attach', no, cascade, + [{'First Call', no, check}, + {'On Break', no, check}, + {'On Exit', no, check}]}, + {'Stack Trace', no, cascade, + [{'Stack On, Tail', no, radio}, + {'Stack On, No Tail', no, radio}, + {'Stack Off', no, radio}]}, + {'Back Trace Size...', no}]}, + {'Windows', []}, + {'Help', [{'Debugger', no}]}]. + +menus(enabled, undefined) -> + []; +menus(disabled, undefined) -> + ['Step','Next','Continue','Finish ','Attach','Kill']; +menus(enabled, #pinfo{status=exit}) -> + ['Attach']; +menus(disabled, #pinfo{status=exit}) -> + ['Step','Next','Continue','Finish ','Kill']; +menus(enabled, #pinfo{status=break}) -> + ['Step','Next','Continue','Finish ','Attach','Kill']; +menus(disabled, #pinfo{status=break}) -> + []; +menus(enabled, _PInfo) -> + ['Attach','Kill']; +menus(disabled, _PInfo) -> + ['Step','Next','Continue','Finish ']. + +shortcut(l) -> {always, 'Load Settings...'}; +shortcut(v) -> {always, 'Save Settings...'}; +shortcut(e) -> {always, 'Exit'}; + +shortcut(i) -> {always, 'Interpret...'}; + +shortcut(s) -> {if_enabled, 'Step'}; +shortcut(n) -> {if_enabled, 'Next'}; +shortcut(c) -> {if_enabled, 'Continue'}; +shortcut(f) -> {if_enabled, 'Finish '}; +shortcut(a) -> {if_enabled, 'Attach'}; + +shortcut(b) -> {always, 'Line Break...'}; +shortcut(d) -> {always, 'Delete All'}; + +shortcut(_) -> false. + +%% Enable/disable functionality depending on the state of the process +%% currently in Focus +gui_enable_functions(PInfo) -> + Enabled = menus(enabled, PInfo), + Disabled = menus(disabled, PInfo), + dbg_wx_mon_win:enable(Enabled, true), + dbg_wx_mon_win:enable(Disabled, false). + +%% Map values used by int to/from GUI names +map('First Call') -> init; % Auto attach +map('On Exit') -> exit; +map('On Break') -> break; +map(init) -> 'First Call'; +map(exit) -> 'On Exit'; +map(break) -> 'On Break'; + +map('Stack On, Tail') -> all; % Stack trace +map('Stack On, No Tail') -> no_tail; +map('Stack Off') -> false; +map(all) -> 'Stack On, Tail'; +map(true) -> 'Stack On, Tail'; +map(no_tail) -> 'Stack On, No Tail'; +map(false) -> 'Stack Off'. + + +%%==================================================================== +%% Debugger settings +%%==================================================================== + +load_settings(SFile, State) -> + case file:read_file(SFile) of + {ok, Binary} -> + case catch binary_to_term(Binary) of + {debugger_settings, Settings} -> + load_settings2(Settings, + State#state{sfile=SFile, + changed=false}); + _Error -> State + end; + {error, _Reason} -> State + end. + +load_settings2(Settings, State) -> + {TraceWin, AutoAttach, StackTrace, BackTrace, Files, Breaks} = + Settings, + + TraceWinAll = ['Button Area', 'Evaluator Area', 'Bindings Area', + 'Trace Area'], + lists:foreach(fun(Area) -> dbg_wx_mon_win:select(Area, true) end, + TraceWin), + lists:foreach(fun(Area) -> dbg_wx_mon_win:select(Area, false) end, + TraceWinAll--TraceWin), + + case AutoAttach of + false -> int:auto_attach(false); + {Flags, Function} -> int:auto_attach(Flags, Function) + end, + + int:stack_trace(StackTrace), + + dbg_wx_mon_win:show_option(State#state.win, back_trace, BackTrace), + + case State#state.mode of + local -> lists:foreach(fun(File) -> int:i(File) end, Files); + global -> lists:foreach(fun(File) -> int:ni(File) end, Files) + end, + lists:foreach(fun(Break) -> + {{Mod, Line}, [Status, Action, _, Cond]} = + Break, + int:break(Mod, Line), + if + Status==inactive -> + int:disable_break(Mod, Line); + true -> ignore + end, + if + Action/=enable -> + int:action_at_break(Mod,Line,Action); + true -> ignore + end, + case Cond of + CFunction when is_tuple(CFunction) -> + int:test_at_break(Mod,Line,CFunction); + null -> ignore + end + end, + Breaks), + + State#state{tracewin=TraceWin, backtrace=BackTrace}. + +save_settings(SFile, State) -> + Settings = {State#state.tracewin, + int:auto_attach(), + int:stack_trace(), + State#state.backtrace, + lists:map(fun(Mod) -> + int:file(Mod) + end, + int:interpreted()), + int:all_breaks()}, + + Binary = term_to_binary({debugger_settings, Settings}), + case file:write_file(SFile, Binary) of + ok -> + State#state{sfile=SFile, changed=false}; + {error, _Reason} -> + State + end. + + +%%==================================================================== +%% Other internal functions +%%==================================================================== + +registered_name(Pid) -> + + %% Yield in order to give Pid more time to register its name + timer:sleep(200), + + Node = node(Pid), + if + Node==node() -> + case erlang:process_info(Pid, registered_name) of + {registered_name, Name} -> Name; + _ -> undefined + end; + true -> + case rpc:call(Node,erlang,process_info, + [Pid,registered_name]) of + {registered_name, Name} -> Name; + _ -> undefined + end + end. + +trace_function(State) -> + {dbg_wx_trace, start, [State#state.tracewin, State#state.backtrace]}. diff --git a/lib/debugger/src/dbg_wx_mon_win.erl b/lib/debugger/src/dbg_wx_mon_win.erl new file mode 100644 index 0000000000..dfb327fa6a --- /dev/null +++ b/lib/debugger/src/dbg_wx_mon_win.erl @@ -0,0 +1,586 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_mon_win). + +%% External exports +-export([init/0]). +-export([create_win/3, get_window/1, + show_option/3, + enable/2, is_enabled/1, select/2, + add_module/3, delete_module/2, + add_process/6, update_process/4, clear_processes/1, + add_break/3, update_break/2, delete_break/2, + clear_breaks/1, clear_breaks/2, + handle_event/2 + ]). + +-import(dbg_wx_win, [to_string/1, to_string/2]). + +-include_lib("wx/include/wx.hrl"). + +-define(default_rows,10). + +-record(moduleInfo, {module, menubtn}). +-record(procInfo, {pid, row}). +-record(breakInfo, {point, status, break}). +-record(break, {mb, smi, emi, dimi, demi}). %% BUGBUG defined in dbg_ui_win +-record(winInfo, {window, % gsobj() + grid, % gsobj() + row, % int() Last row in grid + + focus, % int() Selected row in grid + + modules=[], % [#moduleInfo{}] Known modules + processes=[], % [#procInfo{}] Known processes + breaks=[], % [#breakInfo{}] Known breakpoints + + listbox, % gsobj() Listinng known modules + + %% Auto attach buttons + fbutton, % gsobj() + bbutton, % gsobj() + ebutton, % gsobj() + selected=[], % ['First Call'|'On Break'|'On Exit'] + + slabel, % showing Stack Trace option + blabel % showing Back Trace Size + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +init() -> + dbg_wx_win:init(). + +%%-------------------------------------------------------------------- +%% create_win(GS, Title, Menus) -> #winInfo{} +%% GS = gsobj() +%% Title = string() +%% Menus = [menu()] See dbg_ui_win.erl +%%-------------------------------------------------------------------- + +-define(GRID,1000). + +-define(PAD, 5). +-define(Wf, 150). +-define(Wg, 770). +-define(W, 800). +-define(H, 390). + +create_win(_Wx, Title, Menus) -> + wx:batch(fun() -> create_win_batch(Title, Menus) end). + +create_win_batch(Title, Menus) -> + Win = wxFrame:new(wx:null(), ?wxID_ANY, Title, + [{size, {?W,?H}}]), + wxFrame:connect(Win, close_window, [{skip, true}]), + MenuBar = wxMenuBar:new(), + dbg_wx_win:create_menus(MenuBar, Menus, Win, 1), + wxFrame:setMenuBar(Win, MenuBar), + + MainSz = wxBoxSizer:new(?wxHORIZONTAL), + LeftSz = wxBoxSizer:new(?wxVERTICAL), + + Panel = wxPanel:new(Win), + Hlb = 200, + Listbox = wxListBox:new(Panel, ?wxID_ANY, [{size,{?Wf,Hlb}}, + {style,?wxLB_SINGLE}]), + wxSizer:add(LeftSz,Listbox,[{border, 3}]), + wxListBox:connect(Listbox, command_listbox_doubleclicked), + wxListBox:connect(Listbox, right_down), + + SBox = wxStaticBox:new(Panel, ?wxID_ANY, "Auto Attach:"), + SBS = wxStaticBoxSizer:new(SBox, ?wxVERTICAL), + Fbtn = wxCheckBox:new(Panel, ?wxID_ANY, "First Call"), + wxSizer:add(SBS,Fbtn), + Bbtn = wxCheckBox:new(Panel, ?wxID_ANY, "On Break"), + wxSizer:add(SBS,Bbtn), + Ebtn = wxCheckBox:new(Panel, ?wxID_ANY, "On Exit"), + wxSizer:add(SBS,Ebtn), + wxFrame:connect(Panel, command_checkbox_clicked), + wxSizer:add(LeftSz,SBS, [{flag,?wxEXPAND}]), + + SLabel = wxStaticText:new(Panel, ?wxID_ANY, "Stack Trace:\n On (with tail)"), + wxSizer:add(LeftSz,SLabel), + BLabel = wxStaticText:new(Panel, ?wxID_ANY, "Back Trace Size:\n 50000"), + wxSizer:add(LeftSz,BLabel), + + %% Create list_crtl / grid + Grid = wxListCtrl:new(Panel, [{winid, ?GRID}, + {style, ?wxLC_REPORT bor ?wxLC_SINGLE_SEL + bor ?wxLC_HRULES }, + {size, {600, -1}}]), + LI = wxListItem:new(), + wxListItem:setText(LI, "Pid"), + wxListItem:setAlign(LI, ?wxLIST_FORMAT_CENTRE), + wxListCtrl:insertColumn(Grid, 0, LI), + wxListItem:setText(LI, "Initial Call"), + wxListItem:setAlign(LI, ?wxLIST_FORMAT_LEFT), + wxListCtrl:insertColumn(Grid, 1, LI), + wxListItem:setText(LI, "Name"), + wxListCtrl:insertColumn(Grid, 2, LI), + wxListItem:setAlign(LI, ?wxLIST_FORMAT_CENTRE), + wxListItem:setText(LI, "Status"), + wxListCtrl:insertColumn(Grid, 3, LI), + wxListItem:setText(LI, "Information"), + wxListItem:setAlign(LI, ?wxLIST_FORMAT_LEFT), + wxListCtrl:insertColumn(Grid, 4, LI), + wxListItem:destroy(LI), + + wxListCtrl:setColumnWidth(Grid, 0, 80), + wxListCtrl:setColumnWidth(Grid, 1, 150), + wxListCtrl:setColumnWidth(Grid, 2, 100), + wxListCtrl:setColumnWidth(Grid, 3, 70), + wxListCtrl:setColumnWidth(Grid, 4, 200), + wxListCtrl:connect(Grid, command_list_item_activated), + wxListCtrl:connect(Grid, command_list_item_selected), + wxListCtrl:connect(Grid, size, [{skip, true}]), + wxListCtrl:connect(Grid, key_up, [{id, ?GRID}, {skip,true}]), + + wxWindow:connect(Win, enter_window, [{skip,true}]), + wxWindow:setFocus(Grid), + + %% Put it in the window + wxSizer:add(MainSz, LeftSz, [{border, 3}, {flag,?wxALL bor ?wxEXPAND}]), + wxSizer:add(MainSz, Grid, [{border, 3}, {flag,?wxALL bor ?wxEXPAND}, + {proportion, 1}]), + + wxWindow:setSizer(Panel,MainSz), + wxSizer:fit(MainSz, Win), + wxSizer:setSizeHints(MainSz,Win), + + IconFile = dbg_wx_win:find_icon("erlang_bug.png"), + Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]), + wxFrame:setIcon(Win, Icon), + wxIcon:destroy(Icon), + wxFrame:show(Win), + dbg_wx_winman:raise(Win), + #winInfo{window=Win, grid=Grid, row=0, focus=0, + listbox=Listbox, + fbutton=Fbtn, bbutton=Bbtn, ebutton=Ebtn, + slabel=SLabel, blabel=BLabel}. + +%%-------------------------------------------------------------------- +%% get_window(WinInfo) -> Window +%% WinInfo = #winInfo{} +%% Window = wxobj() +%%-------------------------------------------------------------------- +get_window(WinInfo) -> + WinInfo#winInfo.window. + +%%-------------------------------------------------------------------- +%% show_option(WinInfo, Option, Value) -> void() +%% WinInfo = #winInfo{} +%% Option = auto_attach | stack_trace | back_trace +%% Value = [Flag] % Option==auto_attach +%% Flag = init | break | exit +%% | true | all | no_tail | false % Option==stack_trace +%% | int() % Option==back_trace +%%-------------------------------------------------------------------- +show_option(WinInfo, Option, Value) -> + case Option of + auto_attach -> + wx:foreach(fun(Button) -> + wxCheckBox:setValue(Button, false) + end, + option_buttons(WinInfo, [init, break, exit])), + wx:foreach(fun(Button) -> + wxCheckBox:setValue(Button, true) + end, + option_buttons(WinInfo, Value)); + + stack_trace -> + Text = case Value of + all -> "Stack Trace:\n On (with tail)"; + true -> "Stack Trace:\n On (with tail)"; + no_tail -> "Stack Trace:\n On (no tail)"; + false -> "Stack Trace:\n Off" + end, + wxStaticText:setLabel(WinInfo#winInfo.slabel, Text); + + back_trace -> + Text = "Back Trace Size:\n " ++ integer_to_list(Value), + wxStaticText:setLabel(WinInfo#winInfo.blabel, Text) + end. + +option_buttons(WinInfo, [init|Flags]) -> + [WinInfo#winInfo.fbutton|option_buttons(WinInfo, Flags)]; +option_buttons(WinInfo, [break|Flags]) -> + [WinInfo#winInfo.bbutton|option_buttons(WinInfo, Flags)]; +option_buttons(WinInfo, [exit|Flags]) -> + [WinInfo#winInfo.ebutton|option_buttons(WinInfo, Flags)]; +option_buttons(_WinInfo, []) -> + []. + +%%-------------------------------------------------------------------- +%% enable([MenuItem], Bool) +%% is_enabled(MenuItem) -> Bool +%% MenuItem = atom() +%% Bool = boolean() +%%-------------------------------------------------------------------- +enable(MenuItems, Bool) -> + lists:foreach(fun(MenuItem) -> + MI = get(MenuItem), + wxMenuItem:enable(MI, [{enable, Bool}]) + end, + MenuItems). + +is_enabled(MenuItem) -> + MI = get(MenuItem), + wxMenuItem:isEnabled(MI). + +%%-------------------------------------------------------------------- +%% select(MenuItem, Bool) +%% MenuItem = atom() +%% Bool = boolean() +%%-------------------------------------------------------------------- +select(MenuItem, Bool) -> + MI = get(MenuItem), + wxMenuItem:check(MI, [{check, Bool}]). + +%%-------------------------------------------------------------------- +%% add_module(WinInfo, Name, Mod) -> WinInfo +%% WinInfo = #winInfo{} +%% Name = atom() +%% Mod = atom() +%%-------------------------------------------------------------------- +add_module(WinInfo, MenuName, Mod) -> + Win = WinInfo#winInfo.window, + Modules = WinInfo#winInfo.modules, + case lists:keysearch(Mod, #moduleInfo.module, Modules) of + {value, _ModInfo} -> WinInfo; + false -> + %% Create a menu for the module + Menu = get(MenuName), + Sub = wxMenu:new([]), + ViewItem = wxMenu:append(Sub, ?wxID_ANY, "View"), + ViewId = wxMenuItem:getId(ViewItem), + wxMenu:connect(Win, command_menu_selected, + [{id,ViewId}, {userData, {module,Mod,view}}]), + DelItem = wxMenu:append(Sub, ?wxID_ANY, "Delete"), + DelId = wxMenuItem:getId(DelItem), + wxMenu:connect(Win, command_menu_selected, + [{id,DelId}, {userData, {module,Mod,delete}}]), + MenuBtn = wxMenu:append(Menu, ?wxID_ANY, atom_to_list(Mod), Sub), + wxListBox:append(WinInfo#winInfo.listbox, atom_to_list(Mod)), + + ModInfo = #moduleInfo{module=Mod, menubtn={Menu,MenuBtn}}, + WinInfo#winInfo{modules=[ModInfo | Modules]} + end. + +%%-------------------------------------------------------------------- +%% delete_module(WinInfo, Mod) -> WinInfo +%% WinInfo = #winInfo{} +%% Mod = atom() +%%-------------------------------------------------------------------- +delete_module(WinInfo, Mod) -> + {value, ModInfo} = lists:keysearch(Mod, #moduleInfo.module, + WinInfo#winInfo.modules), + {Menu, MenuBtn} = ModInfo#moduleInfo.menubtn, + wxMenu:'Destroy'(Menu, MenuBtn), + ListBox = WinInfo#winInfo.listbox, + Id = wxListBox:findString(ListBox, atom_to_list(Mod)), + wxListBox:delete(ListBox,Id), + WinInfo#winInfo{modules=lists:keydelete(Mod, #moduleInfo.module, + WinInfo#winInfo.modules)}. + +%%-------------------------------------------------------------------- +%% add_process(WinInfo, Pid, Name, Function, Status, Info) -> WinInfo +%% WinInfo = #winInfo{} +%% Pid = pid() +%% Name = undefined | atom() +%% Function = {Mod, Func, Args} +%% Status = idle | running | break | exit +%% Info = {} | term() +%%-------------------------------------------------------------------- +add_process(WinInfo, Pid, Name, {Mod,Func,Args}, Status, Info) -> + Grid = WinInfo#winInfo.grid, + Row = (WinInfo#winInfo.row), + + Name2 = case Name of undefined -> ""; _ -> to_string(Name) end, + FuncS = to_string("~w:~w/~w", [Mod, Func, length(Args)]), + Info2 = case Info of {} -> ""; _ -> to_string(Info) end, + Pid2 = to_string("~p",[Pid]), + + Add = fun() -> + _Dbg = wxListCtrl:insertItem(Grid, Row,""), + %%wxListCtrl:setItemData(Grid,Temp,Row), + if (Row rem 2) =:= 0 -> + wxListCtrl:setItemBackgroundColour(Grid, Row, {240,240,255}); + true -> ignore + end, + + wxListCtrl:setItem(Grid, Row, 0, Pid2), + wxListCtrl:setItem(Grid, Row, 1, FuncS), + wxListCtrl:setItem(Grid, Row, 2, Name2), + wxListCtrl:setItem(Grid, Row, 3, to_string(Status)), + wxListCtrl:setItem(Grid, Row, 4, Info2), + ok + end, + wx:batch(Add), + + ProcInfo = #procInfo{pid=Pid, row=Row}, + WinInfo#winInfo{processes=[ProcInfo|WinInfo#winInfo.processes], + row=Row+1}. + +%%-------------------------------------------------------------------- +%% update_process(WinInfo, Pid, Status, Info) +%% WinInfo = #winInfo{} +%% Pid = pid() +%% Status = idle | running | break | exit +%% Info = {} | term() +%%-------------------------------------------------------------------- +update_process(WinInfo, Pid, Status, Info) -> + {value, ProcInfo} = lists:keysearch(Pid, #procInfo.pid, + WinInfo#winInfo.processes), + + Grid = WinInfo#winInfo.grid, + Row = ProcInfo#procInfo.row, + Info2 = case Info of {} -> ""; _ -> Info end, + wxListCtrl:setItem(Grid, Row, 3, to_string(Status)), + wxListCtrl:setItem(Grid, Row, 4, to_string(Info2)). + +%%-------------------------------------------------------------------- +%% clear_processes(WinInfo) -> WinInfo +%% WinInfo = #winInfo{} +%%-------------------------------------------------------------------- +clear_processes(WinInfo) -> + Grid = WinInfo#winInfo.grid, + Max = WinInfo#winInfo.row, + wx:batch(fun() -> clear_processes(Grid, Max-1) end), + WinInfo#winInfo{row=0, focus=0, processes=[]}. + +clear_processes(Grid, Row) when Row >= 0 -> + Item = wxListItem:new(), + wxListItem:setId(Item,Row), + wxListItem:setColumn(Item, 3), + case wxListCtrl:getItem(Grid, Item) of + true -> + case wxListItem:getText(Item) of + "exit" -> + wxListItem:setColumn(Item, 0), + wxListCtrl:getItem(Grid, Item), + Pid = list_to_pid(wxListItem:getText(Item)), + dbg_wx_winman:clear_process(dbg_wx_trace:title(Pid)); + _ -> + ok + end; + false -> + ignore + end, + wxListItem:destroy(Item), + wxListCtrl:deleteItem(Grid, Row), + clear_processes(Grid, Row-1); +clear_processes(_Grid, _Row) -> + done. + +%%-------------------------------------------------------------------- +%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo +%% WinInfo = #winInfo{} +%% Name = atom() +%% Point = {Mod, Line} +%% Options = [Status, Action, Mods, Cond] +%% Status = active | inactive +%% Action = enable | disable | delete +%% Mods = null (not used) +%% Cond = null | {Mod, Func} +%%-------------------------------------------------------------------- +add_break(WinInfo, Menu, {Point, Options}) -> + Break = dbg_wx_win:add_break(WinInfo#winInfo.window, Menu, Point), + dbg_wx_win:update_break(Break, Options), + BreakInfo = #breakInfo{point=Point, break=Break}, + WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}. + +%%-------------------------------------------------------------------- +%% update_break(WinInfo, {Point, Options}) +%% WinInfo = #winInfo{} +%% Point = {Mod, Line} +%% Options = [Status, Action, Mods, Cond] +%% Status = active | inactive +%% Action = enable | disable | delete +%% Mods = null (not used) +%% Cond = null | {Mod, Func} +%%-------------------------------------------------------------------- +update_break(WinInfo, {Point, Options}) -> + {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks), + dbg_wx_win:update_break(BreakInfo#breakInfo.break, Options). + +%%-------------------------------------------------------------------- +%% delete_break(WinInfo, Point) -> WinInfo +%% WinInfo = #winInfo{} +%% Point = {Mod, Line} +%%-------------------------------------------------------------------- +delete_break(WinInfo, Point) -> + {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks), + dbg_wx_win:delete_break(BreakInfo#breakInfo.break), + WinInfo#winInfo{breaks=lists:keydelete(Point, #breakInfo.point, + WinInfo#winInfo.breaks)}. + +%%-------------------------------------------------------------------- +%% clear_breaks(WinInfo) -> WinInfo +%% clear_breaks(WinInfo, Mod) -> WinInfo +%% WinInfo = #winInfo{} +%%-------------------------------------------------------------------- +clear_breaks(WinInfo) -> + lists:foreach(fun(BreakInfo) -> + dbg_wx_win:delete_break(BreakInfo#breakInfo.break) + end, + WinInfo#winInfo.breaks), + WinInfo#winInfo{breaks=[]}. +clear_breaks(WinInfo, Mod) -> + Fun = + fun(BreakInfo) -> + case BreakInfo#breakInfo.point of + {Mod, _Line} -> + dbg_wx_win:delete_break(BreakInfo#breakInfo.break), + false; + _ -> true + end + end, + Breaks = lists:filter(Fun, WinInfo#winInfo.breaks), + WinInfo#winInfo{breaks=Breaks}. + +%%-------------------------------------------------------------------- +%% handle_event(WxEvent, WinInfo) -> Command +%% WxEvent = #wx{} +%% WinInfo = #winInfo{} +%% Command = ignore +%% | stopped +%% | {coords, {X,Y}} +%% +%% | {shortcut, Key} +%% | MenuItem | {Menu, [MenuItem]} +%% MenuItem = Menu = atom() +%% | {break, Point, What} +%% What = delete | {status, Status} | {trigger, Trigger} +%% | {module, Mod, What} +%% What = view | delete +%% +%% | {focus, Pid, WinInfo} +%% | default +%%-------------------------------------------------------------------- +%% Window events +handle_event(#wx{event=#wxSize{size={W,_}}}, #winInfo{grid=Grid}) -> + wx:batch(fun() -> + Tot = wx:foldl(fun(C,Sum) -> + Sum + wxListCtrl:getColumnWidth(Grid, C) + end, 0, [0,1,2,3]), + wxListCtrl:setColumnWidth(Grid, 4, W-Tot-4) + end), + ignore; +handle_event(_Ev=#wx{event=#wxClose{}}, _WinInfo) -> +%% io:format("~p Received ~p close ~p~n", [?MODULE, self(), _Ev]), + stopped; + +%% Menus and keyboard shortcuts +handle_event(#wx{userData={dbg_ui_winman, Win}, + event=#wxCommand{type=command_menu_selected}}, _Wi) -> + dbg_wx_winman:raise(Win), + ignore; +handle_event(_Ev = #wx{event=#wxKey{keyCode=Key, controlDown=true}}, _WinInfo) -> + if + Key/=?WXK_UP, Key/=?WXK_DOWN, Key /=? WXK_RETURN -> + try + {shortcut, list_to_atom([Key+($a-$A)])} + catch _:_ -> ignore + end; + true -> + ignore + end; + +handle_event(#wx{userData={break, Point, status}, + event=#wxCommand{type=command_menu_selected}}, + WinInfo) -> + {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks), + %% This is a temporary hack !! + #breakInfo{break=#break{smi=Smi}} = BreakInfo, + + case wxMenuItem:getText(Smi) of + "Enable" -> {break, Point, {status, active}}; + "Disable" -> {break, Point, {status, inactive}} + end; + +%% Listbox +handle_event(#wx{event=#wxCommand{type=command_listbox_doubleclicked, cmdString=ModS}}, + _WinInfo) -> + {module, list_to_atom(ModS), view}; +handle_event(#wx{obj=ListBox, event=#wxMouse{type=right_down, x=X,y=Y}}, + #winInfo{listbox=ListBox}) -> + case wxListBox:hitTest(ListBox, {X,Y}) of + ?wxNOT_FOUND -> ignore; + Row -> + ModS = wxListBox:getString(ListBox,Row), + io:format("Re-loading/interpreting: ~s~n", [ModS]), + int:i(list_to_atom(ModS)), + ignore + end; + +%% Auto attach buttons +handle_event(#wx{event=#wxCommand{type=command_checkbox_clicked}}, + WinInfo) -> + Check = fun(Button, NamesAcc) -> + case wxCheckBox:isChecked(Button) of + true -> + Name = wxCheckBox:getLabel(Button), + [list_to_atom(Name)|NamesAcc]; + false -> + NamesAcc + end + end, + Names = wx:foldl(Check, [], + [WinInfo#winInfo.ebutton, + WinInfo#winInfo.bbutton, + WinInfo#winInfo.fbutton]), + {'Auto Attach', Names}; + +%% Process grid +handle_event(#wx{event=#wxList{type=command_list_item_selected, + itemIndex=Row}}, WinInfo) -> + #winInfo{processes=Pids} = WinInfo, + {value, #procInfo{pid=Pid}} = + lists:keysearch(Row, #procInfo.row, Pids), + {focus, Pid, WinInfo#winInfo{focus=Row}}; +handle_event(#wx{event=#wxList{type=command_list_item_activated}}, + _WinInfo) -> + default; +handle_event(#wx{event=#wxMouse{type=enter_window}}, #winInfo{grid=Grid}) -> + %% Keyboard focus + wxWindow:setFocus(Grid), + ignore; + +%% Menu Events +handle_event(#wx{userData=Data, + event=_Cmd=#wxCommand{type=command_menu_selected}}, + _WinInfo) -> + Data; +handle_event(_Event, _WinInfo) -> +%% io:format("Ev: ~p~n",[_Event]), + ignore. + +%%==================================================================== +%% Internal functions +%%==================================================================== + + diff --git a/lib/debugger/src/dbg_wx_settings.erl b/lib/debugger/src/dbg_wx_settings.erl new file mode 100644 index 0000000000..8f87815949 --- /dev/null +++ b/lib/debugger/src/dbg_wx_settings.erl @@ -0,0 +1,110 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_settings). + +-include_lib("kernel/include/file.hrl"). +-include_lib("wx/include/wx.hrl"). + +%% External exports +-export([load/3, save/3]). + + +%%==================================================================== +%% External exports +%%==================================================================== + +%%==================================================================== +%% load/save(Win, Pos, Str) -> {ok, FileName} | cancel +%%==================================================================== +load(Win, Pos, SFile) -> + Str = "Load Settings Dialog", + open_win(Win, Pos, SFile, Str, ?wxFD_OPEN). + +save(Win, Pos, SFile) -> + Str = "Save Settings Dialog", + open_win(Win, Pos, SFile, Str, ?wxFD_SAVE). + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +open_win(Win, Pos, SFile, Str, What) -> + {SDir, SFileName} = + if + %% If settings are saved for the first time, and to + %% the default directory HOME/erlang.tools/debugger, + %% make sure the directory exists, or create it if + %% desired and possible + SFile==default -> {default_settings_dir(Win), "NoName.state"}; + true -> {filename:dirname(SFile), filename:basename(SFile)} + end, + + FD = wxFileDialog:new(Win, [{message,Str},{pos, Pos}, + {defaultDir,SDir}, + {defaultFile,SFileName}, + {wildCard, "*.state"}, + {style,What}]), + case wxFileDialog:showModal(FD) of + ?wxID_OK -> + File = wxFileDialog:getFilename(FD), + Dir = wxFileDialog:getDirectory(FD), + wxFileDialog:destroy(FD), + {ok, filename:join(Dir,File)}; + _ -> + wxFileDialog:destroy(FD), + cancel + end. + +default_settings_dir(Win) -> + {ok, [[Home]]} = init:get_argument(home), + DefDir = filename:join([Home, ".erlang_tools", "debugger"]), + + case filelib:is_dir(DefDir) of + true -> DefDir; + false -> + {ok, CWD} = file:get_cwd(), + + Msg = ["Default directory", DefDir, "does not exist.", + "Click Ok to create it or", + "Cancel to use other directory!"], + case dbg_wx_win:confirm(Win, Msg) of + ok -> + ToolsDir = filename:dirname(DefDir), + case filelib:is_dir(ToolsDir) of + true -> + case file:make_dir(DefDir) of + ok -> DefDir; + _Error -> CWD + end; + false -> + case file:make_dir(ToolsDir) of + ok -> + case file:make_dir(DefDir) of + ok -> DefDir; + _Error -> CWD + end; + _Error -> CWD + end + end; + cancel -> CWD + end + end. diff --git a/lib/debugger/src/dbg_wx_src_view.erl b/lib/debugger/src/dbg_wx_src_view.erl new file mode 100644 index 0000000000..5337ec2aac --- /dev/null +++ b/lib/debugger/src/dbg_wx_src_view.erl @@ -0,0 +1,66 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_src_view). + +-export([code_area/2]). +-include_lib("wx/include/wx.hrl"). + +-define(stc, wxStyledTextCtrl). + +code_area(Parent, Sizer) -> + FixedFont = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL,[]), + Ed = wxStyledTextCtrl:new(Parent, [{size, {400, 500}}]), + ?stc:styleClearAll(Ed), + ?stc:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont), + ?stc:setLexer(Ed, ?wxSTC_LEX_ERLANG), + ?stc:setMarginType(Ed, 0, ?wxSTC_MARGIN_NUMBER), + LW = ?stc:textWidth(Ed, ?wxSTC_STYLE_LINENUMBER, "999"), + ?stc:setMarginWidth(Ed, 0, LW), + + ?stc:setReadOnly(Ed, true), + + Styles = [{?wxSTC_ERLANG_DEFAULT, {0,0,0}}, + {?wxSTC_ERLANG_COMMENT, {222,53,35}}, + {?wxSTC_ERLANG_VARIABLE, {170,110,50}}, + {?wxSTC_ERLANG_NUMBER, {5,5,100}}, + {?wxSTC_ERLANG_KEYWORD, {238,80,239}}, + {?wxSTC_ERLANG_STRING, {236,155,172}}, + {?wxSTC_ERLANG_OPERATOR, {30,0,0}}, + {?wxSTC_ERLANG_ATOM, {0,0,0}}, + {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}}, + {?wxSTC_ERLANG_CHARACTER,{236,155,172}}, + {?wxSTC_ERLANG_MACRO, {92,194,241}}, + {?wxSTC_ERLANG_RECORD, {60,150,40}}, + {?wxSTC_ERLANG_SEPARATOR,{0,0,0}}, + {?wxSTC_ERLANG_NODE_NAME,{0,0,0}}], + SetStyle = fun({Style, Color}) -> + ?stc:styleSetFont(Ed, Style, FixedFont), + ?stc:styleSetForeground(Ed, Style, Color) + end, + [SetStyle(Style) || Style <- Styles], + ?stc:setKeyWords(Ed, 0, keyWords()), + wxSizer:add(Sizer, Ed, [{proportion,1}, {flag, ?wxEXPAND}]), + Ed. + + +keyWords() -> + L = ["after","begin","case","try","cond","catch","andalso","orelse", + "end","fun","if","let","of","query","receive","when","bnot","not", + "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"], + lists:flatten([K ++ " " || K <- L] ++ [0]). diff --git a/lib/debugger/src/dbg_wx_trace.erl b/lib/debugger/src/dbg_wx_trace.erl new file mode 100644 index 0000000000..f9fdf593c4 --- /dev/null +++ b/lib/debugger/src/dbg_wx_trace.erl @@ -0,0 +1,839 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_trace). + +%% External exports +-export([start/1, start/3]). +-export([title/1]). + +-define(TRACEWIN, ['Button Area', 'Evaluator Area', 'Bindings Area']). +-define(BACKTRACE, 100). + +-record(state, {win, % term() Attach process window data + coords, % {X,Y} Mouse point position + + pid, % pid() Debugged process + meta, % pid() Meta process + status, % {Status,Mod,Line} � {exit,Where,Reason} + % Status = init � idle | break + % | wait_break � wait_running + % � running + % Where={Mod,Line} | null + + cm, % atom() | undefined Current module + cm_obsolete=false, % boolean() Curr mod needs reloading + + stack, % {Cur,Max} + + trace, % boolean() + stack_trace, % all | no_tail | false + backtrace % integer() #call frames to fetch + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start(Pid) +%% start(Parent, Pid, TraceWin, BackTrace) +%% Parent = #wxObj{} +%% Pid = pid() +%% TraceWin = [WinArea] +%% WinArea = 'Button|Evaluator|Bindings|Trace Area' +%% Backtrace = integer() +%%-------------------------------------------------------------------- +start(Pid) -> % Used by debugger:quick/3 (no monitor) + start(Pid, ?TRACEWIN, ?BACKTRACE). +start(Pid, TraceWin, BackTrace) -> + case {whereis(dbg_wx_mon), whereis(dbg_ui_mon)} of + {undefined, undefined} -> + case which_gui() of + gs -> + dbg_ui_trace:start(Pid, TraceWin, BackTrace); + wx -> + Parent = wx:new(), + Env = wx:get_env(), + start(Pid, Env, Parent, TraceWin, BackTrace) + end; + {undefined, Monitor} when is_pid(Monitor) -> + dbg_ui_trace:start(Pid, TraceWin, BackTrace); + {Monitor, _} when is_pid(Monitor) -> + Monitor ! {?MODULE, self(), get_env}, + receive + {env, Monitor, Env, Parent} -> + start(Pid, Env, Parent, TraceWin, BackTrace) + end + end. + +start(Pid, Env, Parent, TraceWin, BackTrace) -> + %% Inform int about my existence and get the meta pid back + case int:attached(Pid) of + {ok, Meta} -> + try + wx:set_env(Env), + init(Pid, Parent, Meta, TraceWin, BackTrace) + catch + _:stop -> + exit(stop); + E:R -> + io:format("TraceWin Crashed ~p~n",[E]), + io:format(" ~p in ~p~n",[R, erlang:get_stacktrace()]), + exit(R) + end; + error -> + ignore + end. + +which_gui() -> + try + wx:new(), + wx:destroy(), + wx + catch _:_ -> + gs + end. + +%%-------------------------------------------------------------------- +%% title(Pid) -> string() +%% By exporting this function, dbg_wx_mon may check with dbg_wx_winman +%% if there already is an attach window for a given pid and thus avoid +%% spawning processes unnecessarily. +%%-------------------------------------------------------------------- +title(Pid) -> + "Attach Process " ++ pid_to_list(Pid). + + +%%==================================================================== +%% Main loop and message handling +%%==================================================================== + +init(Pid, Parent, Meta, TraceWin, BackTrace) -> + + %% Start necessary stuff + dbg_wx_trace_win:init(), % Graphics system + + %% Create attach process window + Title = title(Pid), + Win = dbg_wx_trace_win:create_win(Parent, Title, TraceWin, menus()), + Window = dbg_wx_trace_win:get_window(Win), + dbg_wx_winman:insert(Title, Window), + + %% Initial process state + State1 = #state{win=Win, coords={0,0}, pid=Pid, meta=Meta, + status={idle,null,null}, + stack={1,1}}, + + State2 = init_options(TraceWin, + int:stack_trace(), % Stack Trace + BackTrace, % Back trace size + State1), + + State3 = init_contents(int:all_breaks(), % Breakpoints + State2), + + int:meta(Meta, trace, State3#state.trace), + + gui_enable_updown(stack_trace, {1,1}), + gui_enable_btrace(false, false), + dbg_wx_trace_win:display(Win,idle), + + loop(State3). + +init_options(TraceWin, StackTrace, BackTrace, State) -> + lists:foreach(fun(Area) -> dbg_wx_trace_win:select(Area, true) end, + TraceWin), + + Trace = lists:member('Trace Area', TraceWin), + + dbg_wx_trace_win:select(map(StackTrace), true), + + %% Backtrace size is (currently) not shown in window + + State#state{trace=Trace,stack_trace=StackTrace,backtrace=BackTrace}. + +init_contents(Breaks, State) -> + Win = + lists:foldl(fun(Break, Win) -> + dbg_wx_trace_win:add_break(Win,'Break',Break) + end, + State#state.win, + Breaks), + + State#state{win=Win}. + +loop(#state{meta=Meta} = State) -> + receive + %% From the GUI main window + GuiEvent when element(1, GuiEvent)==wx -> + Cmd = wx:batch(fun() -> + dbg_wx_trace_win:handle_event(GuiEvent,State#state.win) + end), + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the GUI help windows + {gui, Cmd} -> + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the interpreter + {int, Cmd} -> + State2 = int_cmd(Cmd, State), + loop(State2); + + %% From the meta process + {Meta, Cmd} -> + State2 = meta_cmd(Cmd, State), + loop(State2); + {NewMeta, {exit_at, Where, Reason, Cur}} -> + State2 = meta_cmd({exit_at, Where, Reason, Cur}, + State#state{meta=NewMeta}), + loop(State2); + + %% From the dbg_wx_winman process (Debugger window manager) + {dbg_ui_winman, update_windows_menu, Data} -> + Window = dbg_wx_trace_win:get_window(State#state.win), + dbg_wx_winman:update_windows_menu(Window,Data), + loop(State); + {dbg_ui_winman, destroy} -> + dbg_wx_trace_win:stop(State#state.win), + exit(stop) + end. + +%%--Commands from the GUI--------------------------------------------- + +gui_cmd(ignore, State) -> + State; +gui_cmd({win, Win}, State) -> + State#state{win=Win}; +gui_cmd(stopped, State) -> + dbg_wx_trace_win:stop(State#state.win), + exit(stop); +gui_cmd({coords, Coords}, State) -> + State#state{coords=Coords}; + +gui_cmd({shortcut, Key}, State) -> + case shortcut(Key) of + {always, Cmd} -> gui_cmd(Cmd, State); + {if_enabled, Cmd} -> + case dbg_wx_trace_win:is_enabled(Cmd) of + true -> gui_cmd(Cmd, State); + false -> State + end; + false -> State + end; + +%% File menu +gui_cmd('Close', State) -> + gui_cmd(stopped, State); + +%% Edit menu +gui_cmd('Go To Line', State) -> + %% Will result in message handled below: {gui, {gotoline, Line}} + Win = dbg_wx_trace_win:helpwin(gotoline, State#state.win), + State#state{win=Win}; +gui_cmd('Search', State) -> + Win = dbg_wx_trace_win:helpwin(search, State#state.win), + State#state{win=Win}; +gui_cmd({gotoline, Line}, State) -> + Win = dbg_wx_trace_win:select_line(State#state.win, Line), + State#state{win=Win}; + +%% Process menu +gui_cmd('Step', State) -> + int:meta(State#state.meta, step), + State; +gui_cmd('Next', State) -> + int:meta(State#state.meta, next), + State; +gui_cmd('Continue', State) -> + int:meta(State#state.meta, continue), + {Status, Mod, Line} = State#state.status, + if + Status==wait_break -> + Win = dbg_wx_trace_win:unmark_line(State#state.win), + gui_enable_functions(wait_running), + State#state{win=Win, status={wait_running,Mod,Line}}; + true -> + dbg_wx_trace_win:enable(['Stop'], true), + dbg_wx_trace_win:enable(['Continue'], false), + State + end; +gui_cmd('Finish', State) -> + int:meta(State#state.meta, finish), + State; +gui_cmd('Skip', State) -> + int:meta(State#state.meta, skip), + State; +gui_cmd('Time Out', State) -> + int:meta(State#state.meta, timeout), + State; +gui_cmd('Stop', State) -> + int:meta(State#state.meta, stop), + {Status, Mod, Line} = State#state.status, + if + Status==wait_running -> + Win = dbg_wx_trace_win:mark_line(State#state.win, Line, + break), + gui_enable_functions(wait_break), + gui_enable_updown(State#state.stack_trace, + State#state.stack), + gui_enable_btrace(State#state.trace, + State#state.stack_trace), + dbg_wx_trace_win:display(State#state.win,{wait, Mod, Line}), + State#state{win=Win, status={wait_break,Mod,Line}}; + true -> + dbg_wx_trace_win:enable(['Stop'], false), + dbg_wx_trace_win:enable(['Continue'], true), + State + end; +gui_cmd('Where', State) -> + {_Cur, Max} = State#state.stack, + Stack = {Max, Max}, + {_Status, Mod, Line} = State#state.status, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, break), + gui_update_bindings(State#state.win, State#state.meta), + gui_enable_updown(State#state.stack_trace, Stack), + dbg_wx_trace_win:display(State#state.win,State#state.status), + State#state{win=Win, cm=Mod, stack=Stack}; + +gui_cmd('Kill', State) -> + exit(State#state.pid, kill), + State; +gui_cmd('Messages', State) -> + case int:meta(State#state.meta, messages) of + [] -> + dbg_wx_trace_win:eval_output(State#state.win,"< No Messages!\n", bold); + Messages -> + dbg_wx_trace_win:eval_output(State#state.win,"< --- Current Messages ---\n", + bold), + lists:foldl( + fun(Msg, N) -> + Str1 = io_lib:format(" ~w:", [N]), + dbg_wx_trace_win:eval_output(State#state.win,Str1, bold), + Str2 = io_lib:format(" ~s~n",[io_lib:print(Msg)]), + dbg_wx_trace_win:eval_output(State#state.win,Str2, normal), + N+1 + end, + 1, + Messages) + end, + State; +gui_cmd('Back Trace', State) -> + dbg_wx_trace_win:trace_output(State#state.win,"\nBACK TRACE\n----------\n"), + lists:foreach( + fun({Le, {Mod,Func,Args}}) -> + Str = io_lib:format("~p > ~p:~p~p~n", + [Le, Mod, Func, Args]), + dbg_wx_trace_win:trace_output(State#state.win,Str); + ({Le, {Fun,Args}}) -> + Str = io_lib:format("~p > ~p~p~n", [Le, Fun, Args]), + dbg_wx_trace_win:trace_output(State#state.win,Str); + (_) -> ignore + end, + int:meta(State#state.meta, backtrace, State#state.backtrace)), + dbg_wx_trace_win:trace_output(State#state.win,"\n"), + State; +gui_cmd('Up', State) -> + {Cur, Max} = State#state.stack, + case int:meta(State#state.meta, stack_frame, {up, Cur}) of + {New, {undefined,-1}, _Bs} -> % call from non-interpreted code + Stack = {New, Max}, + Win = dbg_wx_trace_win:show_no_code(State#state.win), + dbg_wx_trace_win:update_bindings(State#state.win,[]), + gui_enable_updown(State#state.stack_trace, Stack), + dbg_wx_trace_win:display(State#state.win,{New,null,null}), + State#state{win=Win, cm=null, stack=Stack}; + + {New, {Mod,Line}, Bs} -> + Stack = {New, Max}, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, + where), + dbg_wx_trace_win:update_bindings(State#state.win,Bs), + gui_enable_updown(State#state.stack_trace, Stack), + dbg_wx_trace_win:display(State#state.win,{New,Mod,Line}), + State#state{win=Win, cm=Mod, stack=Stack}; + top -> + dbg_wx_trace_win:enable(['Up'], false), + State + end; +gui_cmd('Down', State) -> + {Cur, Max} = State#state.stack, + case int:meta(State#state.meta, stack_frame, {down, Cur}) of + {New, {undefined,-1}, _Bs} -> % call from non-interpreted code + Stack = {New, Max}, + Win = dbg_wx_trace_win:show_no_code(State#state.win), + dbg_wx_trace_win:update_bindings(State#state.win, []), + gui_enable_updown(State#state.stack_trace, Stack), + dbg_wx_trace_win:display(State#state.win, {New,null,null}), + State#state{win=Win, cm=null, stack=Stack}; + + {New, {Mod,Line}, Bs} -> + Stack = {New, Max}, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, + where), + dbg_wx_trace_win:update_bindings(State#state.win, Bs), + gui_enable_updown(State#state.stack_trace, Stack), + dbg_wx_trace_win:display(State#state.win, {New,Mod,Line}), + State#state{win=Win, cm=Mod, stack=Stack}; + + bottom -> + gui_cmd('Where', State) + end; + +%% Break menu +gui_cmd('Line Break...', State) -> + add_break(State#state.win, State#state.coords, line, + State#state.cm, + dbg_wx_trace_win:selected_line(State#state.win)), + State; +gui_cmd('Conditional Break...', State) -> + add_break(State#state.win, State#state.coords, conditional, + State#state.cm, + dbg_wx_trace_win:selected_line(State#state.win)), + State; +gui_cmd('Function Break...', State) -> + add_break(State#state.win, State#state.coords, function, + State#state.cm, undefined), + State; +gui_cmd('Enable All', State) -> + Breaks = int:all_breaks(), + ThisMod = State#state.cm, + lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod -> + int:enable_break(Mod, Line); + (_Break) -> + ignore + end, + Breaks), + State; +gui_cmd('Disable All', State) -> + Breaks = int:all_breaks(), + ThisMod = State#state.cm, + lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod -> + int:disable_break(Mod, Line); + (_Break) -> + ignore + end, + Breaks), + State; +gui_cmd('Delete All', State) -> + int:no_break(State#state.cm), + State; +gui_cmd({break, {Mod, Line}, What}, State) -> + case What of + add -> int:break(Mod, Line); + delete -> int:delete_break(Mod, Line); + {status, inactive} -> int:disable_break(Mod, Line); + {status, active} -> int:enable_break(Mod, Line); + {trigger, Action} -> int:action_at_break(Mod, Line, Action) + end, + State; + +%% Options menu +gui_cmd({'Trace Window', TraceWin}, State) -> + Trace = lists:member('Trace Area', TraceWin), + int:meta(State#state.meta, trace, Trace), + Win = dbg_wx_trace_win:configure(State#state.win, TraceWin), + {Status,_,_} = State#state.status, + if + Status==break; Status==wait_break -> + gui_enable_btrace(Trace, State#state.stack_trace); + true -> ignore + end, + State#state{win=Win, trace=Trace}; +gui_cmd({'Stack Trace', [Name]}, State) -> + int:meta(State#state.meta, stack_trace, map(Name)), + {Status,_,_} = State#state.status, + if + Status==break; Status==wait_break -> + gui_enable_btrace(State#state.trace, map(Name)); + true -> ignore + end, + State; +gui_cmd('Back Trace Size...', State) -> + Win = dbg_wx_trace_win:get_window(State#state.win), + case dbg_wx_win:entry(Win, "Backtrace",'Backtrace:', {integer, State#state.backtrace}) of + cancel -> State; + {_, BackTrace} -> State#state{backtrace=BackTrace} + end; + +%% Help menu +gui_cmd('Debugger', State) -> + Window = dbg_wx_trace_win:get_window(State#state.win), + HelpFile = filename:join([code:lib_dir(debugger), + "doc", "html", "part_frame.html"]), + dbg_wx_win:open_help(Window, HelpFile), + State; + +gui_cmd({user_command, Cmd}, State) -> + {Status, _Mod, _Line} = State#state.status, + if + Status==break; + Status==wait_break; + Status==wait_running -> + Cm = State#state.cm, + Arg = case State#state.stack of + {Cur, Max} when Cur<Max -> {Cm, Cmd, Cur}; + _Stack -> {Cm, Cmd} + end, + + %% Reply will be received as {Meta, {eval_rsp, Res}} + int:meta(State#state.meta, eval, Arg); + true -> + Str = "Commands not allowed", + dbg_wx_trace_win:eval_output(State#state.win, [$<,Str,10], normal) + end, + State; + +gui_cmd({edit, {Var, Val}}, State) -> + Window = dbg_wx_trace_win:get_window(State#state.win), + case dbg_wx_win:entry(Window, "Edit variable", Var, {term, Val}) of + cancel -> + State; + {Var, Term} -> + Cmd = atom_to_list(Var)++"="++io_lib:format("~p", [Term]), + gui_cmd({user_command, lists:flatten(Cmd)}, State) + end. + +add_break(WI, Coords, Type, undefined, _Line) -> + Win = dbg_wx_trace_win:get_window(WI), + dbg_wx_break:start(Win, Coords, Type); +add_break(WI, Coords, Type, Mod, undefined) -> + Win = dbg_wx_trace_win:get_window(WI), + dbg_wx_break:start(Win, Coords, Type, Mod); +add_break(WI, Coords, Type, Mod, Line) -> + Win = dbg_wx_trace_win:get_window(WI), + dbg_wx_break:start(Win, Coords, Type, Mod, Line). + +%%--Commands from the interpreter------------------------------------- + +int_cmd({interpret, Mod}, State) -> + if + Mod==State#state.cm -> + State#state{cm_obsolete=true}; + true -> + State + end; +int_cmd({no_interpret, Mod}, State) -> + if + Mod==State#state.cm -> + State#state{cm_obsolete=true}; + true -> + Win = dbg_wx_trace_win:remove_code(State#state.win, Mod), + State#state{win=Win} + end; + +int_cmd({new_break, Break}, State) -> + Win = dbg_wx_trace_win:add_break(State#state.win, 'Break', Break), + State#state{win=Win}; +int_cmd({delete_break, Point}, State) -> + Win = dbg_wx_trace_win:delete_break(State#state.win, Point), + State#state{win=Win}; +int_cmd({break_options, Break}, State) -> + Win = dbg_wx_trace_win:update_break(State#state.win, Break), + State#state{win=Win}; +int_cmd(no_break, State) -> + Win = dbg_wx_trace_win:clear_breaks(State#state.win), + State#state{win=Win}; +int_cmd({no_break, Mod}, State) -> + Win = dbg_wx_trace_win:clear_breaks(State#state.win, Mod), + State#state{win=Win}. + +%%--Commands from the meta process------------------------------------ + +%% Message received when first attached to a living process +%% '_Trace' is a boolean indicating if the process is traced or not -- +%% ignore this as we already have ordered tracing or not depending on if +%% the Trace Area is shown or not. +meta_cmd({attached, Mod, Line, _Trace}, State) -> + Win = if + Mod/=undefined -> + gui_enable_functions(init), + gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, + break); + true -> State#state.win + end, + State#state{win=Win, status={init,Mod,Line}, cm=Mod}; + +%% Message received when returning to interpreted code +meta_cmd({re_entry, dbg_ieval, eval_fun}, State) -> + State; +meta_cmd({re_entry, Mod, _Func}, State) -> + Obs = State#state.cm_obsolete, + case State#state.cm of + Mod when Obs==true -> + Win = gui_load_module(State#state.win, Mod,State#state.pid), + State#state{win=Win, cm_obsolete=false}; + Mod -> State; + Cm -> + Win = gui_show_module(State#state.win, Mod, 0, + Cm, State#state.pid, break), + State#state{win=Win, cm=Mod} + end; + +%% Message received when attached to a terminated process +meta_cmd({exit_at, null, Reason, Cur}, State) -> + Stack = {Cur, Cur}, + gui_enable_functions(exit), + gui_enable_updown(false, Stack), + dbg_wx_trace_win:display(State#state.win, {exit, null, Reason}), + State#state{status={exit,null,Reason}, stack=Stack}; +meta_cmd({exit_at, {Mod,Line}, Reason, Cur}, State) -> + Stack = {Cur+1, Cur+1}, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, break), + gui_enable_functions(exit), + gui_enable_updown(State#state.stack_trace, Stack), + gui_enable_btrace(State#state.trace, State#state.stack_trace), + gui_update_bindings(State#state.win, State#state.meta), + dbg_wx_trace_win:display(State#state.win, {exit, {Mod,Line}, Reason}), + State#state{win=Win, cm=Mod,status={exit,{Mod,Line},Reason}, + stack=Stack}; + +meta_cmd({break_at, Mod, Line, Cur}, State) -> + Stack = {Cur,Cur}, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, break), + gui_enable_functions(break), + gui_enable_updown(State#state.stack_trace, Stack), + gui_enable_btrace(State#state.trace, State#state.stack_trace), + gui_update_bindings(State#state.win, State#state.meta), + dbg_wx_trace_win:display(State#state.win, {break, Mod, Line}), + State#state{win=Win, cm=Mod, status={break,Mod,Line}, stack=Stack}; +meta_cmd({func_at, Mod, Line, Cur}, State) -> + Stack = {Cur,Cur}, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, where), + gui_enable_functions(idle), + dbg_wx_trace_win:display(State#state.win, idle), + State#state{win=Win, cm=Mod, status={idle,Mod,Line}, stack=Stack}; +meta_cmd({wait_at, Mod, Line, Cur}, #state{status={Status,_,_}}=State) + when Status/=init, Status/=break -> + Stack = {Cur,Cur}, + gui_enable_functions(wait_running), + dbg_wx_trace_win:display(State#state.win, {wait,Mod,Line}), + State#state{status={wait_running,Mod,Line}, stack=Stack}; +meta_cmd({wait_at, Mod, Line, Cur}, State) -> + Stack = {Cur,Cur}, + Win = gui_show_module(State#state.win, Mod, Line, + State#state.cm, State#state.pid, break), + gui_enable_functions(wait_break), + gui_enable_updown(State#state.stack_trace, Stack), + gui_enable_btrace(State#state.trace, State#state.stack_trace), + gui_update_bindings(State#state.win, State#state.meta), + dbg_wx_trace_win:display(State#state.win, {wait, Mod, Line}), + State#state{win=Win, cm=Mod, status={wait_break,Mod,Line}, + stack=Stack}; +meta_cmd({wait_after_at, Mod, Line, Sp}, State) -> + meta_cmd({wait_at, Mod, Line, Sp}, State); +meta_cmd(running, State) -> + Win = dbg_wx_trace_win:unmark_line(State#state.win), + gui_enable_functions(running), + dbg_wx_trace_win:update_bindings(State#state.win, []), + dbg_wx_trace_win:display(State#state.win, {running, State#state.cm}), + State#state{win=Win, status={running,null,null}}; + +meta_cmd(idle, State) -> + Win = dbg_wx_trace_win:show_no_code(State#state.win), + gui_enable_functions(idle), + dbg_wx_trace_win:update_bindings(State#state.win, []), + dbg_wx_trace_win:display(State#state.win, idle), + State#state{win=Win, status={idle,null,null}, cm=undefined}; + +%% Message about changed trace option can be ignored, the change must +%% have been ordered by this process. (In theory, the change could have +%% been ordered by another attached process. The Debugger, though, +%% allows max one attached process per debugged process). +meta_cmd({trace, _Bool}, State) -> + State; + +meta_cmd({stack_trace, Flag}, State) -> + dbg_wx_trace_win:select(map(Flag), true), + gui_enable_updown(Flag, State#state.stack), + {Status,_,_} = State#state.status, + if + Status==break; Status==wait_break -> + gui_enable_btrace(State#state.trace, Flag); + true -> ignore + end, + State#state{stack_trace=Flag}; + +meta_cmd({trace_output, Str}, State) -> + dbg_wx_trace_win:trace_output(State#state.win, Str), + State; + +%% Reply on a user command +meta_cmd({eval_rsp, Res}, State) -> + Str = io_lib:print(Res), + dbg_wx_trace_win:eval_output(State#state.win, [$<,Str,10], normal), + State. + + +%%==================================================================== +%% GUI auxiliary functions +%%==================================================================== + +menus() -> + [{'File', [{'Close', no}]}, + {'Edit', [{'Go To Line', 0}, + {'Search', 1}]}, + {'Process', [{'Step', 0}, + {'Next', 0}, + {'Continue', 0}, + {'Finish', 0}, + {'Skip', no}, + {'Time Out', no}, + {'Stop', no}, + separator, + {'Kill', no}, + separator, + {'Messages', 0}, + {'Back Trace', no}, + separator, + {'Where', 0}, + {'Up', no}, + {'Down', no}]}, + {'Break', [{'Line Break...', 5}, + {'Conditional Break...', no}, + {'Function Break...', no}, + separator, + {'Enable All', no}, + {'Disable All', no}, + {'Delete All', 0}, + separator]}, + {'Options', [{'Trace Window', no, cascade, + [{'Search Area', no, check}, + {'Button Area', no, check}, + {'Evaluator Area', no, check}, + {'Bindings Area', no, check}, + {'Trace Area', no, check}]}, + {'Stack Trace', no, cascade, + [{'Stack On, Tail', no, radio}, + {'Stack On, No Tail', no, radio}, + {'Stack Off', no, radio}]}, + {'Back Trace Size...', no}]}, + {'Windows', []}, + {'Help', [{'Debugger', no}]}]. + +%% enable(Status) -> [MenuItem] +%% Status = init % when first message from Meta has arrived +%% | idle | break | exit | wait_break � wait_running | running +enable(init) -> []; +enable(idle) -> ['Stop','Kill']; +enable(break) -> ['Step','Next','Continue','Finish','Skip', + 'Kill','Messages']; +enable(exit) -> []; +enable(wait_break) -> ['Continue','Time Out','Kill']; +enable(wait_running) -> ['Stop','Kill']; +enable(running) -> ['Stop','Kill']. + +all_buttons() -> + ['Step','Next','Continue','Finish','Skip','Time Out','Stop', + 'Kill','Messages','Back Trace','Where','Up','Down']. + +shortcut(e) -> {if_enabled, 'Search'}; +shortcut(g) -> {if_enabled, 'Go To Line'}; +shortcut(s) -> {if_enabled, 'Step'}; +shortcut(n) -> {if_enabled, 'Next'}; +shortcut(c) -> {if_enabled, 'Continue'}; +shortcut(f) -> {if_enabled, 'Finish'}; +shortcut(m) -> {if_enabled, 'Messages'}; +shortcut(w) -> {if_enabled, 'Where'}; + +shortcut(b) -> {always, 'Line Break...'}; +shortcut(d) -> {always, 'Delete All'}; + +shortcut(_) -> false. + +map('Stack On, Tail') -> all; % Stack trace +map('Stack On, No Tail') -> no_tail; +map('Stack Off') -> false; +map(all) -> 'Stack On, Tail'; +map(true) -> 'Stack On, Tail'; +map(no_tail) -> 'Stack On, No Tail'; +map(false) -> 'Stack Off'. + + +%% gui_show_module(Win, Mod, Line, Cm, Pid, How) -> Win +%% gui_show_module(Win, {Mod,Line}, _Reason, Cm, Pid, How) -> Win +%% How = where | break +%% Show contents of a module in code area +gui_show_module(Win, {Mod,Line}, _Reason, Cm, Pid, How) -> + gui_show_module(Win, Mod, Line, Cm, Pid, How); +gui_show_module(Win, Mod, Line, Mod, _Pid, How) -> + dbg_wx_trace_win:mark_line(Win, Line, How); +gui_show_module(Win, Mod, Line, _Cm, Pid, How) -> + Win2 = case dbg_wx_trace_win:is_shown(Win, Mod) of + {true, Win3} -> Win3; + false -> gui_load_module(Win, Mod, Pid) + end, + dbg_wx_trace_win:mark_line(Win2, Line, How). + +gui_load_module(Win, Mod, _Pid) -> + dbg_wx_trace_win:display(Win,{text, "Loading module..."}), + %% Contents = int:contents(Mod, Pid), + {ok, Contents} = dbg_iserver:call({raw_contents, Mod, any}), + Win2 = dbg_wx_trace_win:show_code(Win, Mod, Contents), + dbg_wx_trace_win:display(Win,{text, ""}), + Win2. + +gui_update_bindings(Win,Meta) -> + Bs = int:meta(Meta, bindings, nostack), + dbg_wx_trace_win:update_bindings(Win,Bs). + +gui_enable_functions(Status) -> + Enable = enable(Status), + Disable = all_buttons() -- Enable, + dbg_wx_trace_win:enable(Disable, false), + dbg_wx_trace_win:enable(Enable, true). + +gui_enable_updown(Flag, Stack) -> + {Enable, Disable} = + if + Flag==false -> {[], ['Up', 'Down']}; + true -> + case Stack of + {1,1} -> {[], ['Up', 'Down']}; + {2,2} -> {[], ['Up', 'Down']}; + {Max,Max} -> {['Up'], ['Down']}; + {2,_Max} -> {['Down'], ['Up']}; + {_Cur,_Max} -> {['Up', 'Down'], []} + end + end, + dbg_wx_trace_win:enable(Enable, true), + dbg_wx_trace_win:enable(Disable, false), + if + Enable==[] -> dbg_wx_trace_win:enable(['Where'], false); + true -> dbg_wx_trace_win:enable(['Where'], true) + end. + +gui_enable_btrace(Trace, StackTrace) -> + Bool = if + Trace==false -> false; + StackTrace==false -> false; + true -> true + end, + dbg_wx_trace_win:enable(['Back Trace'], Bool). diff --git a/lib/debugger/src/dbg_wx_trace_win.erl b/lib/debugger/src/dbg_wx_trace_win.erl new file mode 100755 index 0000000000..6e7a291493 --- /dev/null +++ b/lib/debugger/src/dbg_wx_trace_win.erl @@ -0,0 +1,1029 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_trace_win). + +%% External exports +-export([init/0, stop/1]). +-export([create_win/4, + get_window/1, + configure/2, + enable/2, is_enabled/1, select/2, + add_break/3, update_break/2, delete_break/2, + clear_breaks/1, clear_breaks/2, + display/2, % Help messages + is_shown/2, % Code area + show_code/3, show_no_code/1, remove_code/2, + mark_line/3, unmark_line/1, + select_line/2, selected_line/1, + eval_output/3, % Evaluator area + update_bindings/2, % Bindings area + trace_output/2, % Trace area + handle_event/2 + ]). +-export([helpwin/2]). + +-include_lib("wx/include/wx.hrl"). + +-record(breakInfo, {point, status, break}). +-record(break, {mb, smi, emi, dimi, demi}). %% BUGBUG defined in dbg_wx_win +-record(winInfo, {window, % wxobj() + size, % {W, H} + find, % #find{} + m_szr, % {Panel, Sizer}, + e_szr, % {bool Shown, Sizer}, + + code, % code editor #sub{} + sb, % status bar + sg, % Search/Goto #sub{} + bs, % Buttons #sub{} subwindow info + eval, % Eval #sub{} subwindow info + bind, % Bindings #sub{} subwindow info + trace, % Trace #sub{} subwindow info + + marked_line=0, % integer() Current line + selected_line=0, % integer() Selected line + + breaks=[], % [#breakInfo{}] Known breakpoints + + editor, % {Mod, Editor} Visible code editor + editors=[] % [{Mod,Editor}] Code editors + }). + +-record(sub, {enable=true, % Subwindow is enabled + win, % Sash Sub window obj + in, % undefined or input window obj + out, % undefined or output window obj + name % name + }). + +-record(sa, {search, % Search input ctrl + goto, % Goto input ctrl + radio}). % Radio buttons + +-record(find, {start, % start pos + strlen, % search string len + found % status + }). + + +-define(StepButton, 401). +-define(NextButton, 402). +-define(ContinueButton, 403). +-define(FinishButton, 404). +-define(WhereButton, 405). +-define(UpButton, 406). +-define(DownButton, 407). + +-define(EVAL_ENTRY, 410). +-define(EVAL_LOG, 411). +-define(BIND_PANEL, 412). +-define(SEARCH_ENTRY, 413). +-define(GOTO_ENTRY, 414). + + +-define(SASH_CODE, 425). +-define(SASH_EVAL, 426). +-define(SASH_TRACE, 427). + +-define(WIN_W, 700). +-define(WIN_H, 650). + +-define(CODE_H, 400). +-define(BUTT_H, 50). % Maximum +-define(EVAL_H, 200). +-define(BIND_H, 200). +-define(TRACE_H, 100). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% init() -> GS +%% GS = term() +%%-------------------------------------------------------------------- +init() -> + dbg_wx_win:init(). + +stop(#winInfo{window=Win}) -> + (catch wxFrame:destroy(Win)), + ok. + +%%-------------------------------------------------------------------- +%% create_win(GS, Title, TraceWin, Menus) -> #winInfo{} +%% GS = gsobj() +%% Title = string() +%% TraceWin = [WinArea] +%% WinArea = 'Button|Evaluator|Bindings|Trace Area' +%% Menus = [menu()] See dbg_wx_win.erl +%%-------------------------------------------------------------------- +create_win(Parent, Title, Windows, Menus) -> + Do = + fun() -> + Win = wxFrame:new(Parent, ?wxID_ANY, dbg_wx_win:to_string(Title), + [{size, {?WIN_W,?WIN_H}}]), + Panel = wxPanel:new(Win, [{size, {?WIN_W,?WIN_H}}]), + MenuBar = wxMenuBar:new(), + dbg_wx_win:create_menus(MenuBar, Menus, Win, 1), + wxFrame:setMenuBar(Win, MenuBar), + + Sizer = wxBoxSizer:new(?wxVERTICAL), + Code = code_area(Panel), + wxSizer:add(Sizer, Code#sub.win, + [{proportion,1}, {border, 2}, + {flag, ?wxEXPAND bor ?wxDOWN}]), + wxSizer:setVirtualSizeHints(Sizer, Code#sub.win), + + ExpandWithBorder = [{border, 3},{flag,?wxEXPAND bor ?wxALL}], + Search = search_area(Panel), + wxSizer:add(Sizer, Search#sub.win, ExpandWithBorder), + Bs = button_area(Panel), + wxSizer:add(Sizer, Bs#sub.win, ExpandWithBorder), + + InfoArea = wxBoxSizer:new(?wxHORIZONTAL), + wxSizer:setMinSize(InfoArea, {100, ?EVAL_H}), + Eval = eval_area(Panel), + wxSizer:add(InfoArea, Eval#sub.win, [{proportion,1},{flag,?wxEXPAND}]), + Bind = bind_area(Panel), + wxSizer:add(InfoArea, Bind#sub.win, + [{proportion,1},{border, 2}, + {flag,?wxEXPAND bor ?wxLEFT}]), + wxSizer:add(Sizer, InfoArea, ExpandWithBorder), + + Trace = trace_area(Panel), + wxSizer:add(Sizer, Trace#sub.win, ExpandWithBorder), + SB = wxFrame:createStatusBar(Win,[]), + + %% Note id and lastId to get the event when it dragged is complete + wxFrame:connect(Win, sash_dragged, [{id,?SASH_CODE}, + {lastId,?SASH_TRACE}]), + wxFrame:connect(Win, close_window, [{skip, true}]), + wxFrame:connect(Win, size, [{skip, true}]), + wxWindow:connect(Win, key_up, [{skip,true}]), + wxWindow:setFocus(Code#sub.out), + + Wi0 = #winInfo{window=Win, + m_szr={Panel, Sizer}, + e_szr={true, InfoArea}, + code=Code, sb=SB, sg=Search, bs=Bs, + eval=Eval, trace=Trace, bind=Bind, + editor={'$top', Code#sub.out}, + editors=[{'$top', Code#sub.out}]}, + + Wi = show_windows(enable_windows(Wi0,Windows)), + wxWindow:setSizer(Panel, Sizer), + wxSizer:fit(Sizer, Win), + wxSizer:setSizeHints(Sizer,Win), + + IconFile = dbg_wx_win:find_icon("erlang_bug.png"), + Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]), + wxFrame:setIcon(Win, Icon), + wxIcon:destroy(Icon), + + wxFrame:show(Win), + put(window, Win), + Wi + end, + + try wx:batch(Do) + catch E:R -> + io:format("Crashed ~p ~p",[E,R]), + erlang:error(E) + end. + + +%%-------------------------------------------------------------------- +%% get_window(WinInfo) -> Window +%% WinInfo = #winInfo{} +%% Window = gsobj() +%%-------------------------------------------------------------------- +get_window(WinInfo) -> + WinInfo#winInfo.window. + +%%-------------------------------------------------------------------- +%% configure(WinInfo, Windows) -> WinInfo +%% WinInfo = #winInfo{} +%% Windows = [WinArea] +%% WinArea = 'Button|Evaluator|Bindings|Trace Area' +%% Window areas should be opened or closed. +%%-------------------------------------------------------------------- +configure(Wi=#winInfo{window=Win,m_szr={Panel,Sizer}}) -> + wx:batch(fun() -> + show_windows(Wi), + wxSizer:layout(Sizer), + %%wxWindow:setSizerAndFit(Panel,Sizer), + wxWindow:setSizer(Panel, Sizer), + wxSizer:fit(Sizer, Win), + wxSizer:setSizeHints(Sizer,Win), + Wi + end). + +configure(Wi0=#winInfo{window=Win,m_szr={Panel,Sizer}}, Windows) -> + wx:batch(fun() -> + Wi = enable_windows(Wi0, Windows), + show_windows(Wi), + wxSizer:layout(Sizer), + wxWindow:setSizer(Panel, Sizer), + wxSizer:fit(Sizer, Win), + wxSizer:setSizeHints(Sizer,Win), + Wi + end). + +enable_windows(Wi=#winInfo{e_szr={_,InfoArea},bs=Bs0,sg=SG0, + eval=Eval0,trace=Trace0,bind=Bind0},Windows) -> + Subs = [Window#sub{enable=lists:member(Window#sub.name,Windows)} + || Window <- [SG0,Bs0,Eval0,Trace0,Bind0]], + [SG, Bs,Eval,Trace,Bind] = Subs, + ESzr = Eval#sub.enable orelse Bind#sub.enable, + Wi#winInfo{e_szr={ESzr, InfoArea},sg=SG,bs=Bs, + eval=Eval,trace=Trace,bind=Bind}. + + +show_windows(Wi=#winInfo{m_szr={_,Sizer}, e_szr={_,InfoArea},bs=Bs,sg=SG, + eval=Eval,trace=Trace,bind=Bind}) -> + case SG#sub.enable of + false -> wxSizer:hide(Sizer, SG#sub.win); + _ -> wxSizer:show(Sizer, SG#sub.win) + end, + case Bs#sub.enable of + false -> wxSizer:hide(Sizer, Bs#sub.win); + _ -> wxSizer:show(Sizer, Bs#sub.win) + end, + if (not Eval#sub.enable) andalso (not Bind#sub.enable) -> + wxSizer:hide(Sizer, InfoArea); + not Eval#sub.enable -> + wxSizer:show(Sizer, InfoArea), + wxSizer:hide(InfoArea, Eval#sub.win), + wxSizer:show(InfoArea, Bind#sub.win); + not Bind#sub.enable -> + [EvalSI|_] = wxSizer:getChildren(InfoArea), + wxSizerItem:setProportion(EvalSI, 1), + wxSizer:show(Sizer, InfoArea), + wxSizer:hide(InfoArea, Bind#sub.win), + wxSizer:show(InfoArea, Eval#sub.win), + true; + true -> + wxSizer:show(Sizer, InfoArea), + wxSizer:show(InfoArea, Eval#sub.win), + wxSizer:show(InfoArea, Bind#sub.win) + end, + case Trace#sub.enable of + false -> wxSizer:hide(Sizer, Trace#sub.win); + _ -> wxSizer:show(Sizer, Trace#sub.win) + end, + Wi. + +%%-------------------------------------------------------------------- +%% enable([MenuItem], Bool) +%% is_enabled(MenuItem) -> Bool +%% MenuItem = atom() +%% Bool = boolean() +%%-------------------------------------------------------------------- +enable(MenuItems, Bool) -> + wx:foreach(fun(MenuItem) -> + MI = get(MenuItem), + wxMenuItem:enable(MI, [{enable, Bool}]), + case is_button(MenuItem) of + {true, ButtonId} -> + Parent = get(window), + Butt = wxWindow:findWindowById(ButtonId, + [{parent, Parent}]), + case wx:is_null(Butt) of + true -> ignore; + false -> + wxButton:enable(Butt, [{enable, Bool}]) + end; + _ -> + ignore + end + end, + MenuItems). + +is_enabled(MenuItem) -> + MI = get(MenuItem), + wxMenuItem:isEnabled(MI). + +%%-------------------------------------------------------------------- +%% select(MenuItem, Bool) +%% MenuItem = atom() +%% Bool = boolean() +%%-------------------------------------------------------------------- +select(MenuItem, Bool) -> + MI = get(MenuItem), + wxMenuItem:check(MI, [{check, Bool}]). + +%%-------------------------------------------------------------------- +%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo +%% WinInfo = #winInfo{} +%% Name = atom() Menu name +%% Point = {Mod, Line} +%% Options = [Status, Action, Mods, Cond] +%% Status = active | inactive +%% Action = enable | disable | delete +%% Mods = null (not used) +%% Cond = null | {Mod, Func} +%%-------------------------------------------------------------------- +add_break(WinInfo, Menu, {{Mod,Line},[Status|_Options]}=Break) -> + case WinInfo#winInfo.editor of + {Mod, Editor} -> + dbg_wx_code:add_break_to_code(Editor, Line, Status); + _ -> ignore + end, + add_break_to_menu(WinInfo, Menu, Break). + +add_break_to_menu(WinInfo, Menu, {Point, [Status|_Options]=Options}) -> + Break = dbg_wx_win:add_break(WinInfo#winInfo.window, Menu, Point), + dbg_wx_win:update_break(Break, Options), + BreakInfo = #breakInfo{point=Point, status=Status, break=Break}, + WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}. + +%%-------------------------------------------------------------------- +%% update_break(WinInfo, {Point, Options}) -> WinInfo +%% WinInfo = #winInfo{} +%% Point = {Mod, Line} +%% Options = [Status, Action, Mods, Cond] +%% Status = active | inactive +%% Action = enable | disable | delete +%% Mods = null (not used) +%% Cond = null | {Mod, Func} +%%-------------------------------------------------------------------- +update_break(WinInfo, {{Mod,Line},[Status|_Options]}=Break) -> + case WinInfo#winInfo.editor of + {Mod, Editor} -> + dbg_wx_code:add_break_to_code(Editor, Line, Status); + _ -> ignore + end, + update_break_in_menu(WinInfo, Break). + +update_break_in_menu(WinInfo, {Point, [Status|_Options]=Options}) -> + {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks), + dbg_wx_win:update_break(BreakInfo#breakInfo.break, Options), + BreakInfo2 = BreakInfo#breakInfo{status=Status}, + WinInfo#winInfo{breaks=lists:keyreplace(Point, #breakInfo.point, + WinInfo#winInfo.breaks, + BreakInfo2)}. + +%%-------------------------------------------------------------------- +%% delete_break(WinInfo, Point) -> WinInfo +%% WinInfo = #winInfo{} +%% Point = {Mod, Line} +%%-------------------------------------------------------------------- +delete_break(WinInfo, {Mod,Line}=Point) -> + case WinInfo#winInfo.editor of + {Mod, Editor} -> dbg_wx_code:del_break_from_code(Editor, Line); + _ -> ignore + end, + delete_break_from_menu(WinInfo, Point). + +delete_break_from_menu(WinInfo, Point) -> + {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks), + dbg_wx_win:delete_break(BreakInfo#breakInfo.break), + WinInfo#winInfo{breaks=lists:keydelete(Point, #breakInfo.point, + WinInfo#winInfo.breaks)}. + +%%-------------------------------------------------------------------- +%% clear_breaks(WinInfo) -> WinInfo +%% clear_breaks(WinInfo, Mod) -> WinInfo +%% WinInfo = #winInfo{} +%%-------------------------------------------------------------------- +clear_breaks(WinInfo) -> + clear_breaks(WinInfo, all). +clear_breaks(WinInfo, Mod) -> + Remove = if + Mod==all -> WinInfo#winInfo.breaks; + true -> + lists:filter(fun(#breakInfo{point={Mod2,_L}}) -> + if + Mod2==Mod -> true; + true -> false + end + end, + WinInfo#winInfo.breaks) + end, + lists:foreach(fun(#breakInfo{point=Point}) -> + delete_break(WinInfo, Point) + end, + Remove), + Remain = WinInfo#winInfo.breaks -- Remove, + WinInfo#winInfo{breaks=Remain}. + +%%-------------------------------------------------------------------- +%% display(Arg) +%% Arg = idle | {Status,Mod,Line} | {running,Mod} +%% � {exit,Where,Reason} | {text,Text} +%% Status = break | wait � Level +%% Level = int() +%% Mod = atom() +%% Line = integer() +%% Where = {Mod,Line} | null +%% Reason = term() +%% Text = string() +%%-------------------------------------------------------------------- +display(#winInfo{window=Win, sb=Sb},Arg) -> + Str = case Arg of + idle -> "State: uninterpreted"; + {exit, {Mod,Line}, Reason} -> + wxWindow:raise(Win), + dbg_wx_win:to_string("State: EXITED [~w.erl/~w], Reason:~w", + [Mod, Line, Reason]); + {exit, null, Reason} -> + wxWindow:raise(Win), + dbg_wx_win:to_string("State: EXITED [uninterpreted], " + "Reason:~w", [Reason]); + {Level, null, _Line} when is_integer(Level) -> + dbg_wx_win:to_string("*** Call level #~w " + "(in non-interpreted code)", + [Level]); + {Level, Mod, Line} when is_integer(Level) -> + dbg_wx_win:to_string("*** Call level #~w [~w.erl/~w]", + [Level, Mod, Line]); + {Status, Mod, Line} -> + What = case Status of + wait -> 'receive'; + _ -> Status + end, + dbg_wx_win:to_string("State: ~w [~w.erl/~w]", + [What, Mod, Line]); + {running, Mod} -> + dbg_wx_win:to_string("State: running [~w.erl]", [Mod]); + {text, Text} -> dbg_wx_win:to_string(Text) + end, + wxStatusBar:setStatusText(Sb, Str). + +%%-------------------------------------------------------------------- +%% is_shown(WinInfo, Mod) -> {true, WinInfo} | false +%% show_code(WinInfo, Mod, Contents) -> WinInfo +%% show_no_code(WinInfo) -> WinInfo +%% remove_code(WinInfo, Mod) -> WinInfo +%% WinInfo = #winInfo{} +%% Mod = atom() +%% Contents = string() +%% Note: remove_code/2 should not be used for currently shown module. +%%-------------------------------------------------------------------- +is_shown(WinInfo, Mod) -> + case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of + {value, {Mod, Editor}} -> + gs:config(Editor, raise), %% BUGBUG + {true, WinInfo#winInfo{editor={Mod, Editor}}}; + false -> false + end. + +show_code(WinInfo = #winInfo{editor={_, Ed}}, Mod, Contents) -> + %% Insert code and update breakpoints, if any + dbg_wx_code:load_code(Ed, Contents), + + lists:foreach(fun(BreakInfo) -> + case BreakInfo#breakInfo.point of + {Mod2, Line} when Mod2==Mod -> + Status = BreakInfo#breakInfo.status, + dbg_wx_code:add_break_to_code(Ed, Line,Status); + _Point -> ignore + end + end, + WinInfo#winInfo.breaks), + + WinInfo#winInfo{editor={Mod,Ed},find=undefined}. + +show_no_code(WinInfo = #winInfo{editor={_, Ed}}) -> + dbg_wx_code:unload_code(Ed), + WinInfo#winInfo{editor={'$top', Ed}}. + +remove_code(WinInfo, _Mod) -> + WinInfo. + +%%-------------------------------------------------------------------- +%% mark_line(WinInfo, Line, How) -> WinInfo +%% WinInfo = #winInfo{} +%% Line = integer() +%% How = break | where +%% Mark the code line where the process is executing. +%%-------------------------------------------------------------------- +mark_line(WinInfo = #winInfo{editor={_,Ed}}, Line, _How) -> + dbg_wx_code:mark_line(Ed, WinInfo#winInfo.marked_line, Line), + WinInfo#winInfo{marked_line=Line}. + +unmark_line(WinInfo) -> + mark_line(WinInfo, 0, false). + + +%%-------------------------------------------------------------------- +%% select_line(WinInfo, Line) -> WinInfo +%% selected_line(WinInfo) -> undefined | Line +%% WinInfo = #winInfo{} +%% Line = integer() +%% Select/unselect a line (unselect if Line=0). +%%-------------------------------------------------------------------- +select_line(WinInfo, Line) -> + {_Mod, Ed} = WinInfo#winInfo.editor, + + %% Since 'Line' may be specified by the user in the 'Go To Line' + %% help window, it must be checked that it is correct + Size = dbg_wx_code:get_no_lines(Ed), + if + Line==0 -> + dbg_wx_code:goto_line(Ed,1), + WinInfo#winInfo{selected_line=0}; + Line<Size -> + dbg_wx_code:goto_line(Ed,Line), + WinInfo#winInfo{selected_line=Line}; + true -> + WinInfo + end. + +selected_line(#winInfo{editor={_,Ed}}) -> + wxStyledTextCtrl:getCurrentLine(Ed)+1. + +%%-------------------------------------------------------------------- +%% eval_output(winInfo{}, Str, Face) +%% Str = string() +%% Face = normal | bold +%%-------------------------------------------------------------------- +eval_output(#winInfo{eval=#sub{out=Log}}, Text, _Face) -> + wxTextCtrl:appendText(Log, dbg_wx_win:to_string(Text)), + ok. + +%%-------------------------------------------------------------------- +%% update_bindings(Bs) +%% Bs = [{Var,Val}] +%%-------------------------------------------------------------------- +update_bindings(#winInfo{bind=#sub{out=BA}}, Bs) -> + wxListCtrl:deleteAllItems(BA), + wx:foldl(fun({Var,Val},Row) -> + wxListCtrl:insertItem(BA, Row, ""), + wxListCtrl:setItem(BA, Row, 0, dbg_wx_win:to_string(Var)), + wxListCtrl:setItem(BA, Row, 1, dbg_wx_win:to_string("~200p",[Val])), + Row+1 + end, 0, Bs), + put(bindings,Bs), + ok. + +%%-------------------------------------------------------------------- +%% trace_output(Str) +%% Str = string() +%%-------------------------------------------------------------------- +trace_output(#winInfo{trace=#sub{out=Log}}, Text) -> + wxTextCtrl:appendText(Log, dbg_wx_win:to_string(Text)), + ok. + +%%-------------------------------------------------------------------- +%% handle_event(GSEvent, WinInfo) -> Command +%% GSEvent = {gs, Id, Event, Data, Arg} +%% WinInfo = #winInfo{} +%% Command = ignore +%% | {win, WinInfo} +%% | stopped +%% | {coords, {X,Y}} +%% +%% | {shortcut, Key} +%% | MenuItem | {Menu, [MenuItem]} +%% MenuItem = Menu = atom() +%% | {break, Point, What} +%% What = add | delete | {status,Status} |{trigger,Trigger} +%% | {module, Mod, view} +%% +%% | {user_command, Cmd} +%% +%% | {edit, {Var, Val}} +%%-------------------------------------------------------------------- +%% Window events +handle_event(_Ev=#wx{event=#wxClose{}}, _WinInfo) -> + stopped; + +handle_event(#wx{event=#wxSize{size=Size}}, Wi0) -> + Wi = Wi0#winInfo{size=Size}, + resize(Wi), + {win, Wi}; + +handle_event(#wx{event=#wxSash{dragStatus=?wxSASH_STATUS_OUT_OF_RANGE}},_Wi) -> + ignore; +handle_event(#wx{id=?SASH_CODE, event=#wxSash{dragRect={_X,_Y,_W,H}}}, Wi) -> + #winInfo{code=Code,m_szr={_,Sizer},e_szr={Enable,InfoSzr},trace=Trace} = Wi, + + case Enable orelse Trace#sub.enable of + false -> + ignore; + true -> + {_, CMH} = wxWindow:getMinSize(Code#sub.win), + case CMH > H of + true -> wxSashWindow:setMinSize(Code#sub.win, {500, H}); + _ -> ignore + end, + {_, CH} = wxWindow:getSize(Code#sub.win), + Change = CH - H, + ChangeH = fun(Item) -> + {ItemW, ItemH} = wxSizerItem:getMinSize(Item), + wxSizerItem:setInitSize(Item, ItemW, max(ItemH+Change,-1)) + end, + if Enable -> + {IW, IH} = wxSizer:getMinSize(InfoSzr), + [ChangeH(Child) || Child <- wxSizer:getChildren(InfoSzr)], + wxSizer:setMinSize(InfoSzr, {IW, IH+Change}), + ok; + Trace#sub.enable -> + {TW, TH} = wxWindow:getMinSize(Trace#sub.win), + wxWindow:setMinSize(Trace#sub.win, {TW, TH+Change}), + ok + end, + wxSizer:layout(Sizer), + ignore + end; + +handle_event(#wx{id=?SASH_EVAL, event=#wxSash{dragRect={_X,_Y,W,_H}}}, Wi) -> + #winInfo{m_szr={_,Sizer},e_szr={Enable,InfoSzr}, + eval=#sub{enable=Enable, win=EvalSzr}} = Wi, + case Enable of + false -> + ignore; + true -> + [Eval,Bind] = wxSizer:getChildren(InfoSzr), + {Tot,_} = wxSizer:getSize(InfoSzr), + EvalWidth = Tot-W, + + Change = fun(Szr, Width) -> + {_EW,EH} = wxSizerItem:getMinSize(Szr), + wxSizerItem:setInitSize(Szr, Width, EH) + end, + + Change(Eval, EvalWidth), + [Change(Kid, EvalWidth) || Kid <- wxSizer:getChildren(EvalSzr)], + Change(Bind, W), + + wxSizerItem:setProportion(Eval, 0), + wxSizer:layout(InfoSzr), + wxSizer:layout(Sizer), + + resize(Wi), + ignore + end; + +handle_event(#wx{id=?SASH_TRACE, event=#wxSash{dragRect={_X,_Y,_W,H}}}, Wi) -> + #winInfo{code=Code,m_szr={_,Sizer},e_szr={Enable,InfoSzr},trace=Trace} = Wi, + {TW, TH} = wxWindow:getSize(Trace#sub.win), + Change = TH - H, + case Enable of + false -> %% Eval Area or Bindings + {_, CH} = wxWindow:getSize(Code#sub.win), + {_, CMH} = wxWindow:getMinSize(Code#sub.win), + case CMH > CH+Change of + true -> wxSashWindow:setMinSize(Code#sub.win, {500, CH+Change}); + _ -> ignore + end, + wxWindow:setMinSize(Trace#sub.win, {TW, H}), + wxSizer:layout(Sizer), + ignore; + true -> %% Change the Eval and Bindings area + ChangeH = fun(Item) -> + {ItemW, ItemH} = wxSizerItem:getMinSize(Item), + wxSizerItem:setInitSize(Item, ItemW, max(ItemH+Change,-1)) + end, + {IW, IH} = wxSizer:getMinSize(InfoSzr), + [ChangeH(Child) || Child <- wxSizer:getChildren(InfoSzr)], + Wanted = IH+Change, + wxSizer:setMinSize(InfoSzr, {IW, Wanted}), + {_,RH} = wxSizer:getMinSize(InfoSzr), + case RH > Wanted of + true -> %% Couldn't get the size we wanted try adjusting the code area + {_, CH} = wxWindow:getSize(Code#sub.win), + {_, CMH} = wxWindow:getMinSize(Code#sub.win), + CC = CH - (RH-Wanted), + case CMH > CC of + true when CC > 50 -> + wxWindow:setMinSize(Trace#sub.win, {TW, H}), + wxSashWindow:setMinSize(Code#sub.win, {500, CC}); + _ when CC < 50 -> + ignore; + _ -> + wxWindow:setMinSize(Trace#sub.win, {TW, H}) + end, + ok; + false -> + wxWindow:setMinSize(Trace#sub.win, {TW, H}) + end, + wxSizer:layout(Sizer), + ignore + end; + +%% Menus, buttons and keyboard shortcuts +handle_event(_Ev = #wx{event=#wxKey{keyCode=Key, controlDown=true}}, _WinInfo) -> + %% io:format("Key ~p ~n",[_Ev]), + if + Key/=?WXK_UP, Key/=?WXK_DOWN, Key /=? WXK_RETURN -> + try + {shortcut, list_to_atom([Key+($a-$A)])} + catch _:_ -> ignore + end; + true -> + ignore + end; +handle_event(#wx{userData={dbg_ui_winman, Win}, + event=#wxCommand{type=command_menu_selected}}, _WinInfo) -> + dbg_wx_winman:raise(Win), + ignore; + +handle_event(#wx{userData={break, Point, status}, + event=#wxCommand{type=command_menu_selected}}, + WinInfo) -> + {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks), + %% This is a temporary hack !! + #breakInfo{break=#break{smi=Smi}} = BreakInfo, + + case wxMenuItem:getText(Smi) of + "Enable" -> {break, Point, {status, active}}; + "Disable" -> {break, Point, {status, inactive}} + end; + +handle_event(#wx{userData=Data, + event=_Cmd=#wxCommand{type=command_menu_selected}}, + _WinInfo) -> + %%io:format("Command ~p ~p~n",[Data,_Cmd]), + Data; + +%% Code area +handle_event(#wx{event=#wxStyledText{type=stc_doubleclick}}, + WinInfo = #winInfo{editor={Mod,Ed}}) -> + Line = wxStyledTextCtrl:getCurrentLine(Ed), + Point = {Mod, Line+1}, + case lists:keysearch(Point, #breakInfo.point,WinInfo#winInfo.breaks) of + {value, _BreakInfo} -> {break, Point, delete}; + false -> {break, Point, add} + end; + +%% Search Area +handle_event(#wx{id=?GOTO_ENTRY, event=#wxCommand{cmdString=Str}}, WinInfo) -> + try + Line = list_to_integer(Str), + {gotoline, Line} + catch + _:_ -> + display(WinInfo, {text,"Not a line number"}), + ignore + end; +handle_event(#wx{id=?SEARCH_ENTRY, event=#wxFocus{}}, Wi) -> + {win, Wi#winInfo{find=undefined}}; +handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{type=command_text_enter,cmdString=Str}}, + Wi = #winInfo{code=Code,find=Find, sg=#sub{in=#sa{radio={NextO,_,CaseO}}}}) + when Find =/= undefined -> + Dir = wxRadioButton:getValue(NextO) xor wx_misc:getKeyState(?WXK_SHIFT), + Case = wxCheckBox:getValue(CaseO), + Pos = if Find#find.found, Dir -> %% Forward Continuation + wxStyledTextCtrl:getAnchor(Code#sub.out); + Find#find.found -> %% Backward Continuation + wxStyledTextCtrl:getCurrentPos(Code#sub.out); + Dir -> %% Forward wrap + 0; + true -> %% Backward wrap + wxStyledTextCtrl:getLength(Code#sub.out) + end, + dbg_wx_code:goto_pos(Code#sub.out,Pos), + case dbg_wx_code:find(Code#sub.out, Str, Case, Dir) of + true -> + display(Wi, {text,""}), + {win, Wi#winInfo{find=Find#find{found=true}}}; + false -> + display(Wi, {text,"Not found (Hit Enter to wrap search)"}), + {win, Wi#winInfo{find=Find#find{found=false}}} + end; +handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=""}}, + Wi=#winInfo{code=Code}) -> + %% Reset search (and selection pos) + Pos = dbg_wx_code:current_pos(Code#sub.out), + dbg_wx_code:goto_pos(Code#sub.out,Pos), + {win, Wi#winInfo{find=undefined}}; +handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=Str}}, + Wi = #winInfo{code=Code,find=Find, + sg=#sub{in=#sa{radio={NextO,_,CaseO}}}}) -> + Dir = wxRadioButton:getValue(NextO), + Case = wxCheckBox:getValue(CaseO), + + Cont = case Find of + undefined -> + Pos = dbg_wx_code:current_pos(Code#sub.out), + #find{start=Pos, strlen=length(Str)}; + #find{strlen=Old} when Old < length(Str) -> + Find#find{strlen=length(Str)}; + _ -> + dbg_wx_code:goto_pos(Code#sub.out,Find#find.start), + Find#find{strlen=length(Str)} + end, + case dbg_wx_code:find(Code#sub.out, Str, Case, Dir) of + true -> + display(Wi, {text,""}), + {win, Wi#winInfo{find=Cont#find{found=true}}}; + false -> + display(Wi, {text,"Not found (Hit Enter to wrap search)"}), + {win, Wi#winInfo{find=Cont#find{found=false}}} + end; + +%% Button area +handle_event(#wx{id=ID, event=#wxCommand{type=command_button_clicked}},_Wi) -> + {value, {Button, _}} = lists:keysearch(ID, 2, buttons()), + Button; + +%% Evaluator area +handle_event(#wx{id=?EVAL_ENTRY, event=#wxCommand{type=command_text_enter}}, + Wi = #winInfo{eval=#sub{in=TC}}) -> + case wxTextCtrl:getValue(TC) of + [10] -> + eval_output(Wi, "\n", normal), + ignore; + Cmd -> + eval_output(Wi, [$>, Cmd, 10], normal), + wxTextCtrl:setValue(TC,""), + {user_command, Cmd} + end; + +%% Bindings area +handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Row}},Wi) -> + Bs = get(bindings), + {Var,Val} = lists:nth(Row+1, Bs), + Str = io_lib:format("< ~s = ~p~n", [Var, Val]), + eval_output(Wi, Str, bold), + ignore; +handle_event(#wx{event=#wxList{type=command_list_item_activated, itemIndex=Row}},_Wi) -> + Bs = get(bindings), + Binding = lists:nth(Row+1, Bs), + {edit, Binding}; + +handle_event(_GSEvent, _WinInfo) -> + %%io:format("~p: unhandled ~p~n",[?MODULE, _GSEvent]), + ignore. + + +%%==================================================================== +%% resize(WinInfo) -> WinInfo + +resize(#winInfo{bind=Bind}) -> + %% Tweak the Binding settings text size + if + Bind#sub.enable =:= false -> + ok; + Bind#sub.enable -> + {EW, _} = wxWindow:getClientSize(Bind#sub.out), + B0W = wxListCtrl:getColumnWidth(Bind#sub.out, 0), + wxListCtrl:setColumnWidth(Bind#sub.out, 1, EW - B0W), + ok + end. + +%%==================================================================== +%% Internal functions +%%==================================================================== + +%%--Code Area------------------------------------------------------- +code_area(Win) -> + CodeWin = wxSashWindow:new(Win, [{id,?SASH_CODE}, + {size, {?WIN_W,?CODE_H}}, + {style, ?wxSW_3D}]), + Code = dbg_wx_code:code_area(CodeWin), + wxSashWindow:setSashVisible(CodeWin, ?wxSASH_BOTTOM, true), + wxWindow:setMinSize(CodeWin, {600, ?CODE_H}), + #sub{name='Code Area',enable=true, win=CodeWin, out=Code}. + + +%%--Button Area------------------------------------------------------- + +buttons() -> + [{'Step',?StepButton}, {'Next',?NextButton}, + {'Continue',?ContinueButton}, {'Finish',?FinishButton}, + {'Where',?WhereButton}, {'Up',?UpButton}, {'Down',?DownButton}]. + +is_button(Name) -> + case lists:keysearch(Name, 1, buttons()) of + {value, {Name, Button}} -> {true, Button}; + false -> false + end. + +button_area(Parent) -> + Sz = wxBoxSizer:new(?wxHORIZONTAL), + wx:foreach(fun({Name, Button}) -> + B=wxButton:new(Parent, Button, + [{label,dbg_wx_win:to_string(Name)}]), + Id = wxWindow:getId(B), + wxSizer:add(Sz,B, []), + wxButton:connect(B, command_button_clicked, [{id,Id}]) + end, buttons()), + #sub{name='Button Area', win=Sz}. + +%%--Search/Goto Area------------------------------------------------- + +search_area(Parent) -> + HSz = wxBoxSizer:new(?wxHORIZONTAL), + wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Find:"), + [{flag,?wxALIGN_CENTER_VERTICAL}]), + TC1 = wxTextCtrl:new(Parent, ?SEARCH_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]), + wxSizer:add(HSz, TC1, [{proportion,3}, {flag, ?wxEXPAND}]), + Nbtn = wxRadioButton:new(Parent, ?wxID_ANY, "Next"), + wxRadioButton:setValue(Nbtn, true), + wxSizer:add(HSz,Nbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]), + Pbtn = wxRadioButton:new(Parent, ?wxID_ANY, "Previous"), + wxSizer:add(HSz,Pbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]), + Cbtn = wxCheckBox:new(Parent, ?wxID_ANY, "Match Case"), + wxSizer:add(HSz,Cbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]), + wxSizer:add(HSz, 15,15, [{proportion,1}, {flag, ?wxEXPAND}]), + wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Goto Line:"), + [{flag,?wxALIGN_CENTER_VERTICAL}]), + TC2 = wxTextCtrl:new(Parent, ?GOTO_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]), + wxSizer:add(HSz, TC2, [{proportion,0}, {flag, ?wxEXPAND}]), + wxTextCtrl:connect(TC1, command_text_updated), + wxTextCtrl:connect(TC1, command_text_enter), + wxTextCtrl:connect(TC1, kill_focus), + wxTextCtrl:connect(TC2, command_text_enter), + wxWindow:connect(Parent, command_button_clicked), + + #sub{name='Search Area', win=HSz, + in=#sa{search=TC1,goto=TC2,radio={Nbtn,Pbtn,Cbtn}}}. + +%%--Evaluator Area---------------------------------------------------- + +eval_area(Parent) -> + VSz = wxBoxSizer:new(?wxVERTICAL), + HSz = wxBoxSizer:new(?wxHORIZONTAL), + + wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Evaluator:"), + [{flag,?wxALIGN_CENTER_VERTICAL}]), + TC = wxTextCtrl:new(Parent, ?EVAL_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]), + wxSizer:add(HSz, TC, [{proportion,1}, {flag, ?wxEXPAND}]), + wxSizer:add(VSz, HSz, [{flag, ?wxEXPAND}]), + TL = wxTextCtrl:new(Parent, ?EVAL_LOG, [{style, ?wxTE_DONTWRAP bor + ?wxTE_MULTILINE bor ?wxTE_READONLY}]), + wxSizer:add(VSz, TL, [{proportion,5}, {flag, ?wxEXPAND}]), + + wxTextCtrl:connect(TC, command_text_enter), + #sub{name='Evaluator Area', win=VSz, in=TC, out=TL}. + +%%--Bindings Area----------------------------------------------------- + +bind_area(Parent) -> + Style = {style, ?wxSW_3D bor ?wxCLIP_CHILDREN}, + Win = wxSashWindow:new(Parent, [{id, ?SASH_EVAL},Style]), + wxSashWindow:setSashVisible(Win, ?wxSASH_LEFT, true), + + BA = wxListCtrl:new(Win, [{style, ?wxLC_REPORT bor ?wxLC_SINGLE_SEL}]), + LI = wxListItem:new(), + + wxListItem:setText(LI, "Name"), + wxListItem:setAlign(LI, ?wxLIST_FORMAT_LEFT), + wxListCtrl:insertColumn(BA, 0, LI), + + wxListItem:setText(LI, "Value"), + wxListCtrl:insertColumn(BA, 1, LI), + wxListItem:destroy(LI), + + wxListCtrl:setColumnWidth(BA, 0, 100), + wxListCtrl:setColumnWidth(BA, 1, 150), + wxListCtrl:connect(BA, command_list_item_selected), + wxListCtrl:connect(BA, command_list_item_activated), + + #sub{name='Bindings Area', win=Win, out=BA}. + +%%--Trace Area-------------------------------------------------------- + +trace_area(Parent) -> + Style = {style, ?wxSW_3D bor ?wxCLIP_CHILDREN}, + Win = wxSashWindow:new(Parent, [{id, ?SASH_TRACE}, + {size, {?WIN_W,?TRACE_H}}, Style]), + wxSashWindow:setSashVisible(Win, ?wxSASH_TOP, true), + wxWindow:setMinSize(Win, {500, ?TRACE_H}), + TC = wxTextCtrl:new(Win, ?wxID_ANY, [{style, ?wxTE_MULTILINE bor ?wxTE_READONLY}]), + #sub{name='Trace Area', win=Win, out=TC}. + +%%==================================================================== +%% 'Go To Line' and 'Search' help windows +%%==================================================================== + +helpwin(Type, WinInfo = #winInfo{sg=Sg =#sub{in=Sa}}) -> + Wi = case Sg#sub.enable of + false -> configure(WinInfo#winInfo{sg=Sg#sub{enable=true}}); + true -> WinInfo + end, + case Type of + gotoline -> wxWindow:setFocus(Sa#sa.goto); + search -> wxWindow:setFocus(Sa#sa.search) + end, + Wi. + +max(X,Y) when X > Y -> X; +max(_,Y) -> Y. + + + diff --git a/lib/debugger/src/dbg_wx_view.erl b/lib/debugger/src/dbg_wx_view.erl new file mode 100644 index 0000000000..6d34e5650c --- /dev/null +++ b/lib/debugger/src/dbg_wx_view.erl @@ -0,0 +1,270 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_view). + +%% External exports +-export([start/2]). + +%% Internal exports +-export([init/4]). + +-record(state, {gs, % term() Graphics system id + win, % term() Attach process window data + coords, % {X,Y} Mouse point position + mod % atom() Module + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start(GS, Mod) +%% Mod = atom() +%%-------------------------------------------------------------------- +start(GS, Mod) -> + Title = "View Module " ++ atom_to_list(Mod), + case dbg_wx_winman:is_started(Title) of + true -> ignore; + false -> + Env = wx:get_env(), + spawn_link(?MODULE, init, [GS, Env, Mod, Title]) + end. + + +%%==================================================================== +%% Main loop and message handling +%%==================================================================== + +init(GS, Env, Mod, Title) -> + wx:set_env(Env), + %% Subscribe to messages from the interpreter + int:subscribe(), + + %% Create attach process window + Win1 = dbg_wx_trace_win:create_win(GS, Title, ['Code Area', 'Search Area'], menus()), + Window = dbg_wx_trace_win:get_window(Win1), + dbg_wx_winman:insert(Title, Window), + + Win2 = gui_load_module(Win1, Mod), + Win3 = + lists:foldl(fun(Break, Win) -> + dbg_wx_trace_win:add_break(Win, 'Break', Break) + end, + Win2, + int:all_breaks(Mod)), + + try loop(#state{gs=GS, win=Win3, coords={0,0}, mod=Mod}) + catch _E:normal -> + exit(normal); + _E:_R -> + io:format("~p:~p ~p~n",[?MODULE,_E,_R]), + exit(_R) + end. + +loop(State) -> + receive + + %% From the GUI main window + GuiEvent when element(1, GuiEvent)==wx -> + Cmd = wx:batch(fun() -> + dbg_wx_trace_win:handle_event(GuiEvent, State#state.win) + end), + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the GUI help windows + {gui, Cmd} -> + State2 = gui_cmd(Cmd, State), + loop(State2); + + %% From the interpreter + {int, Cmd} -> + State2 = int_cmd(Cmd, State), + loop(State2); + + %% From the dbg_wx_winman process (Debugger window manager) + {dbg_ui_winman, update_windows_menu, Data} -> + Window = dbg_wx_trace_win:get_window(State#state.win), + dbg_wx_winman:update_windows_menu(Window,Data), + loop(State); + {dbg_ui_winman, destroy} -> + dbg_wx_trace_win:stop(State#state.win), + exit(stop); + + %% Help window termination -- ignore + {'EXIT', _Pid, _Reason} -> + loop(State) + end. + +%%--Commands from the GUI--------------------------------------------- + +gui_cmd(ignore, State) -> + State; +gui_cmd({win, Win}, State) -> + State#state{win=Win}; +gui_cmd(stopped, _State) -> + exit(normal); +gui_cmd({coords, Coords}, State) -> + State#state{coords=Coords}; + +gui_cmd({shortcut, Key}, State) -> + case shortcut(Key) of + false -> State; + Cmd -> gui_cmd(Cmd, State) + end; + +%% File menu +gui_cmd('Close', State) -> + dbg_wx_trace_win:stop(State#state.win), + gui_cmd(stopped, State); + +%% Edit menu +gui_cmd('Go To Line', State) -> + %% Will result in message handled below: {gui, {gotoline, Line}} + Win = dbg_wx_trace_win:helpwin(gotoline, State#state.win), + State#state{win=Win}; +gui_cmd({gotoline, Line}, State) -> + Win = dbg_wx_trace_win:select_line(State#state.win, Line), + State#state{win=Win}; +gui_cmd('Search', State) -> + Win = dbg_wx_trace_win:helpwin(search, State#state.win), + State#state{win=Win}; + +%% Break menu +gui_cmd('Line Break...', State) -> + add_break(State#state.gs, State#state.coords, line, + State#state.mod, + dbg_wx_trace_win:selected_line(State#state.win)), + State; +gui_cmd('Conditional Break...', State) -> + add_break(State#state.gs, State#state.coords, conditional, + State#state.mod, + dbg_wx_trace_win:selected_line(State#state.win)), + State; +gui_cmd('Function Break...', State) -> + add_break(State#state.gs, State#state.coords, function, + State#state.mod, undefined), + State; +gui_cmd('Enable All', State) -> + Breaks = int:all_breaks(), + ThisMod = State#state.mod, + lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod -> + int:enable_break(Mod, Line); + (_Break) -> + ignore + end, + Breaks), + State; +gui_cmd('Disable All', State) -> + Breaks = int:all_breaks(), + ThisMod = State#state.mod, + lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod -> + int:disable_break(Mod, Line); + (_Break) -> + ignore + end, + Breaks), + State; +gui_cmd('Delete All', State) -> + int:no_break(State#state.mod), + State; +gui_cmd({break, {Mod, Line}, What}, State) -> + case What of + add -> int:break(Mod, Line); + delete -> int:delete_break(Mod, Line); + {status, inactive} -> int:disable_break(Mod, Line); + {status, active} -> int:enable_break(Mod, Line); + {trigger, Action} -> int:action_at_break(Mod, Line, Action) + end, + State; + +%% Help menu +gui_cmd('Debugger', State) -> + Window = dbg_wx_trace_win:get_window(State#state.win), + HelpFile = filename:join([code:lib_dir(debugger), + "doc", "html", "part_frame.html"]), + dbg_wx_win:open_help(Window, HelpFile), + State. + +add_break(GS, Coords, Type, undefined, _Line) -> + dbg_wx_break:start(GS, Coords, Type); +add_break(GS, Coords, Type, Mod, undefined) -> + dbg_wx_break:start(GS, Coords, Type, Mod); +add_break(GS, Coords, Type, Mod, Line) -> + dbg_wx_break:start(GS, Coords, Type, Mod, Line). + +%%--Commands from the interpreter------------------------------------- + +int_cmd({new_break, {{Mod,_Line},_Options}=Break}, #state{mod=Mod}=State) -> + Win = dbg_wx_trace_win:add_break(State#state.win, 'Break', Break), + State#state{win=Win}; +int_cmd({delete_break, {Mod,_Line}=Point}, #state{mod=Mod}=State) -> + Win = dbg_wx_trace_win:delete_break(State#state.win, Point), + State#state{win=Win}; +int_cmd({break_options, {{Mod,_Line},_Options}=Break}, #state{mod=Mod}=State) -> + Win = dbg_wx_trace_win:update_break(State#state.win, Break), + State#state{win=Win}; +int_cmd(no_break, State) -> + Win = dbg_wx_trace_win:clear_breaks(State#state.win), + State#state{win=Win}; +int_cmd({no_break, _Mod}, State) -> + Win = dbg_wx_trace_win:clear_breaks(State#state.win), + State#state{win=Win}; +int_cmd(_, State) -> + State. + + +%%==================================================================== +%% GUI auxiliary functions +%%==================================================================== + +menus() -> + [{'File', [{'Close', 0}]}, + {'Edit', [{'Go To Line', 0}, + {'Search', 0}]}, + {'Break', [{'Line Break...', 5}, + {'Conditional Break...', 13}, + {'Function Break...', 0}, + separator, + {'Enable All', no}, + {'Disable All', no}, + {'Delete All', 0}, + separator]}, + {'Windows', []}, + {'Help', [{'Debugger', no}]}]. + +shortcut(c) -> 'Close'; +shortcut(g) -> 'Go To Line'; +shortcut(s) -> 'Search'; +shortcut(b) -> 'Line Break...'; +shortcut(r) -> 'Conditional Break...'; +shortcut(f) -> 'Function Break...'; +shortcut(d) -> 'Delete All'; + +shortcut(_) -> false. + +gui_load_module(Win, Mod) -> + dbg_wx_trace_win:display(Win,{text, "Loading module..."}), + {ok, Contents} = dbg_iserver:call({raw_contents, Mod, any}), + Win2 = dbg_wx_trace_win:show_code(Win, Mod, Contents), + dbg_wx_trace_win:display(Win,{text, ""}), + Win2. diff --git a/lib/debugger/src/dbg_wx_win.erl b/lib/debugger/src/dbg_wx_win.erl new file mode 100644 index 0000000000..f029990aa4 --- /dev/null +++ b/lib/debugger/src/dbg_wx_win.erl @@ -0,0 +1,332 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_win). + +%% External exports +-export([init/0, + create_menus/4, %% For wx + add_break/3, update_break/2, delete_break/1, + motion/2, + confirm/2, notify/2, entry/4, open_help/2, + to_string/1, to_string/2, + find_icon/1 + ]). + +-record(break, {mb, smi, emi, dimi, demi}). +-include_lib("wx/include/wx.hrl"). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% init() -> GS +%% GS = term() +%%-------------------------------------------------------------------- +init() -> + wx:new(). + +%%-------------------------------------------------------------------- +%% create_menus(MenuBar, [Menu]) +%% MenuBar = gsobj() +%% Menu = {Name, [Item]} +%% Name = atom() +%% Item = {Name, N} | {Name, N, Type} | {Name, N, cascade, [Item]} +%% | separator +%% N = no | integer() +%% Type = check | radio +%% Create the specified menus and menuitems. +%% +%% Normal menuitems are specified as {Name, N}. Generates the event: +%% {gs, _Id, click, {menuitem, Name}, _Arg} +%% +%% Check and radio menuitems are specified as {Name, N, check|radio}. +%% They are assumed to be children to a cascade menuitem! (And all children +%% to one cascade menuitem are assumed to be either check OR radio +%% menuitems)! +%% Selecting a check/radio menuitem generates the event: +%% {gs, _Id, click, {menu, Menu}, _Arg} +%% where Menu is the name of the parent, the cascade menuitem. +%% Use selected(Menu) to retrieve which check/radio menuitems are +%% selected. +%%-------------------------------------------------------------------- + +create_menus(MB, [{Title,Items}|Ms], Win, Id0) -> + Menu = wxMenu:new([]), + put(Title,Menu), + Id = create_menu_item(Menu, Items, Win, Id0, true), + wxMenuBar:append(MB,Menu,menu_name(Title,ignore)), + create_menus(MB,Ms,Win,Id); +create_menus(_MB,[], _Win,Id) -> Id. + +create_menu_item(Menu, [separator|Is], Win, Id,Connect) -> + wxMenu:appendSeparator(Menu), + create_menu_item(Menu,Is,Win,Id+1,Connect); +create_menu_item(Menu, [{Name, _N, cascade, Items}|Is], Win, Id0,Connect) -> + Sub = wxMenu:new([]), + Id = create_menu_item(Sub, Items, Win, Id0, false), + wxMenu:append(Menu, ?wxID_ANY, menu_name(Name,ignore), Sub), + %% Simulate GS sub checkBox/RadioBox behaviour + Self = self(), + Butts = [{MI,get(MI)} || {MI,_,_} <- Items], + IsChecked = fun({MiName,MI},Acc) -> + case wxMenuItem:isChecked(MI) of + true -> [MiName|Acc]; + false -> Acc + end + end, + Filter = fun(_,_) -> + Enabled = lists:foldl(IsChecked, [], Butts), + Self ! #wx{userData={Name, Enabled}, + event=#wxCommand{type=command_menu_selected}} + end, + wxMenu:connect(Win, command_menu_selected, + [{id,Id0},{lastId, Id-1},{callback,Filter}]), + create_menu_item(Menu, Is, Win, Id, Connect); +create_menu_item(Menu, [{Name,Pos}|Is], Win, Id, Connect) -> + Item = wxMenu:append(Menu, Id, menu_name(Name,Pos)), + put(Name,Item), + if Connect -> + wxMenu:connect(Win, command_menu_selected, [{id,Id},{userData, Name}]); + true -> ignore + end, + create_menu_item(Menu,Is,Win,Id+1, Connect); +create_menu_item(Menu, [{Name,N,check}|Is], Win, Id, Connect) -> + Item = wxMenu:appendCheckItem(Menu, Id, menu_name(Name,N)), + put(Name,Item), + if Connect -> + wxMenu:connect(Win, command_menu_selected, [{id,Id},{userData, Name}]); + true -> ignore + end, + create_menu_item(Menu,Is,Win,Id+1,Connect); +create_menu_item(Menu, [{Name, N, radio}|Is], Win, Id,Connect) -> + Item = wxMenu:appendRadioItem(Menu, Id, menu_name(Name,N)), + put(Name,Item), + if Connect -> + wxMenu:connect(Win, command_menu_selected, [{id,Id},{userData, Name}]); + true -> ignore + end, + create_menu_item(Menu,Is,Win,Id+1,Connect); +create_menu_item(_, [], _, Id,_) -> + Id. + +%%-------------------------------------------------------------------- +%% add_break(Name, Point) -> #break{} +%% Name = atom() +%% Point = {Mod, Line} +%% The break will generate the following events: +%% #wx{userData= {break, Point, Event}} +%% Event = delete | {trigger, Action} | {status, Status} +%% Action = enable | disable | delete +%% Status = active | inactive +%%-------------------------------------------------------------------- +add_break(Win, MenuName, Point) -> + %% Create a name for the breakpoint + {Mod, Line} = Point, + Label = to_string("~w ~5w", [Mod, Line]), + + Menu = get(MenuName), + %% Create a menu for the breakpoint + Add = fun(Item,Action) -> + Id = wxMenuItem:getId(Item), + wxMenu:connect(Win, command_menu_selected, + [{id,Id}, {userData, Action}]) + end, + Sub = wxMenu:new([]), + Dis = wxMenu:append(Sub, ?wxID_ANY, "Disable"), + Add(Dis, {break,Point,status}), + Del = wxMenu:append(Sub, ?wxID_ANY, "Delete"), + Add(Del, {break,Point,delete}), + Trigger = wxMenu:new([]), + Enable = wxMenu:appendRadioItem(Trigger, ?wxID_ANY,"Enable"), + Add(Enable, {break,Point,{trigger,enable}}), + TDisable = wxMenu:appendRadioItem(Trigger, ?wxID_ANY,"Disable"), + Add(TDisable, {break,Point,{trigger,disable}}), + Delete = wxMenu:appendRadioItem(Trigger, ?wxID_ANY,"Delete"), + Add(Delete, {break,Point,{trigger,delete}}), + + wxMenu:append(Sub, ?wxID_ANY, "Trigger Action", Trigger), + MenuBtn = wxMenu:append(Menu,?wxID_ANY, Label, Sub), + + #break{mb={Menu,MenuBtn}, + smi=Dis, emi=Enable, dimi=TDisable, demi=Delete}. + +%%-------------------------------------------------------------------- +%% update_break(Break, Options) +%% Break = #break{} +%% Options = [Status, Action, Mods, Cond] +%% Status = active | inactive +%% Action = enable | disable | delete +%% Mods = null (not used) +%% Cond = null | {Mod, Func} +%%-------------------------------------------------------------------- +update_break(Break, Options) -> + [Status, Trigger|_] = Options, + + Label = case Status of + active -> "Disable"; + inactive -> "Enable" + end, + wxMenuItem:setText(Break#break.smi, Label), + + TriggerMI = case Trigger of + enable -> Break#break.emi; + disable -> Break#break.dimi; + delete -> Break#break.demi + end, + wxMenuItem:check(TriggerMI). + +%%-------------------------------------------------------------------- +%% delete_break(Break) +%% Break = #break{} +%%-------------------------------------------------------------------- +delete_break(Break) -> + {Menu, MenuBtn} = Break#break.mb, + wxMenu:'Destroy'(Menu,MenuBtn). + +%%-------------------------------------------------------------------- +%% motion(X, Y) -> {X, Y} +%% X = Y = integer() +%%-------------------------------------------------------------------- +motion(X, Y) -> + receive + {gs, _Id, motion, _Data, [NX,NY]} -> + motion(NX, NY) + after 0 -> + {X, Y} + end. + + +%%-------------------------------------------------------------------- +%% confirm(MonWin, String) -> ok | cancel +%%-------------------------------------------------------------------- + +confirm(Win,Message) -> + MD = wxMessageDialog:new(Win,to_string(Message), + [{style, ?wxOK bor ?wxCANCEL}, + {caption, "Confirm"}]), + Res = case wxDialog:showModal(MD) of + ?wxID_OK -> ok; + _ -> cancel + end, + wxDialog:destroy(MD), + Res. + +%%-------------------------------------------------------------------- +%% notify(MonWin, String) -> ok +%%-------------------------------------------------------------------- + +notify(Win,Message) -> + MD = wxMessageDialog:new(Win,to_string(Message), + [{style, ?wxOK}, + {caption, "Confirm"}]), + wxDialog:showModal(MD), + wxDialog:destroy(MD), + ok. + +%%-------------------------------------------------------------------- +%% entry(Parent, Title, Prompt, {Type, Value}) -> {Prompt, Val} | cancel +%%-------------------------------------------------------------------- + +entry(Parent, Title, Prompt, {Type, Value}) -> + Ted = wxTextEntryDialog:new(Parent, to_string(Prompt), + [{caption, to_string(Title)}, + {value, to_string(Value)}]), + + case wxDialog:showModal(Ted) of + ?wxID_OK -> + Res = case verify(Type, wxTextEntryDialog:getValue(Ted)) of + {edit,NewVal} -> + {Prompt,NewVal}; + ignore -> + cancel + end, + wxTextEntryDialog:destroy(Ted), + Res; + _ -> + cancel + end. + + +verify(Type, Str) -> + case erl_scan:string(Str) of + {ok, Tokens, _EndLine} when Type==term -> + + case erl_parse:parse_term(Tokens++[{dot, 1}]) of + {ok, Value} -> {edit, Value}; + _Error -> + ignore + end; + {ok, [{Type, _Line, Value}], _EndLine} when Type/=term -> + {edit, Value}; + _Err -> + ignore + end. + +%%-------------------------------------------------------------------- +%% open_help/2 +%% opens browser with help file +%%-------------------------------------------------------------------- +open_help(_Parent, HelpHtmlFile) -> + wx_misc:launchDefaultBrowser("file://" ++ HelpHtmlFile). + +%%-------------------------------------------------------------------- +%% to_string(Term) -> [integer()] +%% to_string(Format,Args) -> [integer()] +%%-------------------------------------------------------------------- + +to_string(Atom) when is_atom(Atom) -> + atom_to_list(Atom); +to_string(Integer) when is_integer(Integer) -> + integer_to_list(Integer); +to_string([]) -> ""; +to_string(List) when is_list(List) -> + List; +to_string(Term) -> + io_lib:format("~p",[Term]). + +to_string(Format,Args) -> + io_lib:format(Format, Args). + +menu_name(Atom, N) when is_atom(Atom) -> + menu_name(atom_to_list(Atom),N); +menu_name(Str, Pos) when is_integer(Pos) -> + {S1,S2} = lists:split(Pos,Str), + S1 ++ [$&|S2]; +menu_name(Str,_) -> + Str. + +%%-------------------------------------------------------------------- +%% find_icon(File) -> Path or exists +%%-------------------------------------------------------------------- + +find_icon(File) -> + PrivDir = code:priv_dir(debugger), + PrivIcon = filename:append(PrivDir, File), + case filelib:is_regular(PrivIcon) of + true -> PrivIcon; + false -> + CurrDir = filename:dirname(code:which(?MODULE)), + CurrIcon = filename:append(CurrDir, File), + true = filelib:is_regular(CurrIcon), + CurrIcon + end. + diff --git a/lib/debugger/src/dbg_wx_winman.erl b/lib/debugger/src/dbg_wx_winman.erl new file mode 100755 index 0000000000..1daabe3435 --- /dev/null +++ b/lib/debugger/src/dbg_wx_winman.erl @@ -0,0 +1,175 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_wx_winman). +-behaviour(gen_server). + +%% External exports +-export([start/0]). +-export([insert/2, is_started/1, + clear_process/1, + raise/1, + update_windows_menu/2]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(win, {owner, % pid() + title, % string() + win % gsobj() + }). + +-record(state, {wins=[] % [#win{}] + }). + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start() +%%-------------------------------------------------------------------- +start() -> + gen_server:start({local,?MODULE}, ?MODULE, [], []). + +%%-------------------------------------------------------------------- +%% insert(Title, Win) +%% Title = string() +%% Win = gsobj() +%%-------------------------------------------------------------------- +insert(Title, Win) -> + gen_server:cast(?MODULE, {insert, self(), Title, Win}). + +%%-------------------------------------------------------------------- +%% is_started(Title) -> true | false +%% Title = string() +%%-------------------------------------------------------------------- +is_started(Title) -> + case gen_server:call(?MODULE, {is_started, Title}, infinity) of + {true, Win} -> + raise(Win), + true; + false -> + false + end. + +%%-------------------------------------------------------------------- +%% clear_process(Title) +%% Title = string() +%%-------------------------------------------------------------------- +clear_process(Title) -> + gen_server:cast(?MODULE, {clear_process, Title}). + +%%-------------------------------------------------------------------- +%% raise(Win) +%% Win = gsobj() +%%-------------------------------------------------------------------- +raise(Win) -> + case wxTopLevelWindow:isIconized(Win) of + true -> wxTopLevelWindow:iconize(Win, [{iconize, false}]); + false -> ignore + end, + wxWindow:raise(Win). + +%%-------------------------------------------------------------------- +%% update_windows_menu(Data) +%% Data = {New, Old} +%% New = Old = list() +%%-------------------------------------------------------------------- +update_windows_menu(Win, [MonInfo|Infos]) -> + Menu = get('Windows'), + OldItems = wxMenu:getMenuItems(Menu), + [wxMenu:delete(Menu, Item) || Item <- OldItems], + menuitem(Win, Menu,MonInfo, 700), + wxMenu:appendSeparator(Menu), + wx:foldl(fun(Info,Acc) -> menuitem(Win,Menu,Info,Acc) end, 701, Infos). + +menuitem(Window, Menu, {Title, Win}, Id) -> + wxMenu:append(Menu, Id, Title), + wxWindow:connect(Window, command_menu_selected, + [{id,Id},{userData,{dbg_ui_winman,Win}}]), + Id+1. + + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +init([]) -> + process_flag(trap_exit, true), + {ok, #state{}}. + +handle_call({is_started, Title}, _From, State) -> + Reply = case lists:keysearch(Title, #win.title, State#state.wins) of + {value, Win} -> {true, Win#win.win}; + false -> false + end, + {reply, Reply, State}. + +handle_cast({insert, Pid, Title, Win}, State) -> + link(Pid), + Wins = State#state.wins ++ [#win{owner=Pid, title=Title, win=Win}], + inform_all(Wins), + {noreply, State#state{wins=Wins}}; + +handle_cast({clear_process, Title}, State) -> + OldWins = State#state.wins, + Wins = case lists:keysearch(Title, #win.title, OldWins) of + {value, #win{owner=Pid}} -> + Msg = {dbg_ui_winman, destroy}, + Pid ! Msg, + lists:keydelete(Title, #win.title, OldWins); + false -> + OldWins + end, + {noreply, State#state{wins=Wins}}. + +handle_info({'EXIT', Pid, _Reason}, State) -> + [Mon | _Wins] = State#state.wins, + if + Pid==Mon#win.owner -> {stop, normal, State}; + true -> + Wins2 = lists:keydelete(Pid, #win.owner, State#state.wins), + inform_all(Wins2), + {noreply, State#state{wins=Wins2}} + end. + +terminate(_Reason, State) -> + delete_all(State#state.wins), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +inform_all(Wins) -> + Infos = lists:map(fun(#win{title=Title, win=Win}) -> {Title, Win} end, + Wins), + Msg = {dbg_ui_winman, update_windows_menu, Infos}, + lists:foreach(fun(#win{owner=Pid}) -> Pid ! Msg end, Wins). + +delete_all(Wins) -> + Msg = {dbg_ui_winman, destroy}, + lists:foreach(fun(#win{owner=Pid}) -> Pid ! Msg end, Wins). diff --git a/lib/debugger/src/debugger.app.src b/lib/debugger/src/debugger.app.src new file mode 100644 index 0000000000..21cf59a2e1 --- /dev/null +++ b/lib/debugger/src/debugger.app.src @@ -0,0 +1,62 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-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% +%% +{application, debugger, + [{description, "Debugger"}, + {vsn, "%VSN%"}, + {modules, [ + dbg_debugged, + dbg_icmd, + dbg_idb, + dbg_ieval, + dbg_iload, + dbg_iserver, + dbg_ui_break, + dbg_ui_break_win, + dbg_ui_edit, + dbg_ui_edit_win, + dbg_ui_filedialog_win, + dbg_ui_interpret, + dbg_ui_mon, + dbg_ui_mon_win, + dbg_ui_settings, + dbg_ui_trace, + dbg_ui_trace_win, + dbg_ui_view, + dbg_ui_win, + dbg_ui_winman, + dbg_wx_break, + dbg_wx_break_win, + dbg_wx_code, + dbg_wx_filedialog_win, + dbg_wx_interpret, + dbg_wx_mon, + dbg_wx_mon_win, + dbg_wx_settings, + dbg_wx_src_view, + dbg_wx_trace, + dbg_wx_trace_win, + dbg_wx_view, + dbg_wx_win, + dbg_wx_winman, + debugger, + i, + int + ]}, + {registered, [dbg_iserver, dbg_ui_mon, dbg_ui_winman]}, + {applications, [kernel, stdlib, gs]}]}. diff --git a/lib/debugger/src/debugger.appup.src b/lib/debugger/src/debugger.appup.src new file mode 100644 index 0000000000..7a435e9b22 --- /dev/null +++ b/lib/debugger/src/debugger.appup.src @@ -0,0 +1,19 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-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% +%% +{"%VSN%",[],[]}. diff --git a/lib/debugger/src/debugger.erl b/lib/debugger/src/debugger.erl new file mode 100644 index 0000000000..b97091ee6b --- /dev/null +++ b/lib/debugger/src/debugger.erl @@ -0,0 +1,118 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-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(debugger). + +%% External exports +-export([start/0, start/1, start/2, stop/0, quick/3, auto_attach/1]). + +%%==Erlang Debugger=================================================== +%% +%% Graphical user interface to the Erlang Interpreter. +%% The code for each process is divided into two modules, Name.erl +%% and Name_win.erl, where Name.erl contains the logic and +%% Name_win.erl the GS specific functionality. +%% +%% debugger +%% -------- +%% Interface module. +%% +%% dbg_ui_winman +%% ------------- +%% Window manager, keeping track of open windows and Debugger +%% processes. +%% +%% dbg_ui_mon, dbg_ui_mon_win +%% -------------------------- +%% Monitor window, main window of Debugger, displaying information +%% about interpreted modules and debugged processes. +%% +%% dbg_ui_trace, dbg_ui_trace_win +%% ------------------------------ +%% Attach process window, showing the code executed by a debugged +%% process and providing a GUI for stepping, inspecting variables etc. +%% +%% dbg_ui_break, dbg_ui_break_win +%% ------------------------------ +%% Help window for creating new breakpoints. +%% +%% dbg_ui_edit, dbg_ui_edit_win +%% -------------------------------------- +%% Help window for editing terms, used for setting backtrace size +%% (i.e. how many stack frames to display in the attach process window) +%% and changing variable values. +%% +%% dbg_ui_interpret, dbg_ui_filedialog_win +%% -------------------------------------- +%% Help window for selecting modules to interpret. +%% +%% dbg_ui_settings, dbg_ui_filedialog_win +%% -------------------------------------- +%% Help window for saving and loading Debugger settings. +%% +%% dbg_ui_view +%% ----------- +%% Help window for viewing interpreted modules (uses dbg_ui_trace_win). +%% +%% dbg_ui_win +%% ---------- +%% GUI specific functionality used by more than one window type. +%% +%%==================================================================== +start() -> + start(global, default, default). +start(Mode) when Mode==local; Mode==global -> + start(Mode, default, default); +start(Gui) when Gui==gs; Gui==wx -> + start(global, default, Gui); +start(SFile) when is_list(SFile), is_integer(hd(SFile)) -> + start(global, SFile, default). + +start(Mode, SFile) -> + start(Mode, SFile, default). + +start(Mode, SFile, gs) -> + dbg_ui_mon:start(Mode, SFile); +start(Mode, SFile, wx) -> + dbg_wx_mon:start(Mode, SFile); +start(Mode, SFile, default) -> + Gui = which_gui(), + start(Mode, SFile, Gui). + +stop() -> + dbg_ui_mon:stop(). + +quick(M, F, A) -> + int:i(M), + auto_attach([init]), + apply(M, F, A). + +auto_attach(Flags) -> + case which_gui() of + gs -> int:auto_attach(Flags, {dbg_ui_trace, start, []}); + wx -> int:auto_attach(Flags, {dbg_wx_trace, start, []}) + end. + +which_gui() -> + try + wx:new(), + wx:destroy(), + wx + catch _:_ -> + gs + end. diff --git a/lib/debugger/src/i.erl b/lib/debugger/src/i.erl new file mode 100644 index 0000000000..7c2fb22946 --- /dev/null +++ b/lib/debugger/src/i.erl @@ -0,0 +1,376 @@ +%% +%% %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% +%% +%% Purpose : User interface to the Erlang debugger/interpreter. + +-module(i). + +-export([help/0,ia/1,ia/2,ia/3,ia/4,iaa/1,iaa/2, + ib/2,ib/3,ib/4,ibd/2,ibe/2,iba/3,ibc/3,ic/0,ii/1,ii/2, + il/0,im/0,ini/1,ini/2,inq/1,ip/0,ipb/0,ipb/1,iq/1, + ir/0,ir/1,ir/2,ir/3,iv/0,ist/1]). + +-import(io, [format/1,format/2]). +-import(lists, [sort/1,foreach/2]). + +iv() -> + Vsn = string:substr(filename:basename(code:lib_dir(debugger)), 10), + list_to_atom(Vsn). + + +%% ------------------------------------------- +%% Start a new graphical monitor. +%% A monitor displays status for all processes +%% running interpreted modules. +%% ------------------------------------------- + +im() -> + case debugger:start() of + {ok, Pid} -> + Pid; + {error, {already_started, Pid}} -> + Pid + end. + +%% ------------------------------------------- +%% Add Module(s) as being interpreted. +%% The actual paths will be searched for the +%% corresponding source file(s) (Module.erl). +%% Module(s) can be given with absolute path. +%% ------------------------------------------- + +ii(Module) -> + int:i(Module). + +ii(Module,_Options) -> + int:i(Module). + +%% ------------------------------------------- +%% Don't interpret module(s). The module will be +%% removed from the set of modules interpreted. +%% ------------------------------------------- + +iq(Module) -> + int:n(Module). + +%% ------------------------------------------- +%% The corresponding functions for distributed +%% erlang. The loading ... will be performed +%% at all nodes using the broadcast facility. +%% ------------------------------------------- + +ini(Module) -> + int:ni(Module). + +ini(Module,_Options) -> + int:ni(Module). + +inq(Module) -> + int:nn(Module). + +%% ------------------------------------------- +%% Add a new break point at Line in Module. +%% ------------------------------------------- + +ib(Module,Line) -> + int:break(Module,Line). + +%% ------------------------------------------- +%% Break at entrance of specified function. +%% Breaks is set at the first expression for +%% all function clauses. +%% ------------------------------------------- + +ib(Module,Function,Arity) -> + int:break_in(Module,Function,Arity). + +%% ------------------------------------------- +%% Break at entrance of specified function. +%% Breaks is set at the first expression for +%% all function clauses. +%% Associate the condition to the break. +%% ------------------------------------------- + +ib(Module,Function,Arity,Cond) -> + Breaks1 = int:all_breaks(Module), + int:break_in(Module,Function,Arity), + Breaks2 = int:all_breaks(Module), + lists:foreach(fun({Mod,Line}) -> int:test_at_break(Mod,Line,Cond) end, + Breaks2--Breaks1). + +%% ------------------------------------------- +%% Make an existing break point inactive. +%% ------------------------------------------- + +ibd(Mod,Line) -> + int:disable_break(Mod,Line). + +%% ------------------------------------------- +%% Make an existing break point active. +%% ------------------------------------------- + +ibe(Mod,Line) -> + int:enable_break(Mod,Line). + +%% ------------------------------------------- +%% Set which status a break point shall have +%% after it has been triggered the next time. +%% Action is: enable, disable or delete. +%% ------------------------------------------- + +iba(Mod,Line,Action) -> + int:action_at_break(Mod,Line,Action). + +%% ------------------------------------------- +%% Add a conditional function to a break point. +%% The given function shall have arity 1 and +%% return either true or false. +%% The argument of the given function is the +%% current variable bindings of the process at +%% the place of the break point, the bindings +%% can be inspected using int:get_binding/2. + +%% Fnk == {Module,Function} +%% Fnk == {Module,Function,ExtraArgs} +%% ------------------------------------------- + +ibc(Mod,Line,Fnk) -> + int:test_at_break(Mod,Line,Fnk). + +%% ------------------------------------------- +%% Delete break point. +%% ------------------------------------------- + +ir(Module,Line) -> + int:delete_break(Module,Line). + +%% ------------------------------------------- +%% Delete break at entrance of specified function. +%% ------------------------------------------- + +ir(Module,Function,Arity) -> + int:del_break_in(Module,Function,Arity). + +%% ------------------------------------------- +%% Delete all break points in module. +%% ------------------------------------------- + +ir(Module) -> + int:no_break(Module). + +%% ------------------------------------------- +%% Delete all break points (for all modules). +%% ------------------------------------------- + +ir() -> + int:no_break(). + +%% ------------------------------------------- +%% Print all interpreted modules. +%% ------------------------------------------- + +il() -> + Mods = sort(int:interpreted()), + ilformat("Module","File"), + foreach(fun(Mod) -> ilformat(atom_to_list(Mod), get_file(Mod)) end, Mods). + +get_file(Mod) -> + case int:file(Mod) of + {error,not_loaded} -> % Marked interpreted but not loaded + "not loaded"; + File -> + File + end. + +ilformat(A1, A2) -> + format("~-20s ~s\n", [A1,A2]). + +%% ------------------------------------------- +%% Print all break points in modules. +%% ------------------------------------------- + +ipb() -> + Bps = lists:keysort(1,int:all_breaks()), + bhformat("Module","Line","Status","Action","Condition"), + pb_print(Bps). + +ipb(Module) when is_atom(Module) -> + ipb1(Module); +ipb(Module) when is_list(Module) -> + ipb1(list_to_atom(Module)). + +ipb1(Module) -> + Bps = lists:keysort(1,int:all_breaks(Module)), + bhformat("Module","Line","Status","Action","Condition"), + pb_print(Bps). + +pb_print([{{Mod,Line},[Status,Action,_,null|_]}|Bps]) -> + bformat(Mod,Line,Status,Action,""), + pb_print(Bps); +pb_print([{{Mod,Line},[Status,Action,_,Cond|_]}|Bps]) -> + bformat(Mod,Line,Status,Action, + io_lib:format("~w",[Cond])), + pb_print(Bps); +pb_print(_) -> + ok. + +bhformat(A1, A2, A3, A4, A5) -> + format("~-15s ~-9s ~-12s ~-12s ~-21s~n", [A1,A2,A3,A4,A5]). + +bformat(A1, A2, A3, A4, A5) -> + format("~-15w ~-9w ~-12w ~-12w ~-21s~n", [A1,A2,A3,A4,A5]). + +%% ------------------------------------------- +%% Set the stack trace flag. +%% Flag can be all (true), no_tail or false. +%% ------------------------------------------- + +ist(Flag) -> + int:stack_trace(Flag), + true. + +%% ------------------------------------------- +%% Set the automatic attachment flag. +%% Flags can be init, break and exit. +%% iaa(Flag) or ia([Flag,Flag,...]) +%% ------------------------------------------- + +iaa(Flag) -> + iaa(Flag,{dbg_ui_trace,start,[]}). + +%% ------------------------------------------- +%% Set the automatic attachment flag. +%% Flags can be init, break and exit. +%% Use given function to start up an attachment +%% window. +%% ia(Flag,Fnk) or ia([Flag,Flag,...],Fnk) +%% where Fnk == {M,F} +%% The given Fnk must have arity 3 or 4. +%% ------------------------------------------- + +iaa(Flag,Fnk) -> + int:auto_attach(Flag,Fnk), + true. + +%% ------------------------------------------- +%% Attach to process. +%% ------------------------------------------- + +ia(Pid) -> + ia(Pid,{dbg_ui_trace,start}). + +%% ------------------------------------------- +%% Attach to process. +%% X,Y,Z is combind to a process identity. +%% ------------------------------------------- + +ia(X,Y,Z) -> + ia(c:pid(X,Y,Z)). + +%% ------------------------------------------- +%% Attach to process. +%% Use Fnk == {M,F} as the attaching interface. +%% ------------------------------------------- + +ia(Pid,Fnk) -> + case lists:keysearch(Pid, 1, int:snapshot()) of + {value, _PidTuple} -> + int:attach(Pid,Fnk); + false -> no_proc + end. + +ia(X,Y,Z,Fnk) -> + ia(c:pid(X,Y,Z),Fnk). + +%% ------------------------------------------- +%% Print status for all interpreted processes. +%% ------------------------------------------- + +ip() -> + Stats = int:snapshot(), + hformat("Pid","Initial Call","Status","Info"), + ip(Stats). + +ip([{Pid,{M,F,A},Status,{}}|Stats]) -> + hformat(io_lib:format("~w",[Pid]), + io_lib:format("~p:~p/~p",[M,F,length(A)]), + io_lib:format("~w",[Status]), + ""), + ip(Stats); +ip([{Pid,{M,F,A},Status,Info}|Stats]) -> + hformat(io_lib:format("~w",[Pid]), + io_lib:format("~p:~p/~p",[M,F,length(A)]), + io_lib:format("~w",[Status]), + io_lib:format("~w",[Info])), + ip(Stats); +ip([]) -> + ok. + +hformat(A1, A2, A3, A4) -> + format("~-12s ~-21s ~-9s ~-21s~n", [A1,A2,A3,A4]). + + +%% ------------------------------------------- +%% Delete all terminated processes from the +%% interpreter. +%% ------------------------------------------- + +ic() -> + int:clear(). + +%% ------------------------------------------- +%% Help printout +%% ------------------------------------------- + +help() -> + format("iv() -- print the current version of the interpreter~n"), + format("im() -- pop up a monitor window~n"), + format("ii(Mod) -- interpret Mod(s) (or AbsMod(s))~n"), + format("ii(Mod,Op) -- interpret Mod(s) (or AbsMod(s))~n"), + format(" use Op as options (same as for compile)~n"), + format("iq(Mod) -- do not interpret Mod(s)~n"), + format("ini(Mod) -- ii/1 at all Erlang nodes~n"), + format("ini(Mod,Op) -- ii/2 at all Erlang nodes~n"), + format("inq(Mod) -- iq at all Erlang nodes~n"), + format("ib(Mod,Line) -- set a break point at Line in Mod~n"), + format("ib(M,F,Arity)-- set a break point in M:F/Arity~n"), + format("ibd(Mod,Line)-- disable the break point at Line in Mod~n"), + format("ibe(Mod,Line)-- enable the break point at Line in Mod~n"), + format("iba(M,L,Action)-- set a new action at break~n"), + format("ibc(M,L,Action)-- set a new condition for break~n"), + format("ir(Mod,Line) -- remove the break point at Line in Mod~n"), + format("ir(M,F,Arity)-- remove the break point in M:F/Arity~n"), + format("ir(Mod) -- remove all break points in Mod~n"), + format("ir() -- remove all existing break points~n"), + format("il() -- list all interpreted modules~n"), + format("ip() -- print status of all interpreted processes~n"), + format("ic() -- remove all terminated interpreted processes~n"), + format("ipb() -- list all break points~n"), + format("ipb(Mod) -- list all break points in Mod~n"), + format("ia(Pid) -- attach to Pid~n"), + format("ia(X,Y,Z) -- attach to pid(X,Y,Z)~n"), + format("ia(Pid,Fun) -- use own Fun = {M,F} as attach application~n"), + format("ia(X,Y,Z,Fun)-- use own Fun = {M,F} as attach application~n"), + format("iaa([Flag]) -- set automatic attach to process~n"), + format(" Flag is init,break and exit~n"), + format("iaa([Fl],Fun)-- use own Fun = {M,F} as attach application~n"), + format("ist(Flag) -- set stack trace flag~n"), + format(" Flag is all (true),no_tail or false~n"), + ok. + + diff --git a/lib/debugger/src/int.erl b/lib/debugger/src/int.erl new file mode 100644 index 0000000000..eeb4df4a8e --- /dev/null +++ b/lib/debugger/src/int.erl @@ -0,0 +1,736 @@ +%% +%% %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(int). + +%% External exports +-export([i/1, i/2, ni/1, ni/2, n/1, nn/1, interpreted/0, file/1, + interpretable/1]). +-export([auto_attach/0, auto_attach/1, auto_attach/2, + stack_trace/0, stack_trace/1]). +-export([break/2, delete_break/2, break_in/3, del_break_in/3, + no_break/0, no_break/1, + disable_break/2, enable_break/2, + action_at_break/3, test_at_break/3, get_binding/2, + all_breaks/0, all_breaks/1]). +-export([snapshot/0, clear/0]). +-export([continue/1, continue/3]). + +%% External exports only to be used by Debugger +-export([start/0, stop/0, subscribe/0]). +-export([attach/2, step/1, next/1, finish/1]). + +%% External exports only to be used by an attached process +-export([attached/1, meta/2, meta/3, contents/2, functions/1]). + +%% External export only to be used by error_handler +-export([eval/3]). + +-include_lib("kernel/include/file.hrl"). + +%%==Erlang Interpreter================================================ +%% +%% int +%% --- +%% Interface module. +%% +%% i +%% - +%% Interface module to int, retained for backwards compatibility only. +%% +%% dbg_debugged +%% ------------ +%% Contains the message loops for a debugged process and is the main +%% entry point from the breakpoint handler in the error_handler module +%% (via the int module). +%% +%% When a process is debugged, most code is executed in another +%% process, called the meta process. When the meta process is +%% interpreting code, the process being debugged just waits in a +%% receive loop in dbg_debugged. However the debugged process itself +%% calls any BIFs that must execute in the correct process (such as +%% link/1 and spawn_link/1), and external code which is not +%% interpreted. +%% +%% dbg_icmd, dbg_ieval +%% ------------------- +%% Code for the meta process. +%% +%% dbg_iserver +%% ----------- +%% Interpreter main process, keeping and distributing information +%% about interpreted modules and debugged processes. +%% +%% dbg_idb +%% ------- +%% ETS wrapper, allowing transparent access to tables at a remote node. +%% +%% dbg_iload +%% --------- +%% Code for interpreting a module. +%%==================================================================== + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% i(AbsMods) -> {module,Mod} | error | ok +%% ni(AbsMods) -> {module,Mod} | error | ok +%% AbsMods = AbsMod | [AbsMod] +%% AbsMod = atom() | string() +%% Mod = atom() +%% Options = term() ignored +%%-------------------------------------------------------------------- +i(AbsMods) -> i2(AbsMods, local, ok). +i(AbsMods, _Options) -> i2(AbsMods, local, ok). +ni(AbsMods) -> i2(AbsMods, distributed, ok). +ni(AbsMods, _Options) -> i2(AbsMods, distributed, ok). + +i2([AbsMod|AbsMods], Dist, Acc) + when is_atom(AbsMod); is_list(AbsMod); is_tuple(AbsMod) -> + Res = int_mod(AbsMod, Dist), + case Acc of + error -> + i2(AbsMods, Dist, Acc); + _ -> + i2(AbsMods, Dist, Res) + end; +i2([], _Dist, Acc) -> + Acc; +i2(AbsMod, Dist, _Acc) when is_atom(AbsMod); is_list(AbsMod); is_tuple(AbsMod) -> + int_mod(AbsMod, Dist). + +%%-------------------------------------------------------------------- +%% n(AbsMods) -> ok +%% nn(AbsMods) -> ok +%%-------------------------------------------------------------------- +n(AbsMods) -> n2(AbsMods, local). +nn(AbsMods) -> n2(AbsMods, distributed). + +n2([AbsMod|AbsMods], Dist) when is_atom(AbsMod); is_list(AbsMod) -> + del_mod(AbsMod, Dist), + n2(AbsMods, Dist); +n2([AbsMod], Dist) when is_atom(AbsMod); is_list(AbsMod) -> + del_mod(AbsMod, Dist); +n2([], _Dist) -> + ok; +n2(AbsMod, Dist) when is_atom(AbsMod); is_list(AbsMod) -> + del_mod(AbsMod, Dist). + +%%-------------------------------------------------------------------- +%% interpreted() -> [Mod] +%%-------------------------------------------------------------------- +interpreted() -> + dbg_iserver:safe_call(all_interpreted). + +%%-------------------------------------------------------------------- +%% file(Mod) -> File | {error, not_loaded} +%% Mod = atom() +%% File = string() +%%-------------------------------------------------------------------- +file(Mod) when is_atom(Mod) -> + dbg_iserver:safe_call({file, Mod}). + +%%-------------------------------------------------------------------- +%% interpretable(AbsMod) -> true | {error, Reason} +%% AbsMod = Mod | File +%% Reason = no_src | no_beam | no_debug_info | badarg | {app, App} +%%-------------------------------------------------------------------- +interpretable(AbsMod) -> + case check(AbsMod) of + {ok, _Res} -> true; + Error -> Error + end. + +%%-------------------------------------------------------------------- +%% auto_attach() -> false | {Flags, Function} +%% auto_attach(false) +%% auto_attach(false|Flags, Function) +%% Flags = Flag | [Flag] +%% Flag = init | break | exit +%% Function = {Mod, Func} | {Mod, Func, Args} +%% Will result in calling: +%% spawn(Mod, Func, [Dist, Pid, Meta | Args]) (living process) or +%% spawn(Mod, Func, [Dist, Pid, Reason, Info | Args]) (dead process) +%%-------------------------------------------------------------------- +auto_attach() -> + dbg_iserver:safe_call(get_auto_attach). + +auto_attach(false) -> + dbg_iserver:safe_cast({set_auto_attach, false}). + +auto_attach([], _Function) -> + auto_attach(false); +auto_attach(Flags, {Mod, Func}) -> + auto_attach(Flags, {Mod, Func, []}); +auto_attach(Flags, {Mod, Func, Args}) when is_atom(Mod),is_atom(Func),is_list(Args) -> + check_flags(Flags), + dbg_iserver:safe_cast({set_auto_attach, Flags, {Mod, Func, Args}}). + +check_flags([init|Flags]) -> check_flags(Flags); +check_flags([break|Flags]) -> check_flags(Flags); +check_flags([exit|Flags]) -> check_flags(Flags); +check_flags([]) -> true. + +%%-------------------------------------------------------------------- +%% stack_trace() -> Flag +%% stack_trace(Flag) +%% Flag = all | true | no_tail | false +%%-------------------------------------------------------------------- +stack_trace() -> + dbg_iserver:safe_call(get_stack_trace). + +stack_trace(true) -> + stack_trace(all); +stack_trace(Flag) -> + check_flag(Flag), + dbg_iserver:safe_cast({set_stack_trace, Flag}). + +check_flag(all) -> true; +check_flag(no_tail) -> true; +check_flag(false) -> true. + +%%-------------------------------------------------------------------- +%% break(Mod, Line) -> ok | {error, break_exists} +%% delete_break(Mod, Line) -> ok +%% break_in(Mod, Func, Arity) -> ok | {error, function_not_found} +%% del_break_in(Mod, Function, Arity) -> ok | {error, function_not_found} +%% no_break() +%% no_break(Mod) +%% disable_break(Mod, Line) -> ok +%% enable_break(Mod, Line) -> ok +%% action_at_break(Mod, Line, Action) -> ok +%% test_at_break(Mod, Line, Function) -> ok +%% get_binding(Var, Bindings) -> {value, Value} | unbound +%% all_breaks() -> [Break] +%% all_breaks(Mod) -> [Break] +%% Mod = atom() +%% Line = integer() +%% Func = atom() function name +%% Arity = integer() +%% Action = enable | disable | delete +%% Function = {Mod, Func} must have arity 1 (Bindings) +%% Var = atom() +%% Bindings = Value = term() +%% Break = {Point, Options} +%% Point = {Mod, Line} +%% Options = [Status, Action, null, Cond] +%% Status = active | inactive +%% Cond = null | Function +%%-------------------------------------------------------------------- +break(Mod, Line) when is_atom(Mod), is_integer(Line) -> + dbg_iserver:safe_call({new_break, {Mod, Line}, + [active, enable, null, null]}). + +delete_break(Mod, Line) when is_atom(Mod), is_integer(Line) -> + dbg_iserver:safe_cast({delete_break, {Mod, Line}}). + +break_in(Mod, Func, Arity) when is_atom(Mod), is_atom(Func), is_integer(Arity) -> + case dbg_iserver:safe_call({is_interpreted, Mod, Func, Arity}) of + {true, Clauses} -> + Lines = first_lines(Clauses), + lists:foreach(fun(Line) -> break(Mod, Line) end, Lines); + false -> + {error, function_not_found} + end. + +del_break_in(Mod, Func, Arity) when is_atom(Mod), is_atom(Func), is_integer(Arity) -> + case dbg_iserver:safe_call({is_interpreted, Mod, Func, Arity}) of + {true, Clauses} -> + Lines = first_lines(Clauses), + lists:foreach(fun(Line) -> delete_break(Mod, Line) end, + Lines); + false -> + {error, function_not_found} + end. + +first_lines(Clauses) -> + lists:map(fun(Clause) -> first_line(Clause) end, Clauses). + +first_line({clause,_L,_Vars,_,Exprs}) -> + first_line(Exprs); +%% Common Test adaptation +first_line([{call_remote,0,ct_line,line,_As}|Exprs]) -> + first_line(Exprs); +first_line([Expr|_Exprs]) -> % Expr = {Op, Line, ..varying no of args..} + element(2, Expr). + +no_break() -> + dbg_iserver:safe_cast(no_break). + +no_break(Mod) when is_atom(Mod) -> + dbg_iserver:safe_cast({no_break, Mod}). + +disable_break(Mod, Line) when is_atom(Mod), is_integer(Line) -> + dbg_iserver:safe_cast({break_option, {Mod, Line}, status, inactive}). + +enable_break(Mod, Line) when is_atom(Mod), is_integer(Line) -> + dbg_iserver:safe_cast({break_option, {Mod, Line}, status, active}). + +action_at_break(Mod, Line, Action) when is_atom(Mod), is_integer(Line) -> + check_action(Action), + dbg_iserver:safe_cast({break_option, {Mod, Line}, action, Action}). + +check_action(enable) -> true; +check_action(disable) -> true; +check_action(delete) -> true. + +test_at_break(Mod, Line, Function) when is_atom(Mod), is_integer(Line) -> + check_function(Function), + dbg_iserver:safe_cast({break_option, {Mod, Line}, condition, Function}). + +check_function({Mod, Func}) when is_atom(Mod), is_atom(Func) -> true. + +get_binding(Var, Bs) -> + dbg_icmd:get_binding(Var, Bs). + +all_breaks() -> + dbg_iserver:safe_call(all_breaks). +all_breaks(Mod) when is_atom(Mod) -> + dbg_iserver:safe_call({all_breaks, Mod}). + +%%-------------------------------------------------------------------- +%% snapshot() -> [{Pid, Init, Status, Info}] +%% Pid = pid() +%% Init = atom() First interpreted function +%% Status = idle | running | waiting | break | exit +%% Info = {} | {Mod, Line} | ExitReason +%% Mod = atom() +%% Line = integer() +%% ExitReason = term() +%%-------------------------------------------------------------------- +snapshot() -> + dbg_iserver:safe_call(snapshot). + +%%-------------------------------------------------------------------- +%% clear() +%%-------------------------------------------------------------------- +clear() -> + dbg_iserver:safe_cast(clear). + +%%-------------------------------------------------------------------- +%% continue(Pid) -> ok | {error, not_interpreted} +%% continue(X, Y, Z) -> ok | {error, not_interpreted} +%%-------------------------------------------------------------------- +continue(Pid) when is_pid(Pid) -> + case dbg_iserver:safe_call({get_meta, Pid}) of + {ok, Meta} when is_pid(Meta) -> + dbg_icmd:continue(Meta), + ok; + Error -> + Error + end. + +continue(X, Y, Z) when is_integer(X), is_integer(Y), is_integer(Z) -> + continue(c:pid(X, Y, Z)). + + +%%==================================================================== +%% External exports only to be used by Debugger +%%==================================================================== + +%%-------------------------------------------------------------------- +%% start() +%% stop() +%% Functions for starting and stopping dbg_iserver explicitly. +%%-------------------------------------------------------------------- +start() -> dbg_iserver:start(). +stop() -> + lists:foreach( + fun(Mod) -> + everywhere(distributed, + fun() -> + erts_debug:breakpoint({Mod,'_','_'}, false) + end) + end, + interpreted()), + dbg_iserver:stop(). + +%%-------------------------------------------------------------------- +%% subscribe() +%% Subscribe to information from dbg_iserver. The process calling this +%% function will receive the following messages: +%% {int, {interpret, Mod}} +%% {int, {no_interpret, Mod}} +%% {int, {new_process, Pid, Function, Status, Info}} +%% {int, {new_status, Pid, Status, Info}} +%% {int, {new_break, {Point, Options}}} +%% {int, {delete_break, Point}} +%% {int, {break_options, {Point, Options}}} +%% {int, no_break} +%% {int, {no_break, Mod}} +%% {int, {auto_attach, false|{Flags, Function}}} +%% {int, {stack_trace, Flag}} +%%-------------------------------------------------------------------- +subscribe() -> dbg_iserver:cast({subscribe, self()}). + +%%-------------------------------------------------------------------- +%% attach(Pid, Function) +%% Pid = pid() +%% Function = {Mod, Func} | {Mod, Func, Args} (see auto_attach/2) +%% Tell dbg_iserver to attach to Pid using Function. Will result in: +%% spawn(Mod, Func, [Pid, Status | Args]) +%%-------------------------------------------------------------------- +attach(Pid, {Mod, Func}) -> + attach(Pid, {Mod, Func, []}); +attach(Pid, Function) -> + dbg_iserver:cast({attach, Pid, Function}). + +%%-------------------------------------------------------------------- +%% step(Pid) +%% next(Pid) +%% (continue(Pid)) +%% finish(Pid) +%%-------------------------------------------------------------------- +step(Pid) -> + {ok, Meta} = dbg_iserver:call({get_meta, Pid}), + dbg_icmd:step(Meta). +next(Pid) -> + {ok, Meta} = dbg_iserver:call({get_meta, Pid}), + dbg_icmd:next(Meta). +finish(Pid) -> + {ok, Meta} = dbg_iserver:call({get_meta, Pid}), + dbg_icmd:finish(Meta). + + +%%==================================================================== +%% External exports only to be used by an attached process +%%==================================================================== + +%%-------------------------------------------------------------------- +%% attached(Pid) -> {ok, Meta} | error +%% Pid = Meta = pid() +%% Tell dbg_iserver that I have attached to Pid. dbg_iserver informs +%% the meta process and returns its pid. dbg_iserver may also refuse, +%% if there already is a process attached to Pid. +%%-------------------------------------------------------------------- +attached(Pid) -> + dbg_iserver:call({attached, self(), Pid}). + +%%-------------------------------------------------------------------- +%% meta(Meta, Cmd) +%% Meta = pid() +%% Cmd = step | next | continue | finish | skip | timeout | stop +%% Cmd = messages => [Message] +%% meta(Meta, Cmd, Arg) +%% Cmd = trace, Arg = bool() +%% Cmd = stack_trace Arg = all | notail | false +%% Cmd = stack_frame Arg = {up|down, Sp} +%% => {Sp, Mod, Line} | top | bottom +%% Cmd = backtrace Arg = integer() +%% => {Sp, Mod, {Func, Arity}, Line} +%% Cmd = eval Arg = {Cm, Cmd} | {Cm, Cmd, Sp} +%%-------------------------------------------------------------------- +meta(Meta, step) -> dbg_icmd:step(Meta); +meta(Meta, next) -> dbg_icmd:next(Meta); +meta(Meta, continue) -> dbg_icmd:continue(Meta); +meta(Meta, finish) -> dbg_icmd:finish(Meta); +meta(Meta, skip) -> dbg_icmd:skip(Meta); +meta(Meta, timeout) -> dbg_icmd:timeout(Meta); +meta(Meta, stop) -> dbg_icmd:stop(Meta); +meta(Meta, messages) -> dbg_icmd:get(Meta, messages, null). + +meta(Meta, trace, Trace) -> dbg_icmd:set(Meta, trace, Trace); +meta(Meta, stack_trace, Flag) -> dbg_icmd:set(Meta, stack_trace, Flag); +meta(Meta, bindings, Stack) -> dbg_icmd:get(Meta, bindings, Stack); +meta(Meta, stack_frame, Arg) -> dbg_icmd:get(Meta, stack_frame, Arg); +meta(Meta, backtrace, N) -> dbg_icmd:get(Meta, backtrace, N); +meta(Meta, eval, Arg) -> dbg_icmd:eval(Meta, Arg). + +%%-------------------------------------------------------------------- +%% contents(Mod, Pid) -> string() +%% Mod = atom() +%% Pid = pid() | any +%% Return the contents of an interpreted module. +%%-------------------------------------------------------------------- +contents(Mod, Pid) -> + {ok, Bin} = dbg_iserver:call({contents, Mod, Pid}), + binary_to_list(Bin). + +%%-------------------------------------------------------------------- +%% functions(Mod) -> [[Name, Arity]] +%% Mod = Name = atom() +%% Arity = integer() +%%-------------------------------------------------------------------- +functions(Mod) -> + lists:filter(fun([module_info, _Arity]) -> false; + (_Func) -> true + end, + dbg_iserver:call({functions, Mod})). + + +%%==================================================================== +%% External exports only to be used by error_handler +%%==================================================================== + +eval(Mod, Func, Args) -> + dbg_debugged:eval(Mod, Func, Args). + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +%%--Interpreting modules---------------------------------------------- + +int_mod({Mod, Src, Beam, BeamBin}, Dist) + when is_atom(Mod), is_list(Src), is_list(Beam), is_binary(BeamBin) -> + try + case is_file(Src) of + true -> + check_application(Src), + case check_beam(BeamBin) of + {ok, Exp, Abst, _BeamBin} -> + load({Mod, Src, Beam, BeamBin, Exp, Abst}, Dist); + error -> + error + end; + false -> + error + end + catch + throw:Reason -> + Reason + end; +int_mod(AbsMod, Dist) when is_atom(AbsMod); is_list(AbsMod) -> + case check(AbsMod) of + {ok, Res} -> + load(Res, Dist); + {error, {app, App}} -> + io:format("** Cannot interpret ~p module: ~p~n", + [App, AbsMod]), + error; + _Error -> + io:format("** Invalid beam file or no abstract code: ~p\n", + [AbsMod]), + error + end. + +check(Mod) when is_atom(Mod) -> catch check_module(Mod); +check(File) when is_list(File) -> catch check_file(File). + +load({Mod, Src, Beam, BeamBin, Exp, Abst}, Dist) -> + everywhere(Dist, + fun() -> + code:purge(Mod), + erts_debug:breakpoint({Mod,'_','_'}, false), + {module,Mod} = code:load_binary(Mod, Beam, BeamBin) + end), + case erl_prim_loader:get_file(filename:absname(Src)) of + {ok, SrcBin, _} -> + MD5 = code:module_md5(BeamBin), + Bin = term_to_binary({interpreter_module,Exp,Abst,SrcBin,MD5}), + {module, Mod} = dbg_iserver:safe_call({load, Mod, Src, Bin}), + everywhere(Dist, + fun() -> + true = erts_debug:breakpoint({Mod,'_','_'}, true) > 0 + end), + {module, Mod}; + error -> + error + end. + +check_module(Mod) -> + case code:which(Mod) of + Beam when is_list(Beam) -> + case find_src(Beam) of + Src when is_list(Src) -> + check_application(Src), + case check_beam(Beam) of + {ok, Exp, Abst, BeamBin} -> + {ok, {Mod, Src, Beam, BeamBin, Exp, Abst}}; + error -> + {error, no_debug_info} + end; + error -> + {error, no_src} + end; + _ -> + {error, badarg} + end. + +check_file(Name0) -> + Src = + case is_file(Name0) of + true -> + Name0; + false -> + Name = Name0 ++ ".erl", + case is_file(Name) of + true -> Name; + false -> error + end + end, + if + is_list(Src) -> + check_application(Src), + Mod = scan_module_name(Src), + case find_beam(Mod, Src) of + Beam when is_list(Beam) -> + case check_beam(Beam) of + {ok, Exp, Abst, BeamBin} -> + {ok, {Mod, Src, Beam, BeamBin, Exp, Abst}}; + error -> + {error, no_debug_info} + end; + error -> + {error, no_beam} + end; + true -> + {error, badarg} + end. + +%% Try to avoid interpreting a kernel, stdlib, gs or debugger module. +check_application(Src) -> + case lists:reverse(filename:split(filename:absname(Src))) of + [_Mod,"src",AppS|_] -> + check_application2(AppS); + _ -> ok + end. +check_application2("kernel-"++_) -> throw({error,{app,kernel}}); +check_application2("stdlib-"++_) -> throw({error,{app,stdlib}}); +check_application2("erts-"++_) -> throw({error,{app,erts}}); +check_application2("gs-"++_) -> throw({error,{app,gs}}); +check_application2("debugger-"++_) -> throw({error,{app,debugger}}); +check_application2(_) -> ok. + +find_src(Beam) -> + Src0 = filename:rootname(Beam) ++ ".erl", + case is_file(Src0) of + true -> Src0; + false -> + EbinDir = filename:dirname(Beam), + Src = filename:join([filename:dirname(EbinDir), "src", + filename:basename(Src0)]), + case is_file(Src) of + true -> Src; + false -> error + end + end. + +find_beam(Mod, Src) -> + SrcDir = filename:dirname(Src), + BeamFile = packages:last(Mod) ++ code:objfile_extension(), + File = filename:join(SrcDir, BeamFile), + case is_file(File) of + true -> File; + false -> find_beam_1(Mod, SrcDir) + end. + +find_beam_1(Mod, SrcDir) -> + RootDir = find_root_dir(SrcDir, packages:first(Mod)), + EbinDir = filename:join(RootDir, "ebin"), + CodePath = [EbinDir | code:get_path()], + BeamFile = to_path(Mod) ++ code:objfile_extension(), + lists:foldl(fun(_, Beam) when is_list(Beam) -> Beam; + (Dir, error) -> + File = filename:join(Dir, BeamFile), + case is_file(File) of + true -> File; + false -> error + end + end, + error, + CodePath). + +to_path(X) -> + filename:join(packages:split(X)). + +find_root_dir(Dir, [_|Ss]) -> + find_root_dir(filename:dirname(Dir), Ss); +find_root_dir(Dir, []) -> + filename:dirname(Dir). + +check_beam(BeamBin) when is_binary(BeamBin) -> + case beam_lib:chunks(BeamBin, [abstract_code,exports]) of + {ok,{_Mod,[{abstract_code,no_abstract_code}|_]}} -> + error; + {ok,{_Mod,[{abstract_code,Abst},{exports,Exp}]}} -> + {ok,Exp,Abst, BeamBin}; + _ -> + error + end; +check_beam(Beam) when is_list(Beam) -> + {ok, Bin, _FullPath} = erl_prim_loader:get_file(filename:absname(Beam)), + check_beam(Bin). + +is_file(Name) -> + filelib:is_regular(filename:absname(Name), erl_prim_loader). + +everywhere(distributed, Fun) -> + case is_alive() of + true -> rpc:multicall(erlang, apply, [Fun,[]]); + false -> Fun() + end; +everywhere(local, Fun) -> + Fun(). + +scan_module_name(File) -> + case erl_prim_loader:get_file(filename:absname(File)) of + {ok, Bin, _FullPath} -> + Chars = binary_to_list(Bin), + R = (catch {ok, scan_module_name_1(Chars)}), + case R of + {ok, A} when is_atom(A) -> A; + _ -> error + end; + _ -> + error + end. + +scan_module_name_1(Chars) -> + case erl_scan:tokens("", Chars, 1) of + {done, {ok, Ts, _}, Rest} -> + scan_module_name_2(Ts, Rest); + _ -> + error + end. + +scan_module_name_2([{'-',_},{atom,_,module},{'(',_} | _]=Ts, _Chars) -> + scan_module_name_3(Ts); +scan_module_name_2([{'-',_},{atom,_,_} | _], Chars) -> + scan_module_name_1(Chars); +scan_module_name_2(_, _) -> + error. + +scan_module_name_3(Ts) -> + case erl_parse:parse_form(Ts) of + {ok, {attribute,_,module,{M,_}}} -> module_atom(M); + {ok, {attribute,_,module,M}} -> module_atom(M); + _ -> error + end. + +module_atom(A) when is_atom(A) -> A; +module_atom(L) when is_list(L) -> list_to_atom(packages:concat(L)). + +%%--Stop interpreting modules----------------------------------------- + +del_mod(AbsMod, Dist) -> + Mod = if + is_atom(AbsMod) -> AbsMod; + is_list(AbsMod) -> + list_to_atom(filename:basename(AbsMod,".erl")) + end, + dbg_iserver:safe_cast({delete, Mod}), + everywhere(Dist, + fun() -> + erts_debug:breakpoint({Mod,'_','_'}, false), + erlang:yield() + end), + ok. |