From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/debugger/src/dbg_ui_mon.erl | 744 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 744 insertions(+) create mode 100644 lib/debugger/src/dbg_ui_mon.erl (limited to 'lib/debugger/src/dbg_ui_mon.erl') 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]}. -- cgit v1.2.3